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