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