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