LatinIME.java revision 00eb3658734078df46b6fe88b8b6aa8b79d327c9
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 com.android.inputmethod.keyboard.Keyboard; 20import com.android.inputmethod.keyboard.KeyboardActionListener; 21import com.android.inputmethod.keyboard.KeyboardId; 22import com.android.inputmethod.keyboard.KeyboardSwitcher; 23import com.android.inputmethod.keyboard.KeyboardView; 24import com.android.inputmethod.keyboard.LatinKeyboard; 25import com.android.inputmethod.keyboard.LatinKeyboardView; 26import com.android.inputmethod.latin.Utils.RingCharBuffer; 27import com.android.inputmethod.voice.VoiceIMEConnector; 28 29import android.app.AlertDialog; 30import android.content.BroadcastReceiver; 31import android.content.Context; 32import android.content.DialogInterface; 33import android.content.Intent; 34import android.content.IntentFilter; 35import android.content.SharedPreferences; 36import android.content.res.Configuration; 37import android.content.res.Resources; 38import android.inputmethodservice.InputMethodService; 39import android.media.AudioManager; 40import android.os.Debug; 41import android.os.Handler; 42import android.os.Message; 43import android.os.SystemClock; 44import android.os.Vibrator; 45import android.preference.PreferenceActivity; 46import android.preference.PreferenceManager; 47import android.text.InputType; 48import android.text.TextUtils; 49import android.util.DisplayMetrics; 50import android.util.Log; 51import android.util.PrintWriterPrinter; 52import android.util.Printer; 53import android.view.Gravity; 54import android.view.HapticFeedbackConstants; 55import android.view.KeyEvent; 56import android.view.LayoutInflater; 57import android.view.View; 58import android.view.ViewGroup; 59import android.view.ViewParent; 60import android.view.Window; 61import android.view.WindowManager; 62import android.view.inputmethod.CompletionInfo; 63import android.view.inputmethod.CorrectionInfo; 64import android.view.inputmethod.EditorInfo; 65import android.view.inputmethod.ExtractedText; 66import android.view.inputmethod.ExtractedTextRequest; 67import android.view.inputmethod.InputConnection; 68import android.view.inputmethod.InputMethodManager; 69import android.view.inputmethod.InputMethodSubtype; 70import android.widget.FrameLayout; 71import android.widget.HorizontalScrollView; 72import android.widget.LinearLayout; 73 74import java.io.FileDescriptor; 75import java.io.PrintWriter; 76import java.util.ArrayList; 77import java.util.Arrays; 78import java.util.Locale; 79 80/** 81 * Input method implementation for Qwerty'ish keyboard. 82 */ 83public class LatinIME extends InputMethodService implements KeyboardActionListener, 84 SharedPreferences.OnSharedPreferenceChangeListener { 85 private static final String TAG = "LatinIME"; 86 private static final boolean PERF_DEBUG = false; 87 private static final boolean TRACE = false; 88 private static boolean DEBUG = LatinImeLogger.sDBG; 89 90 private static final int DELAY_UPDATE_SUGGESTIONS = 180; 91 private static final int DELAY_UPDATE_OLD_SUGGESTIONS = 300; 92 private static final int DELAY_UPDATE_SHIFT_STATE = 300; 93 private static final int EXTENDED_TOUCHABLE_REGION_HEIGHT = 100; 94 95 // How many continuous deletes at which to start deleting at a higher speed. 96 private static final int DELETE_ACCELERATE_AT = 20; 97 // Key events coming any faster than this are long-presses. 98 private static final int QUICK_PRESS = 200; 99 100 // Contextual menu positions 101 private static final int POS_METHOD = 0; 102 private static final int POS_SETTINGS = 1; 103 104 private int mSuggestionVisibility; 105 private static final int SUGGESTION_VISIBILILTY_SHOW_VALUE 106 = R.string.prefs_suggestion_visibility_show_value; 107 private static final int SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE 108 = R.string.prefs_suggestion_visibility_show_only_portrait_value; 109 private static final int SUGGESTION_VISIBILILTY_HIDE_VALUE 110 = R.string.prefs_suggestion_visibility_hide_value; 111 112 private static final int[] SUGGESTION_VISIBILITY_VALUE_ARRAY = new int[] { 113 SUGGESTION_VISIBILILTY_SHOW_VALUE, 114 SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE, 115 SUGGESTION_VISIBILILTY_HIDE_VALUE 116 }; 117 118 private View mCandidateViewContainer; 119 private CandidateView mCandidateView; 120 private Suggest mSuggest; 121 private CompletionInfo[] mApplicationSpecifiedCompletions; 122 123 private AlertDialog mOptionsDialog; 124 125 private InputMethodManager mImm; 126 private Resources mResources; 127 private SharedPreferences mPrefs; 128 private String mInputMethodId; 129 private KeyboardSwitcher mKeyboardSwitcher; 130 private SubtypeSwitcher mSubtypeSwitcher; 131 private VoiceIMEConnector mVoiceConnector; 132 133 private UserDictionary mUserDictionary; 134 private UserBigramDictionary mUserBigramDictionary; 135 private ContactsDictionary mContactsDictionary; 136 private AutoDictionary mAutoDictionary; 137 138 // These variables are initialized according to the {@link EditorInfo#inputType}. 139 private boolean mAutoSpace; 140 private boolean mInputTypeNoAutoCorrect; 141 private boolean mIsSettingsSuggestionStripOn; 142 private boolean mApplicationSpecifiedCompletionOn; 143 144 private final StringBuilder mComposing = new StringBuilder(); 145 private WordComposer mWord = new WordComposer(); 146 private CharSequence mBestWord; 147 private boolean mHasValidSuggestions; 148 private boolean mHasDictionary; 149 private boolean mJustAddedAutoSpace; 150 private boolean mAutoCorrectEnabled; 151 private boolean mReCorrectionEnabled; 152 private boolean mBigramSuggestionEnabled; 153 private boolean mAutoCorrectOn; 154 private boolean mVibrateOn; 155 private boolean mSoundOn; 156 private boolean mPopupOn; 157 private boolean mAutoCap; 158 private boolean mQuickFixes; 159 private boolean mConfigEnableShowSubtypeSettings; 160 private boolean mConfigSwipeDownDismissKeyboardEnabled; 161 private int mConfigDelayBeforeFadeoutLanguageOnSpacebar; 162 private int mConfigDurationOfFadeoutLanguageOnSpacebar; 163 private float mConfigFinalFadeoutFactorOfLanguageOnSpacebar; 164 165 private int mCorrectionMode; 166 private int mCommittedLength; 167 private int mOrientation; 168 // Keep track of the last selection range to decide if we need to show word alternatives 169 private int mLastSelectionStart; 170 private int mLastSelectionEnd; 171 private SuggestedWords mSuggestPuncList; 172 173 // Indicates whether the suggestion strip is to be on in landscape 174 private boolean mJustAccepted; 175 private boolean mJustReverted; 176 private int mDeleteCount; 177 private long mLastKeyTime; 178 179 private AudioManager mAudioManager; 180 // Align sound effect volume on music volume 181 private static final float FX_VOLUME = -1.0f; 182 private boolean mSilentMode; 183 184 /* package */ String mWordSeparators; 185 private String mSentenceSeparators; 186 private String mSuggestPuncs; 187 // TODO: Move this flag to VoiceIMEConnector 188 private boolean mConfigurationChanging; 189 190 // Keeps track of most recently inserted text (multi-character key) for reverting 191 private CharSequence mEnteredText; 192 private boolean mRefreshKeyboardRequired; 193 194 private final ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>(); 195 196 public abstract static class WordAlternatives { 197 protected CharSequence mChosenWord; 198 199 public WordAlternatives() { 200 // Nothing 201 } 202 203 public WordAlternatives(CharSequence chosenWord) { 204 mChosenWord = chosenWord; 205 } 206 207 @Override 208 public int hashCode() { 209 return mChosenWord.hashCode(); 210 } 211 212 public abstract CharSequence getOriginalWord(); 213 214 public CharSequence getChosenWord() { 215 return mChosenWord; 216 } 217 218 public abstract SuggestedWords.Builder getAlternatives(); 219 } 220 221 public class TypedWordAlternatives extends WordAlternatives { 222 private WordComposer word; 223 224 public TypedWordAlternatives() { 225 // Nothing 226 } 227 228 public TypedWordAlternatives(CharSequence chosenWord, WordComposer wordComposer) { 229 super(chosenWord); 230 word = wordComposer; 231 } 232 233 @Override 234 public CharSequence getOriginalWord() { 235 return word.getTypedWord(); 236 } 237 238 @Override 239 public SuggestedWords.Builder getAlternatives() { 240 return getTypedSuggestions(word); 241 } 242 } 243 244 public final UIHandler mHandler = new UIHandler(); 245 246 public class UIHandler extends Handler { 247 private static final int MSG_UPDATE_SUGGESTIONS = 0; 248 private static final int MSG_UPDATE_OLD_SUGGESTIONS = 1; 249 private static final int MSG_UPDATE_SHIFT_STATE = 2; 250 private static final int MSG_VOICE_RESULTS = 3; 251 private static final int MSG_FADEOUT_LANGUAGE_ON_SPACEBAR = 4; 252 private static final int MSG_DISMISS_LANGUAGE_ON_SPACEBAR = 5; 253 254 @Override 255 public void handleMessage(Message msg) { 256 final KeyboardSwitcher switcher = mKeyboardSwitcher; 257 final LatinKeyboardView inputView = switcher.getInputView(); 258 switch (msg.what) { 259 case MSG_UPDATE_SUGGESTIONS: 260 updateSuggestions(); 261 break; 262 case MSG_UPDATE_OLD_SUGGESTIONS: 263 setOldSuggestions(); 264 break; 265 case MSG_UPDATE_SHIFT_STATE: 266 switcher.updateShiftState(); 267 break; 268 case MSG_VOICE_RESULTS: 269 mVoiceConnector.handleVoiceResults(preferCapitalization() 270 || (switcher.isAlphabetMode() && switcher.isShiftedOrShiftLocked())); 271 break; 272 case MSG_FADEOUT_LANGUAGE_ON_SPACEBAR: 273 if (inputView != null) 274 inputView.setSpacebarTextFadeFactor( 275 (1.0f + mConfigFinalFadeoutFactorOfLanguageOnSpacebar) / 2, 276 (LatinKeyboard)msg.obj); 277 sendMessageDelayed(obtainMessage(MSG_DISMISS_LANGUAGE_ON_SPACEBAR, msg.obj), 278 mConfigDurationOfFadeoutLanguageOnSpacebar); 279 break; 280 case MSG_DISMISS_LANGUAGE_ON_SPACEBAR: 281 if (inputView != null) 282 inputView.setSpacebarTextFadeFactor( 283 mConfigFinalFadeoutFactorOfLanguageOnSpacebar, (LatinKeyboard)msg.obj); 284 break; 285 } 286 } 287 288 public void postUpdateSuggestions() { 289 removeMessages(MSG_UPDATE_SUGGESTIONS); 290 sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTIONS), DELAY_UPDATE_SUGGESTIONS); 291 } 292 293 public void cancelUpdateSuggestions() { 294 removeMessages(MSG_UPDATE_SUGGESTIONS); 295 } 296 297 public boolean hasPendingUpdateSuggestions() { 298 return hasMessages(MSG_UPDATE_SUGGESTIONS); 299 } 300 301 public void postUpdateOldSuggestions() { 302 removeMessages(MSG_UPDATE_OLD_SUGGESTIONS); 303 sendMessageDelayed(obtainMessage(MSG_UPDATE_OLD_SUGGESTIONS), 304 DELAY_UPDATE_OLD_SUGGESTIONS); 305 } 306 307 public void cancelUpdateOldSuggestions() { 308 removeMessages(MSG_UPDATE_OLD_SUGGESTIONS); 309 } 310 311 public void postUpdateShiftKeyState() { 312 removeMessages(MSG_UPDATE_SHIFT_STATE); 313 sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), DELAY_UPDATE_SHIFT_STATE); 314 } 315 316 public void cancelUpdateShiftState() { 317 removeMessages(MSG_UPDATE_SHIFT_STATE); 318 } 319 320 public void updateVoiceResults() { 321 sendMessage(obtainMessage(MSG_VOICE_RESULTS)); 322 } 323 324 public void startDisplayLanguageOnSpacebar(boolean localeChanged) { 325 removeMessages(MSG_FADEOUT_LANGUAGE_ON_SPACEBAR); 326 removeMessages(MSG_DISMISS_LANGUAGE_ON_SPACEBAR); 327 final LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); 328 if (inputView != null) { 329 final LatinKeyboard keyboard = inputView.getLatinKeyboard(); 330 // The language is never displayed when the delay is zero. 331 if (mConfigDelayBeforeFadeoutLanguageOnSpacebar != 0) 332 inputView.setSpacebarTextFadeFactor(localeChanged ? 1.0f 333 : mConfigFinalFadeoutFactorOfLanguageOnSpacebar, keyboard); 334 // The language is always displayed when the delay is negative. 335 if (localeChanged && mConfigDelayBeforeFadeoutLanguageOnSpacebar > 0) { 336 sendMessageDelayed(obtainMessage(MSG_FADEOUT_LANGUAGE_ON_SPACEBAR, keyboard), 337 mConfigDelayBeforeFadeoutLanguageOnSpacebar); 338 } 339 } 340 } 341 } 342 343 @Override 344 public void onCreate() { 345 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 346 mPrefs = prefs; 347 LatinImeLogger.init(this, prefs); 348 SubtypeSwitcher.init(this, prefs); 349 KeyboardSwitcher.init(this, prefs); 350 351 super.onCreate(); 352 353 mImm = ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)); 354 mInputMethodId = Utils.getInputMethodId(mImm, getApplicationInfo().packageName); 355 mSubtypeSwitcher = SubtypeSwitcher.getInstance(); 356 mKeyboardSwitcher = KeyboardSwitcher.getInstance(); 357 358 final Resources res = getResources(); 359 mResources = res; 360 361 // If the option should not be shown, do not read the recorrection preference 362 // but always use the default setting defined in the resources. 363 if (res.getBoolean(R.bool.config_enable_show_recorrection_option)) { 364 mReCorrectionEnabled = prefs.getBoolean(Settings.PREF_RECORRECTION_ENABLED, 365 res.getBoolean(R.bool.default_recorrection_enabled)); 366 } else { 367 mReCorrectionEnabled = res.getBoolean(R.bool.default_recorrection_enabled); 368 } 369 370 mConfigEnableShowSubtypeSettings = res.getBoolean( 371 R.bool.config_enable_show_subtype_settings); 372 mConfigSwipeDownDismissKeyboardEnabled = res.getBoolean( 373 R.bool.config_swipe_down_dismiss_keyboard_enabled); 374 mConfigDelayBeforeFadeoutLanguageOnSpacebar = res.getInteger( 375 R.integer.config_delay_before_fadeout_language_on_spacebar); 376 mConfigDurationOfFadeoutLanguageOnSpacebar = res.getInteger( 377 R.integer.config_duration_of_fadeout_language_on_spacebar); 378 mConfigFinalFadeoutFactorOfLanguageOnSpacebar = res.getInteger( 379 R.integer.config_final_fadeout_percentage_of_language_on_spacebar) / 100.0f; 380 381 Utils.GCUtils.getInstance().reset(); 382 boolean tryGC = true; 383 for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) { 384 try { 385 initSuggest(); 386 tryGC = false; 387 } catch (OutOfMemoryError e) { 388 tryGC = Utils.GCUtils.getInstance().tryGCOrWait("InitSuggest", e); 389 } 390 } 391 392 mOrientation = res.getConfiguration().orientation; 393 initSuggestPuncList(); 394 395 // register to receive ringer mode changes for silent mode 396 IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); 397 registerReceiver(mReceiver, filter); 398 mVoiceConnector = VoiceIMEConnector.init(this, prefs, mHandler); 399 prefs.registerOnSharedPreferenceChangeListener(this); 400 } 401 402 /** 403 * Returns a main dictionary resource id 404 * @return main dictionary resource id 405 */ 406 public static int getMainDictionaryResourceId(Resources res) { 407 final String MAIN_DIC_NAME = "main"; 408 String packageName = LatinIME.class.getPackage().getName(); 409 return res.getIdentifier(MAIN_DIC_NAME, "raw", packageName); 410 } 411 412 private void initSuggest() { 413 updateAutoTextEnabled(); 414 String locale = mSubtypeSwitcher.getInputLocaleStr(); 415 416 Locale savedLocale = mSubtypeSwitcher.changeSystemLocale(new Locale(locale)); 417 if (mSuggest != null) { 418 mSuggest.close(); 419 } 420 final SharedPreferences prefs = mPrefs; 421 mQuickFixes = isQuickFixesEnabled(prefs); 422 423 final Resources res = mResources; 424 int mainDicResId = getMainDictionaryResourceId(res); 425 mSuggest = new Suggest(this, mainDicResId); 426 loadAndSetAutoCorrectionThreshold(prefs); 427 428 mUserDictionary = new UserDictionary(this, locale); 429 mSuggest.setUserDictionary(mUserDictionary); 430 431 mContactsDictionary = new ContactsDictionary(this, Suggest.DIC_CONTACTS); 432 mSuggest.setContactsDictionary(mContactsDictionary); 433 434 mAutoDictionary = new AutoDictionary(this, this, locale, Suggest.DIC_AUTO); 435 mSuggest.setAutoDictionary(mAutoDictionary); 436 437 mUserBigramDictionary = new UserBigramDictionary(this, this, locale, Suggest.DIC_USER); 438 mSuggest.setUserBigramDictionary(mUserBigramDictionary); 439 440 updateCorrectionMode(); 441 mWordSeparators = res.getString(R.string.word_separators); 442 mSentenceSeparators = res.getString(R.string.sentence_separators); 443 444 mSubtypeSwitcher.changeSystemLocale(savedLocale); 445 } 446 447 @Override 448 public void onDestroy() { 449 if (mSuggest != null) { 450 mSuggest.close(); 451 mSuggest = null; 452 } 453 unregisterReceiver(mReceiver); 454 mVoiceConnector.destroy(); 455 LatinImeLogger.commit(); 456 LatinImeLogger.onDestroy(); 457 super.onDestroy(); 458 } 459 460 @Override 461 public void onConfigurationChanged(Configuration conf) { 462 mSubtypeSwitcher.onConfigurationChanged(conf); 463 // If orientation changed while predicting, commit the change 464 if (conf.orientation != mOrientation) { 465 InputConnection ic = getCurrentInputConnection(); 466 commitTyped(ic); 467 if (ic != null) ic.finishComposingText(); // For voice input 468 mOrientation = conf.orientation; 469 if (isShowingOptionDialog()) 470 mOptionsDialog.dismiss(); 471 } 472 473 mConfigurationChanging = true; 474 super.onConfigurationChanged(conf); 475 mVoiceConnector.onConfigurationChanged(conf); 476 mConfigurationChanging = false; 477 } 478 479 @Override 480 public View onCreateInputView() { 481 return mKeyboardSwitcher.onCreateInputView(); 482 } 483 484 @Override 485 public View onCreateCandidatesView() { 486 LayoutInflater inflater = getLayoutInflater(); 487 LinearLayout container = (LinearLayout)inflater.inflate(R.layout.candidates, null); 488 mCandidateViewContainer = container; 489 if (container.getPaddingRight() != 0) { 490 HorizontalScrollView scrollView = 491 (HorizontalScrollView) container.findViewById(R.id.candidates_scroll_view); 492 scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER); 493 container.setGravity(Gravity.CENTER_HORIZONTAL); 494 } 495 mCandidateView = (CandidateView) container.findViewById(R.id.candidates); 496 mCandidateView.setService(this); 497 setCandidatesViewShown(true); 498 return container; 499 } 500 501 private static boolean isPasswordVariation(int variation) { 502 return variation == InputType.TYPE_TEXT_VARIATION_PASSWORD 503 || variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD 504 || variation == InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD; 505 } 506 507 private static boolean isEmailVariation(int variation) { 508 return variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 509 || variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; 510 } 511 512 @Override 513 public void onStartInputView(EditorInfo attribute, boolean restarting) { 514 final KeyboardSwitcher switcher = mKeyboardSwitcher; 515 LatinKeyboardView inputView = switcher.getInputView(); 516 517 if(DEBUG) { 518 Log.d(TAG, "onStartInputView: " + inputView); 519 } 520 // In landscape mode, this method gets called without the input view being created. 521 if (inputView == null) { 522 return; 523 } 524 525 mSubtypeSwitcher.updateParametersOnStartInputView(); 526 527 if (mRefreshKeyboardRequired) { 528 mRefreshKeyboardRequired = false; 529 onRefreshKeyboard(); 530 } 531 532 TextEntryState.newSession(this); 533 534 // Most such things we decide below in initializeInputAttributesAndGetMode, but we need to 535 // know now whether this is a password text field, because we need to know now whether we 536 // want to enable the voice button. 537 mVoiceConnector.resetVoiceStates(isPasswordVariation( 538 attribute.inputType & InputType.TYPE_MASK_VARIATION)); 539 540 final int mode = initializeInputAttributesAndGetMode(attribute.inputType); 541 542 inputView.closing(); 543 mEnteredText = null; 544 mComposing.setLength(0); 545 mHasValidSuggestions = false; 546 mDeleteCount = 0; 547 mJustAddedAutoSpace = false; 548 549 loadSettings(attribute); 550 if (mSubtypeSwitcher.isKeyboardMode()) { 551 switcher.loadKeyboard(mode, attribute.imeOptions, 552 mVoiceConnector.isVoiceButtonEnabled(), 553 mVoiceConnector.isVoiceButtonOnPrimary()); 554 switcher.updateShiftState(); 555 } 556 557 setCandidatesViewShownInternal(isCandidateStripVisible(), 558 false /* needsInputViewShown */ ); 559 // Delay updating suggestions because keyboard input view may not be shown at this point. 560 mHandler.postUpdateSuggestions(); 561 562 updateCorrectionMode(); 563 564 inputView.setPreviewEnabled(mPopupOn); 565 inputView.setProximityCorrectionEnabled(true); 566 // If we just entered a text field, maybe it has some old text that requires correction 567 checkReCorrectionOnStart(); 568 inputView.setForeground(true); 569 570 mVoiceConnector.onStartInputView(inputView.getWindowToken()); 571 572 if (TRACE) Debug.startMethodTracing("/data/trace/latinime"); 573 } 574 575 private int initializeInputAttributesAndGetMode(int inputType) { 576 final int variation = inputType & InputType.TYPE_MASK_VARIATION; 577 mAutoSpace = false; 578 mInputTypeNoAutoCorrect = false; 579 mIsSettingsSuggestionStripOn = false; 580 mApplicationSpecifiedCompletionOn = false; 581 mApplicationSpecifiedCompletions = null; 582 583 final int mode; 584 switch (inputType & InputType.TYPE_MASK_CLASS) { 585 case InputType.TYPE_CLASS_NUMBER: 586 case InputType.TYPE_CLASS_DATETIME: 587 mode = KeyboardId.MODE_NUMBER; 588 break; 589 case InputType.TYPE_CLASS_PHONE: 590 mode = KeyboardId.MODE_PHONE; 591 break; 592 case InputType.TYPE_CLASS_TEXT: 593 mIsSettingsSuggestionStripOn = true; 594 // Make sure that passwords are not displayed in candidate view 595 if (isPasswordVariation(variation)) { 596 mIsSettingsSuggestionStripOn = false; 597 } 598 if (isEmailVariation(variation) 599 || variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) { 600 mAutoSpace = false; 601 } else { 602 mAutoSpace = true; 603 } 604 if (isEmailVariation(variation)) { 605 mIsSettingsSuggestionStripOn = false; 606 mode = KeyboardId.MODE_EMAIL; 607 } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) { 608 mIsSettingsSuggestionStripOn = false; 609 mode = KeyboardId.MODE_URL; 610 } else if (variation == InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE) { 611 mode = KeyboardId.MODE_IM; 612 } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) { 613 mIsSettingsSuggestionStripOn = false; 614 mode = KeyboardId.MODE_TEXT; 615 } else if (variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) { 616 mode = KeyboardId.MODE_WEB; 617 // If it's a browser edit field and auto correct is not ON explicitly, then 618 // disable auto correction, but keep suggestions on. 619 if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) { 620 mInputTypeNoAutoCorrect = true; 621 } 622 } else { 623 mode = KeyboardId.MODE_TEXT; 624 } 625 626 // If NO_SUGGESTIONS is set, don't do prediction. 627 if ((inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) { 628 mIsSettingsSuggestionStripOn = false; 629 mInputTypeNoAutoCorrect = true; 630 } 631 // If it's not multiline and the autoCorrect flag is not set, then don't correct 632 if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0 && 633 (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) == 0) { 634 mInputTypeNoAutoCorrect = true; 635 } 636 if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) { 637 mIsSettingsSuggestionStripOn = false; 638 mApplicationSpecifiedCompletionOn = isFullscreenMode(); 639 } 640 break; 641 default: 642 mode = KeyboardId.MODE_TEXT; 643 break; 644 } 645 return mode; 646 } 647 648 private void checkReCorrectionOnStart() { 649 if (!mReCorrectionEnabled) return; 650 651 final InputConnection ic = getCurrentInputConnection(); 652 if (ic == null) return; 653 // There could be a pending composing span. Clean it up first. 654 ic.finishComposingText(); 655 656 if (isShowingSuggestionsStrip() && isSuggestionsRequested()) { 657 // First get the cursor position. This is required by setOldSuggestions(), so that 658 // it can pass the correct range to setComposingRegion(). At this point, we don't 659 // have valid values for mLastSelectionStart/End because onUpdateSelection() has 660 // not been called yet. 661 ExtractedTextRequest etr = new ExtractedTextRequest(); 662 etr.token = 0; // anything is fine here 663 ExtractedText et = ic.getExtractedText(etr, 0); 664 if (et == null) return; 665 666 mLastSelectionStart = et.startOffset + et.selectionStart; 667 mLastSelectionEnd = et.startOffset + et.selectionEnd; 668 669 // Then look for possible corrections in a delayed fashion 670 if (!TextUtils.isEmpty(et.text) && isCursorTouchingWord()) { 671 mHandler.postUpdateOldSuggestions(); 672 } 673 } 674 } 675 676 @Override 677 public void onFinishInput() { 678 super.onFinishInput(); 679 680 LatinImeLogger.commit(); 681 mKeyboardSwitcher.onAutoCorrectionStateChanged(false); 682 683 mVoiceConnector.flushVoiceInputLogs(mConfigurationChanging); 684 685 KeyboardView inputView = mKeyboardSwitcher.getInputView(); 686 if (inputView != null) inputView.closing(); 687 if (mAutoDictionary != null) mAutoDictionary.flushPendingWrites(); 688 if (mUserBigramDictionary != null) mUserBigramDictionary.flushPendingWrites(); 689 } 690 691 @Override 692 public void onFinishInputView(boolean finishingInput) { 693 super.onFinishInputView(finishingInput); 694 KeyboardView inputView = mKeyboardSwitcher.getInputView(); 695 if (inputView != null) inputView.setForeground(false); 696 // Remove pending messages related to update suggestions 697 mHandler.cancelUpdateSuggestions(); 698 mHandler.cancelUpdateOldSuggestions(); 699 } 700 701 @Override 702 public void onUpdateExtractedText(int token, ExtractedText text) { 703 super.onUpdateExtractedText(token, text); 704 mVoiceConnector.showPunctuationHintIfNecessary(); 705 } 706 707 @Override 708 public void onUpdateSelection(int oldSelStart, int oldSelEnd, 709 int newSelStart, int newSelEnd, 710 int candidatesStart, int candidatesEnd) { 711 super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, 712 candidatesStart, candidatesEnd); 713 714 if (DEBUG) { 715 Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart 716 + ", ose=" + oldSelEnd 717 + ", nss=" + newSelStart 718 + ", nse=" + newSelEnd 719 + ", cs=" + candidatesStart 720 + ", ce=" + candidatesEnd); 721 } 722 723 mVoiceConnector.setCursorAndSelection(newSelEnd, newSelStart); 724 725 // If the current selection in the text view changes, we should 726 // clear whatever candidate text we have. 727 if ((((mComposing.length() > 0 && mHasValidSuggestions) 728 || mVoiceConnector.isVoiceInputHighlighted()) && (newSelStart != candidatesEnd 729 || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart)) { 730 mComposing.setLength(0); 731 mHasValidSuggestions = false; 732 mHandler.postUpdateSuggestions(); 733 TextEntryState.reset(); 734 InputConnection ic = getCurrentInputConnection(); 735 if (ic != null) { 736 ic.finishComposingText(); 737 } 738 mVoiceConnector.setVoiceInputHighlighted(false); 739 } else if (!mHasValidSuggestions && !mJustAccepted) { 740 switch (TextEntryState.getState()) { 741 case ACCEPTED_DEFAULT: 742 TextEntryState.reset(); 743 // $FALL-THROUGH$ 744 case SPACE_AFTER_PICKED: 745 mJustAddedAutoSpace = false; // The user moved the cursor. 746 break; 747 default: 748 break; 749 } 750 } 751 mJustAccepted = false; 752 mHandler.postUpdateShiftKeyState(); 753 754 // Make a note of the cursor position 755 mLastSelectionStart = newSelStart; 756 mLastSelectionEnd = newSelEnd; 757 758 if (mReCorrectionEnabled && isShowingSuggestionsStrip()) { 759 // Don't look for corrections if the keyboard is not visible 760 if (mKeyboardSwitcher.isInputViewShown()) { 761 // Check if we should go in or out of correction mode. 762 if (isSuggestionsRequested() && !mJustReverted 763 && (candidatesStart == candidatesEnd || newSelStart != oldSelStart 764 || TextEntryState.isCorrecting()) 765 && (newSelStart < newSelEnd - 1 || !mHasValidSuggestions)) { 766 if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) { 767 mHandler.postUpdateOldSuggestions(); 768 } else { 769 abortCorrection(false); 770 // Show the punctuation suggestions list if the current one is not 771 // and if not showing "Touch again to save". 772 if (mCandidateView != null && !isShowingPunctuationList() 773 && !mCandidateView.isShowingAddToDictionaryHint()) { 774 setPunctuationSuggestions(); 775 } 776 } 777 } 778 } 779 } 780 } 781 782 /** 783 * This is called when the user has clicked on the extracted text view, 784 * when running in fullscreen mode. The default implementation hides 785 * the candidates view when this happens, but only if the extracted text 786 * editor has a vertical scroll bar because its text doesn't fit. 787 * Here we override the behavior due to the possibility that a re-correction could 788 * cause the candidate strip to disappear and re-appear. 789 */ 790 @Override 791 public void onExtractedTextClicked() { 792 if (mReCorrectionEnabled && isSuggestionsRequested()) return; 793 794 super.onExtractedTextClicked(); 795 } 796 797 /** 798 * This is called when the user has performed a cursor movement in the 799 * extracted text view, when it is running in fullscreen mode. The default 800 * implementation hides the candidates view when a vertical movement 801 * happens, but only if the extracted text editor has a vertical scroll bar 802 * because its text doesn't fit. 803 * Here we override the behavior due to the possibility that a re-correction could 804 * cause the candidate strip to disappear and re-appear. 805 */ 806 @Override 807 public void onExtractedCursorMovement(int dx, int dy) { 808 if (mReCorrectionEnabled && isSuggestionsRequested()) return; 809 810 super.onExtractedCursorMovement(dx, dy); 811 } 812 813 @Override 814 public void hideWindow() { 815 LatinImeLogger.commit(); 816 mKeyboardSwitcher.onAutoCorrectionStateChanged(false); 817 818 if (TRACE) Debug.stopMethodTracing(); 819 if (mOptionsDialog != null && mOptionsDialog.isShowing()) { 820 mOptionsDialog.dismiss(); 821 mOptionsDialog = null; 822 } 823 mVoiceConnector.hideVoiceWindow(mConfigurationChanging); 824 mWordHistory.clear(); 825 super.hideWindow(); 826 TextEntryState.endSession(); 827 } 828 829 @Override 830 public void onDisplayCompletions(CompletionInfo[] applicationSpecifiedCompletions) { 831 if (DEBUG) { 832 Log.i(TAG, "Received completions:"); 833 final int count = (applicationSpecifiedCompletions != null) 834 ? applicationSpecifiedCompletions.length : 0; 835 for (int i = 0; i < count; i++) { 836 Log.i(TAG, " #" + i + ": " + applicationSpecifiedCompletions[i]); 837 } 838 } 839 if (mApplicationSpecifiedCompletionOn) { 840 mApplicationSpecifiedCompletions = applicationSpecifiedCompletions; 841 if (applicationSpecifiedCompletions == null) { 842 clearSuggestions(); 843 return; 844 } 845 846 SuggestedWords.Builder builder = new SuggestedWords.Builder() 847 .setApplicationSpecifiedCompletions(applicationSpecifiedCompletions) 848 .setTypedWordValid(true) 849 .setHasMinimalSuggestion(true); 850 // When in fullscreen mode, show completions generated by the application 851 setSuggestions(builder.build()); 852 mBestWord = null; 853 setCandidatesViewShown(true); 854 } 855 } 856 857 private void setCandidatesViewShownInternal(boolean shown, boolean needsInputViewShown) { 858 // TODO: Remove this if we support candidates with hard keyboard 859 if (onEvaluateInputViewShown()) { 860 super.setCandidatesViewShown(shown 861 && (needsInputViewShown ? mKeyboardSwitcher.isInputViewShown() : true)); 862 } 863 } 864 865 @Override 866 public void setCandidatesViewShown(boolean shown) { 867 setCandidatesViewShownInternal(shown, true /* needsInputViewShown */ ); 868 } 869 870 @Override 871 public void onComputeInsets(InputMethodService.Insets outInsets) { 872 super.onComputeInsets(outInsets); 873 if (!isFullscreenMode()) { 874 outInsets.contentTopInsets = outInsets.visibleTopInsets; 875 } 876 KeyboardView inputView = mKeyboardSwitcher.getInputView(); 877 // Need to set touchable region only if input view is being shown 878 if (inputView != null && mKeyboardSwitcher.isInputViewShown()) { 879 final int x = 0; 880 int y = 0; 881 final int width = inputView.getWidth(); 882 int height = inputView.getHeight() + EXTENDED_TOUCHABLE_REGION_HEIGHT; 883 if (mCandidateViewContainer != null) { 884 ViewParent candidateParent = mCandidateViewContainer.getParent(); 885 if (candidateParent instanceof FrameLayout) { 886 FrameLayout fl = (FrameLayout) candidateParent; 887 if (fl != null) { 888 // Check frame layout's visibility 889 if (fl.getVisibility() == View.INVISIBLE) { 890 y = fl.getHeight(); 891 height += y; 892 } else if (fl.getVisibility() == View.VISIBLE) { 893 height += fl.getHeight(); 894 } 895 } 896 } 897 } 898 if (DEBUG) { 899 Log.d(TAG, "Touchable region " + x + ", " + y + ", " + width + ", " + height); 900 } 901 outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION; 902 outInsets.touchableRegion.set(x, y, width, height); 903 } 904 } 905 906 @Override 907 public boolean onEvaluateFullscreenMode() { 908 final Resources res = mResources; 909 DisplayMetrics dm = res.getDisplayMetrics(); 910 float displayHeight = dm.heightPixels; 911 // If the display is more than X inches high, don't go to fullscreen mode 912 float dimen = res.getDimension(R.dimen.max_height_for_fullscreen); 913 if (displayHeight > dimen) { 914 return false; 915 } else { 916 return super.onEvaluateFullscreenMode(); 917 } 918 } 919 920 @Override 921 public boolean onKeyDown(int keyCode, KeyEvent event) { 922 switch (keyCode) { 923 case KeyEvent.KEYCODE_BACK: 924 if (event.getRepeatCount() == 0 && mKeyboardSwitcher.getInputView() != null) { 925 if (mKeyboardSwitcher.getInputView().handleBack()) { 926 return true; 927 } 928 } 929 break; 930 } 931 return super.onKeyDown(keyCode, event); 932 } 933 934 @Override 935 public boolean onKeyUp(int keyCode, KeyEvent event) { 936 switch (keyCode) { 937 case KeyEvent.KEYCODE_DPAD_DOWN: 938 case KeyEvent.KEYCODE_DPAD_UP: 939 case KeyEvent.KEYCODE_DPAD_LEFT: 940 case KeyEvent.KEYCODE_DPAD_RIGHT: 941 // Enable shift key and DPAD to do selections 942 if (mKeyboardSwitcher.isInputViewShown() 943 && mKeyboardSwitcher.isShiftedOrShiftLocked()) { 944 KeyEvent newEvent = new KeyEvent(event.getDownTime(), event.getEventTime(), 945 event.getAction(), event.getKeyCode(), event.getRepeatCount(), 946 event.getDeviceId(), event.getScanCode(), 947 KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON); 948 InputConnection ic = getCurrentInputConnection(); 949 if (ic != null) 950 ic.sendKeyEvent(newEvent); 951 return true; 952 } 953 break; 954 } 955 return super.onKeyUp(keyCode, event); 956 } 957 958 public void commitTyped(InputConnection inputConnection) { 959 if (mHasValidSuggestions) { 960 mHasValidSuggestions = false; 961 if (mComposing.length() > 0) { 962 if (inputConnection != null) { 963 inputConnection.commitText(mComposing, 1); 964 } 965 mCommittedLength = mComposing.length(); 966 TextEntryState.acceptedTyped(mComposing); 967 addToDictionaries(mComposing, AutoDictionary.FREQUENCY_FOR_TYPED); 968 } 969 updateSuggestions(); 970 } 971 } 972 973 public boolean getCurrentAutoCapsState() { 974 InputConnection ic = getCurrentInputConnection(); 975 EditorInfo ei = getCurrentInputEditorInfo(); 976 if (mAutoCap && ic != null && ei != null && ei.inputType != InputType.TYPE_NULL) { 977 return ic.getCursorCapsMode(ei.inputType) != 0; 978 } 979 return false; 980 } 981 982 private void swapPunctuationAndSpace() { 983 final InputConnection ic = getCurrentInputConnection(); 984 if (ic == null) return; 985 CharSequence lastTwo = ic.getTextBeforeCursor(2, 0); 986 if (lastTwo != null && lastTwo.length() == 2 987 && lastTwo.charAt(0) == Keyboard.CODE_SPACE 988 && isSentenceSeparator(lastTwo.charAt(1))) { 989 ic.beginBatchEdit(); 990 ic.deleteSurroundingText(2, 0); 991 ic.commitText(lastTwo.charAt(1) + " ", 1); 992 ic.endBatchEdit(); 993 mKeyboardSwitcher.updateShiftState(); 994 mJustAddedAutoSpace = true; 995 } 996 } 997 998 private void reswapPeriodAndSpace() { 999 final InputConnection ic = getCurrentInputConnection(); 1000 if (ic == null) return; 1001 CharSequence lastThree = ic.getTextBeforeCursor(3, 0); 1002 if (lastThree != null && lastThree.length() == 3 1003 && lastThree.charAt(0) == Keyboard.CODE_PERIOD 1004 && lastThree.charAt(1) == Keyboard.CODE_SPACE 1005 && lastThree.charAt(2) == Keyboard.CODE_PERIOD) { 1006 ic.beginBatchEdit(); 1007 ic.deleteSurroundingText(3, 0); 1008 ic.commitText(" ..", 1); 1009 ic.endBatchEdit(); 1010 mKeyboardSwitcher.updateShiftState(); 1011 } 1012 } 1013 1014 private void doubleSpace() { 1015 //if (!mAutoPunctuate) return; 1016 if (mCorrectionMode == Suggest.CORRECTION_NONE) return; 1017 final InputConnection ic = getCurrentInputConnection(); 1018 if (ic == null) return; 1019 CharSequence lastThree = ic.getTextBeforeCursor(3, 0); 1020 if (lastThree != null && lastThree.length() == 3 1021 && Character.isLetterOrDigit(lastThree.charAt(0)) 1022 && lastThree.charAt(1) == Keyboard.CODE_SPACE 1023 && lastThree.charAt(2) == Keyboard.CODE_SPACE) { 1024 ic.beginBatchEdit(); 1025 ic.deleteSurroundingText(2, 0); 1026 ic.commitText(". ", 1); 1027 ic.endBatchEdit(); 1028 mKeyboardSwitcher.updateShiftState(); 1029 mJustAddedAutoSpace = true; 1030 } 1031 } 1032 1033 private void maybeRemovePreviousPeriod(CharSequence text) { 1034 final InputConnection ic = getCurrentInputConnection(); 1035 if (ic == null) return; 1036 1037 // When the text's first character is '.', remove the previous period 1038 // if there is one. 1039 CharSequence lastOne = ic.getTextBeforeCursor(1, 0); 1040 if (lastOne != null && lastOne.length() == 1 1041 && lastOne.charAt(0) == Keyboard.CODE_PERIOD 1042 && text.charAt(0) == Keyboard.CODE_PERIOD) { 1043 ic.deleteSurroundingText(1, 0); 1044 } 1045 } 1046 1047 private void removeTrailingSpace() { 1048 final InputConnection ic = getCurrentInputConnection(); 1049 if (ic == null) return; 1050 1051 CharSequence lastOne = ic.getTextBeforeCursor(1, 0); 1052 if (lastOne != null && lastOne.length() == 1 1053 && lastOne.charAt(0) == Keyboard.CODE_SPACE) { 1054 ic.deleteSurroundingText(1, 0); 1055 } 1056 } 1057 1058 public boolean addWordToDictionary(String word) { 1059 mUserDictionary.addWord(word, 128); 1060 // Suggestion strip should be updated after the operation of adding word to the 1061 // user dictionary 1062 mHandler.postUpdateSuggestions(); 1063 return true; 1064 } 1065 1066 private boolean isAlphabet(int code) { 1067 if (Character.isLetter(code)) { 1068 return true; 1069 } else { 1070 return false; 1071 } 1072 } 1073 1074 private void onSettingsKeyPressed() { 1075 if (!isShowingOptionDialog()) { 1076 if (!mConfigEnableShowSubtypeSettings) { 1077 showSubtypeSelectorAndSettings(); 1078 } else if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm)) { 1079 showOptionsMenu(); 1080 } else { 1081 launchSettings(); 1082 } 1083 } 1084 } 1085 1086 private void onSettingsKeyLongPressed() { 1087 if (!isShowingOptionDialog()) { 1088 if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm)) { 1089 mImm.showInputMethodPicker(); 1090 } else { 1091 launchSettings(); 1092 } 1093 } 1094 } 1095 1096 private boolean isShowingOptionDialog() { 1097 return mOptionsDialog != null && mOptionsDialog.isShowing(); 1098 } 1099 1100 // Implementation of {@link KeyboardActionListener}. 1101 @Override 1102 public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) { 1103 long when = SystemClock.uptimeMillis(); 1104 if (primaryCode != Keyboard.CODE_DELETE || when > mLastKeyTime + QUICK_PRESS) { 1105 mDeleteCount = 0; 1106 } 1107 mLastKeyTime = when; 1108 KeyboardSwitcher switcher = mKeyboardSwitcher; 1109 final boolean distinctMultiTouch = switcher.hasDistinctMultitouch(); 1110 switch (primaryCode) { 1111 case Keyboard.CODE_DELETE: 1112 handleBackspace(); 1113 mDeleteCount++; 1114 LatinImeLogger.logOnDelete(); 1115 break; 1116 case Keyboard.CODE_SHIFT: 1117 // Shift key is handled in onPress() when device has distinct multi-touch panel. 1118 if (!distinctMultiTouch) 1119 switcher.toggleShift(); 1120 break; 1121 case Keyboard.CODE_SWITCH_ALPHA_SYMBOL: 1122 // Symbol key is handled in onPress() when device has distinct multi-touch panel. 1123 if (!distinctMultiTouch) 1124 switcher.changeKeyboardMode(); 1125 break; 1126 case Keyboard.CODE_CANCEL: 1127 if (!isShowingOptionDialog()) { 1128 handleClose(); 1129 } 1130 break; 1131 case Keyboard.CODE_SETTINGS: 1132 onSettingsKeyPressed(); 1133 break; 1134 case Keyboard.CODE_SETTINGS_LONGPRESS: 1135 onSettingsKeyLongPressed(); 1136 break; 1137 case Keyboard.CODE_NEXT_LANGUAGE: 1138 toggleLanguage(false, true); 1139 break; 1140 case Keyboard.CODE_PREV_LANGUAGE: 1141 toggleLanguage(false, false); 1142 break; 1143 case Keyboard.CODE_CAPSLOCK: 1144 switcher.toggleCapsLock(); 1145 break; 1146 case Keyboard.CODE_VOICE: 1147 mSubtypeSwitcher.switchToShortcutIME(); 1148 break; 1149 case Keyboard.CODE_TAB: 1150 handleTab(); 1151 break; 1152 default: 1153 if (primaryCode != Keyboard.CODE_ENTER) { 1154 mJustAddedAutoSpace = false; 1155 } 1156 RingCharBuffer.getInstance().push((char)primaryCode, x, y); 1157 LatinImeLogger.logOnInputChar(); 1158 if (isWordSeparator(primaryCode)) { 1159 handleSeparator(primaryCode); 1160 } else { 1161 handleCharacter(primaryCode, keyCodes); 1162 } 1163 // Cancel the just reverted state 1164 mJustReverted = false; 1165 } 1166 switcher.onKey(primaryCode); 1167 // Reset after any single keystroke 1168 mEnteredText = null; 1169 } 1170 1171 @Override 1172 public void onTextInput(CharSequence text) { 1173 mVoiceConnector.commitVoiceInput(); 1174 InputConnection ic = getCurrentInputConnection(); 1175 if (ic == null) return; 1176 abortCorrection(false); 1177 ic.beginBatchEdit(); 1178 commitTyped(ic); 1179 maybeRemovePreviousPeriod(text); 1180 ic.commitText(text, 1); 1181 ic.endBatchEdit(); 1182 mKeyboardSwitcher.updateShiftState(); 1183 mKeyboardSwitcher.onKey(Keyboard.CODE_DUMMY); 1184 mJustReverted = false; 1185 mJustAddedAutoSpace = false; 1186 mEnteredText = text; 1187 } 1188 1189 @Override 1190 public void onCancelInput() { 1191 // User released a finger outside any key 1192 mKeyboardSwitcher.onCancelInput(); 1193 } 1194 1195 private void handleBackspace() { 1196 if (mVoiceConnector.logAndRevertVoiceInput()) return; 1197 1198 final InputConnection ic = getCurrentInputConnection(); 1199 if (ic == null) return; 1200 ic.beginBatchEdit(); 1201 1202 mVoiceConnector.handleBackspace(); 1203 1204 boolean deleteChar = false; 1205 if (mHasValidSuggestions) { 1206 final int length = mComposing.length(); 1207 if (length > 0) { 1208 mComposing.delete(length - 1, length); 1209 mWord.deleteLast(); 1210 ic.setComposingText(mComposing, 1); 1211 if (mComposing.length() == 0) { 1212 mHasValidSuggestions = false; 1213 } 1214 mHandler.postUpdateSuggestions(); 1215 } else { 1216 ic.deleteSurroundingText(1, 0); 1217 } 1218 } else { 1219 deleteChar = true; 1220 } 1221 mHandler.postUpdateShiftKeyState(); 1222 1223 TextEntryState.backspace(); 1224 if (TextEntryState.getState() == TextEntryState.State.UNDO_COMMIT) { 1225 revertLastWord(deleteChar); 1226 ic.endBatchEdit(); 1227 return; 1228 } 1229 1230 if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) { 1231 ic.deleteSurroundingText(mEnteredText.length(), 0); 1232 } else if (deleteChar) { 1233 if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) { 1234 // Go back to the suggestion mode if the user canceled the 1235 // "Touch again to save". 1236 // NOTE: In gerenal, we don't revert the word when backspacing 1237 // from a manual suggestion pick. We deliberately chose a 1238 // different behavior only in the case of picking the first 1239 // suggestion (typed word). It's intentional to have made this 1240 // inconsistent with backspacing after selecting other suggestions. 1241 revertLastWord(deleteChar); 1242 } else { 1243 sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); 1244 if (mDeleteCount > DELETE_ACCELERATE_AT) { 1245 sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); 1246 } 1247 } 1248 } 1249 mJustReverted = false; 1250 ic.endBatchEdit(); 1251 } 1252 1253 private void handleTab() { 1254 final int imeOptions = getCurrentInputEditorInfo().imeOptions; 1255 final int navigationFlags = 1256 EditorInfo.IME_FLAG_NAVIGATE_NEXT | EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; 1257 if ((imeOptions & navigationFlags) == 0) { 1258 sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB); 1259 return; 1260 } 1261 1262 final InputConnection ic = getCurrentInputConnection(); 1263 if (ic == null) 1264 return; 1265 1266 // True if keyboard is in either chording shift or manual temporary upper case mode. 1267 final boolean isManualTemporaryUpperCase = mKeyboardSwitcher.isManualTemporaryUpperCase(); 1268 if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0 1269 && !isManualTemporaryUpperCase) { 1270 ic.performEditorAction(EditorInfo.IME_ACTION_NEXT); 1271 } else if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS) != 0 1272 && isManualTemporaryUpperCase) { 1273 ic.performEditorAction(EditorInfo.IME_ACTION_PREVIOUS); 1274 } 1275 } 1276 1277 private void abortCorrection(boolean force) { 1278 if (force || TextEntryState.isCorrecting()) { 1279 TextEntryState.onAbortCorrection(); 1280 setCandidatesViewShown(isCandidateStripVisible()); 1281 getCurrentInputConnection().finishComposingText(); 1282 clearSuggestions(); 1283 } 1284 } 1285 1286 private void handleCharacter(int primaryCode, int[] keyCodes) { 1287 mVoiceConnector.handleCharacter(); 1288 1289 if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isCorrecting()) { 1290 abortCorrection(false); 1291 } 1292 1293 int code = primaryCode; 1294 if (isAlphabet(code) && isSuggestionsRequested() && !isCursorTouchingWord()) { 1295 if (!mHasValidSuggestions) { 1296 mHasValidSuggestions = true; 1297 mComposing.setLength(0); 1298 saveWordInHistory(mBestWord); 1299 mWord.reset(); 1300 } 1301 } 1302 KeyboardSwitcher switcher = mKeyboardSwitcher; 1303 if (switcher.isShiftedOrShiftLocked()) { 1304 if (keyCodes == null || keyCodes[0] < Character.MIN_CODE_POINT 1305 || keyCodes[0] > Character.MAX_CODE_POINT) { 1306 return; 1307 } 1308 code = keyCodes[0]; 1309 if (switcher.isAlphabetMode() && Character.isLowerCase(code)) { 1310 int upperCaseCode = Character.toUpperCase(code); 1311 if (upperCaseCode != code) { 1312 code = upperCaseCode; 1313 } else { 1314 // Some keys, such as [eszett], have upper case as multi-characters. 1315 String upperCase = new String(new int[] {code}, 0, 1).toUpperCase(); 1316 onTextInput(upperCase); 1317 return; 1318 } 1319 } 1320 } 1321 if (mHasValidSuggestions) { 1322 if (mComposing.length() == 0 && switcher.isAlphabetMode() 1323 && switcher.isShiftedOrShiftLocked()) { 1324 mWord.setFirstCharCapitalized(true); 1325 } 1326 mComposing.append((char) code); 1327 mWord.add(code, keyCodes); 1328 InputConnection ic = getCurrentInputConnection(); 1329 if (ic != null) { 1330 // If it's the first letter, make note of auto-caps state 1331 if (mWord.size() == 1) { 1332 mWord.setAutoCapitalized(getCurrentAutoCapsState()); 1333 } 1334 ic.setComposingText(mComposing, 1); 1335 } 1336 mHandler.postUpdateSuggestions(); 1337 } else { 1338 sendKeyChar((char)code); 1339 } 1340 switcher.updateShiftState(); 1341 if (LatinIME.PERF_DEBUG) measureCps(); 1342 TextEntryState.typedCharacter((char) code, isWordSeparator(code)); 1343 } 1344 1345 private void handleSeparator(int primaryCode) { 1346 mVoiceConnector.handleSeparator(); 1347 1348 // Should dismiss the "Touch again to save" message when handling separator 1349 if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) { 1350 mHandler.postUpdateSuggestions(); 1351 } 1352 1353 boolean pickedDefault = false; 1354 // Handle separator 1355 final InputConnection ic = getCurrentInputConnection(); 1356 if (ic != null) { 1357 ic.beginBatchEdit(); 1358 abortCorrection(false); 1359 } 1360 if (mHasValidSuggestions) { 1361 // In certain languages where single quote is a separator, it's better 1362 // not to auto correct, but accept the typed word. For instance, 1363 // in Italian dov' should not be expanded to dove' because the elision 1364 // requires the last vowel to be removed. 1365 if (mAutoCorrectOn && primaryCode != '\'' && !mJustReverted) { 1366 pickedDefault = pickDefaultSuggestion(); 1367 // Picked the suggestion by the space key. We consider this 1368 // as "added an auto space". 1369 if (primaryCode == Keyboard.CODE_SPACE) { 1370 mJustAddedAutoSpace = true; 1371 } 1372 } else { 1373 commitTyped(ic); 1374 } 1375 } 1376 if (mJustAddedAutoSpace && primaryCode == Keyboard.CODE_ENTER) { 1377 removeTrailingSpace(); 1378 mJustAddedAutoSpace = false; 1379 } 1380 sendKeyChar((char)primaryCode); 1381 1382 // Handle the case of ". ." -> " .." with auto-space if necessary 1383 // before changing the TextEntryState. 1384 if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED 1385 && primaryCode == Keyboard.CODE_PERIOD) { 1386 reswapPeriodAndSpace(); 1387 } 1388 1389 TextEntryState.typedCharacter((char) primaryCode, true); 1390 if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED 1391 && primaryCode != Keyboard.CODE_ENTER) { 1392 swapPunctuationAndSpace(); 1393 } else if (isSuggestionsRequested() && primaryCode == Keyboard.CODE_SPACE) { 1394 doubleSpace(); 1395 } 1396 if (pickedDefault) { 1397 CharSequence typedWord = mWord.getTypedWord(); 1398 TextEntryState.backToAcceptedDefault(typedWord); 1399 if (!TextUtils.isEmpty(typedWord) && !typedWord.equals(mBestWord)) { 1400 if (ic != null) { 1401 CorrectionInfo correctionInfo = new CorrectionInfo( 1402 mLastSelectionEnd - typedWord.length(), typedWord, mBestWord); 1403 ic.commitCorrection(correctionInfo); 1404 } 1405 if (mCandidateView != null) 1406 mCandidateView.onAutoCorrectionInverted(mBestWord); 1407 } 1408 setPunctuationSuggestions(); 1409 } 1410 mKeyboardSwitcher.updateShiftState(); 1411 if (ic != null) { 1412 ic.endBatchEdit(); 1413 } 1414 } 1415 1416 private void handleClose() { 1417 commitTyped(getCurrentInputConnection()); 1418 mVoiceConnector.handleClose(); 1419 requestHideSelf(0); 1420 LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); 1421 if (inputView != null) 1422 inputView.closing(); 1423 TextEntryState.endSession(); 1424 } 1425 1426 private void saveWordInHistory(CharSequence result) { 1427 if (mWord.size() <= 1) { 1428 mWord.reset(); 1429 return; 1430 } 1431 // Skip if result is null. It happens in some edge case. 1432 if (TextUtils.isEmpty(result)) { 1433 return; 1434 } 1435 1436 // Make a copy of the CharSequence, since it is/could be a mutable CharSequence 1437 final String resultCopy = result.toString(); 1438 TypedWordAlternatives entry = new TypedWordAlternatives(resultCopy, 1439 new WordComposer(mWord)); 1440 mWordHistory.add(entry); 1441 } 1442 1443 private boolean isSuggestionsRequested() { 1444 return mIsSettingsSuggestionStripOn 1445 && (mCorrectionMode > 0 || isShowingSuggestionsStrip()); 1446 } 1447 1448 private boolean isShowingPunctuationList() { 1449 return mSuggestPuncList == mCandidateView.getSuggestions(); 1450 } 1451 1452 private boolean isShowingSuggestionsStrip() { 1453 return (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_VALUE) 1454 || (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE 1455 && mOrientation == Configuration.ORIENTATION_PORTRAIT); 1456 } 1457 1458 private boolean isCandidateStripVisible() { 1459 if (mCandidateView.isShowingAddToDictionaryHint() || TextEntryState.isCorrecting()) 1460 return true; 1461 if (!isShowingSuggestionsStrip()) 1462 return false; 1463 if (mApplicationSpecifiedCompletionOn) 1464 return true; 1465 return isSuggestionsRequested(); 1466 } 1467 1468 public void switchToKeyboardView() { 1469 mHandler.post(new Runnable() { 1470 @Override 1471 public void run() { 1472 if (DEBUG) { 1473 Log.d(TAG, "Switch to keyboard view."); 1474 } 1475 View v = mKeyboardSwitcher.getInputView(); 1476 if (v != null) { 1477 // Confirms that the keyboard view doesn't have parent view. 1478 ViewParent p = v.getParent(); 1479 if (p != null && p instanceof ViewGroup) { 1480 ((ViewGroup) p).removeView(v); 1481 } 1482 setInputView(v); 1483 } 1484 setCandidatesViewShown(isCandidateStripVisible()); 1485 updateInputViewShown(); 1486 mHandler.postUpdateSuggestions(); 1487 } 1488 }); 1489 } 1490 1491 public void clearSuggestions() { 1492 setSuggestions(SuggestedWords.EMPTY); 1493 } 1494 1495 public void setSuggestions(SuggestedWords words) { 1496 if (mVoiceConnector.getAndResetIsShowingHint()) { 1497 setCandidatesView(mCandidateViewContainer); 1498 } 1499 1500 if (mCandidateView != null) { 1501 mCandidateView.setSuggestions(words); 1502 if (mCandidateView.isConfigCandidateHighlightFontColorEnabled()) { 1503 mKeyboardSwitcher.onAutoCorrectionStateChanged( 1504 words.hasWordAboveAutoCorrectionScoreThreshold()); 1505 } 1506 } 1507 } 1508 1509 public void updateSuggestions() { 1510 mKeyboardSwitcher.setPreferredLetters(null); 1511 1512 // Check if we have a suggestion engine attached. 1513 if ((mSuggest == null || !isSuggestionsRequested()) 1514 && !mVoiceConnector.isVoiceInputHighlighted()) { 1515 return; 1516 } 1517 1518 if (!mHasValidSuggestions) { 1519 setPunctuationSuggestions(); 1520 return; 1521 } 1522 showSuggestions(mWord); 1523 } 1524 1525 private SuggestedWords.Builder getTypedSuggestions(WordComposer word) { 1526 return mSuggest.getSuggestedWordBuilder(mKeyboardSwitcher.getInputView(), word, null); 1527 } 1528 1529 private void showCorrections(WordAlternatives alternatives) { 1530 mKeyboardSwitcher.setPreferredLetters(null); 1531 SuggestedWords.Builder builder = alternatives.getAlternatives(); 1532 builder.setTypedWordValid(false).setHasMinimalSuggestion(false); 1533 showSuggestions(builder.build(), alternatives.getOriginalWord()); 1534 } 1535 1536 private void showSuggestions(WordComposer word) { 1537 // TODO Maybe need better way of retrieving previous word 1538 CharSequence prevWord = EditingUtils.getPreviousWord(getCurrentInputConnection(), 1539 mWordSeparators); 1540 SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder( 1541 mKeyboardSwitcher.getInputView(), word, prevWord); 1542 1543 int[] nextLettersFrequencies = mSuggest.getNextLettersFrequencies(); 1544 mKeyboardSwitcher.setPreferredLetters(nextLettersFrequencies); 1545 1546 boolean correctionAvailable = !mInputTypeNoAutoCorrect && !mJustReverted 1547 && mSuggest.hasAutoCorrection(); 1548 final CharSequence typedWord = word.getTypedWord(); 1549 // If we're in basic correct 1550 final boolean typedWordValid = mSuggest.isValidWord(typedWord) || 1551 (preferCapitalization() 1552 && mSuggest.isValidWord(typedWord.toString().toLowerCase())); 1553 if (mCorrectionMode == Suggest.CORRECTION_FULL 1554 || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) { 1555 correctionAvailable |= typedWordValid; 1556 } 1557 // Don't auto-correct words with multiple capital letter 1558 correctionAvailable &= !word.isMostlyCaps(); 1559 correctionAvailable &= !TextEntryState.isCorrecting(); 1560 1561 // Basically, we update the suggestion strip only when suggestion count > 1. However, 1562 // there is an exception: We update the suggestion strip whenever typed word's length 1563 // is 1 or typed word is found in dictionary, regardless of suggestion count. Actually, 1564 // in most cases, suggestion count is 1 when typed word's length is 1, but we do always 1565 // need to clear the previous state when the user starts typing a word (i.e. typed word's 1566 // length == 1). 1567 if (builder.size() > 1 || typedWord.length() == 1 || typedWordValid 1568 || mCandidateView.isShowingAddToDictionaryHint()) { 1569 builder.setTypedWordValid(typedWordValid).setHasMinimalSuggestion(correctionAvailable); 1570 } else { 1571 final SuggestedWords previousSuggestions = mCandidateView.getSuggestions(); 1572 if (previousSuggestions == mSuggestPuncList) 1573 return; 1574 builder.addTypedWordAndPreviousSuggestions(typedWord, previousSuggestions); 1575 } 1576 showSuggestions(builder.build(), typedWord); 1577 } 1578 1579 private void showSuggestions(SuggestedWords suggestedWords, CharSequence typedWord) { 1580 setSuggestions(suggestedWords); 1581 if (suggestedWords.size() > 0) { 1582 if (Utils.shouldBlockedBySafetyNetForAutoCorrection(suggestedWords)) { 1583 mBestWord = typedWord; 1584 } else if (suggestedWords.hasAutoCorrectionWord()) { 1585 mBestWord = suggestedWords.getWord(1); 1586 } else { 1587 mBestWord = typedWord; 1588 } 1589 } else { 1590 mBestWord = null; 1591 } 1592 setCandidatesViewShown(isCandidateStripVisible()); 1593 } 1594 1595 private boolean pickDefaultSuggestion() { 1596 // Complete any pending candidate query first 1597 if (mHandler.hasPendingUpdateSuggestions()) { 1598 mHandler.cancelUpdateSuggestions(); 1599 updateSuggestions(); 1600 } 1601 if (mBestWord != null && mBestWord.length() > 0) { 1602 TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord); 1603 mJustAccepted = true; 1604 pickSuggestion(mBestWord); 1605 // Add the word to the auto dictionary if it's not a known word 1606 addToDictionaries(mBestWord, AutoDictionary.FREQUENCY_FOR_TYPED); 1607 return true; 1608 1609 } 1610 return false; 1611 } 1612 1613 public void pickSuggestionManually(int index, CharSequence suggestion) { 1614 SuggestedWords suggestions = mCandidateView.getSuggestions(); 1615 mVoiceConnector.flushAndLogAllTextModificationCounters(index, suggestion, mWordSeparators); 1616 1617 final boolean correcting = TextEntryState.isCorrecting(); 1618 InputConnection ic = getCurrentInputConnection(); 1619 if (ic != null) { 1620 ic.beginBatchEdit(); 1621 } 1622 if (mApplicationSpecifiedCompletionOn && mApplicationSpecifiedCompletions != null 1623 && index >= 0 && index < mApplicationSpecifiedCompletions.length) { 1624 CompletionInfo ci = mApplicationSpecifiedCompletions[index]; 1625 if (ic != null) { 1626 ic.commitCompletion(ci); 1627 } 1628 mCommittedLength = suggestion.length(); 1629 if (mCandidateView != null) { 1630 mCandidateView.clear(); 1631 } 1632 mKeyboardSwitcher.updateShiftState(); 1633 if (ic != null) { 1634 ic.endBatchEdit(); 1635 } 1636 return; 1637 } 1638 1639 // If this is a punctuation, apply it through the normal key press 1640 if (suggestion.length() == 1 && (isWordSeparator(suggestion.charAt(0)) 1641 || isSuggestedPunctuation(suggestion.charAt(0)))) { 1642 // Word separators are suggested before the user inputs something. 1643 // So, LatinImeLogger logs "" as a user's input. 1644 LatinImeLogger.logOnManualSuggestion( 1645 "", suggestion.toString(), index, suggestions.mWords); 1646 final char primaryCode = suggestion.charAt(0); 1647 onCodeInput(primaryCode, new int[] { primaryCode }, 1648 KeyboardActionListener.NOT_A_TOUCH_COORDINATE, 1649 KeyboardActionListener.NOT_A_TOUCH_COORDINATE); 1650 if (ic != null) { 1651 ic.endBatchEdit(); 1652 } 1653 return; 1654 } 1655 mJustAccepted = true; 1656 pickSuggestion(suggestion); 1657 // Add the word to the auto dictionary if it's not a known word 1658 if (index == 0) { 1659 addToDictionaries(suggestion, AutoDictionary.FREQUENCY_FOR_PICKED); 1660 } else { 1661 addToBigramDictionary(suggestion, 1); 1662 } 1663 LatinImeLogger.logOnManualSuggestion(mComposing.toString(), suggestion.toString(), 1664 index, suggestions.mWords); 1665 TextEntryState.acceptedSuggestion(mComposing.toString(), suggestion); 1666 // Follow it with a space 1667 if (mAutoSpace && !correcting) { 1668 sendSpace(); 1669 mJustAddedAutoSpace = true; 1670 } 1671 1672 final boolean showingAddToDictionaryHint = index == 0 && mCorrectionMode > 0 1673 && !mSuggest.isValidWord(suggestion) 1674 && !mSuggest.isValidWord(suggestion.toString().toLowerCase()); 1675 1676 if (!correcting) { 1677 // Fool the state watcher so that a subsequent backspace will not do a revert, unless 1678 // we just did a correction, in which case we need to stay in 1679 // TextEntryState.State.PICKED_SUGGESTION state. 1680 TextEntryState.typedCharacter((char) Keyboard.CODE_SPACE, true); 1681 setPunctuationSuggestions(); 1682 } else if (!showingAddToDictionaryHint) { 1683 // If we're not showing the "Touch again to save", then show corrections again. 1684 // In case the cursor position doesn't change, make sure we show the suggestions again. 1685 clearSuggestions(); 1686 mHandler.postUpdateOldSuggestions(); 1687 } 1688 if (showingAddToDictionaryHint) { 1689 mCandidateView.showAddToDictionaryHint(suggestion); 1690 } 1691 if (ic != null) { 1692 ic.endBatchEdit(); 1693 } 1694 } 1695 1696 /** 1697 * Commits the chosen word to the text field and saves it for later 1698 * retrieval. 1699 * @param suggestion the suggestion picked by the user to be committed to 1700 * the text field 1701 */ 1702 private void pickSuggestion(CharSequence suggestion) { 1703 KeyboardSwitcher switcher = mKeyboardSwitcher; 1704 if (!switcher.isKeyboardAvailable()) 1705 return; 1706 InputConnection ic = getCurrentInputConnection(); 1707 if (ic != null) { 1708 mVoiceConnector.rememberReplacedWord(suggestion, mWordSeparators); 1709 ic.commitText(suggestion, 1); 1710 } 1711 saveWordInHistory(suggestion); 1712 mHasValidSuggestions = false; 1713 mCommittedLength = suggestion.length(); 1714 switcher.setPreferredLetters(null); 1715 } 1716 1717 /** 1718 * Tries to apply any typed alternatives for the word if we have any cached alternatives, 1719 * otherwise tries to find new corrections and completions for the word. 1720 * @param touching The word that the cursor is touching, with position information 1721 * @return true if an alternative was found, false otherwise. 1722 */ 1723 private boolean applyTypedAlternatives(EditingUtils.SelectedWord touching) { 1724 // If we didn't find a match, search for result in typed word history 1725 WordComposer foundWord = null; 1726 WordAlternatives alternatives = null; 1727 for (WordAlternatives entry : mWordHistory) { 1728 if (TextUtils.equals(entry.getChosenWord(), touching.mWord)) { 1729 if (entry instanceof TypedWordAlternatives) { 1730 foundWord = ((TypedWordAlternatives) entry).word; 1731 } 1732 alternatives = entry; 1733 break; 1734 } 1735 } 1736 // If we didn't find a match, at least suggest corrections. 1737 if (foundWord == null 1738 && (mSuggest.isValidWord(touching.mWord) 1739 || mSuggest.isValidWord(touching.mWord.toString().toLowerCase()))) { 1740 foundWord = new WordComposer(); 1741 for (int i = 0; i < touching.mWord.length(); i++) { 1742 foundWord.add(touching.mWord.charAt(i), new int[] { 1743 touching.mWord.charAt(i) 1744 }); 1745 } 1746 foundWord.setFirstCharCapitalized(Character.isUpperCase(touching.mWord.charAt(0))); 1747 } 1748 // Found a match, show suggestions 1749 if (foundWord != null || alternatives != null) { 1750 if (alternatives == null) { 1751 alternatives = new TypedWordAlternatives(touching.mWord, foundWord); 1752 } 1753 showCorrections(alternatives); 1754 if (foundWord != null) { 1755 mWord = new WordComposer(foundWord); 1756 } else { 1757 mWord.reset(); 1758 } 1759 return true; 1760 } 1761 return false; 1762 } 1763 1764 private void setOldSuggestions() { 1765 mVoiceConnector.setShowingVoiceSuggestions(false); 1766 if (mCandidateView != null && mCandidateView.isShowingAddToDictionaryHint()) { 1767 return; 1768 } 1769 InputConnection ic = getCurrentInputConnection(); 1770 if (ic == null) return; 1771 if (!mHasValidSuggestions) { 1772 // Extract the selected or touching text 1773 EditingUtils.SelectedWord touching = EditingUtils.getWordAtCursorOrSelection(ic, 1774 mLastSelectionStart, mLastSelectionEnd, mWordSeparators); 1775 1776 if (touching != null && touching.mWord.length() > 1) { 1777 ic.beginBatchEdit(); 1778 1779 if (!mVoiceConnector.applyVoiceAlternatives(touching) 1780 && !applyTypedAlternatives(touching)) { 1781 abortCorrection(true); 1782 } else { 1783 TextEntryState.selectedForCorrection(); 1784 EditingUtils.underlineWord(ic, touching); 1785 } 1786 1787 ic.endBatchEdit(); 1788 } else { 1789 abortCorrection(true); 1790 setPunctuationSuggestions(); // Show the punctuation suggestions list 1791 } 1792 } else { 1793 abortCorrection(true); 1794 } 1795 } 1796 1797 private void setPunctuationSuggestions() { 1798 setSuggestions(mSuggestPuncList); 1799 setCandidatesViewShown(isCandidateStripVisible()); 1800 } 1801 1802 private void addToDictionaries(CharSequence suggestion, int frequencyDelta) { 1803 checkAddToDictionary(suggestion, frequencyDelta, false); 1804 } 1805 1806 private void addToBigramDictionary(CharSequence suggestion, int frequencyDelta) { 1807 checkAddToDictionary(suggestion, frequencyDelta, true); 1808 } 1809 1810 /** 1811 * Adds to the UserBigramDictionary and/or AutoDictionary 1812 * @param addToBigramDictionary true if it should be added to bigram dictionary if possible 1813 */ 1814 private void checkAddToDictionary(CharSequence suggestion, int frequencyDelta, 1815 boolean addToBigramDictionary) { 1816 if (suggestion == null || suggestion.length() < 1) return; 1817 // Only auto-add to dictionary if auto-correct is ON. Otherwise we'll be 1818 // adding words in situations where the user or application really didn't 1819 // want corrections enabled or learned. 1820 if (!(mCorrectionMode == Suggest.CORRECTION_FULL 1821 || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM)) { 1822 return; 1823 } 1824 if (!addToBigramDictionary && mAutoDictionary.isValidWord(suggestion) 1825 || (!mSuggest.isValidWord(suggestion.toString()) 1826 && !mSuggest.isValidWord(suggestion.toString().toLowerCase()))) { 1827 mAutoDictionary.addWord(suggestion.toString(), frequencyDelta); 1828 } 1829 1830 if (mUserBigramDictionary != null) { 1831 CharSequence prevWord = EditingUtils.getPreviousWord(getCurrentInputConnection(), 1832 mSentenceSeparators); 1833 if (!TextUtils.isEmpty(prevWord)) { 1834 mUserBigramDictionary.addBigrams(prevWord.toString(), suggestion.toString()); 1835 } 1836 } 1837 } 1838 1839 private boolean isCursorTouchingWord() { 1840 InputConnection ic = getCurrentInputConnection(); 1841 if (ic == null) return false; 1842 CharSequence toLeft = ic.getTextBeforeCursor(1, 0); 1843 CharSequence toRight = ic.getTextAfterCursor(1, 0); 1844 if (!TextUtils.isEmpty(toLeft) 1845 && !isWordSeparator(toLeft.charAt(0)) 1846 && !isSuggestedPunctuation(toLeft.charAt(0))) { 1847 return true; 1848 } 1849 if (!TextUtils.isEmpty(toRight) 1850 && !isWordSeparator(toRight.charAt(0)) 1851 && !isSuggestedPunctuation(toRight.charAt(0))) { 1852 return true; 1853 } 1854 return false; 1855 } 1856 1857 private boolean sameAsTextBeforeCursor(InputConnection ic, CharSequence text) { 1858 CharSequence beforeText = ic.getTextBeforeCursor(text.length(), 0); 1859 return TextUtils.equals(text, beforeText); 1860 } 1861 1862 public void revertLastWord(boolean deleteChar) { 1863 final int length = mComposing.length(); 1864 if (!mHasValidSuggestions && length > 0) { 1865 final InputConnection ic = getCurrentInputConnection(); 1866 mJustReverted = true; 1867 final CharSequence punctuation = ic.getTextBeforeCursor(1, 0); 1868 if (deleteChar) ic.deleteSurroundingText(1, 0); 1869 int toDelete = mCommittedLength; 1870 final CharSequence toTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0); 1871 if (!TextUtils.isEmpty(toTheLeft) && isWordSeparator(toTheLeft.charAt(0))) { 1872 toDelete--; 1873 } 1874 ic.deleteSurroundingText(toDelete, 0); 1875 // Re-insert punctuation only when the deleted character was word separator and the 1876 // composing text wasn't equal to the auto-corrected text. 1877 if (deleteChar 1878 && !TextUtils.isEmpty(punctuation) && isWordSeparator(punctuation.charAt(0)) 1879 && !TextUtils.equals(mComposing, toTheLeft)) { 1880 ic.commitText(mComposing, 1); 1881 TextEntryState.acceptedTyped(mComposing); 1882 ic.commitText(punctuation, 1); 1883 TextEntryState.typedCharacter(punctuation.charAt(0), true); 1884 // Clear composing text 1885 mComposing.setLength(0); 1886 } else { 1887 mHasValidSuggestions = true; 1888 ic.setComposingText(mComposing, 1); 1889 TextEntryState.backspace(); 1890 } 1891 mHandler.postUpdateSuggestions(); 1892 } else { 1893 sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); 1894 mJustReverted = false; 1895 } 1896 } 1897 1898 protected String getWordSeparators() { 1899 return mWordSeparators; 1900 } 1901 1902 public boolean isWordSeparator(int code) { 1903 String separators = getWordSeparators(); 1904 return separators.contains(String.valueOf((char)code)); 1905 } 1906 1907 private boolean isSentenceSeparator(int code) { 1908 return mSentenceSeparators.contains(String.valueOf((char)code)); 1909 } 1910 1911 private void sendSpace() { 1912 sendKeyChar((char)Keyboard.CODE_SPACE); 1913 mKeyboardSwitcher.updateShiftState(); 1914 } 1915 1916 public boolean preferCapitalization() { 1917 return mWord.isFirstCharCapitalized(); 1918 } 1919 1920 // Notify that language or mode have been changed and toggleLanguage will update KeyboaredID 1921 // according to new language or mode. 1922 public void onRefreshKeyboard() { 1923 toggleLanguage(true, true); 1924 } 1925 1926 // "reset" and "next" are used only for USE_SPACEBAR_LANGUAGE_SWITCHER. 1927 private void toggleLanguage(boolean reset, boolean next) { 1928 if (mSubtypeSwitcher.useSpacebarLanguageSwitcher()) { 1929 mSubtypeSwitcher.toggleLanguage(reset, next); 1930 } 1931 // Reload keyboard because the current language has been changed. 1932 KeyboardSwitcher switcher = mKeyboardSwitcher; 1933 final EditorInfo attribute = getCurrentInputEditorInfo(); 1934 final int mode = initializeInputAttributesAndGetMode((attribute != null) 1935 ? attribute.inputType : 0); 1936 final int imeOptions = (attribute != null) ? attribute.imeOptions : 0; 1937 switcher.loadKeyboard(mode, imeOptions, mVoiceConnector.isVoiceButtonEnabled(), 1938 mVoiceConnector.isVoiceButtonOnPrimary()); 1939 initSuggest(); 1940 switcher.updateShiftState(); 1941 } 1942 1943 @Override 1944 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, 1945 String key) { 1946 mSubtypeSwitcher.onSharedPreferenceChanged(sharedPreferences, key); 1947 if (Settings.PREF_SELECTED_LANGUAGES.equals(key)) { 1948 mRefreshKeyboardRequired = true; 1949 } else if (Settings.PREF_RECORRECTION_ENABLED.equals(key)) { 1950 mReCorrectionEnabled = sharedPreferences.getBoolean( 1951 Settings.PREF_RECORRECTION_ENABLED, 1952 mResources.getBoolean(R.bool.default_recorrection_enabled)); 1953 } 1954 } 1955 1956 @Override 1957 public void onSwipeDown() { 1958 if (mConfigSwipeDownDismissKeyboardEnabled) 1959 handleClose(); 1960 } 1961 1962 @Override 1963 public void onPress(int primaryCode) { 1964 if (mKeyboardSwitcher.isVibrateAndSoundFeedbackRequired()) { 1965 vibrate(); 1966 playKeyClick(primaryCode); 1967 } 1968 KeyboardSwitcher switcher = mKeyboardSwitcher; 1969 final boolean distinctMultiTouch = switcher.hasDistinctMultitouch(); 1970 if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) { 1971 switcher.onPressShift(); 1972 } else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { 1973 switcher.onPressSymbol(); 1974 } else { 1975 switcher.onOtherKeyPressed(); 1976 } 1977 } 1978 1979 @Override 1980 public void onRelease(int primaryCode) { 1981 KeyboardSwitcher switcher = mKeyboardSwitcher; 1982 // Reset any drag flags in the keyboard 1983 switcher.keyReleased(); 1984 final boolean distinctMultiTouch = switcher.hasDistinctMultitouch(); 1985 if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) { 1986 switcher.onReleaseShift(); 1987 } else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { 1988 switcher.onReleaseSymbol(); 1989 } 1990 } 1991 1992 1993 // receive ringer mode changes to detect silent mode 1994 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 1995 @Override 1996 public void onReceive(Context context, Intent intent) { 1997 updateRingerMode(); 1998 } 1999 }; 2000 2001 // update flags for silent mode 2002 private void updateRingerMode() { 2003 if (mAudioManager == null) { 2004 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 2005 } 2006 if (mAudioManager != null) { 2007 mSilentMode = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL); 2008 } 2009 } 2010 2011 private void playKeyClick(int primaryCode) { 2012 // if mAudioManager is null, we don't have the ringer state yet 2013 // mAudioManager will be set by updateRingerMode 2014 if (mAudioManager == null) { 2015 if (mKeyboardSwitcher.getInputView() != null) { 2016 updateRingerMode(); 2017 } 2018 } 2019 if (mSoundOn && !mSilentMode) { 2020 // FIXME: Volume and enable should come from UI settings 2021 // FIXME: These should be triggered after auto-repeat logic 2022 int sound = AudioManager.FX_KEYPRESS_STANDARD; 2023 switch (primaryCode) { 2024 case Keyboard.CODE_DELETE: 2025 sound = AudioManager.FX_KEYPRESS_DELETE; 2026 break; 2027 case Keyboard.CODE_ENTER: 2028 sound = AudioManager.FX_KEYPRESS_RETURN; 2029 break; 2030 case Keyboard.CODE_SPACE: 2031 sound = AudioManager.FX_KEYPRESS_SPACEBAR; 2032 break; 2033 } 2034 mAudioManager.playSoundEffect(sound, FX_VOLUME); 2035 } 2036 } 2037 2038 public void vibrate() { 2039 if (!mVibrateOn) { 2040 return; 2041 } 2042 LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); 2043 if (inputView != null) { 2044 inputView.performHapticFeedback( 2045 HapticFeedbackConstants.KEYBOARD_TAP, 2046 HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); 2047 } 2048 } 2049 2050 public void promoteToUserDictionary(String word, int frequency) { 2051 if (mUserDictionary.isValidWord(word)) return; 2052 mUserDictionary.addWord(word, frequency); 2053 } 2054 2055 public WordComposer getCurrentWord() { 2056 return mWord; 2057 } 2058 2059 public boolean getPopupOn() { 2060 return mPopupOn; 2061 } 2062 2063 private void updateCorrectionMode() { 2064 mHasDictionary = mSuggest != null ? mSuggest.hasMainDictionary() : false; 2065 mAutoCorrectOn = (mAutoCorrectEnabled || mQuickFixes) 2066 && !mInputTypeNoAutoCorrect && mHasDictionary; 2067 mCorrectionMode = (mAutoCorrectOn && mAutoCorrectEnabled) 2068 ? Suggest.CORRECTION_FULL 2069 : (mAutoCorrectOn ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE); 2070 mCorrectionMode = (mBigramSuggestionEnabled && mAutoCorrectOn && mAutoCorrectEnabled) 2071 ? Suggest.CORRECTION_FULL_BIGRAM : mCorrectionMode; 2072 if (mSuggest != null) { 2073 mSuggest.setCorrectionMode(mCorrectionMode); 2074 } 2075 } 2076 2077 private void updateAutoTextEnabled() { 2078 if (mSuggest == null) return; 2079 mSuggest.setAutoTextEnabled(mQuickFixes 2080 && SubtypeSwitcher.getInstance().isSystemLanguageSameAsInputLanguage()); 2081 } 2082 2083 private void updateSuggestionVisibility(SharedPreferences prefs) { 2084 final Resources res = mResources; 2085 final String suggestionVisiblityStr = prefs.getString( 2086 Settings.PREF_SHOW_SUGGESTIONS_SETTING, 2087 res.getString(R.string.prefs_suggestion_visibility_default_value)); 2088 for (int visibility : SUGGESTION_VISIBILITY_VALUE_ARRAY) { 2089 if (suggestionVisiblityStr.equals(res.getString(visibility))) { 2090 mSuggestionVisibility = visibility; 2091 break; 2092 } 2093 } 2094 } 2095 2096 protected void launchSettings() { 2097 launchSettings(Settings.class); 2098 } 2099 2100 public void launchDebugSettings() { 2101 launchSettings(DebugSettings.class); 2102 } 2103 2104 protected void launchSettings(Class<? extends PreferenceActivity> settingsClass) { 2105 handleClose(); 2106 Intent intent = new Intent(); 2107 intent.setClass(LatinIME.this, settingsClass); 2108 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 2109 startActivity(intent); 2110 } 2111 2112 private void loadSettings(EditorInfo attribute) { 2113 // Get the settings preferences 2114 final SharedPreferences prefs = mPrefs; 2115 Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); 2116 mVibrateOn = vibrator != null && vibrator.hasVibrator() 2117 && prefs.getBoolean(Settings.PREF_VIBRATE_ON, false); 2118 mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON, false); 2119 mPopupOn = prefs.getBoolean(Settings.PREF_POPUP_ON, 2120 mResources.getBoolean(R.bool.config_default_popup_preview)); 2121 mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true); 2122 mQuickFixes = isQuickFixesEnabled(prefs); 2123 2124 mAutoCorrectEnabled = isAutoCorrectEnabled(prefs); 2125 mBigramSuggestionEnabled = mAutoCorrectEnabled && isBigramSuggestionEnabled(prefs); 2126 loadAndSetAutoCorrectionThreshold(prefs); 2127 2128 mVoiceConnector.loadSettings(attribute, prefs); 2129 2130 updateCorrectionMode(); 2131 updateAutoTextEnabled(); 2132 updateSuggestionVisibility(prefs); 2133 SubtypeSwitcher.getInstance().loadSettings(); 2134 } 2135 2136 /** 2137 * Load Auto correction threshold from SharedPreferences, and modify mSuggest's threshold. 2138 */ 2139 private void loadAndSetAutoCorrectionThreshold(SharedPreferences sp) { 2140 // When mSuggest is not initialized, cannnot modify mSuggest's threshold. 2141 if (mSuggest == null) return; 2142 // When auto correction setting is turned off, the threshold is ignored. 2143 if (!isAutoCorrectEnabled(sp)) return; 2144 2145 final String currentAutoCorrectionSetting = sp.getString( 2146 Settings.PREF_AUTO_CORRECTION_THRESHOLD, 2147 mResources.getString(R.string.auto_correction_threshold_mode_index_modest)); 2148 final String[] autoCorrectionThresholdValues = mResources.getStringArray( 2149 R.array.auto_correction_threshold_values); 2150 // When autoCrrectionThreshold is greater than 1.0, auto correction is virtually turned off. 2151 double autoCorrectionThreshold = Double.MAX_VALUE; 2152 try { 2153 final int arrayIndex = Integer.valueOf(currentAutoCorrectionSetting); 2154 if (arrayIndex >= 0 && arrayIndex < autoCorrectionThresholdValues.length) { 2155 autoCorrectionThreshold = Double.parseDouble( 2156 autoCorrectionThresholdValues[arrayIndex]); 2157 } 2158 } catch (NumberFormatException e) { 2159 // Whenever the threshold settings are correct, never come here. 2160 autoCorrectionThreshold = Double.MAX_VALUE; 2161 Log.w(TAG, "Cannot load auto correction threshold setting." 2162 + " currentAutoCorrectionSetting: " + currentAutoCorrectionSetting 2163 + ", autoCorrectionThresholdValues: " 2164 + Arrays.toString(autoCorrectionThresholdValues)); 2165 } 2166 // TODO: This should be refactored : 2167 // setAutoCorrectionThreshold should be called outside of this method. 2168 mSuggest.setAutoCorrectionThreshold(autoCorrectionThreshold); 2169 } 2170 2171 private boolean isQuickFixesEnabled(SharedPreferences sp) { 2172 final boolean showQuickFixesOption = mResources.getBoolean( 2173 R.bool.config_enable_quick_fixes_option); 2174 if (!showQuickFixesOption) { 2175 return isAutoCorrectEnabled(sp); 2176 } 2177 return sp.getBoolean(Settings.PREF_QUICK_FIXES, mResources.getBoolean( 2178 R.bool.config_default_quick_fixes)); 2179 } 2180 2181 private boolean isAutoCorrectEnabled(SharedPreferences sp) { 2182 final String currentAutoCorrectionSetting = sp.getString( 2183 Settings.PREF_AUTO_CORRECTION_THRESHOLD, 2184 mResources.getString(R.string.auto_correction_threshold_mode_index_modest)); 2185 final String autoCorrectionOff = mResources.getString( 2186 R.string.auto_correction_threshold_mode_index_off); 2187 return !currentAutoCorrectionSetting.equals(autoCorrectionOff); 2188 } 2189 2190 private boolean isBigramSuggestionEnabled(SharedPreferences sp) { 2191 final boolean showBigramSuggestionsOption = mResources.getBoolean( 2192 R.bool.config_enable_bigram_suggestions_option); 2193 if (!showBigramSuggestionsOption) { 2194 return isAutoCorrectEnabled(sp); 2195 } 2196 return sp.getBoolean(Settings.PREF_BIGRAM_SUGGESTIONS, mResources.getBoolean( 2197 R.bool.config_default_bigram_suggestions)); 2198 } 2199 2200 private void initSuggestPuncList() { 2201 if (mSuggestPuncs != null || mSuggestPuncList != null) 2202 return; 2203 SuggestedWords.Builder builder = new SuggestedWords.Builder(); 2204 String puncs = mResources.getString(R.string.suggested_punctuations); 2205 if (puncs != null) { 2206 for (int i = 0; i < puncs.length(); i++) { 2207 builder.addWord(puncs.subSequence(i, i + 1)); 2208 } 2209 } 2210 mSuggestPuncList = builder.build(); 2211 mSuggestPuncs = puncs; 2212 } 2213 2214 private boolean isSuggestedPunctuation(int code) { 2215 return mSuggestPuncs.contains(String.valueOf((char)code)); 2216 } 2217 2218 private void showSubtypeSelectorAndSettings() { 2219 showOptionsMenuInternal(new DialogInterface.OnClickListener() { 2220 @Override 2221 public void onClick(DialogInterface di, int position) { 2222 di.dismiss(); 2223 switch (position) { 2224 case POS_SETTINGS: 2225 launchSettings(); 2226 break; 2227 case POS_METHOD: 2228 Intent intent = new Intent( 2229 android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS); 2230 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 2231 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 2232 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 2233 intent.putExtra(android.provider.Settings.EXTRA_INPUT_METHOD_ID, 2234 mInputMethodId); 2235 startActivity(intent); 2236 break; 2237 } 2238 } 2239 }); 2240 } 2241 2242 private void showOptionsMenu() { 2243 showOptionsMenuInternal(new DialogInterface.OnClickListener() { 2244 @Override 2245 public void onClick(DialogInterface di, int position) { 2246 di.dismiss(); 2247 switch (position) { 2248 case POS_SETTINGS: 2249 launchSettings(); 2250 break; 2251 case POS_METHOD: 2252 mImm.showInputMethodPicker(); 2253 break; 2254 } 2255 } 2256 }); 2257 } 2258 2259 private void showOptionsMenuInternal(DialogInterface.OnClickListener listener) { 2260 AlertDialog.Builder builder = new AlertDialog.Builder(this); 2261 builder.setCancelable(true); 2262 builder.setIcon(R.drawable.ic_dialog_keyboard); 2263 builder.setNegativeButton(android.R.string.cancel, null); 2264 CharSequence itemSettings = getString(R.string.english_ime_settings); 2265 CharSequence itemInputMethod = getString(R.string.selectInputMethod); 2266 builder.setItems(new CharSequence[] { 2267 itemInputMethod, itemSettings}, listener); 2268 builder.setTitle(mResources.getString(R.string.english_ime_input_options)); 2269 mOptionsDialog = builder.create(); 2270 mOptionsDialog.setCanceledOnTouchOutside(true); 2271 Window window = mOptionsDialog.getWindow(); 2272 WindowManager.LayoutParams lp = window.getAttributes(); 2273 lp.token = mKeyboardSwitcher.getInputView().getWindowToken(); 2274 lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; 2275 window.setAttributes(lp); 2276 window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 2277 mOptionsDialog.show(); 2278 } 2279 2280 @Override 2281 protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { 2282 super.dump(fd, fout, args); 2283 2284 final Printer p = new PrintWriterPrinter(fout); 2285 p.println("LatinIME state :"); 2286 p.println(" Keyboard mode = " + mKeyboardSwitcher.getKeyboardMode()); 2287 p.println(" mComposing=" + mComposing.toString()); 2288 p.println(" mIsSuggestionsRequested=" + mIsSettingsSuggestionStripOn); 2289 p.println(" mCorrectionMode=" + mCorrectionMode); 2290 p.println(" mHasValidSuggestions=" + mHasValidSuggestions); 2291 p.println(" mAutoCorrectOn=" + mAutoCorrectOn); 2292 p.println(" mAutoSpace=" + mAutoSpace); 2293 p.println(" mApplicationSpecifiedCompletionOn=" + mApplicationSpecifiedCompletionOn); 2294 p.println(" TextEntryState.state=" + TextEntryState.getState()); 2295 p.println(" mSoundOn=" + mSoundOn); 2296 p.println(" mVibrateOn=" + mVibrateOn); 2297 p.println(" mPopupOn=" + mPopupOn); 2298 } 2299 2300 // Characters per second measurement 2301 2302 private long mLastCpsTime; 2303 private static final int CPS_BUFFER_SIZE = 16; 2304 private long[] mCpsIntervals = new long[CPS_BUFFER_SIZE]; 2305 private int mCpsIndex; 2306 2307 private void measureCps() { 2308 long now = System.currentTimeMillis(); 2309 if (mLastCpsTime == 0) mLastCpsTime = now - 100; // Initial 2310 mCpsIntervals[mCpsIndex] = now - mLastCpsTime; 2311 mLastCpsTime = now; 2312 mCpsIndex = (mCpsIndex + 1) % CPS_BUFFER_SIZE; 2313 long total = 0; 2314 for (int i = 0; i < CPS_BUFFER_SIZE; i++) total += mCpsIntervals[i]; 2315 System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total)); 2316 } 2317 2318 @Override 2319 public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) { 2320 SubtypeSwitcher.getInstance().updateSubtype(subtype); 2321 } 2322} 2323