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