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