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