LatinIME.java revision b715299125e8fbaaa941d994217baf823e6c4013
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package com.android.inputmethod.latin; 18 19import android.app.AlertDialog; 20import android.content.BroadcastReceiver; 21import android.content.Context; 22import android.content.DialogInterface; 23import android.content.Intent; 24import android.content.IntentFilter; 25import android.content.SharedPreferences; 26import android.content.res.Configuration; 27import android.content.res.Resources; 28import android.inputmethodservice.InputMethodService; 29import android.media.AudioManager; 30import android.net.ConnectivityManager; 31import android.os.Debug; 32import android.os.Message; 33import android.os.SystemClock; 34import android.preference.PreferenceActivity; 35import android.preference.PreferenceManager; 36import android.text.InputType; 37import android.text.TextUtils; 38import android.util.Log; 39import android.util.PrintWriterPrinter; 40import android.util.Printer; 41import android.view.HapticFeedbackConstants; 42import android.view.KeyEvent; 43import android.view.View; 44import android.view.ViewGroup; 45import android.view.ViewParent; 46import android.view.inputmethod.CompletionInfo; 47import android.view.inputmethod.EditorInfo; 48import android.view.inputmethod.ExtractedText; 49import android.view.inputmethod.InputConnection; 50 51import com.android.inputmethod.accessibility.AccessibilityUtils; 52import com.android.inputmethod.compat.CompatUtils; 53import com.android.inputmethod.compat.EditorInfoCompatUtils; 54import com.android.inputmethod.compat.InputConnectionCompatUtils; 55import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; 56import com.android.inputmethod.compat.InputMethodServiceCompatWrapper; 57import com.android.inputmethod.compat.InputTypeCompatUtils; 58import com.android.inputmethod.compat.SuggestionSpanUtils; 59import com.android.inputmethod.compat.VibratorCompatWrapper; 60import com.android.inputmethod.deprecated.LanguageSwitcherProxy; 61import com.android.inputmethod.deprecated.VoiceProxy; 62import com.android.inputmethod.keyboard.Key; 63import com.android.inputmethod.keyboard.Keyboard; 64import com.android.inputmethod.keyboard.KeyboardActionListener; 65import com.android.inputmethod.keyboard.KeyboardSwitcher; 66import com.android.inputmethod.keyboard.KeyboardView; 67import com.android.inputmethod.keyboard.LatinKeyboard; 68import com.android.inputmethod.keyboard.LatinKeyboardView; 69 70import java.io.FileDescriptor; 71import java.io.PrintWriter; 72import java.util.Locale; 73 74/** 75 * Input method implementation for Qwerty'ish keyboard. 76 */ 77public class LatinIME extends InputMethodServiceCompatWrapper implements KeyboardActionListener, 78 SuggestionsView.Listener { 79 private static final String TAG = LatinIME.class.getSimpleName(); 80 private static final boolean PERF_DEBUG = false; 81 private static final boolean TRACE = false; 82 private static boolean DEBUG; 83 84 /** 85 * The private IME option used to indicate that no microphone should be 86 * shown for a given text field. For instance, this is specified by the 87 * search dialog when the dialog is already showing a voice search button. 88 * 89 * @deprecated Use {@link LatinIME#IME_OPTION_NO_MICROPHONE} with package name prefixed. 90 */ 91 @SuppressWarnings("dep-ann") 92 public static final String IME_OPTION_NO_MICROPHONE_COMPAT = "nm"; 93 94 /** 95 * The private IME option used to indicate that no microphone should be 96 * shown for a given text field. For instance, this is specified by the 97 * search dialog when the dialog is already showing a voice search button. 98 */ 99 public static final String IME_OPTION_NO_MICROPHONE = "noMicrophoneKey"; 100 101 /** 102 * The private IME option used to indicate that no settings key should be 103 * shown for a given text field. 104 */ 105 public static final String IME_OPTION_NO_SETTINGS_KEY = "noSettingsKey"; 106 107 /** 108 * The private IME option used to indicate that the given text field needs 109 * ASCII code points input. 110 */ 111 public static final String IME_OPTION_FORCE_ASCII = "forceAscii"; 112 113 /** 114 * The subtype extra value used to indicate that the subtype keyboard layout is capable for 115 * typing ASCII characters. 116 */ 117 public static final String SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE = "AsciiCapable"; 118 119 /** 120 * The subtype extra value used to indicate that the subtype keyboard layout supports touch 121 * position correction. 122 */ 123 public static final String SUBTYPE_EXTRA_VALUE_SUPPORT_TOUCH_POSITION_CORRECTION = 124 "SupportTouchPositionCorrection"; 125 /** 126 * The subtype extra value used to indicate that the subtype keyboard layout should be loaded 127 * from the specified locale. 128 */ 129 public static final String SUBTYPE_EXTRA_VALUE_KEYBOARD_LOCALE = "KeyboardLocale"; 130 131 private static final int EXTENDED_TOUCHABLE_REGION_HEIGHT = 100; 132 133 // How many continuous deletes at which to start deleting at a higher speed. 134 private static final int DELETE_ACCELERATE_AT = 20; 135 // Key events coming any faster than this are long-presses. 136 private static final int QUICK_PRESS = 200; 137 138 private static final int PENDING_IMS_CALLBACK_DURATION = 800; 139 140 /** 141 * The name of the scheme used by the Package Manager to warn of a new package installation, 142 * replacement or removal. 143 */ 144 private static final String SCHEME_PACKAGE = "package"; 145 146 private int mSuggestionVisibility; 147 private static final int SUGGESTION_VISIBILILTY_SHOW_VALUE 148 = R.string.prefs_suggestion_visibility_show_value; 149 private static final int SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE 150 = R.string.prefs_suggestion_visibility_show_only_portrait_value; 151 private static final int SUGGESTION_VISIBILILTY_HIDE_VALUE 152 = R.string.prefs_suggestion_visibility_hide_value; 153 154 private static final int[] SUGGESTION_VISIBILITY_VALUE_ARRAY = new int[] { 155 SUGGESTION_VISIBILILTY_SHOW_VALUE, 156 SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE, 157 SUGGESTION_VISIBILILTY_HIDE_VALUE 158 }; 159 160 private Settings.Values mSettingsValues; 161 162 private View mExtractArea; 163 private View mKeyPreviewBackingView; 164 private View mSuggestionsContainer; 165 private SuggestionsView mSuggestionsView; 166 private Suggest mSuggest; 167 private CompletionInfo[] mApplicationSpecifiedCompletions; 168 169 private InputMethodManagerCompatWrapper mImm; 170 private Resources mResources; 171 private SharedPreferences mPrefs; 172 private String mInputMethodId; 173 private KeyboardSwitcher mKeyboardSwitcher; 174 private SubtypeSwitcher mSubtypeSwitcher; 175 private VoiceProxy mVoiceProxy; 176 177 private UserDictionary mUserDictionary; 178 private UserBigramDictionary mUserBigramDictionary; 179 private UserUnigramDictionary mUserUnigramDictionary; 180 private boolean mIsUserDictionaryAvaliable; 181 182 // TODO: Create an inner class to group options and pseudo-options to improve readability. 183 // These variables are initialized according to the {@link EditorInfo#inputType}. 184 private boolean mInsertSpaceOnPickSuggestionManually; 185 private boolean mInputTypeNoAutoCorrect; 186 private boolean mIsSettingsSuggestionStripOn; 187 private boolean mApplicationSpecifiedCompletionOn; 188 189 private final StringBuilder mComposingStringBuilder = new StringBuilder(); 190 private WordComposer mWordComposer = new WordComposer(); 191 private CharSequence mBestWord; 192 private boolean mHasUncommittedTypedChars; 193 // Magic space: a space that should disappear on space/apostrophe insertion, move after the 194 // punctuation on punctuation insertion, and become a real space on alpha char insertion. 195 private boolean mJustAddedMagicSpace; // This indicates whether the last char is a magic space. 196 // This indicates whether the last keypress resulted in processing of double space replacement 197 // with period-space. 198 private boolean mJustReplacedDoubleSpace; 199 200 private int mCorrectionMode; 201 private int mCommittedLength; 202 // Keep track of the last selection range to decide if we need to show word alternatives 203 private int mLastSelectionStart; 204 private int mLastSelectionEnd; 205 206 // Whether we are expecting an onUpdateSelection event to fire. If it does when we don't 207 // "expect" it, it means the user actually moved the cursor. 208 private boolean mExpectingUpdateSelection; 209 private int mDeleteCount; 210 private long mLastKeyTime; 211 212 private AudioManager mAudioManager; 213 private float mFxVolume = -1.0f; // default volume 214 private boolean mSilentModeOn; // System-wide current configuration 215 216 private VibratorCompatWrapper mVibrator; 217 private long mKeypressVibrationDuration = -1; 218 219 // TODO: Move this flag to VoiceProxy 220 private boolean mConfigurationChanging; 221 222 // Member variables for remembering the current device orientation. 223 private int mDisplayOrientation; 224 225 // Object for reacting to adding/removing a dictionary pack. 226 private BroadcastReceiver mDictionaryPackInstallReceiver = 227 new DictionaryPackInstallBroadcastReceiver(this); 228 229 // Keeps track of most recently inserted text (multi-character key) for reverting 230 private CharSequence mEnteredText; 231 232 private final ComposingStateManager mComposingStateManager = 233 ComposingStateManager.getInstance(); 234 235 public final UIHandler mHandler = new UIHandler(this); 236 237 public static class UIHandler extends StaticInnerHandlerWrapper<LatinIME> { 238 private static final int MSG_UPDATE_SUGGESTIONS = 0; 239 private static final int MSG_UPDATE_SHIFT_STATE = 1; 240 private static final int MSG_VOICE_RESULTS = 2; 241 private static final int MSG_FADEOUT_LANGUAGE_ON_SPACEBAR = 3; 242 private static final int MSG_DISMISS_LANGUAGE_ON_SPACEBAR = 4; 243 private static final int MSG_SPACE_TYPED = 5; 244 private static final int MSG_SET_BIGRAM_PREDICTIONS = 6; 245 private static final int MSG_PENDING_IMS_CALLBACK = 7; 246 247 public UIHandler(LatinIME outerInstance) { 248 super(outerInstance); 249 } 250 251 @Override 252 public void handleMessage(Message msg) { 253 final LatinIME latinIme = getOuterInstance(); 254 final KeyboardSwitcher switcher = latinIme.mKeyboardSwitcher; 255 final LatinKeyboardView inputView = switcher.getKeyboardView(); 256 switch (msg.what) { 257 case MSG_UPDATE_SUGGESTIONS: 258 latinIme.updateSuggestions(); 259 break; 260 case MSG_UPDATE_SHIFT_STATE: 261 switcher.updateShiftState(); 262 break; 263 case MSG_SET_BIGRAM_PREDICTIONS: 264 latinIme.updateBigramPredictions(); 265 break; 266 case MSG_VOICE_RESULTS: 267 latinIme.mVoiceProxy.handleVoiceResults(latinIme.preferCapitalization() 268 || (switcher.isAlphabetMode() && switcher.isShiftedOrShiftLocked())); 269 break; 270 case MSG_FADEOUT_LANGUAGE_ON_SPACEBAR: 271 if (inputView != null) { 272 inputView.setSpacebarTextFadeFactor( 273 (1.0f + latinIme.mSettingsValues. 274 mFinalFadeoutFactorOfLanguageOnSpacebar) / 2, 275 (LatinKeyboard)msg.obj); 276 } 277 sendMessageDelayed(obtainMessage(MSG_DISMISS_LANGUAGE_ON_SPACEBAR, msg.obj), 278 latinIme.mSettingsValues.mDurationOfFadeoutLanguageOnSpacebar); 279 break; 280 case MSG_DISMISS_LANGUAGE_ON_SPACEBAR: 281 if (inputView != null) { 282 inputView.setSpacebarTextFadeFactor( 283 latinIme.mSettingsValues.mFinalFadeoutFactorOfLanguageOnSpacebar, 284 (LatinKeyboard)msg.obj); 285 } 286 break; 287 } 288 } 289 290 public void postUpdateSuggestions() { 291 removeMessages(MSG_UPDATE_SUGGESTIONS); 292 sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTIONS), 293 getOuterInstance().mSettingsValues.mDelayUpdateSuggestions); 294 } 295 296 public void cancelUpdateSuggestions() { 297 removeMessages(MSG_UPDATE_SUGGESTIONS); 298 } 299 300 public boolean hasPendingUpdateSuggestions() { 301 return hasMessages(MSG_UPDATE_SUGGESTIONS); 302 } 303 304 public void postUpdateShiftKeyState() { 305 removeMessages(MSG_UPDATE_SHIFT_STATE); 306 sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), 307 getOuterInstance().mSettingsValues.mDelayUpdateShiftState); 308 } 309 310 public void cancelUpdateShiftState() { 311 removeMessages(MSG_UPDATE_SHIFT_STATE); 312 } 313 314 public void postUpdateBigramPredictions() { 315 removeMessages(MSG_SET_BIGRAM_PREDICTIONS); 316 sendMessageDelayed(obtainMessage(MSG_SET_BIGRAM_PREDICTIONS), 317 getOuterInstance().mSettingsValues.mDelayUpdateSuggestions); 318 } 319 320 public void cancelUpdateBigramPredictions() { 321 removeMessages(MSG_SET_BIGRAM_PREDICTIONS); 322 } 323 324 public void updateVoiceResults() { 325 sendMessage(obtainMessage(MSG_VOICE_RESULTS)); 326 } 327 328 public void startDisplayLanguageOnSpacebar(boolean localeChanged) { 329 final LatinIME latinIme = getOuterInstance(); 330 removeMessages(MSG_FADEOUT_LANGUAGE_ON_SPACEBAR); 331 removeMessages(MSG_DISMISS_LANGUAGE_ON_SPACEBAR); 332 final LatinKeyboardView inputView = latinIme.mKeyboardSwitcher.getKeyboardView(); 333 if (inputView != null) { 334 final LatinKeyboard keyboard = latinIme.mKeyboardSwitcher.getLatinKeyboard(); 335 // The language is always displayed when the delay is negative. 336 final boolean needsToDisplayLanguage = localeChanged 337 || latinIme.mSettingsValues.mDelayBeforeFadeoutLanguageOnSpacebar < 0; 338 // The language is never displayed when the delay is zero. 339 if (latinIme.mSettingsValues.mDelayBeforeFadeoutLanguageOnSpacebar != 0) { 340 inputView.setSpacebarTextFadeFactor(needsToDisplayLanguage ? 1.0f 341 : latinIme.mSettingsValues.mFinalFadeoutFactorOfLanguageOnSpacebar, 342 keyboard); 343 } 344 // The fadeout animation will start when the delay is positive. 345 if (localeChanged 346 && latinIme.mSettingsValues.mDelayBeforeFadeoutLanguageOnSpacebar > 0) { 347 sendMessageDelayed(obtainMessage(MSG_FADEOUT_LANGUAGE_ON_SPACEBAR, keyboard), 348 latinIme.mSettingsValues.mDelayBeforeFadeoutLanguageOnSpacebar); 349 } 350 } 351 } 352 353 public void startDoubleSpacesTimer() { 354 removeMessages(MSG_SPACE_TYPED); 355 sendMessageDelayed(obtainMessage(MSG_SPACE_TYPED), 356 getOuterInstance().mSettingsValues.mDoubleSpacesTurnIntoPeriodTimeout); 357 } 358 359 public void cancelDoubleSpacesTimer() { 360 removeMessages(MSG_SPACE_TYPED); 361 } 362 363 public boolean isAcceptingDoubleSpaces() { 364 return hasMessages(MSG_SPACE_TYPED); 365 } 366 367 // Working variables for the following methods. 368 private boolean mIsOrientationChanging; 369 private boolean mPendingSuccesiveImsCallback; 370 private boolean mHasPendingStartInput; 371 private boolean mHasPendingFinishInputView; 372 private boolean mHasPendingFinishInput; 373 374 public void startOrientationChanging() { 375 mIsOrientationChanging = true; 376 final LatinIME latinIme = getOuterInstance(); 377 latinIme.mKeyboardSwitcher.saveKeyboardState(); 378 } 379 380 private void resetPendingImsCallback() { 381 mHasPendingFinishInputView = false; 382 mHasPendingFinishInput = false; 383 mHasPendingStartInput = false; 384 } 385 386 private void executePendingImsCallback(LatinIME latinIme, EditorInfo attribute, 387 boolean restarting) { 388 if (mHasPendingFinishInputView) 389 latinIme.onFinishInputViewInternal(mHasPendingFinishInput); 390 if (mHasPendingFinishInput) 391 latinIme.onFinishInputInternal(); 392 if (mHasPendingStartInput) 393 latinIme.onStartInputInternal(attribute, restarting); 394 resetPendingImsCallback(); 395 } 396 397 public void onStartInput(EditorInfo attribute, boolean restarting) { 398 if (hasMessages(MSG_PENDING_IMS_CALLBACK)) { 399 // Typically this is the second onStartInput after orientation changed. 400 mHasPendingStartInput = true; 401 } else { 402 if (mIsOrientationChanging && restarting) { 403 // This is the first onStartInput after orientation changed. 404 mIsOrientationChanging = false; 405 mPendingSuccesiveImsCallback = true; 406 } 407 final LatinIME latinIme = getOuterInstance(); 408 executePendingImsCallback(latinIme, attribute, restarting); 409 latinIme.onStartInputInternal(attribute, restarting); 410 } 411 } 412 413 public void onStartInputView(EditorInfo attribute, boolean restarting) { 414 if (hasMessages(MSG_PENDING_IMS_CALLBACK)) { 415 // Typically this is the second onStartInputView after orientation changed. 416 resetPendingImsCallback(); 417 } else { 418 if (mPendingSuccesiveImsCallback) { 419 // This is the first onStartInputView after orientation changed. 420 mPendingSuccesiveImsCallback = false; 421 resetPendingImsCallback(); 422 sendMessageDelayed(obtainMessage(MSG_PENDING_IMS_CALLBACK), 423 PENDING_IMS_CALLBACK_DURATION); 424 } 425 final LatinIME latinIme = getOuterInstance(); 426 executePendingImsCallback(latinIme, attribute, restarting); 427 latinIme.onStartInputViewInternal(attribute, restarting); 428 } 429 } 430 431 public void onFinishInputView(boolean finishingInput) { 432 if (hasMessages(MSG_PENDING_IMS_CALLBACK)) { 433 // Typically this is the first onFinishInputView after orientation changed. 434 mHasPendingFinishInputView = true; 435 } else { 436 final LatinIME latinIme = getOuterInstance(); 437 latinIme.onFinishInputViewInternal(finishingInput); 438 } 439 } 440 441 public void onFinishInput() { 442 if (hasMessages(MSG_PENDING_IMS_CALLBACK)) { 443 // Typically this is the first onFinishInput after orientation changed. 444 mHasPendingFinishInput = true; 445 } else { 446 final LatinIME latinIme = getOuterInstance(); 447 executePendingImsCallback(latinIme, null, false); 448 latinIme.onFinishInputInternal(); 449 } 450 } 451 } 452 453 @Override 454 public void onCreate() { 455 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 456 mPrefs = prefs; 457 LatinImeLogger.init(this, prefs); 458 LanguageSwitcherProxy.init(this, prefs); 459 InputMethodManagerCompatWrapper.init(this); 460 SubtypeSwitcher.init(this); 461 KeyboardSwitcher.init(this, prefs); 462 AccessibilityUtils.init(this, prefs); 463 464 super.onCreate(); 465 466 mImm = InputMethodManagerCompatWrapper.getInstance(); 467 mInputMethodId = Utils.getInputMethodId(mImm, getPackageName()); 468 mSubtypeSwitcher = SubtypeSwitcher.getInstance(); 469 mKeyboardSwitcher = KeyboardSwitcher.getInstance(); 470 mVibrator = VibratorCompatWrapper.getInstance(this); 471 DEBUG = LatinImeLogger.sDBG; 472 473 final Resources res = getResources(); 474 mResources = res; 475 476 loadSettings(); 477 478 Utils.GCUtils.getInstance().reset(); 479 boolean tryGC = true; 480 for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) { 481 try { 482 initSuggest(); 483 tryGC = false; 484 } catch (OutOfMemoryError e) { 485 tryGC = Utils.GCUtils.getInstance().tryGCOrWait("InitSuggest", e); 486 } 487 } 488 489 mDisplayOrientation = res.getConfiguration().orientation; 490 491 // Register to receive ringer mode change and network state change. 492 // Also receive installation and removal of a dictionary pack. 493 final IntentFilter filter = new IntentFilter(); 494 filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); 495 filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); 496 registerReceiver(mReceiver, filter); 497 mVoiceProxy = VoiceProxy.init(this, prefs, mHandler); 498 499 final IntentFilter packageFilter = new IntentFilter(); 500 packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); 501 packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); 502 packageFilter.addDataScheme(SCHEME_PACKAGE); 503 registerReceiver(mDictionaryPackInstallReceiver, packageFilter); 504 505 final IntentFilter newDictFilter = new IntentFilter(); 506 newDictFilter.addAction( 507 DictionaryPackInstallBroadcastReceiver.NEW_DICTIONARY_INTENT_ACTION); 508 registerReceiver(mDictionaryPackInstallReceiver, newDictFilter); 509 } 510 511 // Has to be package-visible for unit tests 512 /* package */ void loadSettings() { 513 if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this); 514 if (null == mSubtypeSwitcher) mSubtypeSwitcher = SubtypeSwitcher.getInstance(); 515 mSettingsValues = new Settings.Values(mPrefs, this, mSubtypeSwitcher.getInputLocaleStr()); 516 resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary()); 517 updateSoundEffectVolume(); 518 updateKeypressVibrationDuration(); 519 } 520 521 private void initSuggest() { 522 final String localeStr = mSubtypeSwitcher.getInputLocaleStr(); 523 final Locale keyboardLocale = LocaleUtils.constructLocaleFromString(localeStr); 524 525 final Resources res = mResources; 526 final Locale savedLocale = LocaleUtils.setSystemLocale(res, keyboardLocale); 527 final ContactsDictionary oldContactsDictionary; 528 if (mSuggest != null) { 529 oldContactsDictionary = mSuggest.getContactsDictionary(); 530 mSuggest.close(); 531 } else { 532 oldContactsDictionary = null; 533 } 534 535 int mainDicResId = Utils.getMainDictionaryResourceId(res); 536 mSuggest = new Suggest(this, mainDicResId, keyboardLocale); 537 if (mSettingsValues.mAutoCorrectEnabled) { 538 mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold); 539 } 540 541 mUserDictionary = new UserDictionary(this, localeStr); 542 mSuggest.setUserDictionary(mUserDictionary); 543 mIsUserDictionaryAvaliable = mUserDictionary.isEnabled(); 544 545 resetContactsDictionary(oldContactsDictionary); 546 547 mUserUnigramDictionary 548 = new UserUnigramDictionary(this, this, localeStr, Suggest.DIC_USER_UNIGRAM); 549 mSuggest.setUserUnigramDictionary(mUserUnigramDictionary); 550 551 mUserBigramDictionary 552 = new UserBigramDictionary(this, this, localeStr, Suggest.DIC_USER_BIGRAM); 553 mSuggest.setUserBigramDictionary(mUserBigramDictionary); 554 555 updateCorrectionMode(); 556 557 LocaleUtils.setSystemLocale(res, savedLocale); 558 } 559 560 /** 561 * Resets the contacts dictionary in mSuggest according to the user settings. 562 * 563 * This method takes an optional contacts dictionary to use. Since the contacts dictionary 564 * does not depend on the locale, it can be reused across different instances of Suggest. 565 * The dictionary will also be opened or closed as necessary depending on the settings. 566 * 567 * @param oldContactsDictionary an optional dictionary to use, or null 568 */ 569 private void resetContactsDictionary(final ContactsDictionary oldContactsDictionary) { 570 final boolean shouldSetDictionary = (null != mSuggest && mSettingsValues.mUseContactsDict); 571 572 final ContactsDictionary dictionaryToUse; 573 if (!shouldSetDictionary) { 574 // Make sure the dictionary is closed. If it is already closed, this is a no-op, 575 // so it's safe to call it anyways. 576 if (null != oldContactsDictionary) oldContactsDictionary.close(); 577 dictionaryToUse = null; 578 } else if (null != oldContactsDictionary) { 579 // Make sure the old contacts dictionary is opened. If it is already open, this is a 580 // no-op, so it's safe to call it anyways. 581 oldContactsDictionary.reopen(this); 582 dictionaryToUse = oldContactsDictionary; 583 } else { 584 dictionaryToUse = new ContactsDictionary(this, Suggest.DIC_CONTACTS); 585 } 586 587 if (null != mSuggest) { 588 mSuggest.setContactsDictionary(dictionaryToUse); 589 } 590 } 591 592 /* package private */ void resetSuggestMainDict() { 593 final String localeStr = mSubtypeSwitcher.getInputLocaleStr(); 594 final Locale keyboardLocale = LocaleUtils.constructLocaleFromString(localeStr); 595 int mainDicResId = Utils.getMainDictionaryResourceId(mResources); 596 mSuggest.resetMainDict(this, mainDicResId, keyboardLocale); 597 } 598 599 @Override 600 public void onDestroy() { 601 if (mSuggest != null) { 602 mSuggest.close(); 603 mSuggest = null; 604 } 605 unregisterReceiver(mReceiver); 606 unregisterReceiver(mDictionaryPackInstallReceiver); 607 mVoiceProxy.destroy(); 608 LatinImeLogger.commit(); 609 LatinImeLogger.onDestroy(); 610 super.onDestroy(); 611 } 612 613 @Override 614 public void onConfigurationChanged(Configuration conf) { 615 mSubtypeSwitcher.onConfigurationChanged(conf); 616 mComposingStateManager.onFinishComposingText(); 617 // If orientation changed while predicting, commit the change 618 if (mDisplayOrientation != conf.orientation) { 619 mDisplayOrientation = conf.orientation; 620 mHandler.startOrientationChanging(); 621 final InputConnection ic = getCurrentInputConnection(); 622 commitTyped(ic); 623 if (ic != null) ic.finishComposingText(); // For voice input 624 if (isShowingOptionDialog()) 625 mOptionsDialog.dismiss(); 626 } 627 628 mConfigurationChanging = true; 629 super.onConfigurationChanged(conf); 630 mVoiceProxy.onConfigurationChanged(conf); 631 mConfigurationChanging = false; 632 633 // This will work only when the subtype is not supported. 634 LanguageSwitcherProxy.onConfigurationChanged(conf); 635 } 636 637 @Override 638 public View onCreateInputView() { 639 return mKeyboardSwitcher.onCreateInputView(); 640 } 641 642 @Override 643 public void setInputView(View view) { 644 super.setInputView(view); 645 mExtractArea = getWindow().getWindow().getDecorView() 646 .findViewById(android.R.id.extractArea); 647 mKeyPreviewBackingView = view.findViewById(R.id.key_preview_backing); 648 mSuggestionsContainer = view.findViewById(R.id.suggestions_container); 649 mSuggestionsView = (SuggestionsView) view.findViewById(R.id.suggestions_view); 650 if (mSuggestionsView != null) 651 mSuggestionsView.setListener(this, view); 652 if (LatinImeLogger.sVISUALDEBUG) { 653 mKeyPreviewBackingView.setBackgroundColor(0x10FF0000); 654 } 655 } 656 657 @Override 658 public void setCandidatesView(View view) { 659 // To ensure that CandidatesView will never be set. 660 return; 661 } 662 663 @Override 664 public void onStartInput(EditorInfo attribute, boolean restarting) { 665 mHandler.onStartInput(attribute, restarting); 666 } 667 668 @Override 669 public void onStartInputView(EditorInfo attribute, boolean restarting) { 670 mHandler.onStartInputView(attribute, restarting); 671 } 672 673 @Override 674 public void onFinishInputView(boolean finishingInput) { 675 mHandler.onFinishInputView(finishingInput); 676 } 677 678 @Override 679 public void onFinishInput() { 680 mHandler.onFinishInput(); 681 } 682 683 private void onStartInputInternal(EditorInfo attribute, boolean restarting) { 684 super.onStartInput(attribute, restarting); 685 } 686 687 private void onStartInputViewInternal(EditorInfo attribute, boolean restarting) { 688 super.onStartInputView(attribute, restarting); 689 final KeyboardSwitcher switcher = mKeyboardSwitcher; 690 LatinKeyboardView inputView = switcher.getKeyboardView(); 691 692 if (DEBUG) { 693 Log.d(TAG, "onStartInputView: attribute:" + ((attribute == null) ? "none" 694 : String.format("inputType=0x%08x imeOptions=0x%08x", 695 attribute.inputType, attribute.imeOptions))); 696 } 697 // In landscape mode, this method gets called without the input view being created. 698 if (inputView == null) { 699 return; 700 } 701 702 // Forward this event to the accessibility utilities, if enabled. 703 final AccessibilityUtils accessUtils = AccessibilityUtils.getInstance(); 704 if (accessUtils.isTouchExplorationEnabled()) { 705 accessUtils.onStartInputViewInternal(attribute, restarting); 706 } 707 708 mSubtypeSwitcher.updateParametersOnStartInputView(); 709 710 TextEntryState.reset(); 711 712 // Most such things we decide below in initializeInputAttributesAndGetMode, but we need to 713 // know now whether this is a password text field, because we need to know now whether we 714 // want to enable the voice button. 715 final VoiceProxy voiceIme = mVoiceProxy; 716 final int inputType = (attribute != null) ? attribute.inputType : 0; 717 voiceIme.resetVoiceStates(InputTypeCompatUtils.isPasswordInputType(inputType) 718 || InputTypeCompatUtils.isVisiblePasswordInputType(inputType)); 719 720 // The EditorInfo might have a flag that affects fullscreen mode. 721 // Note: This call should be done by InputMethodService? 722 updateFullscreenMode(); 723 initializeInputAttributes(attribute); 724 725 inputView.closing(); 726 mEnteredText = null; 727 mComposingStringBuilder.setLength(0); 728 mHasUncommittedTypedChars = false; 729 mDeleteCount = 0; 730 mJustAddedMagicSpace = false; 731 mJustReplacedDoubleSpace = false; 732 733 loadSettings(); 734 updateCorrectionMode(); 735 updateSuggestionVisibility(mPrefs, mResources); 736 737 if (mSuggest != null && mSettingsValues.mAutoCorrectEnabled) { 738 mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold); 739 } 740 mVoiceProxy.loadSettings(attribute, mPrefs); 741 // This will work only when the subtype is not supported. 742 LanguageSwitcherProxy.loadSettings(); 743 744 if (mSubtypeSwitcher.isKeyboardMode()) { 745 switcher.loadKeyboard(attribute, mSettingsValues); 746 } 747 748 if (mSuggestionsView != null) 749 mSuggestionsView.clear(); 750 setSuggestionStripShownInternal( 751 isSuggestionsStripVisible(), /* needsInputViewShown */ false); 752 // Delay updating suggestions because keyboard input view may not be shown at this point. 753 mHandler.postUpdateSuggestions(); 754 755 inputView.setKeyPreviewPopupEnabled(mSettingsValues.mKeyPreviewPopupOn, 756 mSettingsValues.mKeyPreviewPopupDismissDelay); 757 inputView.setProximityCorrectionEnabled(true); 758 759 voiceIme.onStartInputView(inputView.getWindowToken()); 760 761 if (TRACE) Debug.startMethodTracing("/data/trace/latinime"); 762 } 763 764 private void initializeInputAttributes(EditorInfo attribute) { 765 if (attribute == null) 766 return; 767 final int inputType = attribute.inputType; 768 if (inputType == InputType.TYPE_NULL) { 769 // TODO: We should honor TYPE_NULL specification. 770 Log.i(TAG, "InputType.TYPE_NULL is specified"); 771 } 772 final int inputClass = inputType & InputType.TYPE_MASK_CLASS; 773 final int variation = inputType & InputType.TYPE_MASK_VARIATION; 774 if (inputClass == 0) { 775 Log.w(TAG, String.format("Unexpected input class: inputType=0x%08x imeOptions=0x%08x", 776 inputType, attribute.imeOptions)); 777 } 778 779 mInsertSpaceOnPickSuggestionManually = false; 780 mInputTypeNoAutoCorrect = false; 781 mIsSettingsSuggestionStripOn = false; 782 mApplicationSpecifiedCompletionOn = false; 783 mApplicationSpecifiedCompletions = null; 784 785 if (inputClass == InputType.TYPE_CLASS_TEXT) { 786 mIsSettingsSuggestionStripOn = true; 787 // Make sure that passwords are not displayed in {@link SuggestionsView}. 788 if (InputTypeCompatUtils.isPasswordInputType(inputType) 789 || InputTypeCompatUtils.isVisiblePasswordInputType(inputType)) { 790 mIsSettingsSuggestionStripOn = false; 791 } 792 if (InputTypeCompatUtils.isEmailVariation(variation) 793 || variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) { 794 // The point in turning this off is that we don't want to insert a space after 795 // a name when filling a form: we can't delete trailing spaces when changing fields 796 mInsertSpaceOnPickSuggestionManually = false; 797 } else { 798 mInsertSpaceOnPickSuggestionManually = true; 799 } 800 if (InputTypeCompatUtils.isEmailVariation(variation)) { 801 mIsSettingsSuggestionStripOn = false; 802 } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) { 803 mIsSettingsSuggestionStripOn = false; 804 } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) { 805 mIsSettingsSuggestionStripOn = false; 806 } else if (variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) { 807 // If it's a browser edit field and auto correct is not ON explicitly, then 808 // disable auto correction, but keep suggestions on. 809 if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) { 810 mInputTypeNoAutoCorrect = true; 811 } 812 } 813 814 // If NO_SUGGESTIONS is set, don't do prediction. 815 if ((inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) { 816 mIsSettingsSuggestionStripOn = false; 817 mInputTypeNoAutoCorrect = true; 818 } 819 // If it's not multiline and the autoCorrect flag is not set, then don't correct 820 if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0 821 && (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) == 0) { 822 mInputTypeNoAutoCorrect = true; 823 } 824 if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) { 825 mIsSettingsSuggestionStripOn = false; 826 mApplicationSpecifiedCompletionOn = isFullscreenMode(); 827 } 828 } 829 } 830 831 @Override 832 public void onWindowHidden() { 833 super.onWindowHidden(); 834 KeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); 835 if (inputView != null) inputView.closing(); 836 } 837 838 private void onFinishInputInternal() { 839 super.onFinishInput(); 840 841 LatinImeLogger.commit(); 842 843 mVoiceProxy.flushVoiceInputLogs(mConfigurationChanging); 844 845 KeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); 846 if (inputView != null) inputView.closing(); 847 if (mUserUnigramDictionary != null) mUserUnigramDictionary.flushPendingWrites(); 848 if (mUserBigramDictionary != null) mUserBigramDictionary.flushPendingWrites(); 849 } 850 851 private void onFinishInputViewInternal(boolean finishingInput) { 852 super.onFinishInputView(finishingInput); 853 mKeyboardSwitcher.onFinishInputView(); 854 KeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); 855 if (inputView != null) inputView.cancelAllMessages(); 856 // Remove pending messages related to update suggestions 857 mHandler.cancelUpdateSuggestions(); 858 } 859 860 @Override 861 public void onUpdateExtractedText(int token, ExtractedText text) { 862 super.onUpdateExtractedText(token, text); 863 mVoiceProxy.showPunctuationHintIfNecessary(); 864 } 865 866 @Override 867 public void onUpdateSelection(int oldSelStart, int oldSelEnd, 868 int newSelStart, int newSelEnd, 869 int candidatesStart, int candidatesEnd) { 870 super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, 871 candidatesStart, candidatesEnd); 872 873 if (DEBUG) { 874 Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart 875 + ", ose=" + oldSelEnd 876 + ", lss=" + mLastSelectionStart 877 + ", lse=" + mLastSelectionEnd 878 + ", nss=" + newSelStart 879 + ", nse=" + newSelEnd 880 + ", cs=" + candidatesStart 881 + ", ce=" + candidatesEnd); 882 } 883 884 mVoiceProxy.setCursorAndSelection(newSelEnd, newSelStart); 885 886 // If the current selection in the text view changes, we should 887 // clear whatever candidate text we have. 888 final boolean selectionChanged = (newSelStart != candidatesEnd 889 || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart; 890 final boolean candidatesCleared = candidatesStart == -1 && candidatesEnd == -1; 891 if (!mExpectingUpdateSelection) { 892 if (((mComposingStringBuilder.length() > 0 && mHasUncommittedTypedChars) 893 || mVoiceProxy.isVoiceInputHighlighted()) 894 && (selectionChanged || candidatesCleared)) { 895 mComposingStringBuilder.setLength(0); 896 mHasUncommittedTypedChars = false; 897 TextEntryState.reset(); 898 updateSuggestions(); 899 final InputConnection ic = getCurrentInputConnection(); 900 if (ic != null) { 901 ic.finishComposingText(); 902 } 903 mComposingStateManager.onFinishComposingText(); 904 mVoiceProxy.setVoiceInputHighlighted(false); 905 } else if (!mHasUncommittedTypedChars) { 906 TextEntryState.reset(); 907 updateSuggestions(); 908 } 909 mJustAddedMagicSpace = false; // The user moved the cursor. 910 mJustReplacedDoubleSpace = false; 911 } 912 mExpectingUpdateSelection = false; 913 mHandler.postUpdateShiftKeyState(); 914 915 // Make a note of the cursor position 916 mLastSelectionStart = newSelStart; 917 mLastSelectionEnd = newSelEnd; 918 } 919 920 public void setLastSelection(int start, int end) { 921 mLastSelectionStart = start; 922 mLastSelectionEnd = end; 923 } 924 925 /** 926 * This is called when the user has clicked on the extracted text view, 927 * when running in fullscreen mode. The default implementation hides 928 * the suggestions view when this happens, but only if the extracted text 929 * editor has a vertical scroll bar because its text doesn't fit. 930 * Here we override the behavior due to the possibility that a re-correction could 931 * cause the suggestions strip to disappear and re-appear. 932 */ 933 @Override 934 public void onExtractedTextClicked() { 935 if (isSuggestionsRequested()) return; 936 937 super.onExtractedTextClicked(); 938 } 939 940 /** 941 * This is called when the user has performed a cursor movement in the 942 * extracted text view, when it is running in fullscreen mode. The default 943 * implementation hides the suggestions view when a vertical movement 944 * happens, but only if the extracted text editor has a vertical scroll bar 945 * because its text doesn't fit. 946 * Here we override the behavior due to the possibility that a re-correction could 947 * cause the suggestions strip to disappear and re-appear. 948 */ 949 @Override 950 public void onExtractedCursorMovement(int dx, int dy) { 951 if (isSuggestionsRequested()) return; 952 953 super.onExtractedCursorMovement(dx, dy); 954 } 955 956 @Override 957 public void hideWindow() { 958 LatinImeLogger.commit(); 959 mKeyboardSwitcher.onHideWindow(); 960 961 if (TRACE) Debug.stopMethodTracing(); 962 if (mOptionsDialog != null && mOptionsDialog.isShowing()) { 963 mOptionsDialog.dismiss(); 964 mOptionsDialog = null; 965 } 966 mVoiceProxy.hideVoiceWindow(mConfigurationChanging); 967 super.hideWindow(); 968 } 969 970 @Override 971 public void onDisplayCompletions(CompletionInfo[] applicationSpecifiedCompletions) { 972 if (DEBUG) { 973 Log.i(TAG, "Received completions:"); 974 if (applicationSpecifiedCompletions != null) { 975 for (int i = 0; i < applicationSpecifiedCompletions.length; i++) { 976 Log.i(TAG, " #" + i + ": " + applicationSpecifiedCompletions[i]); 977 } 978 } 979 } 980 if (mApplicationSpecifiedCompletionOn) { 981 mApplicationSpecifiedCompletions = applicationSpecifiedCompletions; 982 if (applicationSpecifiedCompletions == null) { 983 clearSuggestions(); 984 return; 985 } 986 987 SuggestedWords.Builder builder = new SuggestedWords.Builder() 988 .setApplicationSpecifiedCompletions(applicationSpecifiedCompletions) 989 .setTypedWordValid(false) 990 .setHasMinimalSuggestion(false); 991 // When in fullscreen mode, show completions generated by the application 992 setSuggestions(builder.build()); 993 mBestWord = null; 994 setSuggestionStripShown(true); 995 } 996 } 997 998 private void setSuggestionStripShownInternal(boolean shown, boolean needsInputViewShown) { 999 // TODO: Modify this if we support suggestions with hard keyboard 1000 if (onEvaluateInputViewShown() && mSuggestionsContainer != null) { 1001 final boolean shouldShowSuggestions = shown 1002 && (needsInputViewShown ? mKeyboardSwitcher.isInputViewShown() : true); 1003 if (isFullscreenMode()) { 1004 mSuggestionsContainer.setVisibility( 1005 shouldShowSuggestions ? View.VISIBLE : View.GONE); 1006 } else { 1007 mSuggestionsContainer.setVisibility( 1008 shouldShowSuggestions ? View.VISIBLE : View.INVISIBLE); 1009 } 1010 } 1011 } 1012 1013 private void setSuggestionStripShown(boolean shown) { 1014 setSuggestionStripShownInternal(shown, /* needsInputViewShown */true); 1015 } 1016 1017 @Override 1018 public void onComputeInsets(InputMethodService.Insets outInsets) { 1019 super.onComputeInsets(outInsets); 1020 final KeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); 1021 if (inputView == null || mSuggestionsContainer == null) 1022 return; 1023 // In fullscreen mode, the height of the extract area managed by InputMethodService should 1024 // be considered. 1025 // See {@link android.inputmethodservice.InputMethodService#onComputeInsets}. 1026 final int extractHeight = isFullscreenMode() ? mExtractArea.getHeight() : 0; 1027 final int backingHeight = (mKeyPreviewBackingView.getVisibility() == View.GONE) ? 0 1028 : mKeyPreviewBackingView.getHeight(); 1029 final int suggestionsHeight = (mSuggestionsContainer.getVisibility() == View.GONE) ? 0 1030 : mSuggestionsContainer.getHeight(); 1031 final int extraHeight = extractHeight + backingHeight + suggestionsHeight; 1032 int touchY = extraHeight; 1033 // Need to set touchable region only if input view is being shown 1034 if (mKeyboardSwitcher.isInputViewShown()) { 1035 if (mSuggestionsContainer.getVisibility() == View.VISIBLE) { 1036 touchY -= suggestionsHeight; 1037 } 1038 final int touchWidth = inputView.getWidth(); 1039 final int touchHeight = inputView.getHeight() + extraHeight 1040 // Extend touchable region below the keyboard. 1041 + EXTENDED_TOUCHABLE_REGION_HEIGHT; 1042 if (DEBUG) { 1043 Log.d(TAG, "Touchable region: y=" + touchY + " width=" + touchWidth 1044 + " height=" + touchHeight); 1045 } 1046 setTouchableRegionCompat(outInsets, 0, touchY, touchWidth, touchHeight); 1047 } 1048 outInsets.contentTopInsets = touchY; 1049 outInsets.visibleTopInsets = touchY; 1050 } 1051 1052 @Override 1053 public boolean onEvaluateFullscreenMode() { 1054 return super.onEvaluateFullscreenMode() 1055 && mResources.getBoolean(R.bool.config_use_fullscreen_mode); 1056 } 1057 1058 @Override 1059 public void updateFullscreenMode() { 1060 super.updateFullscreenMode(); 1061 1062 if (mKeyPreviewBackingView == null) return; 1063 // In extract mode, no need to have extra space to show the key preview. 1064 // If not, we should have extra space above the keyboard to show the key preview. 1065 mKeyPreviewBackingView.setVisibility(isExtractViewShown() ? View.GONE : View.VISIBLE); 1066 } 1067 1068 @Override 1069 public boolean onKeyDown(int keyCode, KeyEvent event) { 1070 switch (keyCode) { 1071 case KeyEvent.KEYCODE_BACK: 1072 if (event.getRepeatCount() == 0) { 1073 if (mSuggestionsView != null && mSuggestionsView.handleBack()) { 1074 return true; 1075 } 1076 final LatinKeyboardView keyboardView = mKeyboardSwitcher.getKeyboardView(); 1077 if (keyboardView != null && keyboardView.handleBack()) { 1078 return true; 1079 } 1080 } 1081 break; 1082 } 1083 return super.onKeyDown(keyCode, event); 1084 } 1085 1086 @Override 1087 public boolean onKeyUp(int keyCode, KeyEvent event) { 1088 switch (keyCode) { 1089 case KeyEvent.KEYCODE_DPAD_DOWN: 1090 case KeyEvent.KEYCODE_DPAD_UP: 1091 case KeyEvent.KEYCODE_DPAD_LEFT: 1092 case KeyEvent.KEYCODE_DPAD_RIGHT: 1093 // Enable shift key and DPAD to do selections 1094 if (mKeyboardSwitcher.isInputViewShown() 1095 && mKeyboardSwitcher.isShiftedOrShiftLocked()) { 1096 KeyEvent newEvent = new KeyEvent(event.getDownTime(), event.getEventTime(), 1097 event.getAction(), event.getKeyCode(), event.getRepeatCount(), 1098 event.getDeviceId(), event.getScanCode(), 1099 KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON); 1100 final InputConnection ic = getCurrentInputConnection(); 1101 if (ic != null) 1102 ic.sendKeyEvent(newEvent); 1103 return true; 1104 } 1105 break; 1106 } 1107 return super.onKeyUp(keyCode, event); 1108 } 1109 1110 public void commitTyped(final InputConnection ic) { 1111 if (!mHasUncommittedTypedChars) return; 1112 mHasUncommittedTypedChars = false; 1113 if (mComposingStringBuilder.length() > 0) { 1114 if (ic != null) { 1115 ic.commitText(mComposingStringBuilder, 1); 1116 } 1117 mCommittedLength = mComposingStringBuilder.length(); 1118 TextEntryState.acceptedTyped(mComposingStringBuilder); 1119 addToUserUnigramAndBigramDictionaries(mComposingStringBuilder, 1120 UserUnigramDictionary.FREQUENCY_FOR_TYPED); 1121 } 1122 updateSuggestions(); 1123 } 1124 1125 public boolean getCurrentAutoCapsState() { 1126 final InputConnection ic = getCurrentInputConnection(); 1127 EditorInfo ei = getCurrentInputEditorInfo(); 1128 if (mSettingsValues.mAutoCap && ic != null && ei != null 1129 && ei.inputType != InputType.TYPE_NULL) { 1130 return ic.getCursorCapsMode(ei.inputType) != 0; 1131 } 1132 return false; 1133 } 1134 1135 // "ic" may be null 1136 private void swapSwapperAndSpaceWhileInBatchEdit(final InputConnection ic) { 1137 if (null == ic) return; 1138 CharSequence lastTwo = ic.getTextBeforeCursor(2, 0); 1139 // It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called. 1140 if (lastTwo != null && lastTwo.length() == 2 1141 && lastTwo.charAt(0) == Keyboard.CODE_SPACE) { 1142 ic.deleteSurroundingText(2, 0); 1143 ic.commitText(lastTwo.charAt(1) + " ", 1); 1144 mKeyboardSwitcher.updateShiftState(); 1145 } 1146 } 1147 1148 private void maybeDoubleSpace() { 1149 if (mCorrectionMode == Suggest.CORRECTION_NONE) return; 1150 final InputConnection ic = getCurrentInputConnection(); 1151 if (ic == null) return; 1152 final CharSequence lastThree = ic.getTextBeforeCursor(3, 0); 1153 if (lastThree != null && lastThree.length() == 3 1154 && Utils.canBeFollowedByPeriod(lastThree.charAt(0)) 1155 && lastThree.charAt(1) == Keyboard.CODE_SPACE 1156 && lastThree.charAt(2) == Keyboard.CODE_SPACE 1157 && mHandler.isAcceptingDoubleSpaces()) { 1158 mHandler.cancelDoubleSpacesTimer(); 1159 ic.beginBatchEdit(); 1160 ic.deleteSurroundingText(2, 0); 1161 ic.commitText(". ", 1); 1162 ic.endBatchEdit(); 1163 mKeyboardSwitcher.updateShiftState(); 1164 mJustReplacedDoubleSpace = true; 1165 } else { 1166 mHandler.startDoubleSpacesTimer(); 1167 } 1168 } 1169 1170 // "ic" must not be null 1171 private void maybeRemovePreviousPeriod(final InputConnection ic, CharSequence text) { 1172 // When the text's first character is '.', remove the previous period 1173 // if there is one. 1174 final CharSequence lastOne = ic.getTextBeforeCursor(1, 0); 1175 if (lastOne != null && lastOne.length() == 1 1176 && lastOne.charAt(0) == Keyboard.CODE_PERIOD 1177 && text.charAt(0) == Keyboard.CODE_PERIOD) { 1178 ic.deleteSurroundingText(1, 0); 1179 } 1180 } 1181 1182 // "ic" may be null 1183 private void removeTrailingSpaceWhileInBatchEdit(final InputConnection ic) { 1184 if (ic == null) return; 1185 final CharSequence lastOne = ic.getTextBeforeCursor(1, 0); 1186 if (lastOne != null && lastOne.length() == 1 1187 && lastOne.charAt(0) == Keyboard.CODE_SPACE) { 1188 ic.deleteSurroundingText(1, 0); 1189 } 1190 } 1191 1192 @Override 1193 public boolean addWordToDictionary(String word) { 1194 mUserDictionary.addWord(word, 128); 1195 // Suggestion strip should be updated after the operation of adding word to the 1196 // user dictionary 1197 mHandler.postUpdateSuggestions(); 1198 return true; 1199 } 1200 1201 private boolean isAlphabet(int code) { 1202 if (Character.isLetter(code)) { 1203 return true; 1204 } else { 1205 return false; 1206 } 1207 } 1208 1209 private void onSettingsKeyPressed() { 1210 if (isShowingOptionDialog()) return; 1211 if (InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) { 1212 showSubtypeSelectorAndSettings(); 1213 } else if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm, false /* exclude aux subtypes */)) { 1214 showOptionsMenu(); 1215 } else { 1216 launchSettings(); 1217 } 1218 } 1219 1220 // Virtual codes representing custom requests. These are used in onCustomRequest() below. 1221 public static final int CODE_SHOW_INPUT_METHOD_PICKER = 1; 1222 1223 @Override 1224 public boolean onCustomRequest(int requestCode) { 1225 if (isShowingOptionDialog()) return false; 1226 switch (requestCode) { 1227 case CODE_SHOW_INPUT_METHOD_PICKER: 1228 if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm, true /* include aux subtypes */)) { 1229 mImm.showInputMethodPicker(); 1230 return true; 1231 } 1232 return false; 1233 } 1234 return false; 1235 } 1236 1237 private boolean isShowingOptionDialog() { 1238 return mOptionsDialog != null && mOptionsDialog.isShowing(); 1239 } 1240 1241 // Implementation of {@link KeyboardActionListener}. 1242 @Override 1243 public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) { 1244 long when = SystemClock.uptimeMillis(); 1245 if (primaryCode != Keyboard.CODE_DELETE || when > mLastKeyTime + QUICK_PRESS) { 1246 mDeleteCount = 0; 1247 } 1248 mLastKeyTime = when; 1249 KeyboardSwitcher switcher = mKeyboardSwitcher; 1250 final boolean distinctMultiTouch = switcher.hasDistinctMultitouch(); 1251 final boolean lastStateOfJustReplacedDoubleSpace = mJustReplacedDoubleSpace; 1252 mJustReplacedDoubleSpace = false; 1253 switch (primaryCode) { 1254 case Keyboard.CODE_DELETE: 1255 handleBackspace(lastStateOfJustReplacedDoubleSpace); 1256 mDeleteCount++; 1257 mExpectingUpdateSelection = true; 1258 LatinImeLogger.logOnDelete(); 1259 break; 1260 case Keyboard.CODE_SHIFT: 1261 // Shift key is handled in onPress() when device has distinct multi-touch panel. 1262 if (!distinctMultiTouch) 1263 switcher.toggleShift(); 1264 break; 1265 case Keyboard.CODE_SWITCH_ALPHA_SYMBOL: 1266 // Symbol key is handled in onPress() when device has distinct multi-touch panel. 1267 if (!distinctMultiTouch) 1268 switcher.changeKeyboardMode(); 1269 break; 1270 case Keyboard.CODE_CANCEL: 1271 if (!isShowingOptionDialog()) { 1272 handleClose(); 1273 } 1274 break; 1275 case Keyboard.CODE_SETTINGS: 1276 onSettingsKeyPressed(); 1277 break; 1278 case Keyboard.CODE_CAPSLOCK: 1279 switcher.toggleCapsLock(); 1280 //$FALL-THROUGH$ 1281 case Keyboard.CODE_HAPTIC_AND_AUDIO_FEEDBACK_ONLY: 1282 // Dummy code for haptic and audio feedbacks. 1283 vibrate(); 1284 playKeyClick(primaryCode); 1285 break; 1286 case Keyboard.CODE_SHORTCUT: 1287 mSubtypeSwitcher.switchToShortcutIME(); 1288 break; 1289 case Keyboard.CODE_TAB: 1290 handleTab(); 1291 // There are two cases for tab. Either we send a "next" event, that may change the 1292 // focus but will never move the cursor. Or, we send a real tab keycode, which some 1293 // applications may accept or ignore, and we don't know whether this will move the 1294 // cursor or not. So actually, we don't really know. 1295 // So to go with the safer option, we'd rather behave as if the user moved the 1296 // cursor when they didn't than the opposite. We also expect that most applications 1297 // will actually use tab only for focus movement. 1298 // To sum it up: do not update mExpectingUpdateSelection here. 1299 break; 1300 default: 1301 if (mSettingsValues.isWordSeparator(primaryCode)) { 1302 handleSeparator(primaryCode, x, y); 1303 } else { 1304 handleCharacter(primaryCode, keyCodes, x, y); 1305 } 1306 mExpectingUpdateSelection = true; 1307 break; 1308 } 1309 switcher.onKey(primaryCode); 1310 // Reset after any single keystroke 1311 mEnteredText = null; 1312 } 1313 1314 @Override 1315 public void onTextInput(CharSequence text) { 1316 mVoiceProxy.commitVoiceInput(); 1317 final InputConnection ic = getCurrentInputConnection(); 1318 if (ic == null) return; 1319 ic.beginBatchEdit(); 1320 commitTyped(ic); 1321 maybeRemovePreviousPeriod(ic, text); 1322 ic.commitText(text, 1); 1323 ic.endBatchEdit(); 1324 mKeyboardSwitcher.updateShiftState(); 1325 mKeyboardSwitcher.onKey(Keyboard.CODE_DUMMY); 1326 mJustAddedMagicSpace = false; 1327 mEnteredText = text; 1328 } 1329 1330 @Override 1331 public void onCancelInput() { 1332 // User released a finger outside any key 1333 mKeyboardSwitcher.onCancelInput(); 1334 } 1335 1336 private void handleBackspace(boolean justReplacedDoubleSpace) { 1337 if (mVoiceProxy.logAndRevertVoiceInput()) return; 1338 1339 final InputConnection ic = getCurrentInputConnection(); 1340 if (ic == null) return; 1341 ic.beginBatchEdit(); 1342 1343 mVoiceProxy.handleBackspace(); 1344 1345 final boolean deleteChar = !mHasUncommittedTypedChars; 1346 if (mHasUncommittedTypedChars) { 1347 final int length = mComposingStringBuilder.length(); 1348 if (length > 0) { 1349 mComposingStringBuilder.delete(length - 1, length); 1350 mWordComposer.deleteLast(); 1351 final CharSequence textWithUnderline = 1352 mComposingStateManager.isAutoCorrectionIndicatorOn() 1353 ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline( 1354 this, mComposingStringBuilder) 1355 : mComposingStringBuilder; 1356 ic.setComposingText(textWithUnderline, 1); 1357 if (mComposingStringBuilder.length() == 0) { 1358 mHasUncommittedTypedChars = false; 1359 } 1360 if (1 == length) { 1361 // 1 == length means we are about to erase the last character of the word, 1362 // so we can show bigrams. 1363 mHandler.postUpdateBigramPredictions(); 1364 } else { 1365 // length > 1, so we still have letters to deduce a suggestion from. 1366 mHandler.postUpdateSuggestions(); 1367 } 1368 } else { 1369 ic.deleteSurroundingText(1, 0); 1370 } 1371 } 1372 mHandler.postUpdateShiftKeyState(); 1373 1374 TextEntryState.backspace(); 1375 if (TextEntryState.isUndoCommit()) { 1376 revertLastWord(ic); 1377 ic.endBatchEdit(); 1378 return; 1379 } 1380 if (justReplacedDoubleSpace) { 1381 if (revertDoubleSpace(ic)) { 1382 ic.endBatchEdit(); 1383 return; 1384 } 1385 } 1386 1387 if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) { 1388 ic.deleteSurroundingText(mEnteredText.length(), 0); 1389 } else if (deleteChar) { 1390 if (mSuggestionsView != null && mSuggestionsView.dismissAddToDictionaryHint()) { 1391 // Go back to the suggestion mode if the user canceled the 1392 // "Touch again to save". 1393 // NOTE: In gerenal, we don't revert the word when backspacing 1394 // from a manual suggestion pick. We deliberately chose a 1395 // different behavior only in the case of picking the first 1396 // suggestion (typed word). It's intentional to have made this 1397 // inconsistent with backspacing after selecting other suggestions. 1398 revertLastWord(ic); 1399 } else { 1400 sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); 1401 if (mDeleteCount > DELETE_ACCELERATE_AT) { 1402 sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); 1403 } 1404 } 1405 } 1406 ic.endBatchEdit(); 1407 } 1408 1409 private void handleTab() { 1410 final int imeOptions = getCurrentInputEditorInfo().imeOptions; 1411 if (!EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions) 1412 && !EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions)) { 1413 sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB); 1414 return; 1415 } 1416 1417 final InputConnection ic = getCurrentInputConnection(); 1418 if (ic == null) 1419 return; 1420 1421 // True if keyboard is in either chording shift or manual temporary upper case mode. 1422 final boolean isManualTemporaryUpperCase = mKeyboardSwitcher.isManualTemporaryUpperCase(); 1423 if (EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions) 1424 && !isManualTemporaryUpperCase) { 1425 EditorInfoCompatUtils.performEditorActionNext(ic); 1426 } else if (EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions) 1427 && isManualTemporaryUpperCase) { 1428 EditorInfoCompatUtils.performEditorActionPrevious(ic); 1429 } 1430 } 1431 1432 private void handleCharacter(int primaryCode, int[] keyCodes, int x, int y) { 1433 mVoiceProxy.handleCharacter(); 1434 1435 final InputConnection ic = getCurrentInputConnection(); 1436 if (ic != null) ic.beginBatchEdit(); 1437 if (mJustAddedMagicSpace && mSettingsValues.isMagicSpaceStripper(primaryCode)) { 1438 removeTrailingSpaceWhileInBatchEdit(ic); 1439 } 1440 1441 int code = primaryCode; 1442 if ((isAlphabet(code) || mSettingsValues.isSymbolExcludedFromWordSeparators(code)) 1443 && isSuggestionsRequested() && !isCursorTouchingWord()) { 1444 if (!mHasUncommittedTypedChars) { 1445 mHasUncommittedTypedChars = true; 1446 mComposingStringBuilder.setLength(0); 1447 mWordComposer.reset(); 1448 clearSuggestions(); 1449 mComposingStateManager.onFinishComposingText(); 1450 } 1451 } 1452 final KeyboardSwitcher switcher = mKeyboardSwitcher; 1453 if (switcher.isShiftedOrShiftLocked()) { 1454 if (keyCodes == null || keyCodes[0] < Character.MIN_CODE_POINT 1455 || keyCodes[0] > Character.MAX_CODE_POINT) { 1456 if (null != ic) ic.endBatchEdit(); 1457 return; 1458 } 1459 code = keyCodes[0]; 1460 if (switcher.isAlphabetMode() && Character.isLowerCase(code)) { 1461 // In some locales, such as Turkish, Character.toUpperCase() may return a wrong 1462 // character because it doesn't take care of locale. 1463 final String upperCaseString = new String(new int[] {code}, 0, 1) 1464 .toUpperCase(mSubtypeSwitcher.getInputLocale()); 1465 if (upperCaseString.codePointCount(0, upperCaseString.length()) == 1) { 1466 code = upperCaseString.codePointAt(0); 1467 } else { 1468 // Some keys, such as [eszett], have upper case as multi-characters. 1469 onTextInput(upperCaseString); 1470 if (null != ic) ic.endBatchEdit(); 1471 return; 1472 } 1473 } 1474 } 1475 if (mHasUncommittedTypedChars) { 1476 mComposingStringBuilder.append((char) code); 1477 mWordComposer.add(code, keyCodes, x, y); 1478 if (ic != null) { 1479 // If it's the first letter, make note of auto-caps state 1480 if (mWordComposer.size() == 1) { 1481 mWordComposer.setAutoCapitalized(getCurrentAutoCapsState()); 1482 mComposingStateManager.onStartComposingText(); 1483 } 1484 final CharSequence textWithUnderline = 1485 mComposingStateManager.isAutoCorrectionIndicatorOn() 1486 ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline( 1487 this, mComposingStringBuilder) 1488 : mComposingStringBuilder; 1489 ic.setComposingText(textWithUnderline, 1); 1490 } 1491 mHandler.postUpdateSuggestions(); 1492 } else { 1493 sendKeyChar((char)code); 1494 } 1495 if (mJustAddedMagicSpace && mSettingsValues.isMagicSpaceSwapper(primaryCode)) { 1496 if (null != ic) swapSwapperAndSpaceWhileInBatchEdit(ic); 1497 } else { 1498 mJustAddedMagicSpace = false; 1499 } 1500 1501 switcher.updateShiftState(); 1502 if (LatinIME.PERF_DEBUG) measureCps(); 1503 TextEntryState.typedCharacter((char) code, mSettingsValues.isWordSeparator(code), x, y); 1504 if (null != ic) ic.endBatchEdit(); 1505 } 1506 1507 private void handleSeparator(int primaryCode, int x, int y) { 1508 mVoiceProxy.handleSeparator(); 1509 mComposingStateManager.onFinishComposingText(); 1510 1511 // Should dismiss the "Touch again to save" message when handling separator 1512 if (mSuggestionsView != null && mSuggestionsView.dismissAddToDictionaryHint()) { 1513 mHandler.cancelUpdateBigramPredictions(); 1514 mHandler.postUpdateSuggestions(); 1515 } 1516 1517 boolean pickedDefault = false; 1518 // Handle separator 1519 final InputConnection ic = getCurrentInputConnection(); 1520 if (ic != null) { 1521 ic.beginBatchEdit(); 1522 } 1523 if (mHasUncommittedTypedChars) { 1524 // In certain languages where single quote is a separator, it's better 1525 // not to auto correct, but accept the typed word. For instance, 1526 // in Italian dov' should not be expanded to dove' because the elision 1527 // requires the last vowel to be removed. 1528 final boolean shouldAutoCorrect = mSettingsValues.mAutoCorrectEnabled 1529 && !mInputTypeNoAutoCorrect; 1530 if (shouldAutoCorrect && primaryCode != Keyboard.CODE_SINGLE_QUOTE) { 1531 pickedDefault = pickDefaultSuggestion(primaryCode); 1532 } else { 1533 commitTyped(ic); 1534 } 1535 } 1536 1537 if (mJustAddedMagicSpace) { 1538 if (mSettingsValues.isMagicSpaceSwapper(primaryCode)) { 1539 sendKeyChar((char)primaryCode); 1540 swapSwapperAndSpaceWhileInBatchEdit(ic); 1541 } else { 1542 if (mSettingsValues.isMagicSpaceStripper(primaryCode)) { 1543 removeTrailingSpaceWhileInBatchEdit(ic); 1544 } 1545 sendKeyChar((char)primaryCode); 1546 mJustAddedMagicSpace = false; 1547 } 1548 } else { 1549 sendKeyChar((char)primaryCode); 1550 } 1551 1552 if (isSuggestionsRequested() && primaryCode == Keyboard.CODE_SPACE) { 1553 maybeDoubleSpace(); 1554 } 1555 1556 TextEntryState.typedCharacter((char) primaryCode, true, x, y); 1557 1558 if (pickedDefault) { 1559 CharSequence typedWord = mWordComposer.getTypedWord(); 1560 TextEntryState.backToAcceptedDefault(typedWord); 1561 if (!TextUtils.isEmpty(typedWord) && !typedWord.equals(mBestWord)) { 1562 InputConnectionCompatUtils.commitCorrection( 1563 ic, mLastSelectionEnd - typedWord.length(), typedWord, mBestWord); 1564 } 1565 } 1566 if (Keyboard.CODE_SPACE == primaryCode) { 1567 if (!isCursorTouchingWord()) { 1568 mHandler.cancelUpdateSuggestions(); 1569 mHandler.postUpdateBigramPredictions(); 1570 } 1571 } else { 1572 // Set punctuation right away. onUpdateSelection will fire but tests whether it is 1573 // already displayed or not, so it's okay. 1574 setPunctuationSuggestions(); 1575 } 1576 mKeyboardSwitcher.updateShiftState(); 1577 if (ic != null) { 1578 ic.endBatchEdit(); 1579 } 1580 } 1581 1582 private void handleClose() { 1583 commitTyped(getCurrentInputConnection()); 1584 mVoiceProxy.handleClose(); 1585 requestHideSelf(0); 1586 LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); 1587 if (inputView != null) 1588 inputView.closing(); 1589 } 1590 1591 public boolean isSuggestionsRequested() { 1592 return mIsSettingsSuggestionStripOn 1593 && (mCorrectionMode > 0 || isShowingSuggestionsStrip()); 1594 } 1595 1596 public boolean isShowingPunctuationList() { 1597 return mSettingsValues.mSuggestPuncList == mSuggestionsView.getSuggestions(); 1598 } 1599 1600 public boolean isShowingSuggestionsStrip() { 1601 return (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_VALUE) 1602 || (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE 1603 && mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT); 1604 } 1605 1606 public boolean isSuggestionsStripVisible() { 1607 if (mSuggestionsView == null) 1608 return false; 1609 if (mSuggestionsView.isShowingAddToDictionaryHint() || TextEntryState.isRecorrecting()) 1610 return true; 1611 if (!isShowingSuggestionsStrip()) 1612 return false; 1613 if (mApplicationSpecifiedCompletionOn) 1614 return true; 1615 return isSuggestionsRequested(); 1616 } 1617 1618 public void switchToKeyboardView() { 1619 if (DEBUG) { 1620 Log.d(TAG, "Switch to keyboard view."); 1621 } 1622 View v = mKeyboardSwitcher.getKeyboardView(); 1623 if (v != null) { 1624 // Confirms that the keyboard view doesn't have parent view. 1625 ViewParent p = v.getParent(); 1626 if (p != null && p instanceof ViewGroup) { 1627 ((ViewGroup) p).removeView(v); 1628 } 1629 setInputView(v); 1630 } 1631 setSuggestionStripShown(isSuggestionsStripVisible()); 1632 updateInputViewShown(); 1633 mHandler.postUpdateSuggestions(); 1634 } 1635 1636 public void clearSuggestions() { 1637 setSuggestions(SuggestedWords.EMPTY); 1638 } 1639 1640 public void setSuggestions(SuggestedWords words) { 1641 if (mSuggestionsView != null) { 1642 mSuggestionsView.setSuggestions(words); 1643 mKeyboardSwitcher.onAutoCorrectionStateChanged( 1644 words.hasWordAboveAutoCorrectionScoreThreshold()); 1645 } 1646 1647 // Put a blue underline to a word in TextView which will be auto-corrected. 1648 final InputConnection ic = getCurrentInputConnection(); 1649 if (ic != null) { 1650 final boolean oldAutoCorrectionIndicator = 1651 mComposingStateManager.isAutoCorrectionIndicatorOn(); 1652 final boolean newAutoCorrectionIndicator = Utils.willAutoCorrect(words); 1653 if (oldAutoCorrectionIndicator != newAutoCorrectionIndicator) { 1654 if (LatinImeLogger.sDBG) { 1655 Log.d(TAG, "Flip the indicator. " + oldAutoCorrectionIndicator 1656 + " -> " + newAutoCorrectionIndicator); 1657 } 1658 final CharSequence textWithUnderline = newAutoCorrectionIndicator 1659 ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline( 1660 this, mComposingStringBuilder) 1661 : mComposingStringBuilder; 1662 if (!TextUtils.isEmpty(textWithUnderline)) { 1663 ic.setComposingText(textWithUnderline, 1); 1664 } 1665 mComposingStateManager.setAutoCorrectionIndicatorOn(newAutoCorrectionIndicator); 1666 } 1667 } 1668 } 1669 1670 public void updateSuggestions() { 1671 // Check if we have a suggestion engine attached. 1672 if ((mSuggest == null || !isSuggestionsRequested()) 1673 && !mVoiceProxy.isVoiceInputHighlighted()) { 1674 return; 1675 } 1676 1677 mHandler.cancelUpdateSuggestions(); 1678 mHandler.cancelUpdateBigramPredictions(); 1679 1680 if (!mHasUncommittedTypedChars) { 1681 setPunctuationSuggestions(); 1682 return; 1683 } 1684 1685 final WordComposer wordComposer = mWordComposer; 1686 // TODO: May need a better way of retrieving previous word 1687 final InputConnection ic = getCurrentInputConnection(); 1688 final CharSequence prevWord; 1689 if (null == ic) { 1690 prevWord = null; 1691 } else { 1692 prevWord = EditingUtils.getPreviousWord(ic, mSettingsValues.mWordSeparators); 1693 } 1694 // getSuggestedWordBuilder handles gracefully a null value of prevWord 1695 final SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder( 1696 wordComposer, prevWord, mKeyboardSwitcher.getLatinKeyboard().getProximityInfo()); 1697 1698 boolean autoCorrectionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasAutoCorrection(); 1699 final CharSequence typedWord = wordComposer.getTypedWord(); 1700 // Here, we want to promote a whitelisted word if exists. 1701 // TODO: Change this scheme - a boolean is not enough. A whitelisted word may be "valid" 1702 // but still autocorrected from - in the case the whitelist only capitalizes the word. 1703 // The whitelist should be case-insensitive, so it's not possible to be consistent with 1704 // a boolean flag. Right now this is handled with a slight hack in 1705 // WhitelistDictionary#shouldForciblyAutoCorrectFrom. 1706 final boolean allowsToBeAutoCorrected = AutoCorrection.allowsToBeAutoCorrected( 1707 mSuggest.getUnigramDictionaries(), typedWord, preferCapitalization()); 1708 if (mCorrectionMode == Suggest.CORRECTION_FULL 1709 || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) { 1710 autoCorrectionAvailable |= (!allowsToBeAutoCorrected); 1711 } 1712 // Don't auto-correct words with multiple capital letter 1713 autoCorrectionAvailable &= !wordComposer.isMostlyCaps(); 1714 autoCorrectionAvailable &= !TextEntryState.isRecorrecting(); 1715 1716 // Basically, we update the suggestion strip only when suggestion count > 1. However, 1717 // there is an exception: We update the suggestion strip whenever typed word's length 1718 // is 1 or typed word is found in dictionary, regardless of suggestion count. Actually, 1719 // in most cases, suggestion count is 1 when typed word's length is 1, but we do always 1720 // need to clear the previous state when the user starts typing a word (i.e. typed word's 1721 // length == 1). 1722 if (typedWord != null) { 1723 if (builder.size() > 1 || typedWord.length() == 1 || (!allowsToBeAutoCorrected) 1724 || mSuggestionsView.isShowingAddToDictionaryHint()) { 1725 builder.setTypedWordValid(!allowsToBeAutoCorrected).setHasMinimalSuggestion( 1726 autoCorrectionAvailable); 1727 } else { 1728 SuggestedWords previousSuggestions = mSuggestionsView.getSuggestions(); 1729 if (previousSuggestions == mSettingsValues.mSuggestPuncList) { 1730 if (builder.size() == 0) { 1731 return; 1732 } 1733 previousSuggestions = SuggestedWords.EMPTY; 1734 } 1735 builder.addTypedWordAndPreviousSuggestions(typedWord, previousSuggestions); 1736 } 1737 } 1738 showSuggestions(builder.build(), typedWord); 1739 } 1740 1741 public void showSuggestions(SuggestedWords suggestedWords, CharSequence typedWord) { 1742 final boolean shouldBlockAutoCorrectionBySafetyNet = 1743 Utils.shouldBlockAutoCorrectionBySafetyNet(suggestedWords, mSuggest); 1744 if (shouldBlockAutoCorrectionBySafetyNet) { 1745 suggestedWords.setShouldBlockAutoCorrection(); 1746 } 1747 setSuggestions(suggestedWords); 1748 if (suggestedWords.size() > 0) { 1749 if (shouldBlockAutoCorrectionBySafetyNet) { 1750 mBestWord = typedWord; 1751 } else if (suggestedWords.hasAutoCorrectionWord()) { 1752 mBestWord = suggestedWords.getWord(1); 1753 } else { 1754 mBestWord = typedWord; 1755 } 1756 } else { 1757 mBestWord = null; 1758 } 1759 setSuggestionStripShown(isSuggestionsStripVisible()); 1760 } 1761 1762 private boolean pickDefaultSuggestion(int separatorCode) { 1763 // Complete any pending suggestions query first 1764 if (mHandler.hasPendingUpdateSuggestions()) { 1765 mHandler.cancelUpdateSuggestions(); 1766 updateSuggestions(); 1767 } 1768 if (mBestWord != null && mBestWord.length() > 0) { 1769 TextEntryState.acceptedDefault(mWordComposer.getTypedWord(), mBestWord, separatorCode); 1770 mExpectingUpdateSelection = true; 1771 commitBestWord(mBestWord); 1772 // Add the word to the user unigram dictionary if it's not a known word 1773 addToUserUnigramAndBigramDictionaries(mBestWord, 1774 UserUnigramDictionary.FREQUENCY_FOR_TYPED); 1775 return true; 1776 } 1777 return false; 1778 } 1779 1780 @Override 1781 public void pickSuggestionManually(int index, CharSequence suggestion) { 1782 mComposingStateManager.onFinishComposingText(); 1783 SuggestedWords suggestions = mSuggestionsView.getSuggestions(); 1784 mVoiceProxy.flushAndLogAllTextModificationCounters(index, suggestion, 1785 mSettingsValues.mWordSeparators); 1786 1787 final boolean recorrecting = TextEntryState.isRecorrecting(); 1788 final InputConnection ic = getCurrentInputConnection(); 1789 if (ic != null) { 1790 ic.beginBatchEdit(); 1791 } 1792 if (mApplicationSpecifiedCompletionOn && mApplicationSpecifiedCompletions != null 1793 && index >= 0 && index < mApplicationSpecifiedCompletions.length) { 1794 if (ic != null) { 1795 final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index]; 1796 ic.commitCompletion(completionInfo); 1797 } 1798 mCommittedLength = suggestion.length(); 1799 if (mSuggestionsView != null) { 1800 mSuggestionsView.clear(); 1801 } 1802 mKeyboardSwitcher.updateShiftState(); 1803 if (ic != null) { 1804 ic.endBatchEdit(); 1805 } 1806 return; 1807 } 1808 1809 // If this is a punctuation, apply it through the normal key press 1810 if (suggestion.length() == 1 && (mSettingsValues.isWordSeparator(suggestion.charAt(0)) 1811 || mSettingsValues.isSuggestedPunctuation(suggestion.charAt(0)))) { 1812 // Word separators are suggested before the user inputs something. 1813 // So, LatinImeLogger logs "" as a user's input. 1814 LatinImeLogger.logOnManualSuggestion( 1815 "", suggestion.toString(), index, suggestions.mWords); 1816 // Find out whether the previous character is a space. If it is, as a special case 1817 // for punctuation entered through the suggestion strip, it should be considered 1818 // a magic space even if it was a normal space. This is meant to help in case the user 1819 // pressed space on purpose of displaying the suggestion strip punctuation. 1820 final int rawPrimaryCode = suggestion.charAt(0); 1821 // Maybe apply the "bidi mirrored" conversions for parentheses 1822 final LatinKeyboard keyboard = mKeyboardSwitcher.getLatinKeyboard(); 1823 final boolean isRtl = keyboard != null && keyboard.mIsRtlKeyboard; 1824 final int primaryCode = Key.getRtlParenthesisCode(rawPrimaryCode, isRtl); 1825 1826 final CharSequence beforeText = ic != null ? ic.getTextBeforeCursor(1, 0) : ""; 1827 final int toLeft = (ic == null || TextUtils.isEmpty(beforeText)) 1828 ? 0 : beforeText.charAt(0); 1829 final boolean oldMagicSpace = mJustAddedMagicSpace; 1830 if (Keyboard.CODE_SPACE == toLeft) mJustAddedMagicSpace = true; 1831 onCodeInput(primaryCode, new int[] { primaryCode }, 1832 KeyboardActionListener.NOT_A_TOUCH_COORDINATE, 1833 KeyboardActionListener.NOT_A_TOUCH_COORDINATE); 1834 mJustAddedMagicSpace = oldMagicSpace; 1835 if (ic != null) { 1836 ic.endBatchEdit(); 1837 } 1838 return; 1839 } 1840 if (!mHasUncommittedTypedChars) { 1841 // If we are not composing a word, then it was a suggestion inferred from 1842 // context - no user input. We should reset the word composer. 1843 mWordComposer.reset(); 1844 } 1845 mExpectingUpdateSelection = true; 1846 commitBestWord(suggestion); 1847 // Add the word to the auto dictionary if it's not a known word 1848 if (index == 0) { 1849 addToUserUnigramAndBigramDictionaries(suggestion, 1850 UserUnigramDictionary.FREQUENCY_FOR_PICKED); 1851 } else { 1852 addToOnlyBigramDictionary(suggestion, 1); 1853 } 1854 LatinImeLogger.logOnManualSuggestion(mComposingStringBuilder.toString(), 1855 suggestion.toString(), index, suggestions.mWords); 1856 TextEntryState.acceptedSuggestion(mComposingStringBuilder.toString(), suggestion); 1857 // Follow it with a space 1858 if (mInsertSpaceOnPickSuggestionManually && !recorrecting) { 1859 sendMagicSpace(); 1860 } 1861 1862 // We should show the "Touch again to save" hint if the user pressed the first entry 1863 // AND either: 1864 // - There is no dictionary (we know that because we tried to load it => null != mSuggest 1865 // AND mSuggest.hasMainDictionary() is false) 1866 // - There is a dictionary and the word is not in it 1867 // Please note that if mSuggest is null, it means that everything is off: suggestion 1868 // and correction, so we shouldn't try to show the hint 1869 // We used to look at mCorrectionMode here, but showing the hint should have nothing 1870 // to do with the autocorrection setting. 1871 final boolean showingAddToDictionaryHint = index == 0 && mSuggest != null 1872 // If there is no dictionary the hint should be shown. 1873 && (!mSuggest.hasMainDictionary() 1874 // If "suggestion" is not in the dictionary, the hint should be shown. 1875 || !AutoCorrection.isValidWord( 1876 mSuggest.getUnigramDictionaries(), suggestion, true)); 1877 1878 if (!recorrecting) { 1879 // Fool the state watcher so that a subsequent backspace will not do a revert, unless 1880 // we just did a correction, in which case we need to stay in 1881 // TextEntryState.State.PICKED_SUGGESTION state. 1882 TextEntryState.typedCharacter((char) Keyboard.CODE_SPACE, true, 1883 WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE); 1884 } 1885 if (!showingAddToDictionaryHint) { 1886 // If we're not showing the "Touch again to save", then show corrections again. 1887 // In case the cursor position doesn't change, make sure we show the suggestions again. 1888 updateBigramPredictions(); 1889 // Updating the predictions right away may be slow and feel unresponsive on slower 1890 // terminals. On the other hand if we just postUpdateBigramPredictions() it will 1891 // take a noticeable delay to update them which may feel uneasy. 1892 } 1893 if (showingAddToDictionaryHint) { 1894 if (mIsUserDictionaryAvaliable) { 1895 mSuggestionsView.showAddToDictionaryHint(suggestion); 1896 } else { 1897 mHandler.postUpdateSuggestions(); 1898 } 1899 } 1900 if (ic != null) { 1901 ic.endBatchEdit(); 1902 } 1903 } 1904 1905 /** 1906 * Commits the chosen word to the text field and saves it for later retrieval. 1907 */ 1908 private void commitBestWord(CharSequence bestWord) { 1909 final KeyboardSwitcher switcher = mKeyboardSwitcher; 1910 if (!switcher.isKeyboardAvailable()) 1911 return; 1912 final InputConnection ic = getCurrentInputConnection(); 1913 if (ic != null) { 1914 mVoiceProxy.rememberReplacedWord(bestWord, mSettingsValues.mWordSeparators); 1915 if (mSettingsValues.mEnableSuggestionSpanInsertion) { 1916 final SuggestedWords suggestedWords = mSuggestionsView.getSuggestions(); 1917 ic.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan( 1918 this, bestWord, suggestedWords), 1); 1919 } else { 1920 ic.commitText(bestWord, 1); 1921 } 1922 } 1923 mHasUncommittedTypedChars = false; 1924 mCommittedLength = bestWord.length(); 1925 } 1926 1927 private static final WordComposer sEmptyWordComposer = new WordComposer(); 1928 public void updateBigramPredictions() { 1929 if (mSuggest == null || !isSuggestionsRequested()) 1930 return; 1931 1932 if (!mSettingsValues.mBigramPredictionEnabled) { 1933 setPunctuationSuggestions(); 1934 return; 1935 } 1936 1937 final CharSequence prevWord = EditingUtils.getThisWord(getCurrentInputConnection(), 1938 mSettingsValues.mWordSeparators); 1939 SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(sEmptyWordComposer, 1940 prevWord, mKeyboardSwitcher.getLatinKeyboard().getProximityInfo()); 1941 1942 if (builder.size() > 0) { 1943 // Explicitly supply an empty typed word (the no-second-arg version of 1944 // showSuggestions will retrieve the word near the cursor, we don't want that here) 1945 showSuggestions(builder.build(), ""); 1946 } else { 1947 if (!isShowingPunctuationList()) setPunctuationSuggestions(); 1948 } 1949 } 1950 1951 public void setPunctuationSuggestions() { 1952 setSuggestions(mSettingsValues.mSuggestPuncList); 1953 setSuggestionStripShown(isSuggestionsStripVisible()); 1954 } 1955 1956 private void addToUserUnigramAndBigramDictionaries(CharSequence suggestion, 1957 int frequencyDelta) { 1958 checkAddToDictionary(suggestion, frequencyDelta, false); 1959 } 1960 1961 private void addToOnlyBigramDictionary(CharSequence suggestion, int frequencyDelta) { 1962 checkAddToDictionary(suggestion, frequencyDelta, true); 1963 } 1964 1965 /** 1966 * Adds to the UserBigramDictionary and/or UserUnigramDictionary 1967 * @param selectedANotTypedWord true if it should be added to bigram dictionary if possible 1968 */ 1969 private void checkAddToDictionary(CharSequence suggestion, int frequencyDelta, 1970 boolean selectedANotTypedWord) { 1971 if (suggestion == null || suggestion.length() < 1) return; 1972 1973 // Only auto-add to dictionary if auto-correct is ON. Otherwise we'll be 1974 // adding words in situations where the user or application really didn't 1975 // want corrections enabled or learned. 1976 if (!(mCorrectionMode == Suggest.CORRECTION_FULL 1977 || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM)) { 1978 return; 1979 } 1980 1981 if (null != mSuggest && null != mUserUnigramDictionary) { 1982 final boolean selectedATypedWordAndItsInUserUnigramDic = 1983 !selectedANotTypedWord && mUserUnigramDictionary.isValidWord(suggestion); 1984 final boolean isValidWord = AutoCorrection.isValidWord( 1985 mSuggest.getUnigramDictionaries(), suggestion, true); 1986 final boolean needsToAddToUserUnigramDictionary = 1987 selectedATypedWordAndItsInUserUnigramDic || !isValidWord; 1988 if (needsToAddToUserUnigramDictionary) { 1989 mUserUnigramDictionary.addWord(suggestion.toString(), frequencyDelta); 1990 } 1991 } 1992 1993 if (mUserBigramDictionary != null) { 1994 // We don't want to register as bigrams words separated by a separator. 1995 // For example "I will, and you too" : we don't want the pair ("will" "and") to be 1996 // a bigram. 1997 final InputConnection ic = getCurrentInputConnection(); 1998 if (null != ic) { 1999 final CharSequence prevWord = 2000 EditingUtils.getPreviousWord(ic, mSettingsValues.mWordSeparators); 2001 if (!TextUtils.isEmpty(prevWord)) { 2002 mUserBigramDictionary.addBigrams(prevWord.toString(), suggestion.toString()); 2003 } 2004 } 2005 } 2006 } 2007 2008 public boolean isCursorTouchingWord() { 2009 final InputConnection ic = getCurrentInputConnection(); 2010 if (ic == null) return false; 2011 CharSequence toLeft = ic.getTextBeforeCursor(1, 0); 2012 CharSequence toRight = ic.getTextAfterCursor(1, 0); 2013 if (!TextUtils.isEmpty(toLeft) 2014 && !mSettingsValues.isWordSeparator(toLeft.charAt(0)) 2015 && !mSettingsValues.isSuggestedPunctuation(toLeft.charAt(0))) { 2016 return true; 2017 } 2018 if (!TextUtils.isEmpty(toRight) 2019 && !mSettingsValues.isWordSeparator(toRight.charAt(0)) 2020 && !mSettingsValues.isSuggestedPunctuation(toRight.charAt(0))) { 2021 return true; 2022 } 2023 return false; 2024 } 2025 2026 // "ic" must not null 2027 private boolean sameAsTextBeforeCursor(final InputConnection ic, CharSequence text) { 2028 CharSequence beforeText = ic.getTextBeforeCursor(text.length(), 0); 2029 return TextUtils.equals(text, beforeText); 2030 } 2031 2032 // "ic" must not null 2033 private void revertLastWord(final InputConnection ic) { 2034 if (mHasUncommittedTypedChars || mComposingStringBuilder.length() <= 0) { 2035 sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); 2036 return; 2037 } 2038 2039 final CharSequence separator = ic.getTextBeforeCursor(1, 0); 2040 ic.deleteSurroundingText(1, 0); 2041 final CharSequence textToTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0); 2042 ic.deleteSurroundingText(mCommittedLength, 0); 2043 2044 // Re-insert "separator" only when the deleted character was word separator and the 2045 // composing text wasn't equal to the auto-corrected text which can be found before 2046 // the cursor. 2047 if (!TextUtils.isEmpty(separator) 2048 && mSettingsValues.isWordSeparator(separator.charAt(0)) 2049 && !TextUtils.equals(mComposingStringBuilder, textToTheLeft)) { 2050 ic.commitText(mComposingStringBuilder, 1); 2051 TextEntryState.acceptedTyped(mComposingStringBuilder); 2052 ic.commitText(separator, 1); 2053 TextEntryState.typedCharacter(separator.charAt(0), true, 2054 WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE); 2055 // Clear composing text 2056 mComposingStringBuilder.setLength(0); 2057 } else { 2058 mHasUncommittedTypedChars = true; 2059 ic.setComposingText(mComposingStringBuilder, 1); 2060 TextEntryState.backspace(); 2061 } 2062 mHandler.cancelUpdateBigramPredictions(); 2063 mHandler.postUpdateSuggestions(); 2064 } 2065 2066 // "ic" must not null 2067 private boolean revertDoubleSpace(final InputConnection ic) { 2068 mHandler.cancelDoubleSpacesTimer(); 2069 // Here we test whether we indeed have a period and a space before us. This should not 2070 // be needed, but it's there just in case something went wrong. 2071 final CharSequence textBeforeCursor = ic.getTextBeforeCursor(2, 0); 2072 if (!". ".equals(textBeforeCursor)) 2073 return false; 2074 ic.beginBatchEdit(); 2075 ic.deleteSurroundingText(2, 0); 2076 ic.commitText(" ", 1); 2077 ic.endBatchEdit(); 2078 return true; 2079 } 2080 2081 public boolean isWordSeparator(int code) { 2082 return mSettingsValues.isWordSeparator(code); 2083 } 2084 2085 private void sendMagicSpace() { 2086 sendKeyChar((char)Keyboard.CODE_SPACE); 2087 mJustAddedMagicSpace = true; 2088 mKeyboardSwitcher.updateShiftState(); 2089 } 2090 2091 public boolean preferCapitalization() { 2092 return mWordComposer.isFirstCharCapitalized(); 2093 } 2094 2095 // Notify that language or mode have been changed and toggleLanguage will update KeyboardID 2096 // according to new language or mode. 2097 public void onRefreshKeyboard() { 2098 if (!CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) { 2099 // Before Honeycomb, Voice IME is in LatinIME and it changes the current input view, 2100 // so that we need to re-create the keyboard input view here. 2101 setInputView(mKeyboardSwitcher.onCreateInputView()); 2102 } 2103 // Reload keyboard because the current language has been changed. 2104 mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettingsValues); 2105 initSuggest(); 2106 loadSettings(); 2107 } 2108 2109 @Override 2110 public void onPress(int primaryCode, boolean withSliding) { 2111 final KeyboardSwitcher switcher = mKeyboardSwitcher; 2112 if (switcher.isVibrateAndSoundFeedbackRequired()) { 2113 vibrate(); 2114 playKeyClick(primaryCode); 2115 } 2116 final boolean distinctMultiTouch = switcher.hasDistinctMultitouch(); 2117 if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) { 2118 switcher.onPressShift(withSliding); 2119 } else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { 2120 switcher.onPressSymbol(); 2121 } else { 2122 switcher.onOtherKeyPressed(); 2123 } 2124 } 2125 2126 @Override 2127 public void onRelease(int primaryCode, boolean withSliding) { 2128 KeyboardSwitcher switcher = mKeyboardSwitcher; 2129 // Reset any drag flags in the keyboard 2130 final boolean distinctMultiTouch = switcher.hasDistinctMultitouch(); 2131 if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) { 2132 switcher.onReleaseShift(withSliding); 2133 } else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { 2134 switcher.onReleaseSymbol(); 2135 } 2136 } 2137 2138 2139 // receive ringer mode change and network state change. 2140 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 2141 @Override 2142 public void onReceive(Context context, Intent intent) { 2143 final String action = intent.getAction(); 2144 if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { 2145 updateRingerMode(); 2146 } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { 2147 mSubtypeSwitcher.onNetworkStateChanged(intent); 2148 } 2149 } 2150 }; 2151 2152 // update keypress sound volume 2153 private void updateSoundEffectVolume() { 2154 mFxVolume = Utils.getCurrentKeypressSoundVolume(mPrefs, mResources); 2155 } 2156 2157 // update flags for silent mode 2158 private void updateRingerMode() { 2159 if (mAudioManager == null) { 2160 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 2161 if (mAudioManager == null) return; 2162 } 2163 mSilentModeOn = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL); 2164 } 2165 2166 private void updateKeypressVibrationDuration() { 2167 mKeypressVibrationDuration = Utils.getCurrentVibrationDuration(mPrefs, mResources); 2168 } 2169 2170 private void playKeyClick(int primaryCode) { 2171 // if mAudioManager is null, we don't have the ringer state yet 2172 // mAudioManager will be set by updateRingerMode 2173 if (mAudioManager == null) { 2174 if (mKeyboardSwitcher.getKeyboardView() != null) { 2175 updateRingerMode(); 2176 } 2177 } 2178 if (isSoundOn()) { 2179 final int sound; 2180 switch (primaryCode) { 2181 case Keyboard.CODE_DELETE: 2182 sound = AudioManager.FX_KEYPRESS_DELETE; 2183 break; 2184 case Keyboard.CODE_ENTER: 2185 sound = AudioManager.FX_KEYPRESS_RETURN; 2186 break; 2187 case Keyboard.CODE_SPACE: 2188 sound = AudioManager.FX_KEYPRESS_SPACEBAR; 2189 break; 2190 default: 2191 sound = AudioManager.FX_KEYPRESS_STANDARD; 2192 break; 2193 } 2194 mAudioManager.playSoundEffect(sound, mFxVolume); 2195 } 2196 } 2197 2198 public void vibrate() { 2199 if (!mSettingsValues.mVibrateOn) { 2200 return; 2201 } 2202 if (mKeypressVibrationDuration < 0) { 2203 // Go ahead with the system default 2204 LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); 2205 if (inputView != null) { 2206 inputView.performHapticFeedback( 2207 HapticFeedbackConstants.KEYBOARD_TAP, 2208 HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); 2209 } 2210 } else if (mVibrator != null) { 2211 mVibrator.vibrate(mKeypressVibrationDuration); 2212 } 2213 } 2214 2215 public WordComposer getCurrentWord() { 2216 return mWordComposer; 2217 } 2218 2219 boolean isSoundOn() { 2220 return mSettingsValues.mSoundOn && !mSilentModeOn; 2221 } 2222 2223 private void updateCorrectionMode() { 2224 // TODO: cleanup messy flags 2225 final boolean shouldAutoCorrect = mSettingsValues.mAutoCorrectEnabled 2226 && !mInputTypeNoAutoCorrect; 2227 mCorrectionMode = (shouldAutoCorrect && mSettingsValues.mAutoCorrectEnabled) 2228 ? Suggest.CORRECTION_FULL 2229 : (shouldAutoCorrect ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE); 2230 mCorrectionMode = (mSettingsValues.mBigramSuggestionEnabled && shouldAutoCorrect 2231 && mSettingsValues.mAutoCorrectEnabled) 2232 ? Suggest.CORRECTION_FULL_BIGRAM : mCorrectionMode; 2233 if (mSuggest != null) { 2234 mSuggest.setCorrectionMode(mCorrectionMode); 2235 } 2236 } 2237 2238 private void updateSuggestionVisibility(final SharedPreferences prefs, final Resources res) { 2239 final String suggestionVisiblityStr = prefs.getString( 2240 Settings.PREF_SHOW_SUGGESTIONS_SETTING, 2241 res.getString(R.string.prefs_suggestion_visibility_default_value)); 2242 for (int visibility : SUGGESTION_VISIBILITY_VALUE_ARRAY) { 2243 if (suggestionVisiblityStr.equals(res.getString(visibility))) { 2244 mSuggestionVisibility = visibility; 2245 break; 2246 } 2247 } 2248 } 2249 2250 protected void launchSettings() { 2251 launchSettingsClass(Settings.class); 2252 } 2253 2254 public void launchDebugSettings() { 2255 launchSettingsClass(DebugSettings.class); 2256 } 2257 2258 protected void launchSettingsClass(Class<? extends PreferenceActivity> settingsClass) { 2259 handleClose(); 2260 Intent intent = new Intent(); 2261 intent.setClass(LatinIME.this, settingsClass); 2262 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 2263 startActivity(intent); 2264 } 2265 2266 private void showSubtypeSelectorAndSettings() { 2267 final CharSequence title = getString(R.string.english_ime_input_options); 2268 final CharSequence[] items = new CharSequence[] { 2269 // TODO: Should use new string "Select active input modes". 2270 getString(R.string.language_selection_title), 2271 getString(R.string.english_ime_settings), 2272 }; 2273 final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { 2274 @Override 2275 public void onClick(DialogInterface di, int position) { 2276 di.dismiss(); 2277 switch (position) { 2278 case 0: 2279 Intent intent = CompatUtils.getInputLanguageSelectionIntent( 2280 mInputMethodId, Intent.FLAG_ACTIVITY_NEW_TASK 2281 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 2282 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 2283 startActivity(intent); 2284 break; 2285 case 1: 2286 launchSettings(); 2287 break; 2288 } 2289 } 2290 }; 2291 final AlertDialog.Builder builder = new AlertDialog.Builder(this) 2292 .setItems(items, listener) 2293 .setTitle(title); 2294 showOptionDialogInternal(builder.create()); 2295 } 2296 2297 private void showOptionsMenu() { 2298 final CharSequence title = getString(R.string.english_ime_input_options); 2299 final CharSequence[] items = new CharSequence[] { 2300 getString(R.string.selectInputMethod), 2301 getString(R.string.english_ime_settings), 2302 }; 2303 final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { 2304 @Override 2305 public void onClick(DialogInterface di, int position) { 2306 di.dismiss(); 2307 switch (position) { 2308 case 0: 2309 mImm.showInputMethodPicker(); 2310 break; 2311 case 1: 2312 launchSettings(); 2313 break; 2314 } 2315 } 2316 }; 2317 final AlertDialog.Builder builder = new AlertDialog.Builder(this) 2318 .setItems(items, listener) 2319 .setTitle(title); 2320 showOptionDialogInternal(builder.create()); 2321 } 2322 2323 @Override 2324 protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { 2325 super.dump(fd, fout, args); 2326 2327 final Printer p = new PrintWriterPrinter(fout); 2328 p.println("LatinIME state :"); 2329 p.println(" Keyboard mode = " + mKeyboardSwitcher.getKeyboardMode()); 2330 p.println(" mComposingStringBuilder=" + mComposingStringBuilder.toString()); 2331 p.println(" mIsSuggestionsRequested=" + mIsSettingsSuggestionStripOn); 2332 p.println(" mCorrectionMode=" + mCorrectionMode); 2333 p.println(" mHasUncommittedTypedChars=" + mHasUncommittedTypedChars); 2334 p.println(" mAutoCorrectEnabled=" + mSettingsValues.mAutoCorrectEnabled); 2335 p.println(" mInsertSpaceOnPickSuggestionManually=" + mInsertSpaceOnPickSuggestionManually); 2336 p.println(" mApplicationSpecifiedCompletionOn=" + mApplicationSpecifiedCompletionOn); 2337 p.println(" TextEntryState.state=" + TextEntryState.getState()); 2338 p.println(" mSoundOn=" + mSettingsValues.mSoundOn); 2339 p.println(" mVibrateOn=" + mSettingsValues.mVibrateOn); 2340 p.println(" mKeyPreviewPopupOn=" + mSettingsValues.mKeyPreviewPopupOn); 2341 } 2342 2343 // Characters per second measurement 2344 2345 private long mLastCpsTime; 2346 private static final int CPS_BUFFER_SIZE = 16; 2347 private long[] mCpsIntervals = new long[CPS_BUFFER_SIZE]; 2348 private int mCpsIndex; 2349 2350 private void measureCps() { 2351 long now = System.currentTimeMillis(); 2352 if (mLastCpsTime == 0) mLastCpsTime = now - 100; // Initial 2353 mCpsIntervals[mCpsIndex] = now - mLastCpsTime; 2354 mLastCpsTime = now; 2355 mCpsIndex = (mCpsIndex + 1) % CPS_BUFFER_SIZE; 2356 long total = 0; 2357 for (int i = 0; i < CPS_BUFFER_SIZE; i++) total += mCpsIntervals[i]; 2358 System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total)); 2359 } 2360} 2361