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