Home | History | Annotate | Download | only in latin
      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 
    178         public HideSoftInputReceiver(InputMethodService ims) {
    179             mIms = ims;
    180         }
    181 
    182         @Override
    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 
    227         public UIHandler(@Nonnull final LatinIME ownerInstance) {
    228             super(ownerInstance);
    229         }
    230 
    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
    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 
    316         public void postUpdateSuggestionStrip(final int inputStyle) {
    317             sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP, inputStyle,
    318                     0 /* ignored */), mDelayInMillisecondsToUpdateSuggestions);
    319         }
    320 
    321         public void postReopenDictionaries() {
    322             sendMessage(obtainMessage(MSG_REOPEN_DICTIONARIES));
    323         }
    324 
    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 
    346         public void postResumeSuggestions(final boolean shouldDelay) {
    347             postResumeSuggestionsInternal(shouldDelay, false /* forStartInput */);
    348         }
    349 
    350         public void postResumeSuggestionsForStartInput(final boolean shouldDelay) {
    351             postResumeSuggestionsInternal(shouldDelay, true /* forStartInput */);
    352         }
    353 
    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 
    360         public void postWaitForDictionaryLoad() {
    361             sendMessageDelayed(obtainMessage(MSG_WAIT_FOR_DICTIONARY_LOAD),
    362                     DELAY_WAIT_FOR_DICTIONARY_LOAD_MILLIS);
    363         }
    364 
    365         public void cancelWaitForDictionaryLoad() {
    366             removeMessages(MSG_WAIT_FOR_DICTIONARY_LOAD);
    367         }
    368 
    369         public boolean hasPendingWaitForDictionaryLoad() {
    370             return hasMessages(MSG_WAIT_FOR_DICTIONARY_LOAD);
    371         }
    372 
    373         public void cancelUpdateSuggestionStrip() {
    374             removeMessages(MSG_UPDATE_SUGGESTION_STRIP);
    375         }
    376 
    377         public boolean hasPendingUpdateSuggestions() {
    378             return hasMessages(MSG_UPDATE_SUGGESTION_STRIP);
    379         }
    380 
    381         public boolean hasPendingReopenDictionaries() {
    382             return hasMessages(MSG_REOPEN_DICTIONARIES);
    383         }
    384 
    385         public void postUpdateShiftState() {
    386             removeMessages(MSG_UPDATE_SHIFT_STATE);
    387             sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE),
    388                     mDelayInMillisecondsToUpdateShiftState);
    389         }
    390 
    391         public void postDeallocateMemory() {
    392             sendMessageDelayed(obtainMessage(MSG_DEALLOCATE_MEMORY),
    393                     DELAY_DEALLOCATE_MEMORY_MILLIS);
    394         }
    395 
    396         public void cancelDeallocateMemory() {
    397             removeMessages(MSG_DEALLOCATE_MEMORY);
    398         }
    399 
    400         public boolean hasPendingDeallocateMemory() {
    401             return hasMessages(MSG_DEALLOCATE_MEMORY);
    402         }
    403 
    404         @UsedForTesting
    405         public void removeAllMessages() {
    406             for (int i = 0; i <= MSG_LAST; ++i) {
    407                 removeMessages(i);
    408             }
    409         }
    410 
    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 
    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 
    427         public void showTailBatchInputResult(final SuggestedWords suggestedWords) {
    428             obtainMessage(MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED, suggestedWords).sendToTarget();
    429         }
    430 
    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 
    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 
    456         private void resetPendingImsCallback() {
    457             mHasPendingFinishInputView = false;
    458             mHasPendingFinishInput = false;
    459             mHasPendingStartInput = false;
    460         }
    461 
    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 
    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 
    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 
    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 
    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 
    551         public void setCurrentSubtypeHasBeenUsed() {
    552             mCurrentSubtypeHasBeenUsed = true;
    553         }
    554 
    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 {
    577         JniUtils.loadNativeLibrary();
    578     }
    579 
    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
    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
    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 
    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
    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 
    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.
    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      */
    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
    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
    747     public void recycle() {
    748         unregisterReceiver(mDictionaryPackInstallReceiver);
    749         unregisterReceiver(mDictionaryDumpBroadcastReceiver);
    750         unregisterReceiver(mRingerModeChangeReceiver);
    751         mInputLogic.recycle();
    752     }
    753 
    754     private boolean isImeSuppressedByHardwareKeyboard() {
    755         final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance();
    756         return !onEvaluateInputViewShown() && switcher.isImeSuppressedByHardwareKeyboard(
    757                 mSettings.getCurrent(), switcher.getKeyboardSwitchState());
    758     }
    759 
    760     @Override
    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
    787     public View onCreateInputView() {
    788         StatsUtils.onCreateInputView();
    789         return mKeyboardSwitcher.onCreateInputView(mIsHardwareAcceleratedDrawingEnabled);
    790     }
    791 
    792     @Override
    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
    805     public void setCandidatesView(final View view) {
    806         // To ensure that CandidatesView will never be set.
    807     }
    808 
    809     @Override
    810     public void onStartInput(final EditorInfo editorInfo, final boolean restarting) {
    811         mHandler.onStartInput(editorInfo, restarting);
    812     }
    813 
    814     @Override
    815     public void onStartInputView(final EditorInfo editorInfo, final boolean restarting) {
    816         mHandler.onStartInputView(editorInfo, restarting);
    817         mStatsUtilsManager.onStartInputView();
    818     }
    819 
    820     @Override
    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
    829     public void onFinishInput() {
    830         mHandler.onFinishInput();
    831     }
    832 
    833     @Override
    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 
    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")
    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
   1034     public void onWindowShown() {
   1035         super.onWindowShown();
   1036         setNavigationBarVisibility(isInputViewShown());
   1037     }
   1038 
   1039     @Override
   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 
   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 
   1059     void onFinishInputViewInternal(final boolean finishingInput) {
   1060         super.onFinishInputView(finishingInput);
   1061         cleanupInternalStateForFinishInput();
   1062     }
   1063 
   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 
   1071     protected void deallocateMemory() {
   1072         mKeyboardSwitcher.deallocateMemory();
   1073     }
   1074 
   1075     @Override
   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
   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
   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
   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
   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
   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 
   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 
   1236     public void stopShowingInputView() {
   1237         showWindow(false /* showInput */);
   1238     }
   1239 
   1240     @Override
   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
   1249     public boolean onEvaluateInputViewShown() {
   1250         if (mIsExecutingStartShowingInputView) {
   1251             return true;
   1252         }
   1253         return super.onEvaluateInputViewShown();
   1254     }
   1255 
   1256     @Override
   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
   1277     public void updateFullscreenMode() {
   1278         super.updateFullscreenMode();
   1279         updateSoftInputWindowLayoutParameters();
   1280     }
   1281 
   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 
   1305     int getCurrentAutoCapsState() {
   1306         return mInputLogic.getCurrentAutoCapsState(mSettings.getCurrent());
   1307     }
   1308 
   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      */
   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
   1329     public void showImportantNoticeContents() {
   1330         PermissionsManager.get(this).requestPermissions(
   1331                 this /* PermissionsResultCallback */,
   1332                 null /* activity */, permission.READ_CONTACTS);
   1333     }
   1334 
   1335     @Override
   1336     public void onRequestPermissionsResult(boolean allGranted) {
   1337         ImportantNoticeUtils.updateContactsNoticeShown(this /* context */);
   1338         setNeutralSuggestionStrip();
   1339     }
   1340 
   1341     public void displaySettingsDialog() {
   1342         if (isShowingOptionDialog()) {
   1343             return;
   1344         }
   1345         showSubtypeSelectorAndSettings();
   1346     }
   1347 
   1348     @Override
   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 
   1362     private boolean isShowingOptionDialog() {
   1363         return mOptionsDialog != null && mOptionsDialog.isShowing();
   1364     }
   1365 
   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.
   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.
   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
   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.
   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
   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
   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
   1457     public void onStartBatchInput() {
   1458         mInputLogic.onStartBatchInput(mSettings.getCurrent(), mKeyboardSwitcher, mHandler);
   1459         mGestureConsumer.onGestureStarted(
   1460                 mRichImm.getCurrentSubtypeLocale(),
   1461                 mKeyboardSwitcher.getKeyboard());
   1462     }
   1463 
   1464     @Override
   1465     public void onUpdateBatchInput(final InputPointers batchPointers) {
   1466         mInputLogic.onUpdateBatchInput(batchPointers);
   1467     }
   1468 
   1469     @Override
   1470     public void onEndBatchInput(final InputPointers batchPointers) {
   1471         mInputLogic.onEndBatchInput(batchPointers);
   1472         mGestureConsumer.onGestureCompleted(batchPointers);
   1473     }
   1474 
   1475     @Override
   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      */
   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.
   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
   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
   1513     public void onCancelInput() {
   1514         // User released a finger outside any key
   1515         // Nothing to do so far.
   1516     }
   1517 
   1518     public boolean hasSuggestionStripView() {
   1519         return null != mSuggestionStripView;
   1520     }
   1521 
   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.
   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
   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
   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
   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
   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      */
   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 
   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
   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
   1711     public void onReleaseKey(final int primaryCode, final boolean withSliding) {
   1712         mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding, getCurrentAutoCapsState(),
   1713                 getCurrentRecapitalizeState());
   1714     }
   1715 
   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
   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
   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 
   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 
   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}.
   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
   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
   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
   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
   1883     void clearPersonalizedDictionariesForTest() {
   1884         mDictionaryFacilitator.clearUserHistoryDictionary(this);
   1885     }
   1886 
   1887     @UsedForTesting
   1888     List<InputMethodSubtype> getEnabledSubtypesForTest() {
   1889         return (mRichImm != null) ? mRichImm.getMyEnabledInputMethodSubtypeList(
   1890                 true /* allowsImplicitlySelectedSubtypes */) : new ArrayList<InputMethodSubtype>();
   1891     }
   1892 
   1893     public void dumpDictionaryForDebug(final String dictName) {
   1894         if (!mDictionaryFacilitator.isActive()) {
   1895             resetDictionaryFacilitatorIfNecessary();
   1896         }
   1897         mDictionaryFacilitator.dumpDictionaryForDebug(dictName);
   1898     }
   1899 
   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
   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 
   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 
   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 
   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