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