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