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