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