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