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