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