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