1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.inputmethod.latin;
18 
19 import static com.android.inputmethod.latin.common.Constants.ImeOption.FORCE_ASCII;
20 import static com.android.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE;
21 import static com.android.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE_COMPAT;
22 
23 import android.Manifest.permission;
24 import android.app.AlertDialog;
25 import android.content.BroadcastReceiver;
26 import android.content.Context;
27 import android.content.DialogInterface;
28 import android.content.DialogInterface.OnClickListener;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.res.Configuration;
32 import android.content.res.Resources;
33 import android.graphics.Color;
34 import android.inputmethodservice.InputMethodService;
35 import android.media.AudioManager;
36 import android.os.Build;
37 import android.os.Debug;
38 import android.os.IBinder;
39 import android.os.Message;
40 import android.preference.PreferenceManager;
41 import android.text.InputType;
42 import android.util.Log;
43 import android.util.PrintWriterPrinter;
44 import android.util.Printer;
45 import android.util.SparseArray;
46 import android.view.Gravity;
47 import android.view.KeyEvent;
48 import android.view.View;
49 import android.view.ViewGroup.LayoutParams;
50 import android.view.Window;
51 import android.view.WindowManager;
52 import android.view.inputmethod.CompletionInfo;
53 import android.view.inputmethod.EditorInfo;
54 import android.view.inputmethod.InputMethodSubtype;
55 
56 import com.android.inputmethod.accessibility.AccessibilityUtils;
57 import com.android.inputmethod.annotations.UsedForTesting;
58 import com.android.inputmethod.compat.BuildCompatUtils;
59 import com.android.inputmethod.compat.EditorInfoCompatUtils;
60 import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
61 import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
62 import com.android.inputmethod.compat.ViewOutlineProviderCompatUtils;
63 import com.android.inputmethod.compat.ViewOutlineProviderCompatUtils.InsetsUpdater;
64 import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
65 import com.android.inputmethod.event.Event;
66 import com.android.inputmethod.event.HardwareEventDecoder;
67 import com.android.inputmethod.event.HardwareKeyboardEventDecoder;
68 import com.android.inputmethod.event.InputTransaction;
69 import com.android.inputmethod.keyboard.Keyboard;
70 import com.android.inputmethod.keyboard.KeyboardActionListener;
71 import com.android.inputmethod.keyboard.KeyboardId;
72 import com.android.inputmethod.keyboard.KeyboardSwitcher;
73 import com.android.inputmethod.keyboard.MainKeyboardView;
74 import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
75 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
76 import com.android.inputmethod.latin.common.Constants;
77 import com.android.inputmethod.latin.common.CoordinateUtils;
78 import com.android.inputmethod.latin.common.InputPointers;
79 import com.android.inputmethod.latin.define.DebugFlags;
80 import com.android.inputmethod.latin.define.ProductionFlags;
81 import com.android.inputmethod.latin.inputlogic.InputLogic;
82 import com.android.inputmethod.latin.permissions.PermissionsManager;
83 import com.android.inputmethod.latin.personalization.PersonalizationHelper;
84 import com.android.inputmethod.latin.settings.Settings;
85 import com.android.inputmethod.latin.settings.SettingsActivity;
86 import com.android.inputmethod.latin.settings.SettingsValues;
87 import com.android.inputmethod.latin.suggestions.SuggestionStripView;
88 import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor;
89 import com.android.inputmethod.latin.touchinputconsumer.GestureConsumer;
90 import com.android.inputmethod.latin.utils.ApplicationUtils;
91 import com.android.inputmethod.latin.utils.DialogUtils;
92 import com.android.inputmethod.latin.utils.ImportantNoticeUtils;
93 import com.android.inputmethod.latin.utils.IntentUtils;
94 import com.android.inputmethod.latin.utils.JniUtils;
95 import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
96 import com.android.inputmethod.latin.utils.StatsUtils;
97 import com.android.inputmethod.latin.utils.StatsUtilsManager;
98 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
99 import com.android.inputmethod.latin.utils.ViewLayoutUtils;
100 
101 import java.io.FileDescriptor;
102 import java.io.PrintWriter;
103 import java.util.ArrayList;
104 import java.util.List;
105 import java.util.Locale;
106 import java.util.concurrent.TimeUnit;
107 
108 import javax.annotation.Nonnull;
109 
110 /**
111  * Input method implementation for Qwerty'ish keyboard.
112  */
113 public class LatinIME extends InputMethodService implements KeyboardActionListener,
114         SuggestionStripView.Listener, SuggestionStripViewAccessor,
115         DictionaryFacilitator.DictionaryInitializationListener,
116         PermissionsManager.PermissionsResultCallback {
117     static final String TAG = LatinIME.class.getSimpleName();
118     private static final boolean TRACE = false;
119 
120     private static final int EXTENDED_TOUCHABLE_REGION_HEIGHT = 100;
121     private static final int PERIOD_FOR_AUDIO_AND_HAPTIC_FEEDBACK_IN_KEY_REPEAT = 2;
122     private static final int PENDING_IMS_CALLBACK_DURATION_MILLIS = 800;
123     static final long DELAY_WAIT_FOR_DICTIONARY_LOAD_MILLIS = TimeUnit.SECONDS.toMillis(2);
124     static final long DELAY_DEALLOCATE_MEMORY_MILLIS = TimeUnit.SECONDS.toMillis(10);
125 
126     /**
127      * The name of the scheme used by the Package Manager to warn of a new package installation,
128      * replacement or removal.
129      */
130     private static final String SCHEME_PACKAGE = "package";
131 
132     final Settings mSettings;
133     private final DictionaryFacilitator mDictionaryFacilitator =
134             DictionaryFacilitatorProvider.getDictionaryFacilitator(
135                     false /* isNeededForSpellChecking */);
136     final InputLogic mInputLogic = new InputLogic(this /* LatinIME */,
137             this /* SuggestionStripViewAccessor */, mDictionaryFacilitator);
138     // We expect to have only one decoder in almost all cases, hence the default capacity of 1.
139     // If it turns out we need several, it will get grown seamlessly.
140     final SparseArray<HardwareEventDecoder> mHardwareEventDecoders = new SparseArray<>(1);
141 
142     // TODO: Move these {@link View}s to {@link KeyboardSwitcher}.
143     private View mInputView;
144     private InsetsUpdater mInsetsUpdater;
145     private SuggestionStripView mSuggestionStripView;
146 
147     private RichInputMethodManager mRichImm;
148     @UsedForTesting final KeyboardSwitcher mKeyboardSwitcher;
149     private final SubtypeState mSubtypeState = new SubtypeState();
150     private EmojiAltPhysicalKeyDetector mEmojiAltPhysicalKeyDetector;
151     private StatsUtilsManager mStatsUtilsManager;
152     // Working variable for {@link #startShowingInputView()} and
153     // {@link #onEvaluateInputViewShown()}.
154     private boolean mIsExecutingStartShowingInputView;
155 
156     // Object for reacting to adding/removing a dictionary pack.
157     private final BroadcastReceiver mDictionaryPackInstallReceiver =
158             new DictionaryPackInstallBroadcastReceiver(this);
159 
160     private final BroadcastReceiver mDictionaryDumpBroadcastReceiver =
161             new DictionaryDumpBroadcastReceiver(this);
162 
163     private AlertDialog mOptionsDialog;
164 
165     private final boolean mIsHardwareAcceleratedDrawingEnabled;
166 
167     private GestureConsumer mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER;
168 
169     public final UIHandler mHandler = new UIHandler(this);
170 
171     public static final class UIHandler extends LeakGuardHandlerWrapper<LatinIME> {
172         private static final int MSG_UPDATE_SHIFT_STATE = 0;
173         private static final int MSG_PENDING_IMS_CALLBACK = 1;
174         private static final int MSG_UPDATE_SUGGESTION_STRIP = 2;
175         private static final int MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP = 3;
176         private static final int MSG_RESUME_SUGGESTIONS = 4;
177         private static final int MSG_REOPEN_DICTIONARIES = 5;
178         private static final int MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED = 6;
179         private static final int MSG_RESET_CACHES = 7;
180         private static final int MSG_WAIT_FOR_DICTIONARY_LOAD = 8;
181         private static final int MSG_DEALLOCATE_MEMORY = 9;
182         private static final int MSG_RESUME_SUGGESTIONS_FOR_START_INPUT = 10;
183         private static final int MSG_SWITCH_LANGUAGE_AUTOMATICALLY = 11;
184         // Update this when adding new messages
185         private static final int MSG_LAST = MSG_SWITCH_LANGUAGE_AUTOMATICALLY;
186 
187         private static final int ARG1_NOT_GESTURE_INPUT = 0;
188         private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
189         private static final int ARG1_SHOW_GESTURE_FLOATING_PREVIEW_TEXT = 2;
190         private static final int ARG2_UNUSED = 0;
191         private static final int ARG1_TRUE = 1;
192 
193         private int mDelayInMillisecondsToUpdateSuggestions;
194         private int mDelayInMillisecondsToUpdateShiftState;
195 
UIHandler(@onnull final LatinIME ownerInstance)196         public UIHandler(@Nonnull final LatinIME ownerInstance) {
197             super(ownerInstance);
198         }
199 
onCreate()200         public void onCreate() {
201             final LatinIME latinIme = getOwnerInstance();
202             if (latinIme == null) {
203                 return;
204             }
205             final Resources res = latinIme.getResources();
206             mDelayInMillisecondsToUpdateSuggestions = res.getInteger(
207                     R.integer.config_delay_in_milliseconds_to_update_suggestions);
208             mDelayInMillisecondsToUpdateShiftState = res.getInteger(
209                     R.integer.config_delay_in_milliseconds_to_update_shift_state);
210         }
211 
212         @Override
handleMessage(final Message msg)213         public void handleMessage(final Message msg) {
214             final LatinIME latinIme = getOwnerInstance();
215             if (latinIme == null) {
216                 return;
217             }
218             final KeyboardSwitcher switcher = latinIme.mKeyboardSwitcher;
219             switch (msg.what) {
220             case MSG_UPDATE_SUGGESTION_STRIP:
221                 cancelUpdateSuggestionStrip();
222                 latinIme.mInputLogic.performUpdateSuggestionStripSync(
223                         latinIme.mSettings.getCurrent(), msg.arg1 /* inputStyle */);
224                 break;
225             case MSG_UPDATE_SHIFT_STATE:
226                 switcher.requestUpdatingShiftState(latinIme.getCurrentAutoCapsState(),
227                         latinIme.getCurrentRecapitalizeState());
228                 break;
229             case MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP:
230                 if (msg.arg1 == ARG1_NOT_GESTURE_INPUT) {
231                     final SuggestedWords suggestedWords = (SuggestedWords) msg.obj;
232                     latinIme.showSuggestionStrip(suggestedWords);
233                 } else {
234                     latinIme.showGesturePreviewAndSuggestionStrip((SuggestedWords) msg.obj,
235                             msg.arg1 == ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT);
236                 }
237                 break;
238             case MSG_RESUME_SUGGESTIONS:
239                 latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor(
240                         latinIme.mSettings.getCurrent(), false /* forStartInput */,
241                         latinIme.mKeyboardSwitcher.getCurrentKeyboardScriptId());
242                 break;
243             case MSG_RESUME_SUGGESTIONS_FOR_START_INPUT:
244                 latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor(
245                         latinIme.mSettings.getCurrent(), true /* forStartInput */,
246                         latinIme.mKeyboardSwitcher.getCurrentKeyboardScriptId());
247                 break;
248             case MSG_REOPEN_DICTIONARIES:
249                 // We need to re-evaluate the currently composing word in case the script has
250                 // changed.
251                 postWaitForDictionaryLoad();
252                 latinIme.resetDictionaryFacilitatorIfNecessary();
253                 break;
254             case MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED:
255                 final SuggestedWords suggestedWords = (SuggestedWords) msg.obj;
256                 latinIme.mInputLogic.onUpdateTailBatchInputCompleted(
257                         latinIme.mSettings.getCurrent(),
258                         suggestedWords, latinIme.mKeyboardSwitcher);
259                 latinIme.onTailBatchInputResultShown(suggestedWords);
260                 break;
261             case MSG_RESET_CACHES:
262                 final SettingsValues settingsValues = latinIme.mSettings.getCurrent();
263                 if (latinIme.mInputLogic.retryResetCachesAndReturnSuccess(
264                         msg.arg1 == ARG1_TRUE /* tryResumeSuggestions */,
265                         msg.arg2 /* remainingTries */, this /* handler */)) {
266                     // If we were able to reset the caches, then we can reload the keyboard.
267                     // Otherwise, we'll do it when we can.
268                     latinIme.mKeyboardSwitcher.loadKeyboard(latinIme.getCurrentInputEditorInfo(),
269                             settingsValues, latinIme.getCurrentAutoCapsState(),
270                             latinIme.getCurrentRecapitalizeState());
271                 }
272                 break;
273             case MSG_WAIT_FOR_DICTIONARY_LOAD:
274                 Log.i(TAG, "Timeout waiting for dictionary load");
275                 break;
276             case MSG_DEALLOCATE_MEMORY:
277                 latinIme.deallocateMemory();
278                 break;
279             case MSG_SWITCH_LANGUAGE_AUTOMATICALLY:
280                 latinIme.switchLanguage((InputMethodSubtype)msg.obj);
281                 break;
282             }
283         }
284 
postUpdateSuggestionStrip(final int inputStyle)285         public void postUpdateSuggestionStrip(final int inputStyle) {
286             sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP, inputStyle,
287                     0 /* ignored */), mDelayInMillisecondsToUpdateSuggestions);
288         }
289 
postReopenDictionaries()290         public void postReopenDictionaries() {
291             sendMessage(obtainMessage(MSG_REOPEN_DICTIONARIES));
292         }
293 
postResumeSuggestionsInternal(final boolean shouldDelay, final boolean forStartInput)294         private void postResumeSuggestionsInternal(final boolean shouldDelay,
295                 final boolean forStartInput) {
296             final LatinIME latinIme = getOwnerInstance();
297             if (latinIme == null) {
298                 return;
299             }
300             if (!latinIme.mSettings.getCurrent().isSuggestionsEnabledPerUserSettings()) {
301                 return;
302             }
303             removeMessages(MSG_RESUME_SUGGESTIONS);
304             removeMessages(MSG_RESUME_SUGGESTIONS_FOR_START_INPUT);
305             final int message = forStartInput ? MSG_RESUME_SUGGESTIONS_FOR_START_INPUT
306                     : MSG_RESUME_SUGGESTIONS;
307             if (shouldDelay) {
308                 sendMessageDelayed(obtainMessage(message),
309                         mDelayInMillisecondsToUpdateSuggestions);
310             } else {
311                 sendMessage(obtainMessage(message));
312             }
313         }
314 
postResumeSuggestions(final boolean shouldDelay)315         public void postResumeSuggestions(final boolean shouldDelay) {
316             postResumeSuggestionsInternal(shouldDelay, false /* forStartInput */);
317         }
318 
postResumeSuggestionsForStartInput(final boolean shouldDelay)319         public void postResumeSuggestionsForStartInput(final boolean shouldDelay) {
320             postResumeSuggestionsInternal(shouldDelay, true /* forStartInput */);
321         }
322 
postResetCaches(final boolean tryResumeSuggestions, final int remainingTries)323         public void postResetCaches(final boolean tryResumeSuggestions, final int remainingTries) {
324             removeMessages(MSG_RESET_CACHES);
325             sendMessage(obtainMessage(MSG_RESET_CACHES, tryResumeSuggestions ? 1 : 0,
326                     remainingTries, null));
327         }
328 
postWaitForDictionaryLoad()329         public void postWaitForDictionaryLoad() {
330             sendMessageDelayed(obtainMessage(MSG_WAIT_FOR_DICTIONARY_LOAD),
331                     DELAY_WAIT_FOR_DICTIONARY_LOAD_MILLIS);
332         }
333 
cancelWaitForDictionaryLoad()334         public void cancelWaitForDictionaryLoad() {
335             removeMessages(MSG_WAIT_FOR_DICTIONARY_LOAD);
336         }
337 
hasPendingWaitForDictionaryLoad()338         public boolean hasPendingWaitForDictionaryLoad() {
339             return hasMessages(MSG_WAIT_FOR_DICTIONARY_LOAD);
340         }
341 
cancelUpdateSuggestionStrip()342         public void cancelUpdateSuggestionStrip() {
343             removeMessages(MSG_UPDATE_SUGGESTION_STRIP);
344         }
345 
hasPendingUpdateSuggestions()346         public boolean hasPendingUpdateSuggestions() {
347             return hasMessages(MSG_UPDATE_SUGGESTION_STRIP);
348         }
349 
hasPendingReopenDictionaries()350         public boolean hasPendingReopenDictionaries() {
351             return hasMessages(MSG_REOPEN_DICTIONARIES);
352         }
353 
postUpdateShiftState()354         public void postUpdateShiftState() {
355             removeMessages(MSG_UPDATE_SHIFT_STATE);
356             sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE),
357                     mDelayInMillisecondsToUpdateShiftState);
358         }
359 
postDeallocateMemory()360         public void postDeallocateMemory() {
361             sendMessageDelayed(obtainMessage(MSG_DEALLOCATE_MEMORY),
362                     DELAY_DEALLOCATE_MEMORY_MILLIS);
363         }
364 
cancelDeallocateMemory()365         public void cancelDeallocateMemory() {
366             removeMessages(MSG_DEALLOCATE_MEMORY);
367         }
368 
hasPendingDeallocateMemory()369         public boolean hasPendingDeallocateMemory() {
370             return hasMessages(MSG_DEALLOCATE_MEMORY);
371         }
372 
373         @UsedForTesting
removeAllMessages()374         public void removeAllMessages() {
375             for (int i = 0; i <= MSG_LAST; ++i) {
376                 removeMessages(i);
377             }
378         }
379 
showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords, final boolean dismissGestureFloatingPreviewText)380         public void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords,
381                 final boolean dismissGestureFloatingPreviewText) {
382             removeMessages(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
383             final int arg1 = dismissGestureFloatingPreviewText
384                     ? ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT
385                     : ARG1_SHOW_GESTURE_FLOATING_PREVIEW_TEXT;
386             obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP, arg1,
387                     ARG2_UNUSED, suggestedWords).sendToTarget();
388         }
389 
showSuggestionStrip(final SuggestedWords suggestedWords)390         public void showSuggestionStrip(final SuggestedWords suggestedWords) {
391             removeMessages(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
392             obtainMessage(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP,
393                     ARG1_NOT_GESTURE_INPUT, ARG2_UNUSED, suggestedWords).sendToTarget();
394         }
395 
showTailBatchInputResult(final SuggestedWords suggestedWords)396         public void showTailBatchInputResult(final SuggestedWords suggestedWords) {
397             obtainMessage(MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED, suggestedWords).sendToTarget();
398         }
399 
postSwitchLanguage(final InputMethodSubtype subtype)400         public void postSwitchLanguage(final InputMethodSubtype subtype) {
401             obtainMessage(MSG_SWITCH_LANGUAGE_AUTOMATICALLY, subtype).sendToTarget();
402         }
403 
404         // Working variables for the following methods.
405         private boolean mIsOrientationChanging;
406         private boolean mPendingSuccessiveImsCallback;
407         private boolean mHasPendingStartInput;
408         private boolean mHasPendingFinishInputView;
409         private boolean mHasPendingFinishInput;
410         private EditorInfo mAppliedEditorInfo;
411 
startOrientationChanging()412         public void startOrientationChanging() {
413             removeMessages(MSG_PENDING_IMS_CALLBACK);
414             resetPendingImsCallback();
415             mIsOrientationChanging = true;
416             final LatinIME latinIme = getOwnerInstance();
417             if (latinIme == null) {
418                 return;
419             }
420             if (latinIme.isInputViewShown()) {
421                 latinIme.mKeyboardSwitcher.saveKeyboardState();
422             }
423         }
424 
resetPendingImsCallback()425         private void resetPendingImsCallback() {
426             mHasPendingFinishInputView = false;
427             mHasPendingFinishInput = false;
428             mHasPendingStartInput = false;
429         }
430 
executePendingImsCallback(final LatinIME latinIme, final EditorInfo editorInfo, boolean restarting)431         private void executePendingImsCallback(final LatinIME latinIme, final EditorInfo editorInfo,
432                 boolean restarting) {
433             if (mHasPendingFinishInputView) {
434                 latinIme.onFinishInputViewInternal(mHasPendingFinishInput);
435             }
436             if (mHasPendingFinishInput) {
437                 latinIme.onFinishInputInternal();
438             }
439             if (mHasPendingStartInput) {
440                 latinIme.onStartInputInternal(editorInfo, restarting);
441             }
442             resetPendingImsCallback();
443         }
444 
onStartInput(final EditorInfo editorInfo, final boolean restarting)445         public void onStartInput(final EditorInfo editorInfo, final boolean restarting) {
446             if (hasMessages(MSG_PENDING_IMS_CALLBACK)) {
447                 // Typically this is the second onStartInput after orientation changed.
448                 mHasPendingStartInput = true;
449             } else {
450                 if (mIsOrientationChanging && restarting) {
451                     // This is the first onStartInput after orientation changed.
452                     mIsOrientationChanging = false;
453                     mPendingSuccessiveImsCallback = true;
454                 }
455                 final LatinIME latinIme = getOwnerInstance();
456                 if (latinIme != null) {
457                     executePendingImsCallback(latinIme, editorInfo, restarting);
458                     latinIme.onStartInputInternal(editorInfo, restarting);
459                 }
460             }
461         }
462 
onStartInputView(final EditorInfo editorInfo, final boolean restarting)463         public void onStartInputView(final EditorInfo editorInfo, final boolean restarting) {
464             if (hasMessages(MSG_PENDING_IMS_CALLBACK)
465                     && KeyboardId.equivalentEditorInfoForKeyboard(editorInfo, mAppliedEditorInfo)) {
466                 // Typically this is the second onStartInputView after orientation changed.
467                 resetPendingImsCallback();
468             } else {
469                 if (mPendingSuccessiveImsCallback) {
470                     // This is the first onStartInputView after orientation changed.
471                     mPendingSuccessiveImsCallback = false;
472                     resetPendingImsCallback();
473                     sendMessageDelayed(obtainMessage(MSG_PENDING_IMS_CALLBACK),
474                             PENDING_IMS_CALLBACK_DURATION_MILLIS);
475                 }
476                 final LatinIME latinIme = getOwnerInstance();
477                 if (latinIme != null) {
478                     executePendingImsCallback(latinIme, editorInfo, restarting);
479                     latinIme.onStartInputViewInternal(editorInfo, restarting);
480                     mAppliedEditorInfo = editorInfo;
481                 }
482                 cancelDeallocateMemory();
483             }
484         }
485 
onFinishInputView(final boolean finishingInput)486         public void onFinishInputView(final boolean finishingInput) {
487             if (hasMessages(MSG_PENDING_IMS_CALLBACK)) {
488                 // Typically this is the first onFinishInputView after orientation changed.
489                 mHasPendingFinishInputView = true;
490             } else {
491                 final LatinIME latinIme = getOwnerInstance();
492                 if (latinIme != null) {
493                     latinIme.onFinishInputViewInternal(finishingInput);
494                     mAppliedEditorInfo = null;
495                 }
496                 if (!hasPendingDeallocateMemory()) {
497                     postDeallocateMemory();
498                 }
499             }
500         }
501 
onFinishInput()502         public void onFinishInput() {
503             if (hasMessages(MSG_PENDING_IMS_CALLBACK)) {
504                 // Typically this is the first onFinishInput after orientation changed.
505                 mHasPendingFinishInput = true;
506             } else {
507                 final LatinIME latinIme = getOwnerInstance();
508                 if (latinIme != null) {
509                     executePendingImsCallback(latinIme, null, false);
510                     latinIme.onFinishInputInternal();
511                 }
512             }
513         }
514     }
515 
516     static final class SubtypeState {
517         private InputMethodSubtype mLastActiveSubtype;
518         private boolean mCurrentSubtypeHasBeenUsed;
519 
setCurrentSubtypeHasBeenUsed()520         public void setCurrentSubtypeHasBeenUsed() {
521             mCurrentSubtypeHasBeenUsed = true;
522         }
523 
switchSubtype(final IBinder token, final RichInputMethodManager richImm)524         public void switchSubtype(final IBinder token, final RichInputMethodManager richImm) {
525             final InputMethodSubtype currentSubtype = richImm.getInputMethodManager()
526                     .getCurrentInputMethodSubtype();
527             final InputMethodSubtype lastActiveSubtype = mLastActiveSubtype;
528             final boolean currentSubtypeHasBeenUsed = mCurrentSubtypeHasBeenUsed;
529             if (currentSubtypeHasBeenUsed) {
530                 mLastActiveSubtype = currentSubtype;
531                 mCurrentSubtypeHasBeenUsed = false;
532             }
533             if (currentSubtypeHasBeenUsed
534                     && richImm.checkIfSubtypeBelongsToThisImeAndEnabled(lastActiveSubtype)
535                     && !currentSubtype.equals(lastActiveSubtype)) {
536                 richImm.setInputMethodAndSubtype(token, lastActiveSubtype);
537                 return;
538             }
539             richImm.switchToNextInputMethod(token, true /* onlyCurrentIme */);
540         }
541     }
542 
543     // Loading the native library eagerly to avoid unexpected UnsatisfiedLinkError at the initial
544     // JNI call as much as possible.
545     static {
JniUtils.loadNativeLibrary()546         JniUtils.loadNativeLibrary();
547     }
548 
LatinIME()549     public LatinIME() {
550         super();
551         mSettings = Settings.getInstance();
552         mKeyboardSwitcher = KeyboardSwitcher.getInstance();
553         mStatsUtilsManager = StatsUtilsManager.getInstance();
554         mIsHardwareAcceleratedDrawingEnabled =
555                 InputMethodServiceCompatUtils.enableHardwareAcceleration(this);
556         Log.i(TAG, "Hardware accelerated drawing: " + mIsHardwareAcceleratedDrawingEnabled);
557     }
558 
559     @Override
onCreate()560     public void onCreate() {
561         Settings.init(this);
562         DebugFlags.init(PreferenceManager.getDefaultSharedPreferences(this));
563         RichInputMethodManager.init(this);
564         mRichImm = RichInputMethodManager.getInstance();
565         KeyboardSwitcher.init(this);
566         AudioAndHapticFeedbackManager.init(this);
567         AccessibilityUtils.init(this);
568         mStatsUtilsManager.onCreate(this /* context */, mDictionaryFacilitator);
569         super.onCreate();
570 
571         mHandler.onCreate();
572 
573         // TODO: Resolve mutual dependencies of {@link #loadSettings()} and
574         // {@link #resetDictionaryFacilitatorIfNecessary()}.
575         loadSettings();
576         resetDictionaryFacilitatorIfNecessary();
577 
578         // Register to receive ringer mode change.
579         final IntentFilter filter = new IntentFilter();
580         filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
581         registerReceiver(mRingerModeChangeReceiver, filter);
582 
583         // Register to receive installation and removal of a dictionary pack.
584         final IntentFilter packageFilter = new IntentFilter();
585         packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
586         packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
587         packageFilter.addDataScheme(SCHEME_PACKAGE);
588         registerReceiver(mDictionaryPackInstallReceiver, packageFilter);
589 
590         final IntentFilter newDictFilter = new IntentFilter();
591         newDictFilter.addAction(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION);
592         registerReceiver(mDictionaryPackInstallReceiver, newDictFilter);
593 
594         final IntentFilter dictDumpFilter = new IntentFilter();
595         dictDumpFilter.addAction(DictionaryDumpBroadcastReceiver.DICTIONARY_DUMP_INTENT_ACTION);
596         registerReceiver(mDictionaryDumpBroadcastReceiver, dictDumpFilter);
597 
598         StatsUtils.onCreate(mSettings.getCurrent(), mRichImm);
599     }
600 
601     // Has to be package-visible for unit tests
602     @UsedForTesting
loadSettings()603     void loadSettings() {
604         final Locale locale = mRichImm.getCurrentSubtypeLocale();
605         final EditorInfo editorInfo = getCurrentInputEditorInfo();
606         final InputAttributes inputAttributes = new InputAttributes(
607                 editorInfo, isFullscreenMode(), getPackageName());
608         mSettings.loadSettings(this, locale, inputAttributes);
609         final SettingsValues currentSettingsValues = mSettings.getCurrent();
610         AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(currentSettingsValues);
611         // This method is called on startup and language switch, before the new layout has
612         // been displayed. Opening dictionaries never affects responsivity as dictionaries are
613         // asynchronously loaded.
614         if (!mHandler.hasPendingReopenDictionaries()) {
615             resetDictionaryFacilitator(locale);
616         }
617         refreshPersonalizationDictionarySession(currentSettingsValues);
618         resetDictionaryFacilitatorIfNecessary();
619         mStatsUtilsManager.onLoadSettings(this /* context */, currentSettingsValues);
620     }
621 
refreshPersonalizationDictionarySession( final SettingsValues currentSettingsValues)622     private void refreshPersonalizationDictionarySession(
623             final SettingsValues currentSettingsValues) {
624         if (!currentSettingsValues.mUsePersonalizedDicts) {
625             // Remove user history dictionaries.
626             PersonalizationHelper.removeAllUserHistoryDictionaries(this);
627             mDictionaryFacilitator.clearUserHistoryDictionary(this);
628         }
629     }
630 
631     // Note that this method is called from a non-UI thread.
632     @Override
onUpdateMainDictionaryAvailability(final boolean isMainDictionaryAvailable)633     public void onUpdateMainDictionaryAvailability(final boolean isMainDictionaryAvailable) {
634         final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
635         if (mainKeyboardView != null) {
636             mainKeyboardView.setMainDictionaryAvailability(isMainDictionaryAvailable);
637         }
638         if (mHandler.hasPendingWaitForDictionaryLoad()) {
639             mHandler.cancelWaitForDictionaryLoad();
640             mHandler.postResumeSuggestions(false /* shouldDelay */);
641         }
642     }
643 
resetDictionaryFacilitatorIfNecessary()644     void resetDictionaryFacilitatorIfNecessary() {
645         final Locale subtypeSwitcherLocale = mRichImm.getCurrentSubtypeLocale();
646         final Locale subtypeLocale;
647         if (subtypeSwitcherLocale == null) {
648             // This happens in very rare corner cases - for example, immediately after a switch
649             // to LatinIME has been requested, about a frame later another switch happens. In this
650             // case, we are about to go down but we still don't know it, however the system tells
651             // us there is no current subtype.
652             Log.e(TAG, "System is reporting no current subtype.");
653             subtypeLocale = getResources().getConfiguration().locale;
654         } else {
655             subtypeLocale = subtypeSwitcherLocale;
656         }
657         if (mDictionaryFacilitator.isForLocale(subtypeLocale)
658                 && mDictionaryFacilitator.isForAccount(mSettings.getCurrent().mAccount)) {
659             return;
660         }
661         resetDictionaryFacilitator(subtypeLocale);
662     }
663 
664     /**
665      * Reset the facilitator by loading dictionaries for the given locale and
666      * the current settings values.
667      *
668      * @param locale the locale
669      */
670     // TODO: make sure the current settings always have the right locales, and read from them.
resetDictionaryFacilitator(final Locale locale)671     private void resetDictionaryFacilitator(final Locale locale) {
672         final SettingsValues settingsValues = mSettings.getCurrent();
673         mDictionaryFacilitator.resetDictionaries(this /* context */, locale,
674                 settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts,
675                 false /* forceReloadMainDictionary */,
676                 settingsValues.mAccount, "" /* dictNamePrefix */,
677                 this /* DictionaryInitializationListener */);
678         if (settingsValues.mAutoCorrectionEnabledPerUserSettings) {
679             mInputLogic.mSuggest.setAutoCorrectionThreshold(
680                     settingsValues.mAutoCorrectionThreshold);
681         }
682         mInputLogic.mSuggest.setPlausibilityThreshold(settingsValues.mPlausibilityThreshold);
683     }
684 
685     /**
686      * Reset suggest by loading the main dictionary of the current locale.
687      */
resetSuggestMainDict()688     /* package private */ void resetSuggestMainDict() {
689         final SettingsValues settingsValues = mSettings.getCurrent();
690         mDictionaryFacilitator.resetDictionaries(this /* context */,
691                 mDictionaryFacilitator.getLocale(), settingsValues.mUseContactsDict,
692                 settingsValues.mUsePersonalizedDicts,
693                 true /* forceReloadMainDictionary */,
694                 settingsValues.mAccount, "" /* dictNamePrefix */,
695                 this /* DictionaryInitializationListener */);
696     }
697 
698     @Override
onDestroy()699     public void onDestroy() {
700         mDictionaryFacilitator.closeDictionaries();
701         mSettings.onDestroy();
702         unregisterReceiver(mRingerModeChangeReceiver);
703         unregisterReceiver(mDictionaryPackInstallReceiver);
704         unregisterReceiver(mDictionaryDumpBroadcastReceiver);
705         mStatsUtilsManager.onDestroy(this /* context */);
706         super.onDestroy();
707     }
708 
709     @UsedForTesting
recycle()710     public void recycle() {
711         unregisterReceiver(mDictionaryPackInstallReceiver);
712         unregisterReceiver(mDictionaryDumpBroadcastReceiver);
713         unregisterReceiver(mRingerModeChangeReceiver);
714         mInputLogic.recycle();
715     }
716 
isImeSuppressedByHardwareKeyboard()717     private boolean isImeSuppressedByHardwareKeyboard() {
718         final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance();
719         return !onEvaluateInputViewShown() && switcher.isImeSuppressedByHardwareKeyboard(
720                 mSettings.getCurrent(), switcher.getKeyboardSwitchState());
721     }
722 
723     @Override
onConfigurationChanged(final Configuration conf)724     public void onConfigurationChanged(final Configuration conf) {
725         SettingsValues settingsValues = mSettings.getCurrent();
726         if (settingsValues.mDisplayOrientation != conf.orientation) {
727             mHandler.startOrientationChanging();
728             mInputLogic.onOrientationChange(mSettings.getCurrent());
729         }
730         if (settingsValues.mHasHardwareKeyboard != Settings.readHasHardwareKeyboard(conf)) {
731             // If the state of having a hardware keyboard changed, then we want to reload the
732             // settings to adjust for that.
733             // TODO: we should probably do this unconditionally here, rather than only when we
734             // have a change in hardware keyboard configuration.
735             loadSettings();
736             settingsValues = mSettings.getCurrent();
737             if (isImeSuppressedByHardwareKeyboard()) {
738                 // We call cleanupInternalStateForFinishInput() because it's the right thing to do;
739                 // however, it seems at the moment the framework is passing us a seemingly valid
740                 // but actually non-functional InputConnection object. So if this bug ever gets
741                 // fixed we'll be able to remove the composition, but until it is this code is
742                 // actually not doing much.
743                 cleanupInternalStateForFinishInput();
744             }
745         }
746         super.onConfigurationChanged(conf);
747     }
748 
749     @Override
onCreateInputView()750     public View onCreateInputView() {
751         StatsUtils.onCreateInputView();
752         return mKeyboardSwitcher.onCreateInputView(mIsHardwareAcceleratedDrawingEnabled);
753     }
754 
755     @Override
setInputView(final View view)756     public void setInputView(final View view) {
757         super.setInputView(view);
758         mInputView = view;
759         mInsetsUpdater = ViewOutlineProviderCompatUtils.setInsetsOutlineProvider(view);
760         updateSoftInputWindowLayoutParameters();
761         mSuggestionStripView = (SuggestionStripView)view.findViewById(R.id.suggestion_strip_view);
762         if (hasSuggestionStripView()) {
763             mSuggestionStripView.setListener(this, view);
764         }
765     }
766 
767     @Override
setCandidatesView(final View view)768     public void setCandidatesView(final View view) {
769         // To ensure that CandidatesView will never be set.
770     }
771 
772     @Override
onStartInput(final EditorInfo editorInfo, final boolean restarting)773     public void onStartInput(final EditorInfo editorInfo, final boolean restarting) {
774         mHandler.onStartInput(editorInfo, restarting);
775     }
776 
777     @Override
onStartInputView(final EditorInfo editorInfo, final boolean restarting)778     public void onStartInputView(final EditorInfo editorInfo, final boolean restarting) {
779         mHandler.onStartInputView(editorInfo, restarting);
780         mStatsUtilsManager.onStartInputView();
781     }
782 
783     @Override
onFinishInputView(final boolean finishingInput)784     public void onFinishInputView(final boolean finishingInput) {
785         StatsUtils.onFinishInputView();
786         mHandler.onFinishInputView(finishingInput);
787         mStatsUtilsManager.onFinishInputView();
788         mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER;
789     }
790 
791     @Override
onFinishInput()792     public void onFinishInput() {
793         mHandler.onFinishInput();
794     }
795 
796     @Override
onCurrentInputMethodSubtypeChanged(final InputMethodSubtype subtype)797     public void onCurrentInputMethodSubtypeChanged(final InputMethodSubtype subtype) {
798         // Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
799         // is not guaranteed. It may even be called at the same time on a different thread.
800         InputMethodSubtype oldSubtype = mRichImm.getCurrentSubtype().getRawSubtype();
801         StatsUtils.onSubtypeChanged(oldSubtype, subtype);
802         mRichImm.onSubtypeChanged(subtype);
803         mInputLogic.onSubtypeChanged(SubtypeLocaleUtils.getCombiningRulesExtraValue(subtype),
804                 mSettings.getCurrent());
805         loadKeyboard();
806     }
807 
onStartInputInternal(final EditorInfo editorInfo, final boolean restarting)808     void onStartInputInternal(final EditorInfo editorInfo, final boolean restarting) {
809         super.onStartInput(editorInfo, restarting);
810 
811         // If the primary hint language does not match the current subtype language, then try
812         // to switch to the primary hint language.
813         // TODO: Support all the locales in EditorInfo#hintLocales.
814         final Locale primaryHintLocale = EditorInfoCompatUtils.getPrimaryHintLocale(editorInfo);
815         if (primaryHintLocale == null) {
816             return;
817         }
818         final InputMethodSubtype newSubtype = mRichImm.findSubtypeByLocale(primaryHintLocale);
819         if (newSubtype == null || newSubtype.equals(mRichImm.getCurrentSubtype().getRawSubtype())) {
820             return;
821         }
822         mHandler.postSwitchLanguage(newSubtype);
823     }
824 
825     @SuppressWarnings("deprecation")
onStartInputViewInternal(final EditorInfo editorInfo, final boolean restarting)826     void onStartInputViewInternal(final EditorInfo editorInfo, final boolean restarting) {
827         super.onStartInputView(editorInfo, restarting);
828 
829         mDictionaryFacilitator.onStartInput();
830         // Switch to the null consumer to handle cases leading to early exit below, for which we
831         // also wouldn't be consuming gesture data.
832         mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER;
833         mRichImm.refreshSubtypeCaches();
834         final KeyboardSwitcher switcher = mKeyboardSwitcher;
835         switcher.updateKeyboardTheme();
836         final MainKeyboardView mainKeyboardView = switcher.getMainKeyboardView();
837         // If we are starting input in a different text field from before, we'll have to reload
838         // settings, so currentSettingsValues can't be final.
839         SettingsValues currentSettingsValues = mSettings.getCurrent();
840 
841         if (editorInfo == null) {
842             Log.e(TAG, "Null EditorInfo in onStartInputView()");
843             if (DebugFlags.DEBUG_ENABLED) {
844                 throw new NullPointerException("Null EditorInfo in onStartInputView()");
845             }
846             return;
847         }
848         if (DebugFlags.DEBUG_ENABLED) {
849             Log.d(TAG, "onStartInputView: editorInfo:"
850                     + String.format("inputType=0x%08x imeOptions=0x%08x",
851                             editorInfo.inputType, editorInfo.imeOptions));
852             Log.d(TAG, "All caps = "
853                     + ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0)
854                     + ", sentence caps = "
855                     + ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0)
856                     + ", word caps = "
857                     + ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS) != 0));
858         }
859         Log.i(TAG, "Starting input. Cursor position = "
860                 + editorInfo.initialSelStart + "," + editorInfo.initialSelEnd);
861         // TODO: Consolidate these checks with {@link InputAttributes}.
862         if (InputAttributes.inPrivateImeOptions(null, NO_MICROPHONE_COMPAT, editorInfo)) {
863             Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions);
864             Log.w(TAG, "Use " + getPackageName() + "." + NO_MICROPHONE + " instead");
865         }
866         if (InputAttributes.inPrivateImeOptions(getPackageName(), FORCE_ASCII, editorInfo)) {
867             Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions);
868             Log.w(TAG, "Use EditorInfo.IME_FLAG_FORCE_ASCII flag instead");
869         }
870 
871         // In landscape mode, this method gets called without the input view being created.
872         if (mainKeyboardView == null) {
873             return;
874         }
875 
876         // Update to a gesture consumer with the current editor and IME state.
877         mGestureConsumer = GestureConsumer.newInstance(editorInfo,
878                 mInputLogic.getPrivateCommandPerformer(),
879                 mRichImm.getCurrentSubtypeLocale(),
880                 switcher.getKeyboard());
881 
882         // Forward this event to the accessibility utilities, if enabled.
883         final AccessibilityUtils accessUtils = AccessibilityUtils.getInstance();
884         if (accessUtils.isTouchExplorationEnabled()) {
885             accessUtils.onStartInputViewInternal(mainKeyboardView, editorInfo, restarting);
886         }
887 
888         final boolean inputTypeChanged = !currentSettingsValues.isSameInputType(editorInfo);
889         final boolean isDifferentTextField = !restarting || inputTypeChanged;
890 
891         StatsUtils.onStartInputView(editorInfo.inputType,
892                 Settings.getInstance().getCurrent().mDisplayOrientation,
893                 !isDifferentTextField);
894 
895         // The EditorInfo might have a flag that affects fullscreen mode.
896         // Note: This call should be done by InputMethodService?
897         updateFullscreenMode();
898 
899         // ALERT: settings have not been reloaded and there is a chance they may be stale.
900         // In the practice, if it is, we should have gotten onConfigurationChanged so it should
901         // be fine, but this is horribly confusing and must be fixed AS SOON AS POSSIBLE.
902 
903         // In some cases the input connection has not been reset yet and we can't access it. In
904         // this case we will need to call loadKeyboard() later, when it's accessible, so that we
905         // can go into the correct mode, so we need to do some housekeeping here.
906         final boolean needToCallLoadKeyboardLater;
907         final Suggest suggest = mInputLogic.mSuggest;
908         if (!isImeSuppressedByHardwareKeyboard()) {
909             // The app calling setText() has the effect of clearing the composing
910             // span, so we should reset our state unconditionally, even if restarting is true.
911             // We also tell the input logic about the combining rules for the current subtype, so
912             // it can adjust its combiners if needed.
913             mInputLogic.startInput(mRichImm.getCombiningRulesExtraValueOfCurrentSubtype(),
914                     currentSettingsValues);
915 
916             resetDictionaryFacilitatorIfNecessary();
917 
918             // TODO[IL]: Can the following be moved to InputLogic#startInput?
919             if (!mInputLogic.mConnection.resetCachesUponCursorMoveAndReturnSuccess(
920                     editorInfo.initialSelStart, editorInfo.initialSelEnd,
921                     false /* shouldFinishComposition */)) {
922                 // Sometimes, while rotating, for some reason the framework tells the app we are not
923                 // connected to it and that means we can't refresh the cache. In this case, schedule
924                 // a refresh later.
925                 // We try resetting the caches up to 5 times before giving up.
926                 mHandler.postResetCaches(isDifferentTextField, 5 /* remainingTries */);
927                 // mLastSelection{Start,End} are reset later in this method, no need to do it here
928                 needToCallLoadKeyboardLater = true;
929             } else {
930                 // When rotating, and when input is starting again in a field from where the focus
931                 // didn't move (the keyboard having been closed with the back key),
932                 // initialSelStart and initialSelEnd sometimes are lying. Make a best effort to
933                 // work around this bug.
934                 mInputLogic.mConnection.tryFixLyingCursorPosition();
935                 mHandler.postResumeSuggestionsForStartInput(true /* shouldDelay */);
936                 needToCallLoadKeyboardLater = false;
937             }
938         } else {
939             // If we have a hardware keyboard we don't need to call loadKeyboard later anyway.
940             needToCallLoadKeyboardLater = false;
941         }
942 
943         if (isDifferentTextField ||
944                 !currentSettingsValues.hasSameOrientation(getResources().getConfiguration())) {
945             loadSettings();
946         }
947         if (isDifferentTextField) {
948             mainKeyboardView.closing();
949             currentSettingsValues = mSettings.getCurrent();
950 
951             if (currentSettingsValues.mAutoCorrectionEnabledPerUserSettings) {
952                 suggest.setAutoCorrectionThreshold(
953                         currentSettingsValues.mAutoCorrectionThreshold);
954             }
955             suggest.setPlausibilityThreshold(currentSettingsValues.mPlausibilityThreshold);
956 
957             switcher.loadKeyboard(editorInfo, currentSettingsValues, getCurrentAutoCapsState(),
958                     getCurrentRecapitalizeState());
959             if (needToCallLoadKeyboardLater) {
960                 // If we need to call loadKeyboard again later, we need to save its state now. The
961                 // later call will be done in #retryResetCaches.
962                 switcher.saveKeyboardState();
963             }
964         } else if (restarting) {
965             // TODO: Come up with a more comprehensive way to reset the keyboard layout when
966             // a keyboard layout set doesn't get reloaded in this method.
967             switcher.resetKeyboardStateToAlphabet(getCurrentAutoCapsState(),
968                     getCurrentRecapitalizeState());
969             // In apps like Talk, we come here when the text is sent and the field gets emptied and
970             // we need to re-evaluate the shift state, but not the whole layout which would be
971             // disruptive.
972             // Space state must be updated before calling updateShiftState
973             switcher.requestUpdatingShiftState(getCurrentAutoCapsState(),
974                     getCurrentRecapitalizeState());
975         }
976         // This will set the punctuation suggestions if next word suggestion is off;
977         // otherwise it will clear the suggestion strip.
978         setNeutralSuggestionStrip();
979 
980         mHandler.cancelUpdateSuggestionStrip();
981 
982         mainKeyboardView.setMainDictionaryAvailability(
983                 mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary());
984         mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn,
985                 currentSettingsValues.mKeyPreviewPopupDismissDelay);
986         mainKeyboardView.setSlidingKeyInputPreviewEnabled(
987                 currentSettingsValues.mSlidingKeyInputPreviewEnabled);
988         mainKeyboardView.setGestureHandlingEnabledByUser(
989                 currentSettingsValues.mGestureInputEnabled,
990                 currentSettingsValues.mGestureTrailEnabled,
991                 currentSettingsValues.mGestureFloatingPreviewTextEnabled);
992 
993         if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
994     }
995 
996     @Override
onWindowShown()997     public void onWindowShown() {
998         super.onWindowShown();
999         setNavigationBarVisibility(isInputViewShown());
1000     }
1001 
1002     @Override
onWindowHidden()1003     public void onWindowHidden() {
1004         super.onWindowHidden();
1005         final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
1006         if (mainKeyboardView != null) {
1007             mainKeyboardView.closing();
1008         }
1009         setNavigationBarVisibility(false);
1010     }
1011 
onFinishInputInternal()1012     void onFinishInputInternal() {
1013         super.onFinishInput();
1014 
1015         mDictionaryFacilitator.onFinishInput(this);
1016         final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
1017         if (mainKeyboardView != null) {
1018             mainKeyboardView.closing();
1019         }
1020     }
1021 
onFinishInputViewInternal(final boolean finishingInput)1022     void onFinishInputViewInternal(final boolean finishingInput) {
1023         super.onFinishInputView(finishingInput);
1024         cleanupInternalStateForFinishInput();
1025     }
1026 
cleanupInternalStateForFinishInput()1027     private void cleanupInternalStateForFinishInput() {
1028         // Remove pending messages related to update suggestions
1029         mHandler.cancelUpdateSuggestionStrip();
1030         // Should do the following in onFinishInputInternal but until JB MR2 it's not called :(
1031         mInputLogic.finishInput();
1032     }
1033 
deallocateMemory()1034     protected void deallocateMemory() {
1035         mKeyboardSwitcher.deallocateMemory();
1036     }
1037 
1038     @Override
onUpdateSelection(final int oldSelStart, final int oldSelEnd, final int newSelStart, final int newSelEnd, final int composingSpanStart, final int composingSpanEnd)1039     public void onUpdateSelection(final int oldSelStart, final int oldSelEnd,
1040             final int newSelStart, final int newSelEnd,
1041             final int composingSpanStart, final int composingSpanEnd) {
1042         super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
1043                 composingSpanStart, composingSpanEnd);
1044         if (DebugFlags.DEBUG_ENABLED) {
1045             Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart + ", ose=" + oldSelEnd
1046                     + ", nss=" + newSelStart + ", nse=" + newSelEnd
1047                     + ", cs=" + composingSpanStart + ", ce=" + composingSpanEnd);
1048         }
1049 
1050         // This call happens whether our view is displayed or not, but if it's not then we should
1051         // not attempt recorrection. This is true even with a hardware keyboard connected: if the
1052         // view is not displayed we have no means of showing suggestions anyway, and if it is then
1053         // we want to show suggestions anyway.
1054         final SettingsValues settingsValues = mSettings.getCurrent();
1055         if (isInputViewShown()
1056                 && mInputLogic.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
1057                         settingsValues)) {
1058             mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(),
1059                     getCurrentRecapitalizeState());
1060         }
1061     }
1062 
1063     /**
1064      * This is called when the user has clicked on the extracted text view,
1065      * when running in fullscreen mode.  The default implementation hides
1066      * the suggestions view when this happens, but only if the extracted text
1067      * editor has a vertical scroll bar because its text doesn't fit.
1068      * Here we override the behavior due to the possibility that a re-correction could
1069      * cause the suggestions strip to disappear and re-appear.
1070      */
1071     @Override
onExtractedTextClicked()1072     public void onExtractedTextClicked() {
1073         if (mSettings.getCurrent().needsToLookupSuggestions()) {
1074             return;
1075         }
1076 
1077         super.onExtractedTextClicked();
1078     }
1079 
1080     /**
1081      * This is called when the user has performed a cursor movement in the
1082      * extracted text view, when it is running in fullscreen mode.  The default
1083      * implementation hides the suggestions view when a vertical movement
1084      * happens, but only if the extracted text editor has a vertical scroll bar
1085      * because its text doesn't fit.
1086      * Here we override the behavior due to the possibility that a re-correction could
1087      * cause the suggestions strip to disappear and re-appear.
1088      */
1089     @Override
onExtractedCursorMovement(final int dx, final int dy)1090     public void onExtractedCursorMovement(final int dx, final int dy) {
1091         if (mSettings.getCurrent().needsToLookupSuggestions()) {
1092             return;
1093         }
1094 
1095         super.onExtractedCursorMovement(dx, dy);
1096     }
1097 
1098     @Override
hideWindow()1099     public void hideWindow() {
1100         mKeyboardSwitcher.onHideWindow();
1101 
1102         if (TRACE) Debug.stopMethodTracing();
1103         if (isShowingOptionDialog()) {
1104             mOptionsDialog.dismiss();
1105             mOptionsDialog = null;
1106         }
1107         super.hideWindow();
1108     }
1109 
1110     @Override
onDisplayCompletions(final CompletionInfo[] applicationSpecifiedCompletions)1111     public void onDisplayCompletions(final CompletionInfo[] applicationSpecifiedCompletions) {
1112         if (DebugFlags.DEBUG_ENABLED) {
1113             Log.i(TAG, "Received completions:");
1114             if (applicationSpecifiedCompletions != null) {
1115                 for (int i = 0; i < applicationSpecifiedCompletions.length; i++) {
1116                     Log.i(TAG, "  #" + i + ": " + applicationSpecifiedCompletions[i]);
1117                 }
1118             }
1119         }
1120         if (!mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()) {
1121             return;
1122         }
1123         // If we have an update request in flight, we need to cancel it so it does not override
1124         // these completions.
1125         mHandler.cancelUpdateSuggestionStrip();
1126         if (applicationSpecifiedCompletions == null) {
1127             setNeutralSuggestionStrip();
1128             return;
1129         }
1130 
1131         final ArrayList<SuggestedWords.SuggestedWordInfo> applicationSuggestedWords =
1132                 SuggestedWords.getFromApplicationSpecifiedCompletions(
1133                         applicationSpecifiedCompletions);
1134         final SuggestedWords suggestedWords = new SuggestedWords(applicationSuggestedWords,
1135                 null /* rawSuggestions */,
1136                 null /* typedWord */,
1137                 false /* typedWordValid */,
1138                 false /* willAutoCorrect */,
1139                 false /* isObsoleteSuggestions */,
1140                 SuggestedWords.INPUT_STYLE_APPLICATION_SPECIFIED /* inputStyle */,
1141                 SuggestedWords.NOT_A_SEQUENCE_NUMBER);
1142         // When in fullscreen mode, show completions generated by the application forcibly
1143         setSuggestedWords(suggestedWords);
1144     }
1145 
1146     @Override
onComputeInsets(final InputMethodService.Insets outInsets)1147     public void onComputeInsets(final InputMethodService.Insets outInsets) {
1148         super.onComputeInsets(outInsets);
1149         // This method may be called before {@link #setInputView(View)}.
1150         if (mInputView == null) {
1151             return;
1152         }
1153         final SettingsValues settingsValues = mSettings.getCurrent();
1154         final View visibleKeyboardView = mKeyboardSwitcher.getVisibleKeyboardView();
1155         if (visibleKeyboardView == null || !hasSuggestionStripView()) {
1156             return;
1157         }
1158         final int inputHeight = mInputView.getHeight();
1159         if (isImeSuppressedByHardwareKeyboard() && !visibleKeyboardView.isShown()) {
1160             // If there is a hardware keyboard and a visible software keyboard view has been hidden,
1161             // no visual element will be shown on the screen.
1162             outInsets.contentTopInsets = inputHeight;
1163             outInsets.visibleTopInsets = inputHeight;
1164             mInsetsUpdater.setInsets(outInsets);
1165             return;
1166         }
1167         final int suggestionsHeight = (!mKeyboardSwitcher.isShowingEmojiPalettes()
1168                 && mSuggestionStripView.getVisibility() == View.VISIBLE)
1169                 ? mSuggestionStripView.getHeight() : 0;
1170         final int visibleTopY = inputHeight - visibleKeyboardView.getHeight() - suggestionsHeight;
1171         mSuggestionStripView.setMoreSuggestionsHeight(visibleTopY);
1172         // Need to set expanded touchable region only if a keyboard view is being shown.
1173         if (visibleKeyboardView.isShown()) {
1174             final int touchLeft = 0;
1175             final int touchTop = mKeyboardSwitcher.isShowingMoreKeysPanel() ? 0 : visibleTopY;
1176             final int touchRight = visibleKeyboardView.getWidth();
1177             final int touchBottom = inputHeight
1178                     // Extend touchable region below the keyboard.
1179                     + EXTENDED_TOUCHABLE_REGION_HEIGHT;
1180             outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION;
1181             outInsets.touchableRegion.set(touchLeft, touchTop, touchRight, touchBottom);
1182         }
1183         outInsets.contentTopInsets = visibleTopY;
1184         outInsets.visibleTopInsets = visibleTopY;
1185         mInsetsUpdater.setInsets(outInsets);
1186     }
1187 
startShowingInputView(final boolean needsToLoadKeyboard)1188     public void startShowingInputView(final boolean needsToLoadKeyboard) {
1189         mIsExecutingStartShowingInputView = true;
1190         // This {@link #showWindow(boolean)} will eventually call back
1191         // {@link #onEvaluateInputViewShown()}.
1192         showWindow(true /* showInput */);
1193         mIsExecutingStartShowingInputView = false;
1194         if (needsToLoadKeyboard) {
1195             loadKeyboard();
1196         }
1197     }
1198 
stopShowingInputView()1199     public void stopShowingInputView() {
1200         showWindow(false /* showInput */);
1201     }
1202 
1203     @Override
onShowInputRequested(final int flags, final boolean configChange)1204     public boolean onShowInputRequested(final int flags, final boolean configChange) {
1205         if (isImeSuppressedByHardwareKeyboard()) {
1206             return true;
1207         }
1208         return super.onShowInputRequested(flags, configChange);
1209     }
1210 
1211     @Override
onEvaluateInputViewShown()1212     public boolean onEvaluateInputViewShown() {
1213         if (mIsExecutingStartShowingInputView) {
1214             return true;
1215         }
1216         return super.onEvaluateInputViewShown();
1217     }
1218 
1219     @Override
onEvaluateFullscreenMode()1220     public boolean onEvaluateFullscreenMode() {
1221         final SettingsValues settingsValues = mSettings.getCurrent();
1222         if (isImeSuppressedByHardwareKeyboard()) {
1223             // If there is a hardware keyboard, disable full screen mode.
1224             return false;
1225         }
1226         // Reread resource value here, because this method is called by the framework as needed.
1227         final boolean isFullscreenModeAllowed = Settings.readUseFullscreenMode(getResources());
1228         if (super.onEvaluateFullscreenMode() && isFullscreenModeAllowed) {
1229             // TODO: Remove this hack. Actually we should not really assume NO_EXTRACT_UI
1230             // implies NO_FULLSCREEN. However, the framework mistakenly does.  i.e. NO_EXTRACT_UI
1231             // without NO_FULLSCREEN doesn't work as expected. Because of this we need this
1232             // hack for now.  Let's get rid of this once the framework gets fixed.
1233             final EditorInfo ei = getCurrentInputEditorInfo();
1234             return !(ei != null && ((ei.imeOptions & EditorInfo.IME_FLAG_NO_EXTRACT_UI) != 0));
1235         }
1236         return false;
1237     }
1238 
1239     @Override
updateFullscreenMode()1240     public void updateFullscreenMode() {
1241         super.updateFullscreenMode();
1242         updateSoftInputWindowLayoutParameters();
1243     }
1244 
updateSoftInputWindowLayoutParameters()1245     private void updateSoftInputWindowLayoutParameters() {
1246         // Override layout parameters to expand {@link SoftInputWindow} to the entire screen.
1247         // See {@link InputMethodService#setinputView(View)} and
1248         // {@link SoftInputWindow#updateWidthHeight(WindowManager.LayoutParams)}.
1249         final Window window = getWindow().getWindow();
1250         ViewLayoutUtils.updateLayoutHeightOf(window, LayoutParams.MATCH_PARENT);
1251         // This method may be called before {@link #setInputView(View)}.
1252         if (mInputView != null) {
1253             // In non-fullscreen mode, {@link InputView} and its parent inputArea should expand to
1254             // the entire screen and be placed at the bottom of {@link SoftInputWindow}.
1255             // In fullscreen mode, these shouldn't expand to the entire screen and should be
1256             // coexistent with {@link #mExtractedArea} above.
1257             // See {@link InputMethodService#setInputView(View) and
1258             // com.android.internal.R.layout.input_method.xml.
1259             final int layoutHeight = isFullscreenMode()
1260                     ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT;
1261             final View inputArea = window.findViewById(android.R.id.inputArea);
1262             ViewLayoutUtils.updateLayoutHeightOf(inputArea, layoutHeight);
1263             ViewLayoutUtils.updateLayoutGravityOf(inputArea, Gravity.BOTTOM);
1264             ViewLayoutUtils.updateLayoutHeightOf(mInputView, layoutHeight);
1265         }
1266     }
1267 
getCurrentAutoCapsState()1268     int getCurrentAutoCapsState() {
1269         return mInputLogic.getCurrentAutoCapsState(mSettings.getCurrent());
1270     }
1271 
getCurrentRecapitalizeState()1272     int getCurrentRecapitalizeState() {
1273         return mInputLogic.getCurrentRecapitalizeState();
1274     }
1275 
1276     /**
1277      * @param codePoints code points to get coordinates for.
1278      * @return x,y coordinates for this keyboard, as a flattened array.
1279      */
getCoordinatesForCurrentKeyboard(final int[] codePoints)1280     public int[] getCoordinatesForCurrentKeyboard(final int[] codePoints) {
1281         final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
1282         if (null == keyboard) {
1283             return CoordinateUtils.newCoordinateArray(codePoints.length,
1284                     Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
1285         }
1286         return keyboard.getCoordinates(codePoints);
1287     }
1288 
1289     // Callback for the {@link SuggestionStripView}, to call when the important notice strip is
1290     // pressed.
1291     @Override
showImportantNoticeContents()1292     public void showImportantNoticeContents() {
1293         PermissionsManager.get(this).requestPermissions(
1294                 this /* PermissionsResultCallback */,
1295                 null /* activity */, permission.READ_CONTACTS);
1296     }
1297 
1298     @Override
onRequestPermissionsResult(boolean allGranted)1299     public void onRequestPermissionsResult(boolean allGranted) {
1300         ImportantNoticeUtils.updateContactsNoticeShown(this /* context */);
1301         setNeutralSuggestionStrip();
1302     }
1303 
displaySettingsDialog()1304     public void displaySettingsDialog() {
1305         if (isShowingOptionDialog()) {
1306             return;
1307         }
1308         showSubtypeSelectorAndSettings();
1309     }
1310 
1311     @Override
onCustomRequest(final int requestCode)1312     public boolean onCustomRequest(final int requestCode) {
1313         if (isShowingOptionDialog()) return false;
1314         switch (requestCode) {
1315         case Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER:
1316             if (mRichImm.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) {
1317                 mRichImm.getInputMethodManager().showInputMethodPicker();
1318                 return true;
1319             }
1320             return false;
1321         }
1322         return false;
1323     }
1324 
isShowingOptionDialog()1325     private boolean isShowingOptionDialog() {
1326         return mOptionsDialog != null && mOptionsDialog.isShowing();
1327     }
1328 
switchLanguage(final InputMethodSubtype subtype)1329     public void switchLanguage(final InputMethodSubtype subtype) {
1330         final IBinder token = getWindow().getWindow().getAttributes().token;
1331         mRichImm.setInputMethodAndSubtype(token, subtype);
1332     }
1333 
1334     // TODO: Revise the language switch key behavior to make it much smarter and more reasonable.
switchToNextSubtype()1335     public void switchToNextSubtype() {
1336         final IBinder token = getWindow().getWindow().getAttributes().token;
1337         if (shouldSwitchToOtherInputMethods()) {
1338             mRichImm.switchToNextInputMethod(token, false /* onlyCurrentIme */);
1339             return;
1340         }
1341         mSubtypeState.switchSubtype(token, mRichImm);
1342     }
1343 
1344     // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for
1345     // alphabetic shift and shift while in symbol layout and get rid of this method.
getCodePointForKeyboard(final int codePoint)1346     private int getCodePointForKeyboard(final int codePoint) {
1347         if (Constants.CODE_SHIFT == codePoint) {
1348             final Keyboard currentKeyboard = mKeyboardSwitcher.getKeyboard();
1349             if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) {
1350                 return codePoint;
1351             }
1352             return Constants.CODE_SYMBOL_SHIFT;
1353         }
1354         return codePoint;
1355     }
1356 
1357     // Implementation of {@link KeyboardActionListener}.
1358     @Override
onCodeInput(final int codePoint, final int x, final int y, final boolean isKeyRepeat)1359     public void onCodeInput(final int codePoint, final int x, final int y,
1360             final boolean isKeyRepeat) {
1361         // TODO: this processing does not belong inside LatinIME, the caller should be doing this.
1362         final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
1363         // x and y include some padding, but everything down the line (especially native
1364         // code) needs the coordinates in the keyboard frame.
1365         // TODO: We should reconsider which coordinate system should be used to represent
1366         // keyboard event. Also we should pull this up -- LatinIME has no business doing
1367         // this transformation, it should be done already before calling onEvent.
1368         final int keyX = mainKeyboardView.getKeyX(x);
1369         final int keyY = mainKeyboardView.getKeyY(y);
1370         final Event event = createSoftwareKeypressEvent(getCodePointForKeyboard(codePoint),
1371                 keyX, keyY, isKeyRepeat);
1372         onEvent(event);
1373     }
1374 
1375     // This method is public for testability of LatinIME, but also in the future it should
1376     // completely replace #onCodeInput.
onEvent(@onnull final Event event)1377     public void onEvent(@Nonnull final Event event) {
1378         if (Constants.CODE_SHORTCUT == event.mKeyCode) {
1379             mRichImm.switchToShortcutIme(this);
1380         }
1381         final InputTransaction completeInputTransaction =
1382                 mInputLogic.onCodeInput(mSettings.getCurrent(), event,
1383                         mKeyboardSwitcher.getKeyboardShiftMode(),
1384                         mKeyboardSwitcher.getCurrentKeyboardScriptId(), mHandler);
1385         updateStateAfterInputTransaction(completeInputTransaction);
1386         mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState());
1387     }
1388 
1389     // A helper method to split the code point and the key code. Ultimately, they should not be
1390     // squashed into the same variable, and this method should be removed.
1391     // public for testing, as we don't want to copy the same logic into test code
1392     @Nonnull
createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int keyX, final int keyY, final boolean isKeyRepeat)1393     public static Event createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int keyX,
1394              final int keyY, final boolean isKeyRepeat) {
1395         final int keyCode;
1396         final int codePoint;
1397         if (keyCodeOrCodePoint <= 0) {
1398             keyCode = keyCodeOrCodePoint;
1399             codePoint = Event.NOT_A_CODE_POINT;
1400         } else {
1401             keyCode = Event.NOT_A_KEY_CODE;
1402             codePoint = keyCodeOrCodePoint;
1403         }
1404         return Event.createSoftwareKeypressEvent(codePoint, keyCode, keyX, keyY, isKeyRepeat);
1405     }
1406 
1407     // Called from PointerTracker through the KeyboardActionListener interface
1408     @Override
onTextInput(final String rawText)1409     public void onTextInput(final String rawText) {
1410         // TODO: have the keyboard pass the correct key code when we need it.
1411         final Event event = Event.createSoftwareTextEvent(rawText, Constants.CODE_OUTPUT_TEXT);
1412         final InputTransaction completeInputTransaction =
1413                 mInputLogic.onTextInput(mSettings.getCurrent(), event,
1414                         mKeyboardSwitcher.getKeyboardShiftMode(), mHandler);
1415         updateStateAfterInputTransaction(completeInputTransaction);
1416         mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState());
1417     }
1418 
1419     @Override
onStartBatchInput()1420     public void onStartBatchInput() {
1421         mInputLogic.onStartBatchInput(mSettings.getCurrent(), mKeyboardSwitcher, mHandler);
1422         mGestureConsumer.onGestureStarted(
1423                 mRichImm.getCurrentSubtypeLocale(),
1424                 mKeyboardSwitcher.getKeyboard());
1425     }
1426 
1427     @Override
onUpdateBatchInput(final InputPointers batchPointers)1428     public void onUpdateBatchInput(final InputPointers batchPointers) {
1429         mInputLogic.onUpdateBatchInput(batchPointers);
1430     }
1431 
1432     @Override
onEndBatchInput(final InputPointers batchPointers)1433     public void onEndBatchInput(final InputPointers batchPointers) {
1434         mInputLogic.onEndBatchInput(batchPointers);
1435         mGestureConsumer.onGestureCompleted(batchPointers);
1436     }
1437 
1438     @Override
onCancelBatchInput()1439     public void onCancelBatchInput() {
1440         mInputLogic.onCancelBatchInput(mHandler);
1441         mGestureConsumer.onGestureCanceled();
1442     }
1443 
1444     /**
1445      * To be called after the InputLogic has gotten a chance to act on the suggested words by the
1446      * IME for the full gesture, possibly updating the TextView to reflect the first suggestion.
1447      * <p>
1448      * This method must be run on the UI Thread.
1449      * @param suggestedWords suggested words by the IME for the full gesture.
1450      */
onTailBatchInputResultShown(final SuggestedWords suggestedWords)1451     public void onTailBatchInputResultShown(final SuggestedWords suggestedWords) {
1452         mGestureConsumer.onImeSuggestionsProcessed(suggestedWords,
1453                 mInputLogic.getComposingStart(), mInputLogic.getComposingLength(),
1454                 mDictionaryFacilitator);
1455     }
1456 
1457     // This method must run on the UI Thread.
showGesturePreviewAndSuggestionStrip(@onnull final SuggestedWords suggestedWords, final boolean dismissGestureFloatingPreviewText)1458     void showGesturePreviewAndSuggestionStrip(@Nonnull final SuggestedWords suggestedWords,
1459             final boolean dismissGestureFloatingPreviewText) {
1460         showSuggestionStrip(suggestedWords);
1461         final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
1462         mainKeyboardView.showGestureFloatingPreviewText(suggestedWords,
1463                 dismissGestureFloatingPreviewText /* dismissDelayed */);
1464     }
1465 
1466     // Called from PointerTracker through the KeyboardActionListener interface
1467     @Override
onFinishSlidingInput()1468     public void onFinishSlidingInput() {
1469         // User finished sliding input.
1470         mKeyboardSwitcher.onFinishSlidingInput(getCurrentAutoCapsState(),
1471                 getCurrentRecapitalizeState());
1472     }
1473 
1474     // Called from PointerTracker through the KeyboardActionListener interface
1475     @Override
onCancelInput()1476     public void onCancelInput() {
1477         // User released a finger outside any key
1478         // Nothing to do so far.
1479     }
1480 
hasSuggestionStripView()1481     public boolean hasSuggestionStripView() {
1482         return null != mSuggestionStripView;
1483     }
1484 
setSuggestedWords(final SuggestedWords suggestedWords)1485     private void setSuggestedWords(final SuggestedWords suggestedWords) {
1486         final SettingsValues currentSettingsValues = mSettings.getCurrent();
1487         mInputLogic.setSuggestedWords(suggestedWords);
1488         // TODO: Modify this when we support suggestions with hard keyboard
1489         if (!hasSuggestionStripView()) {
1490             return;
1491         }
1492         if (!onEvaluateInputViewShown()) {
1493             return;
1494         }
1495 
1496         final boolean shouldShowImportantNotice =
1497                 ImportantNoticeUtils.shouldShowImportantNotice(this, currentSettingsValues);
1498         final boolean shouldShowSuggestionCandidates =
1499                 currentSettingsValues.mInputAttributes.mShouldShowSuggestions
1500                 && currentSettingsValues.isSuggestionsEnabledPerUserSettings();
1501         final boolean shouldShowSuggestionsStripUnlessPassword = shouldShowImportantNotice
1502                 || currentSettingsValues.mShowsVoiceInputKey
1503                 || shouldShowSuggestionCandidates
1504                 || currentSettingsValues.isApplicationSpecifiedCompletionsOn();
1505         final boolean shouldShowSuggestionsStrip = shouldShowSuggestionsStripUnlessPassword
1506                 && !currentSettingsValues.mInputAttributes.mIsPasswordField;
1507         mSuggestionStripView.updateVisibility(shouldShowSuggestionsStrip, isFullscreenMode());
1508         if (!shouldShowSuggestionsStrip) {
1509             return;
1510         }
1511 
1512         final boolean isEmptyApplicationSpecifiedCompletions =
1513                 currentSettingsValues.isApplicationSpecifiedCompletionsOn()
1514                 && suggestedWords.isEmpty();
1515         final boolean noSuggestionsFromDictionaries = suggestedWords.isEmpty()
1516                 || suggestedWords.isPunctuationSuggestions()
1517                 || isEmptyApplicationSpecifiedCompletions;
1518         final boolean isBeginningOfSentencePrediction = (suggestedWords.mInputStyle
1519                 == SuggestedWords.INPUT_STYLE_BEGINNING_OF_SENTENCE_PREDICTION);
1520         final boolean noSuggestionsToOverrideImportantNotice = noSuggestionsFromDictionaries
1521                 || isBeginningOfSentencePrediction;
1522         if (shouldShowImportantNotice && noSuggestionsToOverrideImportantNotice) {
1523             if (mSuggestionStripView.maybeShowImportantNoticeTitle()) {
1524                 return;
1525             }
1526         }
1527 
1528         if (currentSettingsValues.isSuggestionsEnabledPerUserSettings()
1529                 || currentSettingsValues.isApplicationSpecifiedCompletionsOn()
1530                 // We should clear the contextual strip if there is no suggestion from dictionaries.
1531                 || noSuggestionsFromDictionaries) {
1532             mSuggestionStripView.setSuggestions(suggestedWords,
1533                     mRichImm.getCurrentSubtype().isRtlSubtype());
1534         }
1535     }
1536 
1537     // TODO[IL]: Move this out of LatinIME.
getSuggestedWords(final int inputStyle, final int sequenceNumber, final OnGetSuggestedWordsCallback callback)1538     public void getSuggestedWords(final int inputStyle, final int sequenceNumber,
1539             final OnGetSuggestedWordsCallback callback) {
1540         final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
1541         if (keyboard == null) {
1542             callback.onGetSuggestedWords(SuggestedWords.getEmptyInstance());
1543             return;
1544         }
1545         mInputLogic.getSuggestedWords(mSettings.getCurrent(), keyboard,
1546                 mKeyboardSwitcher.getKeyboardShiftMode(), inputStyle, sequenceNumber, callback);
1547     }
1548 
1549     @Override
showSuggestionStrip(final SuggestedWords suggestedWords)1550     public void showSuggestionStrip(final SuggestedWords suggestedWords) {
1551         if (suggestedWords.isEmpty()) {
1552             setNeutralSuggestionStrip();
1553         } else {
1554             setSuggestedWords(suggestedWords);
1555         }
1556         // Cache the auto-correction in accessibility code so we can speak it if the user
1557         // touches a key that will insert it.
1558         AccessibilityUtils.getInstance().setAutoCorrection(suggestedWords);
1559     }
1560 
1561     // Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener}
1562     // interface
1563     @Override
pickSuggestionManually(final SuggestedWordInfo suggestionInfo)1564     public void pickSuggestionManually(final SuggestedWordInfo suggestionInfo) {
1565         final InputTransaction completeInputTransaction = mInputLogic.onPickSuggestionManually(
1566                 mSettings.getCurrent(), suggestionInfo,
1567                 mKeyboardSwitcher.getKeyboardShiftMode(),
1568                 mKeyboardSwitcher.getCurrentKeyboardScriptId(),
1569                 mHandler);
1570         updateStateAfterInputTransaction(completeInputTransaction);
1571     }
1572 
1573     // This will show either an empty suggestion strip (if prediction is enabled) or
1574     // punctuation suggestions (if it's disabled).
1575     @Override
setNeutralSuggestionStrip()1576     public void setNeutralSuggestionStrip() {
1577         final SettingsValues currentSettings = mSettings.getCurrent();
1578         final SuggestedWords neutralSuggestions = currentSettings.mBigramPredictionEnabled
1579                 ? SuggestedWords.getEmptyInstance()
1580                 : currentSettings.mSpacingAndPunctuations.mSuggestPuncList;
1581         setSuggestedWords(neutralSuggestions);
1582     }
1583 
1584     // Outside LatinIME, only used by the {@link InputTestsBase} test suite.
1585     @UsedForTesting
loadKeyboard()1586     void loadKeyboard() {
1587         // Since we are switching languages, the most urgent thing is to let the keyboard graphics
1588         // update. LoadKeyboard does that, but we need to wait for buffer flip for it to be on
1589         // the screen. Anything we do right now will delay this, so wait until the next frame
1590         // before we do the rest, like reopening dictionaries and updating suggestions. So we
1591         // post a message.
1592         mHandler.postReopenDictionaries();
1593         loadSettings();
1594         if (mKeyboardSwitcher.getMainKeyboardView() != null) {
1595             // Reload keyboard because the current language has been changed.
1596             mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettings.getCurrent(),
1597                     getCurrentAutoCapsState(), getCurrentRecapitalizeState());
1598         }
1599     }
1600 
1601     /**
1602      * After an input transaction has been executed, some state must be updated. This includes
1603      * the shift state of the keyboard and suggestions. This method looks at the finished
1604      * inputTransaction to find out what is necessary and updates the state accordingly.
1605      * @param inputTransaction The transaction that has been executed.
1606      */
updateStateAfterInputTransaction(final InputTransaction inputTransaction)1607     private void updateStateAfterInputTransaction(final InputTransaction inputTransaction) {
1608         switch (inputTransaction.getRequiredShiftUpdate()) {
1609         case InputTransaction.SHIFT_UPDATE_LATER:
1610             mHandler.postUpdateShiftState();
1611             break;
1612         case InputTransaction.SHIFT_UPDATE_NOW:
1613             mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(),
1614                     getCurrentRecapitalizeState());
1615             break;
1616         default: // SHIFT_NO_UPDATE
1617         }
1618         if (inputTransaction.requiresUpdateSuggestions()) {
1619             final int inputStyle;
1620             if (inputTransaction.mEvent.isSuggestionStripPress()) {
1621                 // Suggestion strip press: no input.
1622                 inputStyle = SuggestedWords.INPUT_STYLE_NONE;
1623             } else if (inputTransaction.mEvent.isGesture()) {
1624                 inputStyle = SuggestedWords.INPUT_STYLE_TAIL_BATCH;
1625             } else {
1626                 inputStyle = SuggestedWords.INPUT_STYLE_TYPING;
1627             }
1628             mHandler.postUpdateSuggestionStrip(inputStyle);
1629         }
1630         if (inputTransaction.didAffectContents()) {
1631             mSubtypeState.setCurrentSubtypeHasBeenUsed();
1632         }
1633     }
1634 
hapticAndAudioFeedback(final int code, final int repeatCount)1635     private void hapticAndAudioFeedback(final int code, final int repeatCount) {
1636         final MainKeyboardView keyboardView = mKeyboardSwitcher.getMainKeyboardView();
1637         if (keyboardView != null && keyboardView.isInDraggingFinger()) {
1638             // No need to feedback while finger is dragging.
1639             return;
1640         }
1641         if (repeatCount > 0) {
1642             if (code == Constants.CODE_DELETE && !mInputLogic.mConnection.canDeleteCharacters()) {
1643                 // No need to feedback when repeat delete key will have no effect.
1644                 return;
1645             }
1646             // TODO: Use event time that the last feedback has been generated instead of relying on
1647             // a repeat count to thin out feedback.
1648             if (repeatCount % PERIOD_FOR_AUDIO_AND_HAPTIC_FEEDBACK_IN_KEY_REPEAT == 0) {
1649                 return;
1650             }
1651         }
1652         final AudioAndHapticFeedbackManager feedbackManager =
1653                 AudioAndHapticFeedbackManager.getInstance();
1654         if (repeatCount == 0) {
1655             // TODO: Reconsider how to perform haptic feedback when repeating key.
1656             feedbackManager.performHapticFeedback(keyboardView);
1657         }
1658         feedbackManager.performAudioFeedback(code);
1659     }
1660 
1661     // Callback of the {@link KeyboardActionListener}. This is called when a key is depressed;
1662     // release matching call is {@link #onReleaseKey(int,boolean)} below.
1663     @Override
onPressKey(final int primaryCode, final int repeatCount, final boolean isSinglePointer)1664     public void onPressKey(final int primaryCode, final int repeatCount,
1665             final boolean isSinglePointer) {
1666         mKeyboardSwitcher.onPressKey(primaryCode, isSinglePointer, getCurrentAutoCapsState(),
1667                 getCurrentRecapitalizeState());
1668         hapticAndAudioFeedback(primaryCode, repeatCount);
1669     }
1670 
1671     // Callback of the {@link KeyboardActionListener}. This is called when a key is released;
1672     // press matching call is {@link #onPressKey(int,int,boolean)} above.
1673     @Override
onReleaseKey(final int primaryCode, final boolean withSliding)1674     public void onReleaseKey(final int primaryCode, final boolean withSliding) {
1675         mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding, getCurrentAutoCapsState(),
1676                 getCurrentRecapitalizeState());
1677     }
1678 
getHardwareKeyEventDecoder(final int deviceId)1679     private HardwareEventDecoder getHardwareKeyEventDecoder(final int deviceId) {
1680         final HardwareEventDecoder decoder = mHardwareEventDecoders.get(deviceId);
1681         if (null != decoder) return decoder;
1682         // TODO: create the decoder according to the specification
1683         final HardwareEventDecoder newDecoder = new HardwareKeyboardEventDecoder(deviceId);
1684         mHardwareEventDecoders.put(deviceId, newDecoder);
1685         return newDecoder;
1686     }
1687 
1688     // Hooks for hardware keyboard
1689     @Override
onKeyDown(final int keyCode, final KeyEvent keyEvent)1690     public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) {
1691         if (mEmojiAltPhysicalKeyDetector == null) {
1692             mEmojiAltPhysicalKeyDetector = new EmojiAltPhysicalKeyDetector(
1693                     getApplicationContext().getResources());
1694         }
1695         mEmojiAltPhysicalKeyDetector.onKeyDown(keyEvent);
1696         if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) {
1697             return super.onKeyDown(keyCode, keyEvent);
1698         }
1699         final Event event = getHardwareKeyEventDecoder(
1700                 keyEvent.getDeviceId()).decodeHardwareKey(keyEvent);
1701         // If the event is not handled by LatinIME, we just pass it to the parent implementation.
1702         // If it's handled, we return true because we did handle it.
1703         if (event.isHandled()) {
1704             mInputLogic.onCodeInput(mSettings.getCurrent(), event,
1705                     mKeyboardSwitcher.getKeyboardShiftMode(),
1706                     // TODO: this is not necessarily correct for a hardware keyboard right now
1707                     mKeyboardSwitcher.getCurrentKeyboardScriptId(),
1708                     mHandler);
1709             return true;
1710         }
1711         return super.onKeyDown(keyCode, keyEvent);
1712     }
1713 
1714     @Override
onKeyUp(final int keyCode, final KeyEvent keyEvent)1715     public boolean onKeyUp(final int keyCode, final KeyEvent keyEvent) {
1716         if (mEmojiAltPhysicalKeyDetector == null) {
1717             mEmojiAltPhysicalKeyDetector = new EmojiAltPhysicalKeyDetector(
1718                     getApplicationContext().getResources());
1719         }
1720         mEmojiAltPhysicalKeyDetector.onKeyUp(keyEvent);
1721         if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) {
1722             return super.onKeyUp(keyCode, keyEvent);
1723         }
1724         final long keyIdentifier = keyEvent.getDeviceId() << 32 + keyEvent.getKeyCode();
1725         if (mInputLogic.mCurrentlyPressedHardwareKeys.remove(keyIdentifier)) {
1726             return true;
1727         }
1728         return super.onKeyUp(keyCode, keyEvent);
1729     }
1730 
1731     // onKeyDown and onKeyUp are the main events we are interested in. There are two more events
1732     // related to handling of hardware key events that we may want to implement in the future:
1733     // boolean onKeyLongPress(final int keyCode, final KeyEvent event);
1734     // boolean onKeyMultiple(final int keyCode, final int count, final KeyEvent event);
1735 
1736     // receive ringer mode change.
1737     private final BroadcastReceiver mRingerModeChangeReceiver = new BroadcastReceiver() {
1738         @Override
1739         public void onReceive(final Context context, final Intent intent) {
1740             final String action = intent.getAction();
1741             if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
1742                 AudioAndHapticFeedbackManager.getInstance().onRingerModeChanged();
1743             }
1744         }
1745     };
1746 
launchSettings(final String extraEntryValue)1747     void launchSettings(final String extraEntryValue) {
1748         mInputLogic.commitTyped(mSettings.getCurrent(), LastComposedWord.NOT_A_SEPARATOR);
1749         requestHideSelf(0);
1750         final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
1751         if (mainKeyboardView != null) {
1752             mainKeyboardView.closing();
1753         }
1754         final Intent intent = new Intent();
1755         intent.setClass(LatinIME.this, SettingsActivity.class);
1756         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1757                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
1758                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
1759         intent.putExtra(SettingsActivity.EXTRA_SHOW_HOME_AS_UP, false);
1760         intent.putExtra(SettingsActivity.EXTRA_ENTRY_KEY, extraEntryValue);
1761         startActivity(intent);
1762     }
1763 
showSubtypeSelectorAndSettings()1764     private void showSubtypeSelectorAndSettings() {
1765         final CharSequence title = getString(R.string.english_ime_input_options);
1766         // TODO: Should use new string "Select active input modes".
1767         final CharSequence languageSelectionTitle = getString(R.string.language_selection_title);
1768         final CharSequence[] items = new CharSequence[] {
1769                 languageSelectionTitle,
1770                 getString(ApplicationUtils.getActivityTitleResId(this, SettingsActivity.class))
1771         };
1772         final String imeId = mRichImm.getInputMethodIdOfThisIme();
1773         final OnClickListener listener = new OnClickListener() {
1774             @Override
1775             public void onClick(DialogInterface di, int position) {
1776                 di.dismiss();
1777                 switch (position) {
1778                 case 0:
1779                     final Intent intent = IntentUtils.getInputLanguageSelectionIntent(
1780                             imeId,
1781                             Intent.FLAG_ACTIVITY_NEW_TASK
1782                                     | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
1783                                     | Intent.FLAG_ACTIVITY_CLEAR_TOP);
1784                     intent.putExtra(Intent.EXTRA_TITLE, languageSelectionTitle);
1785                     startActivity(intent);
1786                     break;
1787                 case 1:
1788                     launchSettings(SettingsActivity.EXTRA_ENTRY_VALUE_LONG_PRESS_COMMA);
1789                     break;
1790                 }
1791             }
1792         };
1793         final AlertDialog.Builder builder = new AlertDialog.Builder(
1794                 DialogUtils.getPlatformDialogThemeContext(this));
1795         builder.setItems(items, listener).setTitle(title);
1796         final AlertDialog dialog = builder.create();
1797         dialog.setCancelable(true /* cancelable */);
1798         dialog.setCanceledOnTouchOutside(true /* cancelable */);
1799         showOptionDialog(dialog);
1800     }
1801 
1802     // TODO: Move this method out of {@link LatinIME}.
showOptionDialog(final AlertDialog dialog)1803     private void showOptionDialog(final AlertDialog dialog) {
1804         final IBinder windowToken = mKeyboardSwitcher.getMainKeyboardView().getWindowToken();
1805         if (windowToken == null) {
1806             return;
1807         }
1808 
1809         final Window window = dialog.getWindow();
1810         final WindowManager.LayoutParams lp = window.getAttributes();
1811         lp.token = windowToken;
1812         lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
1813         window.setAttributes(lp);
1814         window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
1815 
1816         mOptionsDialog = dialog;
1817         dialog.show();
1818     }
1819 
1820     @UsedForTesting
getSuggestedWordsForTest()1821     SuggestedWords getSuggestedWordsForTest() {
1822         // You may not use this method for anything else than debug
1823         return DebugFlags.DEBUG_ENABLED ? mInputLogic.mSuggestedWords : null;
1824     }
1825 
1826     // DO NOT USE THIS for any other purpose than testing. This is information private to LatinIME.
1827     @UsedForTesting
waitForLoadingDictionaries(final long timeout, final TimeUnit unit)1828     void waitForLoadingDictionaries(final long timeout, final TimeUnit unit)
1829             throws InterruptedException {
1830         mDictionaryFacilitator.waitForLoadingDictionariesForTesting(timeout, unit);
1831     }
1832 
1833     // DO NOT USE THIS for any other purpose than testing. This can break the keyboard badly.
1834     @UsedForTesting
replaceDictionariesForTest(final Locale locale)1835     void replaceDictionariesForTest(final Locale locale) {
1836         final SettingsValues settingsValues = mSettings.getCurrent();
1837         mDictionaryFacilitator.resetDictionaries(this, locale,
1838             settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts,
1839             false /* forceReloadMainDictionary */,
1840             settingsValues.mAccount, "", /* dictionaryNamePrefix */
1841             this /* DictionaryInitializationListener */);
1842     }
1843 
1844     // DO NOT USE THIS for any other purpose than testing.
1845     @UsedForTesting
clearPersonalizedDictionariesForTest()1846     void clearPersonalizedDictionariesForTest() {
1847         mDictionaryFacilitator.clearUserHistoryDictionary(this);
1848     }
1849 
1850     @UsedForTesting
getEnabledSubtypesForTest()1851     List<InputMethodSubtype> getEnabledSubtypesForTest() {
1852         return (mRichImm != null) ? mRichImm.getMyEnabledInputMethodSubtypeList(
1853                 true /* allowsImplicitlySelectedSubtypes */) : new ArrayList<InputMethodSubtype>();
1854     }
1855 
dumpDictionaryForDebug(final String dictName)1856     public void dumpDictionaryForDebug(final String dictName) {
1857         if (!mDictionaryFacilitator.isActive()) {
1858             resetDictionaryFacilitatorIfNecessary();
1859         }
1860         mDictionaryFacilitator.dumpDictionaryForDebug(dictName);
1861     }
1862 
debugDumpStateAndCrashWithException(final String context)1863     public void debugDumpStateAndCrashWithException(final String context) {
1864         final SettingsValues settingsValues = mSettings.getCurrent();
1865         final StringBuilder s = new StringBuilder(settingsValues.toString());
1866         s.append("\nAttributes : ").append(settingsValues.mInputAttributes)
1867                 .append("\nContext : ").append(context);
1868         throw new RuntimeException(s.toString());
1869     }
1870 
1871     @Override
dump(final FileDescriptor fd, final PrintWriter fout, final String[] args)1872     protected void dump(final FileDescriptor fd, final PrintWriter fout, final String[] args) {
1873         super.dump(fd, fout, args);
1874 
1875         final Printer p = new PrintWriterPrinter(fout);
1876         p.println("LatinIME state :");
1877         p.println("  VersionCode = " + ApplicationUtils.getVersionCode(this));
1878         p.println("  VersionName = " + ApplicationUtils.getVersionName(this));
1879         final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
1880         final int keyboardMode = keyboard != null ? keyboard.mId.mMode : -1;
1881         p.println("  Keyboard mode = " + keyboardMode);
1882         final SettingsValues settingsValues = mSettings.getCurrent();
1883         p.println(settingsValues.dump());
1884         p.println(mDictionaryFacilitator.dump(this /* context */));
1885         // TODO: Dump all settings values
1886     }
1887 
shouldSwitchToOtherInputMethods()1888     public boolean shouldSwitchToOtherInputMethods() {
1889         // TODO: Revisit here to reorganize the settings. Probably we can/should use different
1890         // strategy once the implementation of
1891         // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} is defined well.
1892         final boolean fallbackValue = mSettings.getCurrent().mIncludesOtherImesInLanguageSwitchList;
1893         final IBinder token = getWindow().getWindow().getAttributes().token;
1894         if (token == null) {
1895             return fallbackValue;
1896         }
1897         return mRichImm.shouldOfferSwitchingToNextInputMethod(token, fallbackValue);
1898     }
1899 
shouldShowLanguageSwitchKey()1900     public boolean shouldShowLanguageSwitchKey() {
1901         // TODO: Revisit here to reorganize the settings. Probably we can/should use different
1902         // strategy once the implementation of
1903         // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} is defined well.
1904         final boolean fallbackValue = mSettings.getCurrent().isLanguageSwitchKeyEnabled();
1905         final IBinder token = getWindow().getWindow().getAttributes().token;
1906         if (token == null) {
1907             return fallbackValue;
1908         }
1909         return mRichImm.shouldOfferSwitchingToNextInputMethod(token, fallbackValue);
1910     }
1911 
setNavigationBarVisibility(final boolean visible)1912     private void setNavigationBarVisibility(final boolean visible) {
1913         if (BuildCompatUtils.EFFECTIVE_SDK_INT > Build.VERSION_CODES.M) {
1914             // For N and later, IMEs can specify Color.TRANSPARENT to make the navigation bar
1915             // transparent.  For other colors the system uses the default color.
1916             getWindow().getWindow().setNavigationBarColor(
1917                     visible ? Color.BLACK : Color.TRANSPARENT);
1918         }
1919     }
1920 }
1921