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