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