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