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