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