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