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