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