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