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