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