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