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