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