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         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
632             registerReceiver(mDictionaryPackInstallReceiver, newDictFilter,
633                     Context.RECEIVER_NOT_EXPORTED);
634         } else {
635             registerReceiver(mDictionaryPackInstallReceiver, newDictFilter);
636         }
637 
638         final IntentFilter dictDumpFilter = new IntentFilter();
639         dictDumpFilter.addAction(DictionaryDumpBroadcastReceiver.DICTIONARY_DUMP_INTENT_ACTION);
640         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
641             registerReceiver(mDictionaryDumpBroadcastReceiver, dictDumpFilter,
642                     Context.RECEIVER_NOT_EXPORTED);
643         } else {
644             registerReceiver(mDictionaryDumpBroadcastReceiver, dictDumpFilter);
645         }
646 
647         final IntentFilter hideSoftInputFilter = new IntentFilter();
648         hideSoftInputFilter.addAction(ACTION_HIDE_SOFT_INPUT);
649         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
650             registerReceiver(mHideSoftInputReceiver, hideSoftInputFilter,
651                     PERMISSION_HIDE_SOFT_INPUT, null /* scheduler */, Context.RECEIVER_EXPORTED);
652         } else {
653             registerReceiver(mHideSoftInputReceiver, hideSoftInputFilter,
654                     PERMISSION_HIDE_SOFT_INPUT, null /* scheduler */);
655         }
656 
657         StatsUtils.onCreate(mSettings.getCurrent(), mRichImm);
658     }
659 
660     // Has to be package-visible for unit tests
661     @UsedForTesting
loadSettings()662     void loadSettings() {
663         final Locale locale = mRichImm.getCurrentSubtypeLocale();
664         final EditorInfo editorInfo = getCurrentInputEditorInfo();
665         final InputAttributes inputAttributes = new InputAttributes(
666                 editorInfo, isFullscreenMode(), getPackageName());
667         mSettings.loadSettings(this, locale, inputAttributes);
668         final SettingsValues currentSettingsValues = mSettings.getCurrent();
669         AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(currentSettingsValues);
670         // This method is called on startup and language switch, before the new layout has
671         // been displayed. Opening dictionaries never affects responsivity as dictionaries are
672         // asynchronously loaded.
673         if (!mHandler.hasPendingReopenDictionaries()) {
674             resetDictionaryFacilitator(locale);
675         }
676         refreshPersonalizationDictionarySession(currentSettingsValues);
677         resetDictionaryFacilitatorIfNecessary();
678         mStatsUtilsManager.onLoadSettings(this /* context */, currentSettingsValues);
679     }
680 
refreshPersonalizationDictionarySession( final SettingsValues currentSettingsValues)681     private void refreshPersonalizationDictionarySession(
682             final SettingsValues currentSettingsValues) {
683         if (!currentSettingsValues.mUsePersonalizedDicts) {
684             // Remove user history dictionaries.
685             PersonalizationHelper.removeAllUserHistoryDictionaries(this);
686             mDictionaryFacilitator.clearUserHistoryDictionary(this);
687         }
688     }
689 
690     // Note that this method is called from a non-UI thread.
691     @Override
onUpdateMainDictionaryAvailability(final boolean isMainDictionaryAvailable)692     public void onUpdateMainDictionaryAvailability(final boolean isMainDictionaryAvailable) {
693         final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
694         if (mainKeyboardView != null) {
695             mainKeyboardView.setMainDictionaryAvailability(isMainDictionaryAvailable);
696         }
697         if (mHandler.hasPendingWaitForDictionaryLoad()) {
698             mHandler.cancelWaitForDictionaryLoad();
699             mHandler.postResumeSuggestions(false /* shouldDelay */);
700         }
701     }
702 
resetDictionaryFacilitatorIfNecessary()703     void resetDictionaryFacilitatorIfNecessary() {
704         final Locale subtypeSwitcherLocale = mRichImm.getCurrentSubtypeLocale();
705         final Locale subtypeLocale;
706         if (subtypeSwitcherLocale == null) {
707             // This happens in very rare corner cases - for example, immediately after a switch
708             // to LatinIME has been requested, about a frame later another switch happens. In this
709             // case, we are about to go down but we still don't know it, however the system tells
710             // us there is no current subtype.
711             Log.e(TAG, "System is reporting no current subtype.");
712             subtypeLocale = getResources().getConfiguration().locale;
713         } else {
714             subtypeLocale = subtypeSwitcherLocale;
715         }
716         if (mDictionaryFacilitator.isForLocale(subtypeLocale)
717                 && mDictionaryFacilitator.isForAccount(mSettings.getCurrent().mAccount)) {
718             return;
719         }
720         resetDictionaryFacilitator(subtypeLocale);
721     }
722 
723     /**
724      * Reset the facilitator by loading dictionaries for the given locale and
725      * the current settings values.
726      *
727      * @param locale the locale
728      */
729     // TODO: make sure the current settings always have the right locales, and read from them.
resetDictionaryFacilitator(final Locale locale)730     private void resetDictionaryFacilitator(final Locale locale) {
731         final SettingsValues settingsValues = mSettings.getCurrent();
732         mDictionaryFacilitator.resetDictionaries(this /* context */, locale,
733                 settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts,
734                 false /* forceReloadMainDictionary */,
735                 settingsValues.mAccount, "" /* dictNamePrefix */,
736                 this /* DictionaryInitializationListener */);
737         if (settingsValues.mAutoCorrectionEnabledPerUserSettings) {
738             mInputLogic.mSuggest.setAutoCorrectionThreshold(
739                     settingsValues.mAutoCorrectionThreshold);
740         }
741         mInputLogic.mSuggest.setPlausibilityThreshold(settingsValues.mPlausibilityThreshold);
742     }
743 
744     /**
745      * Reset suggest by loading the main dictionary of the current locale.
746      */
resetSuggestMainDict()747     /* package private */ void resetSuggestMainDict() {
748         final SettingsValues settingsValues = mSettings.getCurrent();
749         mDictionaryFacilitator.resetDictionaries(this /* context */,
750                 mDictionaryFacilitator.getLocale(), settingsValues.mUseContactsDict,
751                 settingsValues.mUsePersonalizedDicts,
752                 true /* forceReloadMainDictionary */,
753                 settingsValues.mAccount, "" /* dictNamePrefix */,
754                 this /* DictionaryInitializationListener */);
755     }
756 
757     @Override
onDestroy()758     public void onDestroy() {
759         mDictionaryFacilitator.closeDictionaries();
760         mSettings.onDestroy();
761         unregisterReceiver(mHideSoftInputReceiver);
762         unregisterReceiver(mRingerModeChangeReceiver);
763         unregisterReceiver(mDictionaryPackInstallReceiver);
764         unregisterReceiver(mDictionaryDumpBroadcastReceiver);
765         mStatsUtilsManager.onDestroy(this /* context */);
766         super.onDestroy();
767     }
768 
769     @UsedForTesting
recycle()770     public void recycle() {
771         unregisterReceiver(mDictionaryPackInstallReceiver);
772         unregisterReceiver(mDictionaryDumpBroadcastReceiver);
773         unregisterReceiver(mRingerModeChangeReceiver);
774         mInputLogic.recycle();
775     }
776 
isImeSuppressedByHardwareKeyboard()777     private boolean isImeSuppressedByHardwareKeyboard() {
778         final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance();
779         return !onEvaluateInputViewShown() && switcher.isImeSuppressedByHardwareKeyboard(
780                 mSettings.getCurrent(), switcher.getKeyboardSwitchState());
781     }
782 
783     @Override
onConfigurationChanged(final Configuration conf)784     public void onConfigurationChanged(final Configuration conf) {
785         SettingsValues settingsValues = mSettings.getCurrent();
786         if (settingsValues.mDisplayOrientation != conf.orientation) {
787             mHandler.startOrientationChanging();
788             mInputLogic.onOrientationChange(mSettings.getCurrent());
789         }
790         if (settingsValues.mHasHardwareKeyboard != Settings.readHasHardwareKeyboard(conf)) {
791             // If the state of having a hardware keyboard changed, then we want to reload the
792             // settings to adjust for that.
793             // TODO: we should probably do this unconditionally here, rather than only when we
794             // have a change in hardware keyboard configuration.
795             loadSettings();
796             settingsValues = mSettings.getCurrent();
797             if (isImeSuppressedByHardwareKeyboard()) {
798                 // We call cleanupInternalStateForFinishInput() because it's the right thing to do;
799                 // however, it seems at the moment the framework is passing us a seemingly valid
800                 // but actually non-functional InputConnection object. So if this bug ever gets
801                 // fixed we'll be able to remove the composition, but until it is this code is
802                 // actually not doing much.
803                 cleanupInternalStateForFinishInput();
804             }
805         }
806         super.onConfigurationChanged(conf);
807     }
808 
809     @Override
onInitializeInterface()810     public void onInitializeInterface() {
811         mDisplayContext = getDisplayContext();
812         mKeyboardSwitcher.updateKeyboardTheme(mDisplayContext);
813     }
814 
815     /**
816      * Returns the context object whose resources are adjusted to match the metrics of the display.
817      *
818      * Note that before {@link android.os.Build.VERSION_CODES#KITKAT}, there is no way to support
819      * multi-display scenarios, so the context object will just return the IME context itself.
820      *
821      * With initiating multi-display APIs from {@link android.os.Build.VERSION_CODES#KITKAT}, the
822      * context object has to return with re-creating the display context according the metrics
823      * of the display in runtime.
824      *
825      * Starts from {@link android.os.Build.VERSION_CODES#S_V2}, the returning context object has
826      * became to IME context self since it ends up capable of updating its resources internally.
827      *
828      * @see android.content.Context#createDisplayContext(Display)
829      */
getDisplayContext()830     private @NonNull Context getDisplayContext() {
831         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
832             // createDisplayContext is not available.
833             return this;
834         }
835         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S_V2) {
836             // IME context sources is now managed by WindowProviderService from Android 12L.
837             return this;
838         }
839         // An issue in Q that non-activity components Resources / DisplayMetrics in
840         // Context doesn't well updated when the IME window moving to external display.
841         // Currently we do a workaround is to create new display context directly and re-init
842         // keyboard layout with this context.
843         final WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
844         return createDisplayContext(wm.getDefaultDisplay());
845     }
846 
847     @Override
onCreateInputView()848     public View onCreateInputView() {
849         StatsUtils.onCreateInputView();
850         return mKeyboardSwitcher.onCreateInputView(mDisplayContext,
851                 mIsHardwareAcceleratedDrawingEnabled);
852     }
853 
854     @Override
setInputView(final View view)855     public void setInputView(final View view) {
856         super.setInputView(view);
857         mInputView = view;
858         mInsetsUpdater = ViewOutlineProviderCompatUtils.setInsetsOutlineProvider(view);
859         updateSoftInputWindowLayoutParameters();
860         mSuggestionStripView = (SuggestionStripView)view.findViewById(R.id.suggestion_strip_view);
861         if (hasSuggestionStripView()) {
862             mSuggestionStripView.setListener(this, view);
863         }
864     }
865 
866     @Override
setCandidatesView(final View view)867     public void setCandidatesView(final View view) {
868         // To ensure that CandidatesView will never be set.
869     }
870 
871     @Override
onStartInput(final EditorInfo editorInfo, final boolean restarting)872     public void onStartInput(final EditorInfo editorInfo, final boolean restarting) {
873         mHandler.onStartInput(editorInfo, restarting);
874     }
875 
876     @Override
onStartInputView(final EditorInfo editorInfo, final boolean restarting)877     public void onStartInputView(final EditorInfo editorInfo, final boolean restarting) {
878         mHandler.onStartInputView(editorInfo, restarting);
879         mStatsUtilsManager.onStartInputView();
880     }
881 
882     @Override
onFinishInputView(final boolean finishingInput)883     public void onFinishInputView(final boolean finishingInput) {
884         StatsUtils.onFinishInputView();
885         mHandler.onFinishInputView(finishingInput);
886         mStatsUtilsManager.onFinishInputView();
887         mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER;
888     }
889 
890     @Override
onFinishInput()891     public void onFinishInput() {
892         mHandler.onFinishInput();
893     }
894 
895     @Override
onCurrentInputMethodSubtypeChanged(final InputMethodSubtype subtype)896     public void onCurrentInputMethodSubtypeChanged(final InputMethodSubtype subtype) {
897         // Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
898         // is not guaranteed. It may even be called at the same time on a different thread.
899         InputMethodSubtype oldSubtype = mRichImm.getCurrentSubtype().getRawSubtype();
900         StatsUtils.onSubtypeChanged(oldSubtype, subtype);
901         mRichImm.onSubtypeChanged(subtype);
902         mInputLogic.onSubtypeChanged(SubtypeLocaleUtils.getCombiningRulesExtraValue(subtype),
903                 mSettings.getCurrent());
904         loadKeyboard();
905     }
906 
onStartInputInternal(final EditorInfo editorInfo, final boolean restarting)907     void onStartInputInternal(final EditorInfo editorInfo, final boolean restarting) {
908         super.onStartInput(editorInfo, restarting);
909 
910         // If the primary hint language does not match the current subtype language, then try
911         // to switch to the primary hint language.
912         // TODO: Support all the locales in EditorInfo#hintLocales.
913         final Locale primaryHintLocale = EditorInfoCompatUtils.getPrimaryHintLocale(editorInfo);
914         if (primaryHintLocale == null) {
915             return;
916         }
917         final InputMethodSubtype newSubtype = mRichImm.findSubtypeByLocale(primaryHintLocale);
918         if (newSubtype == null || newSubtype.equals(mRichImm.getCurrentSubtype().getRawSubtype())) {
919             return;
920         }
921         mHandler.postSwitchLanguage(newSubtype);
922     }
923 
924     @SuppressWarnings("deprecation")
onStartInputViewInternal(final EditorInfo editorInfo, final boolean restarting)925     void onStartInputViewInternal(final EditorInfo editorInfo, final boolean restarting) {
926         super.onStartInputView(editorInfo, restarting);
927 
928         mDictionaryFacilitator.onStartInput();
929         // Switch to the null consumer to handle cases leading to early exit below, for which we
930         // also wouldn't be consuming gesture data.
931         mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER;
932         mRichImm.refreshSubtypeCaches();
933         final KeyboardSwitcher switcher = mKeyboardSwitcher;
934         switcher.updateKeyboardTheme(mDisplayContext);
935         final MainKeyboardView mainKeyboardView = switcher.getMainKeyboardView();
936         // If we are starting input in a different text field from before, we'll have to reload
937         // settings, so currentSettingsValues can't be final.
938         SettingsValues currentSettingsValues = mSettings.getCurrent();
939 
940         if (editorInfo == null) {
941             Log.e(TAG, "Null EditorInfo in onStartInputView()");
942             if (DebugFlags.DEBUG_ENABLED) {
943                 throw new NullPointerException("Null EditorInfo in onStartInputView()");
944             }
945             return;
946         }
947         if (DebugFlags.DEBUG_ENABLED) {
948             Log.d(TAG, "onStartInputView: editorInfo:"
949                     + String.format("inputType=0x%08x imeOptions=0x%08x",
950                             editorInfo.inputType, editorInfo.imeOptions));
951             Log.d(TAG, "All caps = "
952                     + ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0)
953                     + ", sentence caps = "
954                     + ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0)
955                     + ", word caps = "
956                     + ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS) != 0));
957         }
958         Log.i(TAG, "Starting input. Cursor position = "
959                 + editorInfo.initialSelStart + "," + editorInfo.initialSelEnd);
960         // TODO: Consolidate these checks with {@link InputAttributes}.
961         if (InputAttributes.inPrivateImeOptions(null, NO_MICROPHONE_COMPAT, editorInfo)) {
962             Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions);
963             Log.w(TAG, "Use " + getPackageName() + "." + NO_MICROPHONE + " instead");
964         }
965         if (InputAttributes.inPrivateImeOptions(getPackageName(), FORCE_ASCII, editorInfo)) {
966             Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions);
967             Log.w(TAG, "Use EditorInfo.IME_FLAG_FORCE_ASCII flag instead");
968         }
969 
970         // In landscape mode, this method gets called without the input view being created.
971         if (mainKeyboardView == null) {
972             return;
973         }
974 
975         // Update to a gesture consumer with the current editor and IME state.
976         mGestureConsumer = GestureConsumer.newInstance(editorInfo,
977                 mInputLogic.getPrivateCommandPerformer(),
978                 mRichImm.getCurrentSubtypeLocale(),
979                 switcher.getKeyboard());
980 
981         // Forward this event to the accessibility utilities, if enabled.
982         final AccessibilityUtils accessUtils = AccessibilityUtils.getInstance();
983         if (accessUtils.isTouchExplorationEnabled()) {
984             accessUtils.onStartInputViewInternal(mainKeyboardView, editorInfo, restarting);
985         }
986 
987         final boolean inputTypeChanged = !currentSettingsValues.isSameInputType(editorInfo);
988         final boolean isDifferentTextField = !restarting || inputTypeChanged;
989 
990         StatsUtils.onStartInputView(editorInfo.inputType,
991                 Settings.getInstance().getCurrent().mDisplayOrientation,
992                 !isDifferentTextField);
993 
994         // The EditorInfo might have a flag that affects fullscreen mode.
995         // Note: This call should be done by InputMethodService?
996         updateFullscreenMode();
997 
998         // ALERT: settings have not been reloaded and there is a chance they may be stale.
999         // In the practice, if it is, we should have gotten onConfigurationChanged so it should
1000         // be fine, but this is horribly confusing and must be fixed AS SOON AS POSSIBLE.
1001 
1002         // In some cases the input connection has not been reset yet and we can't access it. In
1003         // this case we will need to call loadKeyboard() later, when it's accessible, so that we
1004         // can go into the correct mode, so we need to do some housekeeping here.
1005         final boolean needToCallLoadKeyboardLater;
1006         final Suggest suggest = mInputLogic.mSuggest;
1007         if (!isImeSuppressedByHardwareKeyboard()) {
1008             // The app calling setText() has the effect of clearing the composing
1009             // span, so we should reset our state unconditionally, even if restarting is true.
1010             // We also tell the input logic about the combining rules for the current subtype, so
1011             // it can adjust its combiners if needed.
1012             mInputLogic.startInput(mRichImm.getCombiningRulesExtraValueOfCurrentSubtype(),
1013                     currentSettingsValues);
1014 
1015             resetDictionaryFacilitatorIfNecessary();
1016 
1017             // TODO[IL]: Can the following be moved to InputLogic#startInput?
1018             if (!mInputLogic.mConnection.resetCachesUponCursorMoveAndReturnSuccess(
1019                     editorInfo.initialSelStart, editorInfo.initialSelEnd,
1020                     false /* shouldFinishComposition */)) {
1021                 // Sometimes, while rotating, for some reason the framework tells the app we are not
1022                 // connected to it and that means we can't refresh the cache. In this case, schedule
1023                 // a refresh later.
1024                 // We try resetting the caches up to 5 times before giving up.
1025                 mHandler.postResetCaches(isDifferentTextField, 5 /* remainingTries */);
1026                 // mLastSelection{Start,End} are reset later in this method, no need to do it here
1027                 needToCallLoadKeyboardLater = true;
1028             } else {
1029                 // When rotating, and when input is starting again in a field from where the focus
1030                 // didn't move (the keyboard having been closed with the back key),
1031                 // initialSelStart and initialSelEnd sometimes are lying. Make a best effort to
1032                 // work around this bug.
1033                 mInputLogic.mConnection.tryFixLyingCursorPosition();
1034                 mHandler.postResumeSuggestionsForStartInput(true /* shouldDelay */);
1035                 needToCallLoadKeyboardLater = false;
1036             }
1037         } else {
1038             // If we have a hardware keyboard we don't need to call loadKeyboard later anyway.
1039             needToCallLoadKeyboardLater = false;
1040         }
1041 
1042         if (isDifferentTextField ||
1043                 !currentSettingsValues.hasSameOrientation(getResources().getConfiguration())) {
1044             loadSettings();
1045         }
1046         if (isDifferentTextField) {
1047             mainKeyboardView.closing();
1048             currentSettingsValues = mSettings.getCurrent();
1049 
1050             if (currentSettingsValues.mAutoCorrectionEnabledPerUserSettings) {
1051                 suggest.setAutoCorrectionThreshold(
1052                         currentSettingsValues.mAutoCorrectionThreshold);
1053             }
1054             suggest.setPlausibilityThreshold(currentSettingsValues.mPlausibilityThreshold);
1055 
1056             switcher.loadKeyboard(editorInfo, currentSettingsValues, getCurrentAutoCapsState(),
1057                     getCurrentRecapitalizeState());
1058             if (needToCallLoadKeyboardLater) {
1059                 // If we need to call loadKeyboard again later, we need to save its state now. The
1060                 // later call will be done in #retryResetCaches.
1061                 switcher.saveKeyboardState();
1062             }
1063         } else if (restarting) {
1064             // TODO: Come up with a more comprehensive way to reset the keyboard layout when
1065             // a keyboard layout set doesn't get reloaded in this method.
1066             switcher.resetKeyboardStateToAlphabet(getCurrentAutoCapsState(),
1067                     getCurrentRecapitalizeState());
1068             // In apps like Talk, we come here when the text is sent and the field gets emptied and
1069             // we need to re-evaluate the shift state, but not the whole layout which would be
1070             // disruptive.
1071             // Space state must be updated before calling updateShiftState
1072             switcher.requestUpdatingShiftState(getCurrentAutoCapsState(),
1073                     getCurrentRecapitalizeState());
1074         }
1075         // This will set the punctuation suggestions if next word suggestion is off;
1076         // otherwise it will clear the suggestion strip.
1077         setNeutralSuggestionStrip();
1078 
1079         mHandler.cancelUpdateSuggestionStrip();
1080 
1081         mainKeyboardView.setMainDictionaryAvailability(
1082                 mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary());
1083         mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn,
1084                 currentSettingsValues.mKeyPreviewPopupDismissDelay);
1085         mainKeyboardView.setSlidingKeyInputPreviewEnabled(
1086                 currentSettingsValues.mSlidingKeyInputPreviewEnabled);
1087         mainKeyboardView.setGestureHandlingEnabledByUser(
1088                 currentSettingsValues.mGestureInputEnabled,
1089                 currentSettingsValues.mGestureTrailEnabled,
1090                 currentSettingsValues.mGestureFloatingPreviewTextEnabled);
1091 
1092         if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
1093     }
1094 
1095     @Override
onWindowShown()1096     public void onWindowShown() {
1097         super.onWindowShown();
1098         setNavigationBarVisibility(isInputViewShown());
1099     }
1100 
1101     @Override
onWindowHidden()1102     public void onWindowHidden() {
1103         super.onWindowHidden();
1104         final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
1105         if (mainKeyboardView != null) {
1106             mainKeyboardView.closing();
1107         }
1108         setNavigationBarVisibility(false);
1109     }
1110 
onFinishInputInternal()1111     void onFinishInputInternal() {
1112         super.onFinishInput();
1113 
1114         mDictionaryFacilitator.onFinishInput(this);
1115         final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
1116         if (mainKeyboardView != null) {
1117             mainKeyboardView.closing();
1118         }
1119     }
1120 
onFinishInputViewInternal(final boolean finishingInput)1121     void onFinishInputViewInternal(final boolean finishingInput) {
1122         super.onFinishInputView(finishingInput);
1123         cleanupInternalStateForFinishInput();
1124     }
1125 
cleanupInternalStateForFinishInput()1126     private void cleanupInternalStateForFinishInput() {
1127         // Remove pending messages related to update suggestions
1128         mHandler.cancelUpdateSuggestionStrip();
1129         // Should do the following in onFinishInputInternal but until JB MR2 it's not called :(
1130         mInputLogic.finishInput();
1131     }
1132 
deallocateMemory()1133     protected void deallocateMemory() {
1134         mKeyboardSwitcher.deallocateMemory();
1135     }
1136 
1137     @Override
onUpdateSelection(final int oldSelStart, final int oldSelEnd, final int newSelStart, final int newSelEnd, final int composingSpanStart, final int composingSpanEnd)1138     public void onUpdateSelection(final int oldSelStart, final int oldSelEnd,
1139             final int newSelStart, final int newSelEnd,
1140             final int composingSpanStart, final int composingSpanEnd) {
1141         super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
1142                 composingSpanStart, composingSpanEnd);
1143         if (DebugFlags.DEBUG_ENABLED) {
1144             Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart + ", ose=" + oldSelEnd
1145                     + ", nss=" + newSelStart + ", nse=" + newSelEnd
1146                     + ", cs=" + composingSpanStart + ", ce=" + composingSpanEnd);
1147         }
1148 
1149         // This call happens whether our view is displayed or not, but if it's not then we should
1150         // not attempt recorrection. This is true even with a hardware keyboard connected: if the
1151         // view is not displayed we have no means of showing suggestions anyway, and if it is then
1152         // we want to show suggestions anyway.
1153         final SettingsValues settingsValues = mSettings.getCurrent();
1154         if (isInputViewShown()
1155                 && mInputLogic.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
1156                         settingsValues)) {
1157             mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(),
1158                     getCurrentRecapitalizeState());
1159         }
1160     }
1161 
1162     /**
1163      * This is called when the user has clicked on the extracted text view,
1164      * when running in fullscreen mode.  The default implementation hides
1165      * the suggestions view when this happens, but only if the extracted text
1166      * editor has a vertical scroll bar because its text doesn't fit.
1167      * Here we override the behavior due to the possibility that a re-correction could
1168      * cause the suggestions strip to disappear and re-appear.
1169      */
1170     @Override
onExtractedTextClicked()1171     public void onExtractedTextClicked() {
1172         if (mSettings.getCurrent().needsToLookupSuggestions()) {
1173             return;
1174         }
1175 
1176         super.onExtractedTextClicked();
1177     }
1178 
1179     /**
1180      * This is called when the user has performed a cursor movement in the
1181      * extracted text view, when it is running in fullscreen mode.  The default
1182      * implementation hides the suggestions view when a vertical movement
1183      * happens, but only if the extracted text editor has a vertical scroll bar
1184      * because its text doesn't fit.
1185      * Here we override the behavior due to the possibility that a re-correction could
1186      * cause the suggestions strip to disappear and re-appear.
1187      */
1188     @Override
onExtractedCursorMovement(final int dx, final int dy)1189     public void onExtractedCursorMovement(final int dx, final int dy) {
1190         if (mSettings.getCurrent().needsToLookupSuggestions()) {
1191             return;
1192         }
1193 
1194         super.onExtractedCursorMovement(dx, dy);
1195     }
1196 
1197     @Override
hideWindow()1198     public void hideWindow() {
1199         mKeyboardSwitcher.onHideWindow();
1200 
1201         if (TRACE) Debug.stopMethodTracing();
1202         if (isShowingOptionDialog()) {
1203             mOptionsDialog.dismiss();
1204             mOptionsDialog = null;
1205         }
1206         super.hideWindow();
1207     }
1208 
1209     @Override
onDisplayCompletions(final CompletionInfo[] applicationSpecifiedCompletions)1210     public void onDisplayCompletions(final CompletionInfo[] applicationSpecifiedCompletions) {
1211         if (DebugFlags.DEBUG_ENABLED) {
1212             Log.i(TAG, "Received completions:");
1213             if (applicationSpecifiedCompletions != null) {
1214                 for (int i = 0; i < applicationSpecifiedCompletions.length; i++) {
1215                     Log.i(TAG, "  #" + i + ": " + applicationSpecifiedCompletions[i]);
1216                 }
1217             }
1218         }
1219         if (!mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()) {
1220             return;
1221         }
1222         // If we have an update request in flight, we need to cancel it so it does not override
1223         // these completions.
1224         mHandler.cancelUpdateSuggestionStrip();
1225         if (applicationSpecifiedCompletions == null) {
1226             setNeutralSuggestionStrip();
1227             return;
1228         }
1229 
1230         final ArrayList<SuggestedWords.SuggestedWordInfo> applicationSuggestedWords =
1231                 SuggestedWords.getFromApplicationSpecifiedCompletions(
1232                         applicationSpecifiedCompletions);
1233         final SuggestedWords suggestedWords = new SuggestedWords(applicationSuggestedWords,
1234                 null /* rawSuggestions */,
1235                 null /* typedWord */,
1236                 false /* typedWordValid */,
1237                 false /* willAutoCorrect */,
1238                 false /* isObsoleteSuggestions */,
1239                 SuggestedWords.INPUT_STYLE_APPLICATION_SPECIFIED /* inputStyle */,
1240                 SuggestedWords.NOT_A_SEQUENCE_NUMBER);
1241         // When in fullscreen mode, show completions generated by the application forcibly
1242         setSuggestedWords(suggestedWords);
1243     }
1244 
1245     @Override
onComputeInsets(final InputMethodService.Insets outInsets)1246     public void onComputeInsets(final InputMethodService.Insets outInsets) {
1247         super.onComputeInsets(outInsets);
1248         // This method may be called before {@link #setInputView(View)}.
1249         if (mInputView == null) {
1250             return;
1251         }
1252         final SettingsValues settingsValues = mSettings.getCurrent();
1253         final View visibleKeyboardView = mKeyboardSwitcher.getVisibleKeyboardView();
1254         if (visibleKeyboardView == null || !hasSuggestionStripView()) {
1255             return;
1256         }
1257         final int inputHeight = mInputView.getHeight();
1258         if (isImeSuppressedByHardwareKeyboard() && !visibleKeyboardView.isShown()) {
1259             // If there is a hardware keyboard and a visible software keyboard view has been hidden,
1260             // no visual element will be shown on the screen.
1261             outInsets.contentTopInsets = inputHeight;
1262             outInsets.visibleTopInsets = inputHeight;
1263             mInsetsUpdater.setInsets(outInsets);
1264             return;
1265         }
1266         final int suggestionsHeight = (!mKeyboardSwitcher.isShowingEmojiPalettes()
1267                 && mSuggestionStripView.getVisibility() == View.VISIBLE)
1268                 ? mSuggestionStripView.getHeight() : 0;
1269         final int visibleTopY = inputHeight - visibleKeyboardView.getHeight() - suggestionsHeight;
1270         mSuggestionStripView.setMoreSuggestionsHeight(visibleTopY);
1271         // Need to set expanded touchable region only if a keyboard view is being shown.
1272         if (visibleKeyboardView.isShown()) {
1273             final int touchLeft = 0;
1274             final int touchTop = mKeyboardSwitcher.isShowingMoreKeysPanel() ? 0 : visibleTopY;
1275             final int touchRight = visibleKeyboardView.getWidth();
1276             final int touchBottom = inputHeight;
1277             outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION;
1278             outInsets.touchableRegion.set(touchLeft, touchTop, touchRight, touchBottom);
1279         }
1280         outInsets.contentTopInsets = visibleTopY;
1281         outInsets.visibleTopInsets = visibleTopY;
1282         mInsetsUpdater.setInsets(outInsets);
1283     }
1284 
startShowingInputView(final boolean needsToLoadKeyboard)1285     public void startShowingInputView(final boolean needsToLoadKeyboard) {
1286         mIsExecutingStartShowingInputView = true;
1287         // This {@link #showWindow(boolean)} will eventually call back
1288         // {@link #onEvaluateInputViewShown()}.
1289         showWindow(true /* showInput */);
1290         mIsExecutingStartShowingInputView = false;
1291         if (needsToLoadKeyboard) {
1292             loadKeyboard();
1293         }
1294     }
1295 
stopShowingInputView()1296     public void stopShowingInputView() {
1297         showWindow(false /* showInput */);
1298     }
1299 
1300     @Override
onShowInputRequested(final int flags, final boolean configChange)1301     public boolean onShowInputRequested(final int flags, final boolean configChange) {
1302         if (isImeSuppressedByHardwareKeyboard()) {
1303             return true;
1304         }
1305         return super.onShowInputRequested(flags, configChange);
1306     }
1307 
1308     @Override
onEvaluateInputViewShown()1309     public boolean onEvaluateInputViewShown() {
1310         if (mIsExecutingStartShowingInputView) {
1311             return true;
1312         }
1313         return super.onEvaluateInputViewShown();
1314     }
1315 
1316     @Override
onEvaluateFullscreenMode()1317     public boolean onEvaluateFullscreenMode() {
1318         final SettingsValues settingsValues = mSettings.getCurrent();
1319         if (isImeSuppressedByHardwareKeyboard()) {
1320             // If there is a hardware keyboard, disable full screen mode.
1321             return false;
1322         }
1323         // Reread resource value here, because this method is called by the framework as needed.
1324         final boolean isFullscreenModeAllowed = Settings.readUseFullscreenMode(getResources());
1325         if (super.onEvaluateFullscreenMode() && isFullscreenModeAllowed) {
1326             // TODO: Remove this hack. Actually we should not really assume NO_EXTRACT_UI
1327             // implies NO_FULLSCREEN. However, the framework mistakenly does.  i.e. NO_EXTRACT_UI
1328             // without NO_FULLSCREEN doesn't work as expected. Because of this we need this
1329             // hack for now.  Let's get rid of this once the framework gets fixed.
1330             final EditorInfo ei = getCurrentInputEditorInfo();
1331             return !(ei != null && ((ei.imeOptions & EditorInfo.IME_FLAG_NO_EXTRACT_UI) != 0));
1332         }
1333         return false;
1334     }
1335 
1336     @Override
updateFullscreenMode()1337     public void updateFullscreenMode() {
1338         super.updateFullscreenMode();
1339         updateSoftInputWindowLayoutParameters();
1340     }
1341 
updateSoftInputWindowLayoutParameters()1342     private void updateSoftInputWindowLayoutParameters() {
1343         // Override layout parameters to expand {@link SoftInputWindow} to the entire screen.
1344         // See {@link InputMethodService#setinputView(View)} and
1345         // {@link SoftInputWindow#updateWidthHeight(WindowManager.LayoutParams)}.
1346         final Window window = getWindow().getWindow();
1347         ViewLayoutUtils.updateLayoutHeightOf(window, LayoutParams.MATCH_PARENT);
1348         // This method may be called before {@link #setInputView(View)}.
1349         if (mInputView != null) {
1350             // In non-fullscreen mode, {@link InputView} and its parent inputArea should expand to
1351             // the entire screen and be placed at the bottom of {@link SoftInputWindow}.
1352             // In fullscreen mode, these shouldn't expand to the entire screen and should be
1353             // coexistent with {@link #mExtractedArea} above.
1354             // See {@link InputMethodService#setInputView(View) and
1355             // com.android.internal.R.layout.input_method.xml.
1356             final int layoutHeight = isFullscreenMode()
1357                     ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT;
1358             final View inputArea = window.findViewById(android.R.id.inputArea);
1359             ViewLayoutUtils.updateLayoutHeightOf(inputArea, layoutHeight);
1360             ViewLayoutUtils.updateLayoutGravityOf(inputArea, Gravity.BOTTOM);
1361             ViewLayoutUtils.updateLayoutHeightOf(mInputView, layoutHeight);
1362         }
1363     }
1364 
getCurrentAutoCapsState()1365     int getCurrentAutoCapsState() {
1366         return mInputLogic.getCurrentAutoCapsState(mSettings.getCurrent());
1367     }
1368 
getCurrentRecapitalizeState()1369     int getCurrentRecapitalizeState() {
1370         return mInputLogic.getCurrentRecapitalizeState();
1371     }
1372 
1373     /**
1374      * @param codePoints code points to get coordinates for.
1375      * @return x,y coordinates for this keyboard, as a flattened array.
1376      */
getCoordinatesForCurrentKeyboard(final int[] codePoints)1377     public int[] getCoordinatesForCurrentKeyboard(final int[] codePoints) {
1378         final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
1379         if (null == keyboard) {
1380             return CoordinateUtils.newCoordinateArray(codePoints.length,
1381                     Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
1382         }
1383         return keyboard.getCoordinates(codePoints);
1384     }
1385 
1386     // Callback for the {@link SuggestionStripView}, to call when the important notice strip is
1387     // pressed.
1388     @Override
showImportantNoticeContents()1389     public void showImportantNoticeContents() {
1390         PermissionsManager.get(this).requestPermissions(
1391                 this /* PermissionsResultCallback */,
1392                 null /* activity */, permission.READ_CONTACTS);
1393     }
1394 
1395     @Override
onRequestPermissionsResult(boolean allGranted)1396     public void onRequestPermissionsResult(boolean allGranted) {
1397         ImportantNoticeUtils.updateContactsNoticeShown(this /* context */);
1398         setNeutralSuggestionStrip();
1399     }
1400 
displaySettingsDialog()1401     public void displaySettingsDialog() {
1402         if (isShowingOptionDialog()) {
1403             return;
1404         }
1405         showSubtypeSelectorAndSettings();
1406     }
1407 
1408     @Override
onCustomRequest(final int requestCode)1409     public boolean onCustomRequest(final int requestCode) {
1410         if (isShowingOptionDialog()) return false;
1411         switch (requestCode) {
1412         case Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER:
1413             if (mRichImm.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) {
1414                 mRichImm.getInputMethodManager().showInputMethodPicker();
1415                 return true;
1416             }
1417             return false;
1418         }
1419         return false;
1420     }
1421 
isShowingOptionDialog()1422     private boolean isShowingOptionDialog() {
1423         return mOptionsDialog != null && mOptionsDialog.isShowing();
1424     }
1425 
switchLanguage(final InputMethodSubtype subtype)1426     public void switchLanguage(final InputMethodSubtype subtype) {
1427         final IBinder token = getWindow().getWindow().getAttributes().token;
1428         mRichImm.setInputMethodAndSubtype(token, subtype);
1429     }
1430 
1431     // TODO: Revise the language switch key behavior to make it much smarter and more reasonable.
switchToNextSubtype()1432     public void switchToNextSubtype() {
1433         final IBinder token = getWindow().getWindow().getAttributes().token;
1434         if (shouldSwitchToOtherInputMethods()) {
1435             mRichImm.switchToNextInputMethod(token, false /* onlyCurrentIme */);
1436             return;
1437         }
1438         mSubtypeState.switchSubtype(token, mRichImm);
1439     }
1440 
1441     // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for
1442     // alphabetic shift and shift while in symbol layout and get rid of this method.
getCodePointForKeyboard(final int codePoint)1443     private int getCodePointForKeyboard(final int codePoint) {
1444         if (Constants.CODE_SHIFT == codePoint) {
1445             final Keyboard currentKeyboard = mKeyboardSwitcher.getKeyboard();
1446             if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) {
1447                 return codePoint;
1448             }
1449             return Constants.CODE_SYMBOL_SHIFT;
1450         }
1451         return codePoint;
1452     }
1453 
1454     // Implementation of {@link KeyboardActionListener}.
1455     @Override
onCodeInput(final int codePoint, final int x, final int y, final boolean isKeyRepeat)1456     public void onCodeInput(final int codePoint, final int x, final int y,
1457             final boolean isKeyRepeat) {
1458         // TODO: this processing does not belong inside LatinIME, the caller should be doing this.
1459         final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
1460         // x and y include some padding, but everything down the line (especially native
1461         // code) needs the coordinates in the keyboard frame.
1462         // TODO: We should reconsider which coordinate system should be used to represent
1463         // keyboard event. Also we should pull this up -- LatinIME has no business doing
1464         // this transformation, it should be done already before calling onEvent.
1465         final int keyX = mainKeyboardView.getKeyX(x);
1466         final int keyY = mainKeyboardView.getKeyY(y);
1467         final Event event = createSoftwareKeypressEvent(getCodePointForKeyboard(codePoint),
1468                 keyX, keyY, isKeyRepeat);
1469         onEvent(event);
1470     }
1471 
1472     // This method is public for testability of LatinIME, but also in the future it should
1473     // completely replace #onCodeInput.
onEvent(@onnull final Event event)1474     public void onEvent(@Nonnull final Event event) {
1475         if (Constants.CODE_SHORTCUT == event.mKeyCode) {
1476             mRichImm.switchToShortcutIme(this);
1477         }
1478         final InputTransaction completeInputTransaction =
1479                 mInputLogic.onCodeInput(mSettings.getCurrent(), event,
1480                         mKeyboardSwitcher.getKeyboardShiftMode(),
1481                         mKeyboardSwitcher.getCurrentKeyboardScriptId(), mHandler);
1482         updateStateAfterInputTransaction(completeInputTransaction);
1483         mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState());
1484     }
1485 
1486     // A helper method to split the code point and the key code. Ultimately, they should not be
1487     // squashed into the same variable, and this method should be removed.
1488     // public for testing, as we don't want to copy the same logic into test code
1489     @Nonnull
createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int keyX, final int keyY, final boolean isKeyRepeat)1490     public static Event createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int keyX,
1491              final int keyY, final boolean isKeyRepeat) {
1492         final int keyCode;
1493         final int codePoint;
1494         if (keyCodeOrCodePoint <= 0) {
1495             keyCode = keyCodeOrCodePoint;
1496             codePoint = Event.NOT_A_CODE_POINT;
1497         } else {
1498             keyCode = Event.NOT_A_KEY_CODE;
1499             codePoint = keyCodeOrCodePoint;
1500         }
1501         return Event.createSoftwareKeypressEvent(codePoint, keyCode, keyX, keyY, isKeyRepeat);
1502     }
1503 
1504     // Called from PointerTracker through the KeyboardActionListener interface
1505     @Override
onTextInput(final String rawText)1506     public void onTextInput(final String rawText) {
1507         // TODO: have the keyboard pass the correct key code when we need it.
1508         final Event event = Event.createSoftwareTextEvent(rawText, Constants.CODE_OUTPUT_TEXT);
1509         final InputTransaction completeInputTransaction =
1510                 mInputLogic.onTextInput(mSettings.getCurrent(), event,
1511                         mKeyboardSwitcher.getKeyboardShiftMode(), mHandler);
1512         updateStateAfterInputTransaction(completeInputTransaction);
1513         mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState());
1514     }
1515 
1516     @Override
onStartBatchInput()1517     public void onStartBatchInput() {
1518         mInputLogic.onStartBatchInput(mSettings.getCurrent(), mKeyboardSwitcher, mHandler);
1519         mGestureConsumer.onGestureStarted(
1520                 mRichImm.getCurrentSubtypeLocale(),
1521                 mKeyboardSwitcher.getKeyboard());
1522     }
1523 
1524     @Override
onUpdateBatchInput(final InputPointers batchPointers)1525     public void onUpdateBatchInput(final InputPointers batchPointers) {
1526         mInputLogic.onUpdateBatchInput(batchPointers);
1527     }
1528 
1529     @Override
onEndBatchInput(final InputPointers batchPointers)1530     public void onEndBatchInput(final InputPointers batchPointers) {
1531         mInputLogic.onEndBatchInput(batchPointers);
1532         mGestureConsumer.onGestureCompleted(batchPointers);
1533     }
1534 
1535     @Override
onCancelBatchInput()1536     public void onCancelBatchInput() {
1537         mInputLogic.onCancelBatchInput(mHandler);
1538         mGestureConsumer.onGestureCanceled();
1539     }
1540 
1541     /**
1542      * To be called after the InputLogic has gotten a chance to act on the suggested words by the
1543      * IME for the full gesture, possibly updating the TextView to reflect the first suggestion.
1544      * <p>
1545      * This method must be run on the UI Thread.
1546      * @param suggestedWords suggested words by the IME for the full gesture.
1547      */
onTailBatchInputResultShown(final SuggestedWords suggestedWords)1548     public void onTailBatchInputResultShown(final SuggestedWords suggestedWords) {
1549         mGestureConsumer.onImeSuggestionsProcessed(suggestedWords,
1550                 mInputLogic.getComposingStart(), mInputLogic.getComposingLength(),
1551                 mDictionaryFacilitator);
1552     }
1553 
1554     // This method must run on the UI Thread.
showGesturePreviewAndSuggestionStrip(@onnull final SuggestedWords suggestedWords, final boolean dismissGestureFloatingPreviewText)1555     void showGesturePreviewAndSuggestionStrip(@Nonnull final SuggestedWords suggestedWords,
1556             final boolean dismissGestureFloatingPreviewText) {
1557         showSuggestionStrip(suggestedWords);
1558         final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
1559         mainKeyboardView.showGestureFloatingPreviewText(suggestedWords,
1560                 dismissGestureFloatingPreviewText /* dismissDelayed */);
1561     }
1562 
1563     // Called from PointerTracker through the KeyboardActionListener interface
1564     @Override
onFinishSlidingInput()1565     public void onFinishSlidingInput() {
1566         // User finished sliding input.
1567         mKeyboardSwitcher.onFinishSlidingInput(getCurrentAutoCapsState(),
1568                 getCurrentRecapitalizeState());
1569     }
1570 
1571     // Called from PointerTracker through the KeyboardActionListener interface
1572     @Override
onCancelInput()1573     public void onCancelInput() {
1574         // User released a finger outside any key
1575         // Nothing to do so far.
1576     }
1577 
hasSuggestionStripView()1578     public boolean hasSuggestionStripView() {
1579         return null != mSuggestionStripView;
1580     }
1581 
setSuggestedWords(final SuggestedWords suggestedWords)1582     private void setSuggestedWords(final SuggestedWords suggestedWords) {
1583         final SettingsValues currentSettingsValues = mSettings.getCurrent();
1584         mInputLogic.setSuggestedWords(suggestedWords);
1585         // TODO: Modify this when we support suggestions with hard keyboard
1586         if (!hasSuggestionStripView()) {
1587             return;
1588         }
1589         if (!onEvaluateInputViewShown()) {
1590             return;
1591         }
1592 
1593         final boolean shouldShowImportantNotice =
1594                 ImportantNoticeUtils.shouldShowImportantNotice(this, currentSettingsValues);
1595         final boolean shouldShowSuggestionCandidates =
1596                 currentSettingsValues.mInputAttributes.mShouldShowSuggestions
1597                 && currentSettingsValues.isSuggestionsEnabledPerUserSettings();
1598         final boolean shouldShowSuggestionsStripUnlessPassword = shouldShowImportantNotice
1599                 || currentSettingsValues.mShowsVoiceInputKey
1600                 || shouldShowSuggestionCandidates
1601                 || currentSettingsValues.isApplicationSpecifiedCompletionsOn();
1602         final boolean shouldShowSuggestionsStrip = shouldShowSuggestionsStripUnlessPassword
1603                 && !currentSettingsValues.mInputAttributes.mIsPasswordField;
1604         mSuggestionStripView.updateVisibility(shouldShowSuggestionsStrip, isFullscreenMode());
1605         if (!shouldShowSuggestionsStrip) {
1606             return;
1607         }
1608 
1609         final boolean isEmptyApplicationSpecifiedCompletions =
1610                 currentSettingsValues.isApplicationSpecifiedCompletionsOn()
1611                 && suggestedWords.isEmpty();
1612         final boolean noSuggestionsFromDictionaries = suggestedWords.isEmpty()
1613                 || suggestedWords.isPunctuationSuggestions()
1614                 || isEmptyApplicationSpecifiedCompletions;
1615         final boolean isBeginningOfSentencePrediction = (suggestedWords.mInputStyle
1616                 == SuggestedWords.INPUT_STYLE_BEGINNING_OF_SENTENCE_PREDICTION);
1617         final boolean noSuggestionsToOverrideImportantNotice = noSuggestionsFromDictionaries
1618                 || isBeginningOfSentencePrediction;
1619         if (shouldShowImportantNotice && noSuggestionsToOverrideImportantNotice) {
1620             if (mSuggestionStripView.maybeShowImportantNoticeTitle()) {
1621                 return;
1622             }
1623         }
1624 
1625         if (currentSettingsValues.isSuggestionsEnabledPerUserSettings()
1626                 || currentSettingsValues.isApplicationSpecifiedCompletionsOn()
1627                 // We should clear the contextual strip if there is no suggestion from dictionaries.
1628                 || noSuggestionsFromDictionaries) {
1629             mSuggestionStripView.setSuggestions(suggestedWords,
1630                     mRichImm.getCurrentSubtype().isRtlSubtype());
1631         }
1632     }
1633 
1634     // TODO[IL]: Move this out of LatinIME.
getSuggestedWords(final int inputStyle, final int sequenceNumber, final OnGetSuggestedWordsCallback callback)1635     public void getSuggestedWords(final int inputStyle, final int sequenceNumber,
1636             final OnGetSuggestedWordsCallback callback) {
1637         final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
1638         if (keyboard == null) {
1639             callback.onGetSuggestedWords(SuggestedWords.getEmptyInstance());
1640             return;
1641         }
1642         mInputLogic.getSuggestedWords(mSettings.getCurrent(), keyboard,
1643                 mKeyboardSwitcher.getKeyboardShiftMode(), inputStyle, sequenceNumber, callback);
1644     }
1645 
1646     @Override
showSuggestionStrip(final SuggestedWords suggestedWords)1647     public void showSuggestionStrip(final SuggestedWords suggestedWords) {
1648         if (suggestedWords.isEmpty()) {
1649             setNeutralSuggestionStrip();
1650         } else {
1651             setSuggestedWords(suggestedWords);
1652         }
1653         // Cache the auto-correction in accessibility code so we can speak it if the user
1654         // touches a key that will insert it.
1655         AccessibilityUtils.getInstance().setAutoCorrection(suggestedWords);
1656     }
1657 
1658     // Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener}
1659     // interface
1660     @Override
pickSuggestionManually(final SuggestedWordInfo suggestionInfo)1661     public void pickSuggestionManually(final SuggestedWordInfo suggestionInfo) {
1662         final InputTransaction completeInputTransaction = mInputLogic.onPickSuggestionManually(
1663                 mSettings.getCurrent(), suggestionInfo,
1664                 mKeyboardSwitcher.getKeyboardShiftMode(),
1665                 mKeyboardSwitcher.getCurrentKeyboardScriptId(),
1666                 mHandler);
1667         updateStateAfterInputTransaction(completeInputTransaction);
1668     }
1669 
1670     // This will show either an empty suggestion strip (if prediction is enabled) or
1671     // punctuation suggestions (if it's disabled).
1672     @Override
setNeutralSuggestionStrip()1673     public void setNeutralSuggestionStrip() {
1674         final SettingsValues currentSettings = mSettings.getCurrent();
1675         final SuggestedWords neutralSuggestions = currentSettings.mBigramPredictionEnabled
1676                 ? SuggestedWords.getEmptyInstance()
1677                 : currentSettings.mSpacingAndPunctuations.mSuggestPuncList;
1678         setSuggestedWords(neutralSuggestions);
1679     }
1680 
1681     // Outside LatinIME, only used by the {@link InputTestsBase} test suite.
1682     @UsedForTesting
loadKeyboard()1683     void loadKeyboard() {
1684         // Since we are switching languages, the most urgent thing is to let the keyboard graphics
1685         // update. LoadKeyboard does that, but we need to wait for buffer flip for it to be on
1686         // the screen. Anything we do right now will delay this, so wait until the next frame
1687         // before we do the rest, like reopening dictionaries and updating suggestions. So we
1688         // post a message.
1689         mHandler.postReopenDictionaries();
1690         loadSettings();
1691         if (mKeyboardSwitcher.getMainKeyboardView() != null) {
1692             // Reload keyboard because the current language has been changed.
1693             mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettings.getCurrent(),
1694                     getCurrentAutoCapsState(), getCurrentRecapitalizeState());
1695         }
1696     }
1697 
1698     /**
1699      * After an input transaction has been executed, some state must be updated. This includes
1700      * the shift state of the keyboard and suggestions. This method looks at the finished
1701      * inputTransaction to find out what is necessary and updates the state accordingly.
1702      * @param inputTransaction The transaction that has been executed.
1703      */
updateStateAfterInputTransaction(final InputTransaction inputTransaction)1704     private void updateStateAfterInputTransaction(final InputTransaction inputTransaction) {
1705         switch (inputTransaction.getRequiredShiftUpdate()) {
1706         case InputTransaction.SHIFT_UPDATE_LATER:
1707             mHandler.postUpdateShiftState();
1708             break;
1709         case InputTransaction.SHIFT_UPDATE_NOW:
1710             mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(),
1711                     getCurrentRecapitalizeState());
1712             break;
1713         default: // SHIFT_NO_UPDATE
1714         }
1715         if (inputTransaction.requiresUpdateSuggestions()) {
1716             final int inputStyle;
1717             if (inputTransaction.mEvent.isSuggestionStripPress()) {
1718                 // Suggestion strip press: no input.
1719                 inputStyle = SuggestedWords.INPUT_STYLE_NONE;
1720             } else if (inputTransaction.mEvent.isGesture()) {
1721                 inputStyle = SuggestedWords.INPUT_STYLE_TAIL_BATCH;
1722             } else {
1723                 inputStyle = SuggestedWords.INPUT_STYLE_TYPING;
1724             }
1725             mHandler.postUpdateSuggestionStrip(inputStyle);
1726         }
1727         if (inputTransaction.didAffectContents()) {
1728             mSubtypeState.setCurrentSubtypeHasBeenUsed();
1729         }
1730     }
1731 
hapticAndAudioFeedback(final int code, final int repeatCount)1732     private void hapticAndAudioFeedback(final int code, final int repeatCount) {
1733         final MainKeyboardView keyboardView = mKeyboardSwitcher.getMainKeyboardView();
1734         if (keyboardView != null && keyboardView.isInDraggingFinger()) {
1735             // No need to feedback while finger is dragging.
1736             return;
1737         }
1738         if (repeatCount > 0) {
1739             if (code == Constants.CODE_DELETE && !mInputLogic.mConnection.canDeleteCharacters()) {
1740                 // No need to feedback when repeat delete key will have no effect.
1741                 return;
1742             }
1743             // TODO: Use event time that the last feedback has been generated instead of relying on
1744             // a repeat count to thin out feedback.
1745             if (repeatCount % PERIOD_FOR_AUDIO_AND_HAPTIC_FEEDBACK_IN_KEY_REPEAT == 0) {
1746                 return;
1747             }
1748         }
1749         final AudioAndHapticFeedbackManager feedbackManager =
1750                 AudioAndHapticFeedbackManager.getInstance();
1751         if (repeatCount == 0) {
1752             // TODO: Reconsider how to perform haptic feedback when repeating key.
1753             feedbackManager.performHapticFeedback(keyboardView);
1754         }
1755         feedbackManager.performAudioFeedback(code);
1756     }
1757 
1758     // Callback of the {@link KeyboardActionListener}. This is called when a key is depressed;
1759     // release matching call is {@link #onReleaseKey(int,boolean)} below.
1760     @Override
onPressKey(final int primaryCode, final int repeatCount, final boolean isSinglePointer)1761     public void onPressKey(final int primaryCode, final int repeatCount,
1762             final boolean isSinglePointer) {
1763         mKeyboardSwitcher.onPressKey(primaryCode, isSinglePointer, getCurrentAutoCapsState(),
1764                 getCurrentRecapitalizeState());
1765         hapticAndAudioFeedback(primaryCode, repeatCount);
1766     }
1767 
1768     // Callback of the {@link KeyboardActionListener}. This is called when a key is released;
1769     // press matching call is {@link #onPressKey(int,int,boolean)} above.
1770     @Override
onReleaseKey(final int primaryCode, final boolean withSliding)1771     public void onReleaseKey(final int primaryCode, final boolean withSliding) {
1772         mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding, getCurrentAutoCapsState(),
1773                 getCurrentRecapitalizeState());
1774     }
1775 
getHardwareKeyEventDecoder(final int deviceId)1776     private HardwareEventDecoder getHardwareKeyEventDecoder(final int deviceId) {
1777         final HardwareEventDecoder decoder = mHardwareEventDecoders.get(deviceId);
1778         if (null != decoder) return decoder;
1779         // TODO: create the decoder according to the specification
1780         final HardwareEventDecoder newDecoder = new HardwareKeyboardEventDecoder(deviceId);
1781         mHardwareEventDecoders.put(deviceId, newDecoder);
1782         return newDecoder;
1783     }
1784 
1785     // Hooks for hardware keyboard
1786     @Override
onKeyDown(final int keyCode, final KeyEvent keyEvent)1787     public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) {
1788         if (mEmojiAltPhysicalKeyDetector == null) {
1789             mEmojiAltPhysicalKeyDetector = new EmojiAltPhysicalKeyDetector(
1790                     getApplicationContext().getResources());
1791         }
1792         mEmojiAltPhysicalKeyDetector.onKeyDown(keyEvent);
1793         if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) {
1794             return super.onKeyDown(keyCode, keyEvent);
1795         }
1796         final Event event = getHardwareKeyEventDecoder(
1797                 keyEvent.getDeviceId()).decodeHardwareKey(keyEvent);
1798         // If the event is not handled by LatinIME, we just pass it to the parent implementation.
1799         // If it's handled, we return true because we did handle it.
1800         if (event.isHandled()) {
1801             mInputLogic.onCodeInput(mSettings.getCurrent(), event,
1802                     mKeyboardSwitcher.getKeyboardShiftMode(),
1803                     // TODO: this is not necessarily correct for a hardware keyboard right now
1804                     mKeyboardSwitcher.getCurrentKeyboardScriptId(),
1805                     mHandler);
1806             return true;
1807         }
1808         return super.onKeyDown(keyCode, keyEvent);
1809     }
1810 
1811     @Override
onKeyUp(final int keyCode, final KeyEvent keyEvent)1812     public boolean onKeyUp(final int keyCode, final KeyEvent keyEvent) {
1813         if (mEmojiAltPhysicalKeyDetector == null) {
1814             mEmojiAltPhysicalKeyDetector = new EmojiAltPhysicalKeyDetector(
1815                     getApplicationContext().getResources());
1816         }
1817         mEmojiAltPhysicalKeyDetector.onKeyUp(keyEvent);
1818         if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) {
1819             return super.onKeyUp(keyCode, keyEvent);
1820         }
1821         final long keyIdentifier = keyEvent.getDeviceId() << 32 + keyEvent.getKeyCode();
1822         if (mInputLogic.mCurrentlyPressedHardwareKeys.remove(keyIdentifier)) {
1823             return true;
1824         }
1825         return super.onKeyUp(keyCode, keyEvent);
1826     }
1827 
1828     // onKeyDown and onKeyUp are the main events we are interested in. There are two more events
1829     // related to handling of hardware key events that we may want to implement in the future:
1830     // boolean onKeyLongPress(final int keyCode, final KeyEvent event);
1831     // boolean onKeyMultiple(final int keyCode, final int count, final KeyEvent event);
1832 
1833     // receive ringer mode change.
1834     private final BroadcastReceiver mRingerModeChangeReceiver = new BroadcastReceiver() {
1835         @Override
1836         public void onReceive(final Context context, final Intent intent) {
1837             final String action = intent.getAction();
1838             if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
1839                 AudioAndHapticFeedbackManager.getInstance().onRingerModeChanged();
1840             }
1841         }
1842     };
1843 
1844     /**
1845      * Starts {@link android.app.Activity} on the same display where the IME is shown.
1846      *
1847      * @param intent {@link Intent} to be used to start {@link android.app.Activity}.
1848      */
startActivityOnTheSameDisplay(Intent intent)1849     private void startActivityOnTheSameDisplay(Intent intent) {
1850         // Note that WindowManager#getDefaultDisplay() returns the display ID associated with the
1851         // Context from which the WindowManager instance was obtained. Therefore the following code
1852         // returns the display ID for the window where the IME is shown.
1853         final int currentDisplayId = ((WindowManager) getSystemService(Context.WINDOW_SERVICE))
1854                 .getDefaultDisplay().getDisplayId();
1855 
1856         startActivity(intent,
1857                 ActivityOptions.makeBasic().setLaunchDisplayId(currentDisplayId).toBundle());
1858     }
1859 
launchSettings(final String extraEntryValue)1860     void launchSettings(final String extraEntryValue) {
1861         mInputLogic.commitTyped(mSettings.getCurrent(), LastComposedWord.NOT_A_SEPARATOR);
1862         requestHideSelf(0);
1863         final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
1864         if (mainKeyboardView != null) {
1865             mainKeyboardView.closing();
1866         }
1867         final Intent intent = new Intent();
1868         intent.setClass(LatinIME.this, SettingsActivity.class);
1869         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1870                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
1871                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
1872         intent.putExtra(SettingsActivity.EXTRA_SHOW_HOME_AS_UP, false);
1873         intent.putExtra(SettingsActivity.EXTRA_ENTRY_KEY, extraEntryValue);
1874         startActivityOnTheSameDisplay(intent);
1875     }
1876 
showSubtypeSelectorAndSettings()1877     private void showSubtypeSelectorAndSettings() {
1878         final CharSequence title = getString(R.string.english_ime_input_options);
1879         // TODO: Should use new string "Select active input modes".
1880         final CharSequence languageSelectionTitle = getString(R.string.language_selection_title);
1881         final CharSequence[] items = new CharSequence[] {
1882                 languageSelectionTitle,
1883                 getString(ApplicationUtils.getActivityTitleResId(this, SettingsActivity.class))
1884         };
1885         final String imeId = mRichImm.getInputMethodIdOfThisIme();
1886         final OnClickListener listener = new OnClickListener() {
1887             @Override
1888             public void onClick(DialogInterface di, int position) {
1889                 di.dismiss();
1890                 switch (position) {
1891                 case 0:
1892                     final Intent intent = IntentUtils.getInputLanguageSelectionIntent(
1893                             imeId,
1894                             Intent.FLAG_ACTIVITY_NEW_TASK
1895                                     | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
1896                                     | Intent.FLAG_ACTIVITY_CLEAR_TOP);
1897                     intent.putExtra(Intent.EXTRA_TITLE, languageSelectionTitle);
1898                     startActivityOnTheSameDisplay(intent);
1899                     break;
1900                 case 1:
1901                     launchSettings(SettingsActivity.EXTRA_ENTRY_VALUE_LONG_PRESS_COMMA);
1902                     break;
1903                 }
1904             }
1905         };
1906         final AlertDialog.Builder builder = new AlertDialog.Builder(
1907                 DialogUtils.getPlatformDialogThemeContext(this));
1908         builder.setItems(items, listener).setTitle(title);
1909         final AlertDialog dialog = builder.create();
1910         dialog.setCancelable(true /* cancelable */);
1911         dialog.setCanceledOnTouchOutside(true /* cancelable */);
1912         showOptionDialog(dialog);
1913     }
1914 
1915     // TODO: Move this method out of {@link LatinIME}.
showOptionDialog(final AlertDialog dialog)1916     private void showOptionDialog(final AlertDialog dialog) {
1917         final IBinder windowToken = mKeyboardSwitcher.getMainKeyboardView().getWindowToken();
1918         if (windowToken == null) {
1919             return;
1920         }
1921 
1922         final Window window = dialog.getWindow();
1923         final WindowManager.LayoutParams lp = window.getAttributes();
1924         lp.token = windowToken;
1925         lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
1926         window.setAttributes(lp);
1927         window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
1928 
1929         mOptionsDialog = dialog;
1930         dialog.show();
1931     }
1932 
1933     @UsedForTesting
getSuggestedWordsForTest()1934     SuggestedWords getSuggestedWordsForTest() {
1935         // You may not use this method for anything else than debug
1936         return DebugFlags.DEBUG_ENABLED ? mInputLogic.mSuggestedWords : null;
1937     }
1938 
1939     // DO NOT USE THIS for any other purpose than testing. This is information private to LatinIME.
1940     @UsedForTesting
waitForLoadingDictionaries(final long timeout, final TimeUnit unit)1941     void waitForLoadingDictionaries(final long timeout, final TimeUnit unit)
1942             throws InterruptedException {
1943         mDictionaryFacilitator.waitForLoadingDictionariesForTesting(timeout, unit);
1944     }
1945 
1946     // DO NOT USE THIS for any other purpose than testing. This can break the keyboard badly.
1947     @UsedForTesting
replaceDictionariesForTest(final Locale locale)1948     void replaceDictionariesForTest(final Locale locale) {
1949         final SettingsValues settingsValues = mSettings.getCurrent();
1950         mDictionaryFacilitator.resetDictionaries(this, locale,
1951             settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts,
1952             false /* forceReloadMainDictionary */,
1953             settingsValues.mAccount, "", /* dictionaryNamePrefix */
1954             this /* DictionaryInitializationListener */);
1955     }
1956 
1957     // DO NOT USE THIS for any other purpose than testing.
1958     @UsedForTesting
clearPersonalizedDictionariesForTest()1959     void clearPersonalizedDictionariesForTest() {
1960         mDictionaryFacilitator.clearUserHistoryDictionary(this);
1961     }
1962 
1963     @UsedForTesting
getEnabledSubtypesForTest()1964     List<InputMethodSubtype> getEnabledSubtypesForTest() {
1965         return (mRichImm != null) ? mRichImm.getMyEnabledInputMethodSubtypeList(
1966                 true /* allowsImplicitlySelectedSubtypes */) : new ArrayList<InputMethodSubtype>();
1967     }
1968 
dumpDictionaryForDebug(final String dictName)1969     public void dumpDictionaryForDebug(final String dictName) {
1970         if (!mDictionaryFacilitator.isActive()) {
1971             resetDictionaryFacilitatorIfNecessary();
1972         }
1973         mDictionaryFacilitator.dumpDictionaryForDebug(dictName);
1974     }
1975 
debugDumpStateAndCrashWithException(final String context)1976     public void debugDumpStateAndCrashWithException(final String context) {
1977         final SettingsValues settingsValues = mSettings.getCurrent();
1978         final StringBuilder s = new StringBuilder(settingsValues.toString());
1979         s.append("\nAttributes : ").append(settingsValues.mInputAttributes)
1980                 .append("\nContext : ").append(context);
1981         throw new RuntimeException(s.toString());
1982     }
1983 
1984     @Override
dump(final FileDescriptor fd, final PrintWriter fout, final String[] args)1985     protected void dump(final FileDescriptor fd, final PrintWriter fout, final String[] args) {
1986         super.dump(fd, fout, args);
1987 
1988         final Printer p = new PrintWriterPrinter(fout);
1989         p.println("LatinIME state :");
1990         p.println("  VersionCode = " + ApplicationUtils.getVersionCode(this));
1991         p.println("  VersionName = " + ApplicationUtils.getVersionName(this));
1992         final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
1993         final int keyboardMode = keyboard != null ? keyboard.mId.mMode : -1;
1994         p.println("  Keyboard mode = " + keyboardMode);
1995         final SettingsValues settingsValues = mSettings.getCurrent();
1996         p.println(settingsValues.dump());
1997         p.println(mDictionaryFacilitator.dump(this /* context */));
1998         // TODO: Dump all settings values
1999     }
2000 
shouldSwitchToOtherInputMethods()2001     public boolean shouldSwitchToOtherInputMethods() {
2002         // TODO: Revisit here to reorganize the settings. Probably we can/should use different
2003         // strategy once the implementation of
2004         // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} is defined well.
2005         final boolean fallbackValue = mSettings.getCurrent().mIncludesOtherImesInLanguageSwitchList;
2006         final IBinder token = getWindow().getWindow().getAttributes().token;
2007         if (token == null) {
2008             return fallbackValue;
2009         }
2010         return mRichImm.shouldOfferSwitchingToNextInputMethod(token, fallbackValue);
2011     }
2012 
shouldShowLanguageSwitchKey()2013     public boolean shouldShowLanguageSwitchKey() {
2014         // TODO: Revisit here to reorganize the settings. Probably we can/should use different
2015         // strategy once the implementation of
2016         // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} is defined well.
2017         final boolean fallbackValue = mSettings.getCurrent().isLanguageSwitchKeyEnabled();
2018         final IBinder token = getWindow().getWindow().getAttributes().token;
2019         if (token == null) {
2020             return fallbackValue;
2021         }
2022         return mRichImm.shouldOfferSwitchingToNextInputMethod(token, fallbackValue);
2023     }
2024 
setNavigationBarVisibility(final boolean visible)2025     private void setNavigationBarVisibility(final boolean visible) {
2026         if (BuildCompatUtils.EFFECTIVE_SDK_INT > Build.VERSION_CODES.M) {
2027             // For N and later, IMEs can specify Color.TRANSPARENT to make the navigation bar
2028             // transparent.  For other colors the system uses the default color.
2029             getWindow().getWindow().setNavigationBarColor(
2030                     visible ? Color.BLACK : Color.TRANSPARENT);
2031         }
2032     }
2033 }
2034