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