LatinIME.java revision c1e2e827b5d1bba59eb99d322dc101657ec28768
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.latin.LatinIMEUtil.RingCharBuffer; 20import com.android.inputmethod.voice.FieldContext; 21import com.android.inputmethod.voice.SettingsUtil; 22import com.android.inputmethod.voice.VoiceInput; 23 24import org.xmlpull.v1.XmlPullParserException; 25 26import android.app.AlertDialog; 27import android.content.BroadcastReceiver; 28import android.content.Context; 29import android.content.DialogInterface; 30import android.content.Intent; 31import android.content.IntentFilter; 32import android.content.SharedPreferences; 33import android.content.res.Configuration; 34import android.content.res.Resources; 35import android.content.res.XmlResourceParser; 36import android.inputmethodservice.InputMethodService; 37import android.media.AudioManager; 38import android.os.Debug; 39import android.os.Handler; 40import android.os.Message; 41import android.os.SystemClock; 42import android.os.Vibrator; 43import android.preference.PreferenceActivity; 44import android.preference.PreferenceManager; 45import android.speech.SpeechRecognizer; 46import android.text.ClipboardManager; 47import android.text.TextUtils; 48import android.util.DisplayMetrics; 49import android.util.Log; 50import android.util.PrintWriterPrinter; 51import android.util.Printer; 52import android.view.HapticFeedbackConstants; 53import android.view.KeyEvent; 54import android.view.LayoutInflater; 55import android.view.View; 56import android.view.ViewGroup; 57import android.view.ViewParent; 58import android.view.Window; 59import android.view.WindowManager; 60import android.view.inputmethod.CompletionInfo; 61import android.view.inputmethod.EditorInfo; 62import android.view.inputmethod.ExtractedText; 63import android.view.inputmethod.ExtractedTextRequest; 64import android.view.inputmethod.InputConnection; 65import android.view.inputmethod.InputMethodManager; 66import android.widget.LinearLayout; 67 68import java.io.FileDescriptor; 69import java.io.IOException; 70import java.io.PrintWriter; 71import java.util.ArrayList; 72import java.util.Arrays; 73import java.util.Collections; 74import java.util.HashMap; 75import java.util.List; 76import java.util.Locale; 77import java.util.Map; 78 79/** 80 * Input method implementation for Qwerty'ish keyboard. 81 */ 82public class LatinIME extends InputMethodService 83 implements BaseKeyboardView.OnKeyboardActionListener, 84 VoiceInput.UiListener, 85 SharedPreferences.OnSharedPreferenceChangeListener, 86 Tutorial.TutorialListener { 87 private static final String TAG = "LatinIME"; 88 private static final boolean PERF_DEBUG = false; 89 static final boolean DEBUG = false; 90 static final boolean TRACE = false; 91 static final boolean VOICE_INSTALLED = true; 92 static final boolean ENABLE_VOICE_BUTTON = true; 93 94 private static final String PREF_SOUND_ON = "sound_on"; 95 private static final String PREF_POPUP_ON = "popup_on"; 96 private static final String PREF_AUTO_CAP = "auto_cap"; 97 private static final String PREF_QUICK_FIXES = "quick_fixes"; 98 private static final String PREF_SHOW_SUGGESTIONS_SETTING = "show_suggestions_setting"; 99 private static final String PREF_AUTO_COMPLETION_THRESHOLD = "auto_completion_threshold"; 100 private static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion"; 101 private static final String PREF_VOICE_MODE = "voice_mode"; 102 103 // Whether or not the user has used voice input before (and thus, whether to show the 104 // first-run warning dialog or not). 105 private static final String PREF_HAS_USED_VOICE_INPUT = "has_used_voice_input"; 106 107 // Whether or not the user has used voice input from an unsupported locale UI before. 108 // For example, the user has a Chinese UI but activates voice input. 109 private static final String PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE = 110 "has_used_voice_input_unsupported_locale"; 111 112 // A list of locales which are supported by default for voice input, unless we get a 113 // different list from Gservices. 114 public static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES = 115 "en " + 116 "en_US " + 117 "en_GB " + 118 "en_AU " + 119 "en_CA " + 120 "en_IE " + 121 "en_IN " + 122 "en_NZ " + 123 "en_SG " + 124 "en_ZA "; 125 126 // The private IME option used to indicate that no microphone should be shown for a 127 // given text field. For instance this is specified by the search dialog when the 128 // dialog is already showing a voice search button. 129 private static final String IME_OPTION_NO_MICROPHONE = "nm"; 130 131 public static final String PREF_SELECTED_LANGUAGES = "selected_languages"; 132 public static final String PREF_INPUT_LANGUAGE = "input_language"; 133 private static final String PREF_RECORRECTION_ENABLED = "recorrection_enabled"; 134 135 private static final int DELAY_UPDATE_SUGGESTIONS = 180; 136 private static final int DELAY_UPDATE_OLD_SUGGESTIONS = 300; 137 private static final int DELAY_UPDATE_SHIFT_STATE = 300; 138 private static final int DELAY_START_TUTORIAL = 500; 139 140 // How many continuous deletes at which to start deleting at a higher speed. 141 private static final int DELETE_ACCELERATE_AT = 20; 142 // Key events coming any faster than this are long-presses. 143 private static final int QUICK_PRESS = 200; 144 145 public static final int KEYCODE_ENTER = '\n'; 146 public static final int KEYCODE_TAB = '\t'; 147 public static final int KEYCODE_SPACE = ' '; 148 public static final int KEYCODE_PERIOD = '.'; 149 150 // Contextual menu positions 151 private static final int POS_METHOD = 0; 152 private static final int POS_SETTINGS = 1; 153 154 private int mSuggestionVisibility; 155 private static final int SUGGESTION_VISIBILILTY_SHOW_VALUE 156 = R.string.prefs_suggestion_visibility_show_value; 157 private static final int SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE 158 = R.string.prefs_suggestion_visibility_show_only_portrait_value; 159 private static final int SUGGESTION_VISIBILILTY_HIDE_VALUE 160 = R.string.prefs_suggestion_visibility_hide_value; 161 162 private static final int[] SUGGESTION_VISIBILITY_VALUE_ARRAY = new int[] { 163 SUGGESTION_VISIBILILTY_SHOW_VALUE, 164 SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE, 165 SUGGESTION_VISIBILILTY_HIDE_VALUE 166 }; 167 168 private LinearLayout mCandidateViewContainer; 169 private CandidateView mCandidateView; 170 private Suggest mSuggest; 171 private CompletionInfo[] mCompletions; 172 173 private AlertDialog mOptionsDialog; 174 private AlertDialog mVoiceWarningDialog; 175 176 private KeyboardSwitcher mKeyboardSwitcher; 177 178 private UserDictionary mUserDictionary; 179 private UserBigramDictionary mUserBigramDictionary; 180 private ContactsDictionary mContactsDictionary; 181 private AutoDictionary mAutoDictionary; 182 183 private Hints mHints; 184 185 private Resources mResources; 186 187 private String mInputLocale; 188 private String mSystemLocale; 189 private LanguageSwitcher mLanguageSwitcher; 190 191 private final StringBuilder mComposing = new StringBuilder(); 192 private WordComposer mWord = new WordComposer(); 193 private int mCommittedLength; 194 private boolean mPredicting; 195 private boolean mRecognizing; 196 private boolean mAfterVoiceInput; 197 private boolean mImmediatelyAfterVoiceInput; 198 private boolean mShowingVoiceSuggestions; 199 private boolean mVoiceInputHighlighted; 200 private CharSequence mBestWord; 201 private boolean mPredictionOn; 202 private boolean mCompletionOn; 203 private boolean mHasDictionary; 204 private boolean mAutoSpace; 205 private boolean mJustAddedAutoSpace; 206 private boolean mAutoCorrectEnabled; 207 private boolean mReCorrectionEnabled; 208 private boolean mBigramSuggestionEnabled; 209 private boolean mAutoCorrectOn; 210 private boolean mPasswordText; 211 private boolean mVibrateOn; 212 private boolean mSoundOn; 213 private boolean mPopupOn; 214 private boolean mAutoCap; 215 private boolean mQuickFixes; 216 private boolean mHasUsedVoiceInput; 217 private boolean mHasUsedVoiceInputUnsupportedLocale; 218 private boolean mLocaleSupportedForVoiceInput; 219 private boolean mIsShowingHint; 220 private int mCorrectionMode; 221 private boolean mVoiceButtonEnabled; 222 private boolean mVoiceButtonOnPrimary; 223 private int mOrientation; 224 private List<CharSequence> mSuggestPuncList; 225 // Keep track of the last selection range to decide if we need to show word alternatives 226 private int mLastSelectionStart; 227 private int mLastSelectionEnd; 228 229 // Input type is such that we should not auto-correct 230 private boolean mInputTypeNoAutoCorrect; 231 232 // Indicates whether the suggestion strip is to be on in landscape 233 private boolean mJustAccepted; 234 private boolean mJustReverted; 235 private int mDeleteCount; 236 private long mLastKeyTime; 237 238 private Tutorial mTutorial; 239 240 private AudioManager mAudioManager; 241 // Align sound effect volume on music volume 242 private static final float FX_VOLUME = -1.0f; 243 private boolean mSilentMode; 244 245 /* package */ String mWordSeparators; 246 private String mSentenceSeparators; 247 private String mSuggestPuncs; 248 private VoiceInput mVoiceInput; 249 private final VoiceResults mVoiceResults = new VoiceResults(); 250 private boolean mConfigurationChanging; 251 252 // Keeps track of most recently inserted text (multi-character key) for reverting 253 private CharSequence mEnteredText; 254 private boolean mRefreshKeyboardRequired; 255 256 // For each word, a list of potential replacements, usually from voice. 257 private final Map<String, List<CharSequence>> mWordToSuggestions = 258 new HashMap<String, List<CharSequence>>(); 259 260 private final ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>(); 261 262 private class VoiceResults { 263 List<String> candidates; 264 Map<String, List<CharSequence>> alternatives; 265 } 266 267 public abstract static class WordAlternatives { 268 protected CharSequence mChosenWord; 269 270 public WordAlternatives() { 271 // Nothing 272 } 273 274 public WordAlternatives(CharSequence chosenWord) { 275 mChosenWord = chosenWord; 276 } 277 278 @Override 279 public int hashCode() { 280 return mChosenWord.hashCode(); 281 } 282 283 public abstract CharSequence getOriginalWord(); 284 285 public CharSequence getChosenWord() { 286 return mChosenWord; 287 } 288 289 public abstract List<CharSequence> getAlternatives(); 290 } 291 292 public class TypedWordAlternatives extends WordAlternatives { 293 private WordComposer word; 294 295 public TypedWordAlternatives() { 296 // Nothing 297 } 298 299 public TypedWordAlternatives(CharSequence chosenWord, WordComposer wordComposer) { 300 super(chosenWord); 301 word = wordComposer; 302 } 303 304 @Override 305 public CharSequence getOriginalWord() { 306 return word.getTypedWord(); 307 } 308 309 @Override 310 public List<CharSequence> getAlternatives() { 311 return getTypedSuggestions(word); 312 } 313 } 314 315 /* package */ UIHandler mHandler = new UIHandler(); 316 317 /* package */ class UIHandler extends Handler { 318 private static final int MSG_UPDATE_SUGGESTIONS = 0; 319 private static final int MSG_UPDATE_OLD_SUGGESTIONS = 1; 320 private static final int MSG_UPDATE_SHIFT_STATE = 2; 321 private static final int MSG_VOICE_RESULTS = 3; 322 private static final int MSG_START_TUTORIAL = 4; 323 324 @Override 325 public void handleMessage(Message msg) { 326 switch (msg.what) { 327 case MSG_UPDATE_SUGGESTIONS: 328 updateSuggestions(); 329 break; 330 case MSG_UPDATE_OLD_SUGGESTIONS: 331 setOldSuggestions(); 332 break; 333 case MSG_UPDATE_SHIFT_STATE: 334 updateShiftKeyState(getCurrentInputEditorInfo()); 335 break; 336 case MSG_VOICE_RESULTS: 337 handleVoiceResults(); 338 break; 339 case MSG_START_TUTORIAL: 340 if (mTutorial == null) { 341 if (mKeyboardSwitcher.isInputViewShown()) { 342 mTutorial = new Tutorial(LatinIME.this, mKeyboardSwitcher); 343 mTutorial.start(); 344 } else { 345 // Try again soon if the view is not yet showing 346 sendMessageDelayed(obtainMessage(MSG_START_TUTORIAL), 100); 347 } 348 } 349 break; 350 } 351 } 352 353 public void postUpdateSuggestions() { 354 removeMessages(MSG_UPDATE_SUGGESTIONS); 355 sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTIONS), DELAY_UPDATE_SUGGESTIONS); 356 } 357 358 public void cancelUpdateSuggestions() { 359 removeMessages(MSG_UPDATE_SUGGESTIONS); 360 } 361 362 public boolean hasPendingUpdateSuggestions() { 363 return hasMessages(MSG_UPDATE_SUGGESTIONS); 364 } 365 366 public void postUpdateOldSuggestions() { 367 removeMessages(MSG_UPDATE_OLD_SUGGESTIONS); 368 sendMessageDelayed(obtainMessage(MSG_UPDATE_OLD_SUGGESTIONS), 369 DELAY_UPDATE_OLD_SUGGESTIONS); 370 } 371 372 public void cancelUpdateOldSuggestions() { 373 removeMessages(MSG_UPDATE_OLD_SUGGESTIONS); 374 } 375 376 public void postUpdateShiftKeyState() { 377 removeMessages(MSG_UPDATE_SHIFT_STATE); 378 sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), DELAY_UPDATE_SHIFT_STATE); 379 } 380 381 public void cancelUpdateShiftState() { 382 removeMessages(MSG_UPDATE_SHIFT_STATE); 383 } 384 385 public void updateVoiceResults() { 386 sendMessage(obtainMessage(MSG_VOICE_RESULTS)); 387 } 388 389 public void startTutorial() { 390 sendMessageDelayed(obtainMessage(MSG_START_TUTORIAL), DELAY_START_TUTORIAL); 391 } 392 } 393 394 @Override 395 public void onCreate() { 396 LatinImeLogger.init(this); 397 super.onCreate(); 398 //setStatusIcon(R.drawable.ime_qwerty); 399 mResources = getResources(); 400 final Configuration conf = mResources.getConfiguration(); 401 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 402 mLanguageSwitcher = new LanguageSwitcher(this); 403 mLanguageSwitcher.loadLocales(prefs); 404 mKeyboardSwitcher = new KeyboardSwitcher(this, mLanguageSwitcher); 405 mSystemLocale = conf.locale.toString(); 406 mLanguageSwitcher.setSystemLocale(conf.locale); 407 String inputLanguage = mLanguageSwitcher.getInputLanguage(); 408 if (inputLanguage == null) { 409 inputLanguage = conf.locale.toString(); 410 } 411 mReCorrectionEnabled = prefs.getBoolean(PREF_RECORRECTION_ENABLED, 412 getResources().getBoolean(R.bool.default_recorrection_enabled)); 413 414 LatinIMEUtil.GCUtils.getInstance().reset(); 415 boolean tryGC = true; 416 for (int i = 0; i < LatinIMEUtil.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) { 417 try { 418 initSuggest(inputLanguage); 419 tryGC = false; 420 } catch (OutOfMemoryError e) { 421 tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait(inputLanguage, e); 422 } 423 } 424 425 mOrientation = conf.orientation; 426 initSuggestPuncList(); 427 428 // register to receive ringer mode changes for silent mode 429 IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); 430 registerReceiver(mReceiver, filter); 431 if (VOICE_INSTALLED) { 432 mVoiceInput = new VoiceInput(this, this); 433 mHints = new Hints(this, new Hints.Display() { 434 public void showHint(int viewResource) { 435 LayoutInflater inflater = (LayoutInflater) getSystemService( 436 Context.LAYOUT_INFLATER_SERVICE); 437 View view = inflater.inflate(viewResource, null); 438 setCandidatesView(view); 439 setCandidatesViewShown(true); 440 mIsShowingHint = true; 441 } 442 }); 443 } 444 prefs.registerOnSharedPreferenceChangeListener(this); 445 } 446 447 /** 448 * Loads a dictionary or multiple separated dictionary 449 * @return returns array of dictionary resource ids 450 */ 451 public static int[] getDictionary(Resources res) { 452 String packageName = LatinIME.class.getPackage().getName(); 453 XmlResourceParser xrp = res.getXml(R.xml.dictionary); 454 ArrayList<Integer> dictionaries = new ArrayList<Integer>(); 455 456 try { 457 int current = xrp.getEventType(); 458 while (current != XmlResourceParser.END_DOCUMENT) { 459 if (current == XmlResourceParser.START_TAG) { 460 String tag = xrp.getName(); 461 if (tag != null) { 462 if (tag.equals("part")) { 463 String dictFileName = xrp.getAttributeValue(null, "name"); 464 dictionaries.add(res.getIdentifier(dictFileName, "raw", packageName)); 465 } 466 } 467 } 468 xrp.next(); 469 current = xrp.getEventType(); 470 } 471 } catch (XmlPullParserException e) { 472 Log.e(TAG, "Dictionary XML parsing failure"); 473 } catch (IOException e) { 474 Log.e(TAG, "Dictionary XML IOException"); 475 } 476 477 int count = dictionaries.size(); 478 int[] dict = new int[count]; 479 for (int i = 0; i < count; i++) { 480 dict[i] = dictionaries.get(i); 481 } 482 483 return dict; 484 } 485 486 private void initSuggest(String locale) { 487 mInputLocale = locale; 488 489 Resources orig = getResources(); 490 Configuration conf = orig.getConfiguration(); 491 Locale saveLocale = conf.locale; 492 conf.locale = new Locale(locale); 493 orig.updateConfiguration(conf, orig.getDisplayMetrics()); 494 if (mSuggest != null) { 495 mSuggest.close(); 496 } 497 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); 498 mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true); 499 500 int[] dictionaries = getDictionary(orig); 501 mSuggest = new Suggest(this, dictionaries); 502 loadAndSetAutoCompletionThreshold(sp); 503 updateAutoTextEnabled(saveLocale); 504 if (mUserDictionary != null) mUserDictionary.close(); 505 mUserDictionary = new UserDictionary(this, mInputLocale); 506 if (mContactsDictionary == null) { 507 mContactsDictionary = new ContactsDictionary(this, Suggest.DIC_CONTACTS); 508 } 509 if (mAutoDictionary != null) { 510 mAutoDictionary.close(); 511 } 512 mAutoDictionary = new AutoDictionary(this, this, mInputLocale, Suggest.DIC_AUTO); 513 if (mUserBigramDictionary != null) { 514 mUserBigramDictionary.close(); 515 } 516 mUserBigramDictionary = new UserBigramDictionary(this, this, mInputLocale, 517 Suggest.DIC_USER); 518 mSuggest.setUserBigramDictionary(mUserBigramDictionary); 519 mSuggest.setUserDictionary(mUserDictionary); 520 mSuggest.setContactsDictionary(mContactsDictionary); 521 mSuggest.setAutoDictionary(mAutoDictionary); 522 updateCorrectionMode(); 523 mWordSeparators = mResources.getString(R.string.word_separators); 524 mSentenceSeparators = mResources.getString(R.string.sentence_separators); 525 526 conf.locale = saveLocale; 527 orig.updateConfiguration(conf, orig.getDisplayMetrics()); 528 } 529 530 @Override 531 public void onDestroy() { 532 if (mUserDictionary != null) { 533 mUserDictionary.close(); 534 } 535 if (mContactsDictionary != null) { 536 mContactsDictionary.close(); 537 } 538 unregisterReceiver(mReceiver); 539 if (VOICE_INSTALLED && mVoiceInput != null) { 540 mVoiceInput.destroy(); 541 } 542 LatinImeLogger.commit(); 543 LatinImeLogger.onDestroy(); 544 super.onDestroy(); 545 } 546 547 @Override 548 public void onConfigurationChanged(Configuration conf) { 549 // If the system locale changes and is different from the saved 550 // locale (mSystemLocale), then reload the input locale list from the 551 // latin ime settings (shared prefs) and reset the input locale 552 // to the first one. 553 final String systemLocale = conf.locale.toString(); 554 if (!TextUtils.equals(systemLocale, mSystemLocale)) { 555 mSystemLocale = systemLocale; 556 mLanguageSwitcher.loadLocales( 557 PreferenceManager.getDefaultSharedPreferences(this)); 558 mLanguageSwitcher.setSystemLocale(conf.locale); 559 toggleLanguage(true, true); 560 } 561 // If orientation changed while predicting, commit the change 562 if (conf.orientation != mOrientation) { 563 InputConnection ic = getCurrentInputConnection(); 564 commitTyped(ic); 565 if (ic != null) ic.finishComposingText(); // For voice input 566 mOrientation = conf.orientation; 567 final int mode = mKeyboardSwitcher.getKeyboardMode(); 568 final EditorInfo attribute = getCurrentInputEditorInfo(); 569 final int imeOptions = (attribute != null) ? attribute.imeOptions : 0; 570 mKeyboardSwitcher.loadKeyboard(mode, imeOptions, mVoiceButtonEnabled, 571 mVoiceButtonOnPrimary); 572 } 573 574 mConfigurationChanging = true; 575 super.onConfigurationChanged(conf); 576 if (mRecognizing) { 577 switchToRecognitionStatusView(); 578 } 579 mConfigurationChanging = false; 580 } 581 582 @Override 583 public View onCreateInputView() { 584 mKeyboardSwitcher.loadKeyboardView(); 585 return mKeyboardSwitcher.getInputView(); 586 } 587 588 @Override 589 public View onCreateCandidatesView() { 590 mCandidateViewContainer = (LinearLayout) getLayoutInflater().inflate( 591 R.layout.candidates, null); 592 mCandidateView = (CandidateView) mCandidateViewContainer.findViewById(R.id.candidates); 593 mCandidateView.setService(this); 594 setCandidatesViewShown(true); 595 return mCandidateViewContainer; 596 } 597 598 private static boolean isPasswordVariation(int variation) { 599 return variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD 600 || variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD 601 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD; 602 } 603 604 private static boolean isEmailVariation(int variation) { 605 return variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 606 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; 607 } 608 609 @Override 610 public void onStartInputView(EditorInfo attribute, boolean restarting) { 611 LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); 612 // In landscape mode, this method gets called without the input view being created. 613 if (inputView == null) { 614 return; 615 } 616 617 if (mRefreshKeyboardRequired) { 618 mRefreshKeyboardRequired = false; 619 toggleLanguage(true, true); 620 } 621 622 TextEntryState.newSession(this); 623 624 // Most such things we decide below in the switch statement, but we need to know 625 // now whether this is a password text field, because we need to know now (before 626 // the switch statement) whether we want to enable the voice button. 627 mPasswordText = false; 628 int variation = attribute.inputType & EditorInfo.TYPE_MASK_VARIATION; 629 if (isPasswordVariation(variation)) { 630 mPasswordText = true; 631 } 632 633 mAfterVoiceInput = false; 634 mImmediatelyAfterVoiceInput = false; 635 mShowingVoiceSuggestions = false; 636 mVoiceInputHighlighted = false; 637 mInputTypeNoAutoCorrect = false; 638 mPredictionOn = false; 639 mCompletionOn = false; 640 mCompletions = null; 641 mEnteredText = null; 642 643 final int mode; 644 switch (attribute.inputType & EditorInfo.TYPE_MASK_CLASS) { 645 case EditorInfo.TYPE_CLASS_NUMBER: 646 case EditorInfo.TYPE_CLASS_DATETIME: 647 // fall through 648 // NOTE: For now, we use the phone keyboard for NUMBER and DATETIME until we get 649 // a dedicated number entry keypad. 650 // TODO: Use a dedicated number entry keypad here when we get one. 651 case EditorInfo.TYPE_CLASS_PHONE: 652 mode = KeyboardSwitcher.MODE_PHONE; 653 break; 654 case EditorInfo.TYPE_CLASS_TEXT: 655 //startPrediction(); 656 mPredictionOn = true; 657 // Make sure that passwords are not displayed in candidate view 658 if (isPasswordVariation(variation)) { 659 mPredictionOn = false; 660 } 661 if (isEmailVariation(variation) 662 || variation == EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME) { 663 mAutoSpace = false; 664 } else { 665 mAutoSpace = true; 666 } 667 if (isEmailVariation(variation)) { 668 mPredictionOn = false; 669 mode = KeyboardSwitcher.MODE_EMAIL; 670 } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_URI) { 671 mPredictionOn = false; 672 mode = KeyboardSwitcher.MODE_URL; 673 } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) { 674 mode = KeyboardSwitcher.MODE_IM; 675 } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_FILTER) { 676 mPredictionOn = false; 677 mode = KeyboardSwitcher.MODE_TEXT; 678 } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) { 679 mode = KeyboardSwitcher.MODE_WEB; 680 // If it's a browser edit field and auto correct is not ON explicitly, then 681 // disable auto correction, but keep suggestions on. 682 if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) { 683 mInputTypeNoAutoCorrect = true; 684 } 685 } else { 686 mode = KeyboardSwitcher.MODE_TEXT; 687 } 688 689 // If NO_SUGGESTIONS is set, don't do prediction. 690 if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) { 691 mPredictionOn = false; 692 mInputTypeNoAutoCorrect = true; 693 } 694 // If it's not multiline and the autoCorrect flag is not set, then don't correct 695 if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0 && 696 (attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) == 0) { 697 mInputTypeNoAutoCorrect = true; 698 } 699 if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) { 700 mPredictionOn = false; 701 mCompletionOn = isFullscreenMode(); 702 } 703 break; 704 default: 705 mode = KeyboardSwitcher.MODE_TEXT; 706 break; 707 } 708 inputView.closing(); 709 mComposing.setLength(0); 710 mPredicting = false; 711 mDeleteCount = 0; 712 mJustAddedAutoSpace = false; 713 714 loadSettings(attribute); 715 mKeyboardSwitcher.loadKeyboard(mode, attribute.imeOptions, mVoiceButtonEnabled, 716 mVoiceButtonOnPrimary); 717 updateShiftKeyState(attribute); 718 719 setCandidatesViewShownInternal(isCandidateStripVisible(), 720 false /* needsInputViewShown */ ); 721 updateSuggestions(); 722 723 // If the dictionary is not big enough, don't auto correct 724 mHasDictionary = mSuggest.hasMainDictionary(); 725 726 updateCorrectionMode(); 727 728 inputView.setPreviewEnabled(mPopupOn); 729 inputView.setProximityCorrectionEnabled(true); 730 mPredictionOn = mPredictionOn && (mCorrectionMode > 0 || isSuggestionShown()); 731 // If we just entered a text field, maybe it has some old text that requires correction 732 checkReCorrectionOnStart(); 733 checkTutorial(attribute.privateImeOptions); 734 inputView.setForeground(true); 735 if (TRACE) Debug.startMethodTracing("/data/trace/latinime"); 736 } 737 738 private void checkReCorrectionOnStart() { 739 if (mReCorrectionEnabled && isPredictionOn()) { 740 // First get the cursor position. This is required by setOldSuggestions(), so that 741 // it can pass the correct range to setComposingRegion(). At this point, we don't 742 // have valid values for mLastSelectionStart/Stop because onUpdateSelection() has 743 // not been called yet. 744 InputConnection ic = getCurrentInputConnection(); 745 if (ic == null) return; 746 ExtractedTextRequest etr = new ExtractedTextRequest(); 747 etr.token = 0; // anything is fine here 748 ExtractedText et = ic.getExtractedText(etr, 0); 749 if (et == null) return; 750 751 mLastSelectionStart = et.startOffset + et.selectionStart; 752 mLastSelectionEnd = et.startOffset + et.selectionEnd; 753 754 // Then look for possible corrections in a delayed fashion 755 if (!TextUtils.isEmpty(et.text) && isCursorTouchingWord()) { 756 mHandler.postUpdateOldSuggestions(); 757 } 758 } 759 } 760 761 @Override 762 public void onFinishInput() { 763 super.onFinishInput(); 764 765 LatinImeLogger.commit(); 766 onAutoCompletionStateChanged(false); 767 768 if (VOICE_INSTALLED && !mConfigurationChanging) { 769 if (mAfterVoiceInput) { 770 mVoiceInput.flushAllTextModificationCounters(); 771 mVoiceInput.logInputEnded(); 772 } 773 mVoiceInput.flushLogs(); 774 mVoiceInput.cancel(); 775 } 776 BaseKeyboardView inputView = mKeyboardSwitcher.getInputView(); 777 if (inputView != null) 778 inputView.closing(); 779 if (mAutoDictionary != null) mAutoDictionary.flushPendingWrites(); 780 if (mUserBigramDictionary != null) mUserBigramDictionary.flushPendingWrites(); 781 } 782 783 @Override 784 public void onFinishInputView(boolean finishingInput) { 785 super.onFinishInputView(finishingInput); 786 BaseKeyboardView inputView = mKeyboardSwitcher.getInputView(); 787 if (inputView != null) 788 inputView.setForeground(false); 789 // Remove pending messages related to update suggestions 790 mHandler.cancelUpdateSuggestions(); 791 mHandler.cancelUpdateOldSuggestions(); 792 } 793 794 @Override 795 public void onUpdateExtractedText(int token, ExtractedText text) { 796 super.onUpdateExtractedText(token, text); 797 InputConnection ic = getCurrentInputConnection(); 798 if (!mImmediatelyAfterVoiceInput && mAfterVoiceInput && ic != null) { 799 if (mHints.showPunctuationHintIfNecessary(ic)) { 800 mVoiceInput.logPunctuationHintDisplayed(); 801 } 802 } 803 mImmediatelyAfterVoiceInput = false; 804 } 805 806 @Override 807 public void onUpdateSelection(int oldSelStart, int oldSelEnd, 808 int newSelStart, int newSelEnd, 809 int candidatesStart, int candidatesEnd) { 810 super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, 811 candidatesStart, candidatesEnd); 812 813 if (DEBUG) { 814 Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart 815 + ", ose=" + oldSelEnd 816 + ", nss=" + newSelStart 817 + ", nse=" + newSelEnd 818 + ", cs=" + candidatesStart 819 + ", ce=" + candidatesEnd); 820 } 821 822 if (mAfterVoiceInput) { 823 mVoiceInput.setCursorPos(newSelEnd); 824 mVoiceInput.setSelectionSpan(newSelEnd - newSelStart); 825 } 826 827 // If the current selection in the text view changes, we should 828 // clear whatever candidate text we have. 829 if ((((mComposing.length() > 0 && mPredicting) || mVoiceInputHighlighted) 830 && (newSelStart != candidatesEnd 831 || newSelEnd != candidatesEnd) 832 && mLastSelectionStart != newSelStart)) { 833 mComposing.setLength(0); 834 mPredicting = false; 835 mHandler.postUpdateSuggestions(); 836 TextEntryState.reset(); 837 InputConnection ic = getCurrentInputConnection(); 838 if (ic != null) { 839 ic.finishComposingText(); 840 } 841 mVoiceInputHighlighted = false; 842 } else if (!mPredicting && !mJustAccepted) { 843 switch (TextEntryState.getState()) { 844 case ACCEPTED_DEFAULT: 845 TextEntryState.reset(); 846 // fall through 847 case SPACE_AFTER_PICKED: 848 mJustAddedAutoSpace = false; // The user moved the cursor. 849 break; 850 } 851 } 852 mJustAccepted = false; 853 mHandler.postUpdateShiftKeyState(); 854 855 // Make a note of the cursor position 856 mLastSelectionStart = newSelStart; 857 mLastSelectionEnd = newSelEnd; 858 859 if (mReCorrectionEnabled) { 860 // Don't look for corrections if the keyboard is not visible 861 if (mKeyboardSwitcher.isInputViewShown()) { 862 // Check if we should go in or out of correction mode. 863 if (isPredictionOn() && !mJustReverted 864 && (candidatesStart == candidatesEnd || newSelStart != oldSelStart 865 || TextEntryState.isCorrecting()) 866 && (newSelStart < newSelEnd - 1 || (!mPredicting)) 867 && !mVoiceInputHighlighted) { 868 if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) { 869 mHandler.postUpdateOldSuggestions(); 870 } else { 871 abortCorrection(false); 872 // Show the punctuation suggestions list if the current one is not 873 // and if not showing "Touch again to save". 874 if (mCandidateView != null && !isShowingPunctuationList() 875 && !mCandidateView.isShowingAddToDictionaryHint()) { 876 setPunctuationSuggestions(); 877 } 878 } 879 } 880 } 881 } 882 } 883 884 /** 885 * This is called when the user has clicked on the extracted text view, 886 * when running in fullscreen mode. The default implementation hides 887 * the candidates view when this happens, but only if the extracted text 888 * editor has a vertical scroll bar because its text doesn't fit. 889 * Here we override the behavior due to the possibility that a re-correction could 890 * cause the candidate strip to disappear and re-appear. 891 */ 892 @Override 893 public void onExtractedTextClicked() { 894 if (mReCorrectionEnabled && isPredictionOn()) return; 895 896 super.onExtractedTextClicked(); 897 } 898 899 /** 900 * This is called when the user has performed a cursor movement in the 901 * extracted text view, when it is running in fullscreen mode. The default 902 * implementation hides the candidates view when a vertical movement 903 * happens, but only if the extracted text editor has a vertical scroll bar 904 * because its text doesn't fit. 905 * Here we override the behavior due to the possibility that a re-correction could 906 * cause the candidate strip to disappear and re-appear. 907 */ 908 @Override 909 public void onExtractedCursorMovement(int dx, int dy) { 910 if (mReCorrectionEnabled && isPredictionOn()) return; 911 912 super.onExtractedCursorMovement(dx, dy); 913 } 914 915 @Override 916 public void hideWindow() { 917 LatinImeLogger.commit(); 918 onAutoCompletionStateChanged(false); 919 920 if (TRACE) Debug.stopMethodTracing(); 921 if (mOptionsDialog != null && mOptionsDialog.isShowing()) { 922 mOptionsDialog.dismiss(); 923 mOptionsDialog = null; 924 } 925 if (!mConfigurationChanging) { 926 if (mAfterVoiceInput) mVoiceInput.logInputEnded(); 927 if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) { 928 mVoiceInput.logKeyboardWarningDialogDismissed(); 929 mVoiceWarningDialog.dismiss(); 930 mVoiceWarningDialog = null; 931 } 932 if (VOICE_INSTALLED & mRecognizing) { 933 mVoiceInput.cancel(); 934 } 935 } 936 mWordToSuggestions.clear(); 937 mWordHistory.clear(); 938 super.hideWindow(); 939 TextEntryState.endSession(); 940 } 941 942 @Override 943 public void onDisplayCompletions(CompletionInfo[] completions) { 944 if (DEBUG) { 945 Log.i("foo", "Received completions:"); 946 for (int i=0; i<(completions != null ? completions.length : 0); i++) { 947 Log.i("foo", " #" + i + ": " + completions[i]); 948 } 949 } 950 if (mCompletionOn) { 951 mCompletions = completions; 952 if (completions == null) { 953 clearSuggestions(); 954 return; 955 } 956 957 List<CharSequence> stringList = new ArrayList<CharSequence>(); 958 for (int i=0; i<(completions != null ? completions.length : 0); i++) { 959 CompletionInfo ci = completions[i]; 960 if (ci != null) stringList.add(ci.getText()); 961 } 962 // When in fullscreen mode, show completions generated by the application 963 setSuggestions(stringList, true, true, true); 964 mBestWord = null; 965 setCandidatesViewShown(true); 966 } 967 } 968 969 private void setCandidatesViewShownInternal(boolean shown, boolean needsInputViewShown) { 970 // TODO: Remove this if we support candidates with hard keyboard 971 if (onEvaluateInputViewShown()) { 972 super.setCandidatesViewShown(shown 973 && (needsInputViewShown ? mKeyboardSwitcher.isInputViewShown() : true)); 974 } 975 } 976 977 @Override 978 public void setCandidatesViewShown(boolean shown) { 979 setCandidatesViewShownInternal(shown, true /* needsInputViewShown */ ); 980 } 981 982 @Override 983 public void onComputeInsets(InputMethodService.Insets outInsets) { 984 super.onComputeInsets(outInsets); 985 if (!isFullscreenMode()) { 986 outInsets.contentTopInsets = outInsets.visibleTopInsets; 987 } 988 } 989 990 @Override 991 public boolean onEvaluateFullscreenMode() { 992 DisplayMetrics dm = getResources().getDisplayMetrics(); 993 float displayHeight = dm.heightPixels; 994 // If the display is more than X inches high, don't go to fullscreen mode 995 float dimen = getResources().getDimension(R.dimen.max_height_for_fullscreen); 996 if (displayHeight > dimen) { 997 return false; 998 } else { 999 return super.onEvaluateFullscreenMode(); 1000 } 1001 } 1002 1003 @Override 1004 public boolean onKeyDown(int keyCode, KeyEvent event) { 1005 switch (keyCode) { 1006 case KeyEvent.KEYCODE_BACK: 1007 if (event.getRepeatCount() == 0 && mKeyboardSwitcher.getInputView() != null) { 1008 if (mKeyboardSwitcher.getInputView().handleBack()) { 1009 return true; 1010 } else if (mTutorial != null) { 1011 mTutorial.close(); 1012 mTutorial = null; 1013 } 1014 } 1015 break; 1016 case KeyEvent.KEYCODE_DPAD_DOWN: 1017 case KeyEvent.KEYCODE_DPAD_UP: 1018 case KeyEvent.KEYCODE_DPAD_LEFT: 1019 case KeyEvent.KEYCODE_DPAD_RIGHT: 1020 // If tutorial is visible, don't allow dpad to work 1021 if (mTutorial != null) { 1022 return true; 1023 } 1024 break; 1025 } 1026 return super.onKeyDown(keyCode, event); 1027 } 1028 1029 @Override 1030 public boolean onKeyUp(int keyCode, KeyEvent event) { 1031 switch (keyCode) { 1032 case KeyEvent.KEYCODE_DPAD_DOWN: 1033 case KeyEvent.KEYCODE_DPAD_UP: 1034 case KeyEvent.KEYCODE_DPAD_LEFT: 1035 case KeyEvent.KEYCODE_DPAD_RIGHT: 1036 // If tutorial is visible, don't allow dpad to work 1037 if (mTutorial != null) { 1038 return true; 1039 } 1040 // Enable shift key and DPAD to do selections 1041 if (mKeyboardSwitcher.isInputViewShown() && mKeyboardSwitcher.isShifted()) { 1042 event = new KeyEvent(event.getDownTime(), event.getEventTime(), 1043 event.getAction(), event.getKeyCode(), event.getRepeatCount(), 1044 event.getDeviceId(), event.getScanCode(), 1045 KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON); 1046 InputConnection ic = getCurrentInputConnection(); 1047 if (ic != null) ic.sendKeyEvent(event); 1048 return true; 1049 } 1050 break; 1051 } 1052 return super.onKeyUp(keyCode, event); 1053 } 1054 1055 private void revertVoiceInput() { 1056 InputConnection ic = getCurrentInputConnection(); 1057 if (ic != null) ic.commitText("", 1); 1058 updateSuggestions(); 1059 mVoiceInputHighlighted = false; 1060 } 1061 1062 private void commitVoiceInput() { 1063 InputConnection ic = getCurrentInputConnection(); 1064 if (ic != null) ic.finishComposingText(); 1065 updateSuggestions(); 1066 mVoiceInputHighlighted = false; 1067 } 1068 1069 private void commitTyped(InputConnection inputConnection) { 1070 if (mPredicting) { 1071 mPredicting = false; 1072 if (mComposing.length() > 0) { 1073 if (inputConnection != null) { 1074 inputConnection.commitText(mComposing, 1); 1075 } 1076 mCommittedLength = mComposing.length(); 1077 TextEntryState.acceptedTyped(mComposing); 1078 addToDictionaries(mComposing, AutoDictionary.FREQUENCY_FOR_TYPED); 1079 } 1080 updateSuggestions(); 1081 } 1082 } 1083 1084 public void updateShiftKeyState(EditorInfo attr) { 1085 InputConnection ic = getCurrentInputConnection(); 1086 KeyboardSwitcher switcher = mKeyboardSwitcher; 1087 if (!switcher.isKeyboardAvailable()) 1088 return; 1089 if (ic != null && attr != null && switcher.isAlphabetMode() 1090 && !switcher.isShiftIgnoring()) { 1091 switcher.setShifted(switcher.isShiftMomentary() 1092 || switcher.isShiftLocked() || getCursorCapsMode(ic, attr) != 0); 1093 } 1094 } 1095 1096 private int getCursorCapsMode(InputConnection ic, EditorInfo attr) { 1097 int caps = 0; 1098 EditorInfo ei = getCurrentInputEditorInfo(); 1099 if (mAutoCap && ei != null && ei.inputType != EditorInfo.TYPE_NULL) { 1100 caps = ic.getCursorCapsMode(attr.inputType); 1101 } 1102 return caps; 1103 } 1104 1105 private void swapPunctuationAndSpace() { 1106 final InputConnection ic = getCurrentInputConnection(); 1107 if (ic == null) return; 1108 CharSequence lastTwo = ic.getTextBeforeCursor(2, 0); 1109 if (lastTwo != null && lastTwo.length() == 2 1110 && lastTwo.charAt(0) == KEYCODE_SPACE && isSentenceSeparator(lastTwo.charAt(1))) { 1111 ic.beginBatchEdit(); 1112 ic.deleteSurroundingText(2, 0); 1113 ic.commitText(lastTwo.charAt(1) + " ", 1); 1114 ic.endBatchEdit(); 1115 updateShiftKeyState(getCurrentInputEditorInfo()); 1116 mJustAddedAutoSpace = true; 1117 } 1118 } 1119 1120 private void reswapPeriodAndSpace() { 1121 final InputConnection ic = getCurrentInputConnection(); 1122 if (ic == null) return; 1123 CharSequence lastThree = ic.getTextBeforeCursor(3, 0); 1124 if (lastThree != null && lastThree.length() == 3 1125 && lastThree.charAt(0) == KEYCODE_PERIOD 1126 && lastThree.charAt(1) == KEYCODE_SPACE 1127 && lastThree.charAt(2) == KEYCODE_PERIOD) { 1128 ic.beginBatchEdit(); 1129 ic.deleteSurroundingText(3, 0); 1130 ic.commitText(" ..", 1); 1131 ic.endBatchEdit(); 1132 updateShiftKeyState(getCurrentInputEditorInfo()); 1133 } 1134 } 1135 1136 private void doubleSpace() { 1137 //if (!mAutoPunctuate) return; 1138 if (mCorrectionMode == Suggest.CORRECTION_NONE) return; 1139 final InputConnection ic = getCurrentInputConnection(); 1140 if (ic == null) return; 1141 CharSequence lastThree = ic.getTextBeforeCursor(3, 0); 1142 if (lastThree != null && lastThree.length() == 3 1143 && Character.isLetterOrDigit(lastThree.charAt(0)) 1144 && lastThree.charAt(1) == KEYCODE_SPACE && lastThree.charAt(2) == KEYCODE_SPACE) { 1145 ic.beginBatchEdit(); 1146 ic.deleteSurroundingText(2, 0); 1147 ic.commitText(". ", 1); 1148 ic.endBatchEdit(); 1149 updateShiftKeyState(getCurrentInputEditorInfo()); 1150 mJustAddedAutoSpace = true; 1151 } 1152 } 1153 1154 private void maybeRemovePreviousPeriod(CharSequence text) { 1155 final InputConnection ic = getCurrentInputConnection(); 1156 if (ic == null) return; 1157 1158 // When the text's first character is '.', remove the previous period 1159 // if there is one. 1160 CharSequence lastOne = ic.getTextBeforeCursor(1, 0); 1161 if (lastOne != null && lastOne.length() == 1 1162 && lastOne.charAt(0) == KEYCODE_PERIOD 1163 && text.charAt(0) == KEYCODE_PERIOD) { 1164 ic.deleteSurroundingText(1, 0); 1165 } 1166 } 1167 1168 private void removeTrailingSpace() { 1169 final InputConnection ic = getCurrentInputConnection(); 1170 if (ic == null) return; 1171 1172 CharSequence lastOne = ic.getTextBeforeCursor(1, 0); 1173 if (lastOne != null && lastOne.length() == 1 1174 && lastOne.charAt(0) == KEYCODE_SPACE) { 1175 ic.deleteSurroundingText(1, 0); 1176 } 1177 } 1178 1179 public boolean addWordToDictionary(String word) { 1180 mUserDictionary.addWord(word, 128); 1181 // Suggestion strip should be updated after the operation of adding word to the 1182 // user dictionary 1183 mHandler.postUpdateSuggestions(); 1184 return true; 1185 } 1186 1187 private boolean isAlphabet(int code) { 1188 if (Character.isLetter(code)) { 1189 return true; 1190 } else { 1191 return false; 1192 } 1193 } 1194 1195 private void showInputMethodSubtypePicker() { 1196 ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)) 1197 .showInputMethodSubtypePicker(); 1198 } 1199 1200 private void onOptionKeyPressed() { 1201 if (!isShowingOptionDialog()) { 1202 if (LatinIMEUtil.hasMultipleEnabledIMEs(this)) { 1203 showOptionsMenu(); 1204 } else { 1205 launchSettings(); 1206 } 1207 } 1208 } 1209 1210 private void onOptionKeyLongPressed() { 1211 if (!isShowingOptionDialog()) { 1212 if (LatinIMEUtil.hasMultipleEnabledIMEs(this)) { 1213 showInputMethodSubtypePicker(); 1214 } else { 1215 launchSettings(); 1216 } 1217 } 1218 } 1219 1220 private boolean isShowingOptionDialog() { 1221 return mOptionsDialog != null && mOptionsDialog.isShowing(); 1222 } 1223 1224 // Implementation of KeyboardViewListener 1225 1226 public void onKey(int primaryCode, int[] keyCodes, int x, int y) { 1227 long when = SystemClock.uptimeMillis(); 1228 if (primaryCode != BaseKeyboard.KEYCODE_DELETE || when > mLastKeyTime + QUICK_PRESS) { 1229 mDeleteCount = 0; 1230 } 1231 mLastKeyTime = when; 1232 final boolean distinctMultiTouch = mKeyboardSwitcher.hasDistinctMultitouch(); 1233 switch (primaryCode) { 1234 case BaseKeyboard.KEYCODE_DELETE: 1235 handleBackspace(); 1236 mDeleteCount++; 1237 LatinImeLogger.logOnDelete(); 1238 break; 1239 case BaseKeyboard.KEYCODE_SHIFT: 1240 // Shift key is handled in onPress() when device has distinct multi-touch panel. 1241 if (!distinctMultiTouch) 1242 handleShift(); 1243 break; 1244 case BaseKeyboard.KEYCODE_MODE_CHANGE: 1245 // Symbol key is handled in onPress() when device has distinct multi-touch panel. 1246 if (!distinctMultiTouch) 1247 changeKeyboardMode(); 1248 break; 1249 case BaseKeyboard.KEYCODE_CANCEL: 1250 if (!isShowingOptionDialog()) { 1251 handleClose(); 1252 } 1253 break; 1254 case LatinKeyboardView.KEYCODE_OPTIONS: 1255 onOptionKeyPressed(); 1256 break; 1257 case LatinKeyboardView.KEYCODE_OPTIONS_LONGPRESS: 1258 onOptionKeyLongPressed(); 1259 break; 1260 case LatinKeyboardView.KEYCODE_NEXT_LANGUAGE: 1261 toggleLanguage(false, true); 1262 break; 1263 case LatinKeyboardView.KEYCODE_PREV_LANGUAGE: 1264 toggleLanguage(false, false); 1265 break; 1266 case LatinKeyboardView.KEYCODE_CAPSLOCK: 1267 handleCapsLock(); 1268 break; 1269 case LatinKeyboardView.KEYCODE_VOICE: 1270 if (VOICE_INSTALLED) { 1271 startListening(false /* was a button press, was not a swipe */); 1272 } 1273 break; 1274 case KEYCODE_TAB: 1275 sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB); 1276 break; 1277 default: 1278 if (primaryCode != KEYCODE_ENTER) { 1279 mJustAddedAutoSpace = false; 1280 } 1281 RingCharBuffer.getInstance().push((char)primaryCode, x, y); 1282 LatinImeLogger.logOnInputChar(); 1283 if (isWordSeparator(primaryCode)) { 1284 handleSeparator(primaryCode); 1285 } else { 1286 handleCharacter(primaryCode, keyCodes); 1287 } 1288 // Cancel the just reverted state 1289 mJustReverted = false; 1290 } 1291 if (mKeyboardSwitcher.onKey(primaryCode)) { 1292 changeKeyboardMode(); 1293 } 1294 // Reset after any single keystroke 1295 mEnteredText = null; 1296 } 1297 1298 public void onText(CharSequence text) { 1299 if (VOICE_INSTALLED && mVoiceInputHighlighted) { 1300 commitVoiceInput(); 1301 } 1302 InputConnection ic = getCurrentInputConnection(); 1303 if (ic == null) return; 1304 abortCorrection(false); 1305 ic.beginBatchEdit(); 1306 if (mPredicting) { 1307 commitTyped(ic); 1308 } 1309 maybeRemovePreviousPeriod(text); 1310 ic.commitText(text, 1); 1311 ic.endBatchEdit(); 1312 updateShiftKeyState(getCurrentInputEditorInfo()); 1313 mJustReverted = false; 1314 mJustAddedAutoSpace = false; 1315 mEnteredText = text; 1316 } 1317 1318 public void onCancel() { 1319 // User released a finger outside any key 1320 } 1321 1322 private void handleBackspace() { 1323 if (VOICE_INSTALLED && mVoiceInputHighlighted) { 1324 mVoiceInput.incrementTextModificationDeleteCount( 1325 mVoiceResults.candidates.get(0).toString().length()); 1326 revertVoiceInput(); 1327 return; 1328 } 1329 boolean deleteChar = false; 1330 InputConnection ic = getCurrentInputConnection(); 1331 if (ic == null) return; 1332 1333 ic.beginBatchEdit(); 1334 1335 if (mAfterVoiceInput) { 1336 // Don't log delete if the user is pressing delete at 1337 // the beginning of the text box (hence not deleting anything) 1338 if (mVoiceInput.getCursorPos() > 0) { 1339 // If anything was selected before the delete was pressed, increment the 1340 // delete count by the length of the selection 1341 int deleteLen = mVoiceInput.getSelectionSpan() > 0 ? 1342 mVoiceInput.getSelectionSpan() : 1; 1343 mVoiceInput.incrementTextModificationDeleteCount(deleteLen); 1344 } 1345 } 1346 1347 if (mPredicting) { 1348 final int length = mComposing.length(); 1349 if (length > 0) { 1350 mComposing.delete(length - 1, length); 1351 mWord.deleteLast(); 1352 ic.setComposingText(mComposing, 1); 1353 if (mComposing.length() == 0) { 1354 mPredicting = false; 1355 } 1356 mHandler.postUpdateSuggestions(); 1357 } else { 1358 ic.deleteSurroundingText(1, 0); 1359 } 1360 } else { 1361 deleteChar = true; 1362 } 1363 mHandler.postUpdateShiftKeyState(); 1364 TextEntryState.backspace(); 1365 if (TextEntryState.getState() == TextEntryState.State.UNDO_COMMIT) { 1366 revertLastWord(deleteChar); 1367 ic.endBatchEdit(); 1368 return; 1369 } else if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) { 1370 ic.deleteSurroundingText(mEnteredText.length(), 0); 1371 } else if (deleteChar) { 1372 if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) { 1373 // Go back to the suggestion mode if the user canceled the 1374 // "Touch again to save". 1375 // NOTE: In gerenal, we don't revert the word when backspacing 1376 // from a manual suggestion pick. We deliberately chose a 1377 // different behavior only in the case of picking the first 1378 // suggestion (typed word). It's intentional to have made this 1379 // inconsistent with backspacing after selecting other suggestions. 1380 revertLastWord(deleteChar); 1381 } else { 1382 sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); 1383 if (mDeleteCount > DELETE_ACCELERATE_AT) { 1384 sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); 1385 } 1386 } 1387 } 1388 mJustReverted = false; 1389 ic.endBatchEdit(); 1390 } 1391 1392 private void resetShift() { 1393 handleShiftInternal(true); 1394 } 1395 1396 private void handleShift() { 1397 handleShiftInternal(false); 1398 } 1399 1400 private void handleShiftInternal(boolean forceNormal) { 1401 mHandler.cancelUpdateShiftState(); 1402 KeyboardSwitcher switcher = mKeyboardSwitcher; 1403 if (switcher.isAlphabetMode()) { 1404 if (!switcher.isKeyboardAvailable()) 1405 return; 1406 if (switcher.isShiftLocked() || forceNormal) { 1407 switcher.setShifted(false); 1408 } else { 1409 switcher.setShifted(!switcher.isShifted()); 1410 } 1411 } else { 1412 switcher.toggleShift(); 1413 } 1414 } 1415 1416 private void handleCapsLock() { 1417 mHandler.cancelUpdateShiftState(); 1418 KeyboardSwitcher switcher = mKeyboardSwitcher; 1419 if (switcher.isAlphabetMode()) { 1420 if (!switcher.isKeyboardAvailable()) 1421 return; 1422 if (switcher.isShiftLocked()) { 1423 // KeyboardSwitcher.setShifted(false) also disable shift locked state. 1424 // Note: Caps lock LED is off when Key.on is false. 1425 switcher.setShifted(false); 1426 } else { 1427 // KeyboardSwitcher.setShiftLocked(true) enable shift state too. 1428 // Note: Caps lock LED is on when Key.on is true. 1429 switcher.setShiftLocked(true); 1430 } 1431 } 1432 } 1433 1434 private void abortCorrection(boolean force) { 1435 if (force || TextEntryState.isCorrecting()) { 1436 TextEntryState.onAbortCorrection(); 1437 setCandidatesViewShown(isCandidateStripVisible()); 1438 getCurrentInputConnection().finishComposingText(); 1439 clearSuggestions(); 1440 } 1441 } 1442 1443 private void handleCharacter(int primaryCode, int[] keyCodes) { 1444 if (VOICE_INSTALLED && mVoiceInputHighlighted) { 1445 commitVoiceInput(); 1446 } 1447 1448 if (mAfterVoiceInput) { 1449 // Assume input length is 1. This assumption fails for smiley face insertions. 1450 mVoiceInput.incrementTextModificationInsertCount(1); 1451 } 1452 if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isCorrecting()) { 1453 abortCorrection(false); 1454 } 1455 1456 if (isAlphabet(primaryCode) && isPredictionOn() && !isCursorTouchingWord()) { 1457 if (!mPredicting) { 1458 mPredicting = true; 1459 mComposing.setLength(0); 1460 saveWordInHistory(mBestWord); 1461 mWord.reset(); 1462 } 1463 } 1464 KeyboardSwitcher switcher = mKeyboardSwitcher; 1465 if (switcher.isShifted()) { 1466 if (keyCodes == null || keyCodes[0] < Character.MIN_CODE_POINT 1467 || keyCodes[0] > Character.MAX_CODE_POINT) { 1468 return; 1469 } 1470 primaryCode = keyCodes[0]; 1471 if (switcher.isAlphabetMode() && Character.isLowerCase(primaryCode)) { 1472 int upperCaseCode = Character.toUpperCase(primaryCode); 1473 if (upperCaseCode != primaryCode) { 1474 primaryCode = upperCaseCode; 1475 } else { 1476 // Some keys, such as [eszett], have upper case as multi-characters. 1477 String upperCase = new String(new int[] {primaryCode}, 0, 1).toUpperCase(); 1478 onText(upperCase); 1479 return; 1480 } 1481 } 1482 } 1483 if (mPredicting) { 1484 if (mComposing.length() == 0 && switcher.isAlphabetMode() && switcher.isShifted()) { 1485 mWord.setFirstCharCapitalized(true); 1486 } 1487 mComposing.append((char) primaryCode); 1488 mWord.add(primaryCode, keyCodes); 1489 InputConnection ic = getCurrentInputConnection(); 1490 if (ic != null) { 1491 // If it's the first letter, make note of auto-caps state 1492 if (mWord.size() == 1) { 1493 mWord.setAutoCapitalized( 1494 getCursorCapsMode(ic, getCurrentInputEditorInfo()) != 0); 1495 } 1496 ic.setComposingText(mComposing, 1); 1497 } 1498 mHandler.postUpdateSuggestions(); 1499 } else { 1500 sendKeyChar((char)primaryCode); 1501 } 1502 updateShiftKeyState(getCurrentInputEditorInfo()); 1503 if (LatinIME.PERF_DEBUG) measureCps(); 1504 TextEntryState.typedCharacter((char) primaryCode, isWordSeparator(primaryCode)); 1505 } 1506 1507 private void handleSeparator(int primaryCode) { 1508 if (VOICE_INSTALLED && mVoiceInputHighlighted) { 1509 commitVoiceInput(); 1510 } 1511 1512 if (mAfterVoiceInput){ 1513 // Assume input length is 1. This assumption fails for smiley face insertions. 1514 mVoiceInput.incrementTextModificationInsertPunctuationCount(1); 1515 } 1516 1517 // Should dismiss the "Touch again to save" message when handling separator 1518 if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) { 1519 mHandler.postUpdateSuggestions(); 1520 } 1521 1522 boolean pickedDefault = false; 1523 // Handle separator 1524 InputConnection ic = getCurrentInputConnection(); 1525 if (ic != null) { 1526 ic.beginBatchEdit(); 1527 abortCorrection(false); 1528 } 1529 if (mPredicting) { 1530 // In certain languages where single quote is a separator, it's better 1531 // not to auto correct, but accept the typed word. For instance, 1532 // in Italian dov' should not be expanded to dove' because the elision 1533 // requires the last vowel to be removed. 1534 if (mAutoCorrectOn && primaryCode != '\'' && !mJustReverted) { 1535 pickedDefault = pickDefaultSuggestion(); 1536 // Picked the suggestion by the space key. We consider this 1537 // as "added an auto space". 1538 if (primaryCode == KEYCODE_SPACE) { 1539 mJustAddedAutoSpace = true; 1540 } 1541 } else { 1542 commitTyped(ic); 1543 } 1544 } 1545 if (mJustAddedAutoSpace && primaryCode == KEYCODE_ENTER) { 1546 removeTrailingSpace(); 1547 mJustAddedAutoSpace = false; 1548 } 1549 sendKeyChar((char)primaryCode); 1550 1551 // Handle the case of ". ." -> " .." with auto-space if necessary 1552 // before changing the TextEntryState. 1553 if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED 1554 && primaryCode == KEYCODE_PERIOD) { 1555 reswapPeriodAndSpace(); 1556 } 1557 1558 TextEntryState.typedCharacter((char) primaryCode, true); 1559 if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED 1560 && primaryCode != KEYCODE_ENTER) { 1561 swapPunctuationAndSpace(); 1562 } else if (isPredictionOn() && primaryCode == KEYCODE_SPACE) { 1563 doubleSpace(); 1564 } 1565 if (pickedDefault) { 1566 TextEntryState.backToAcceptedDefault(mWord.getTypedWord()); 1567 } 1568 updateShiftKeyState(getCurrentInputEditorInfo()); 1569 if (ic != null) { 1570 ic.endBatchEdit(); 1571 } 1572 } 1573 1574 private void handleClose() { 1575 commitTyped(getCurrentInputConnection()); 1576 if (VOICE_INSTALLED & mRecognizing) { 1577 mVoiceInput.cancel(); 1578 } 1579 requestHideSelf(0); 1580 LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); 1581 if (inputView != null) 1582 inputView.closing(); 1583 TextEntryState.endSession(); 1584 } 1585 1586 private void saveWordInHistory(CharSequence result) { 1587 if (mWord.size() <= 1) { 1588 mWord.reset(); 1589 return; 1590 } 1591 // Skip if result is null. It happens in some edge case. 1592 if (TextUtils.isEmpty(result)) { 1593 return; 1594 } 1595 1596 // Make a copy of the CharSequence, since it is/could be a mutable CharSequence 1597 final String resultCopy = result.toString(); 1598 TypedWordAlternatives entry = new TypedWordAlternatives(resultCopy, 1599 new WordComposer(mWord)); 1600 mWordHistory.add(entry); 1601 } 1602 1603 private boolean isPredictionOn() { 1604 return mPredictionOn; 1605 } 1606 1607 private boolean isShowingPunctuationList() { 1608 return mSuggestPuncList.equals(mCandidateView.getSuggestions()); 1609 } 1610 1611 private boolean isSuggestionShown() { 1612 return (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_VALUE) 1613 || (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE 1614 && mOrientation == Configuration.ORIENTATION_PORTRAIT); 1615 } 1616 1617 private boolean isCandidateStripVisible() { 1618 boolean forceVisible = mCandidateView.isShowingAddToDictionaryHint() 1619 || TextEntryState.isCorrecting(); 1620 return forceVisible || (isSuggestionShown() 1621 && (isPredictionOn() || mCompletionOn || isShowingPunctuationList())); 1622 } 1623 1624 public void onCancelVoice() { 1625 if (mRecognizing) { 1626 switchToKeyboardView(); 1627 } 1628 } 1629 1630 private void switchToKeyboardView() { 1631 mHandler.post(new Runnable() { 1632 public void run() { 1633 mRecognizing = false; 1634 if (mKeyboardSwitcher.getInputView() != null) { 1635 setInputView(mKeyboardSwitcher.getInputView()); 1636 } 1637 setCandidatesViewShown(isCandidateStripVisible()); 1638 updateInputViewShown(); 1639 mHandler.postUpdateSuggestions(); 1640 }}); 1641 } 1642 1643 private void switchToRecognitionStatusView() { 1644 final boolean configChanged = mConfigurationChanging; 1645 mHandler.post(new Runnable() { 1646 public void run() { 1647 setCandidatesViewShown(false); 1648 mRecognizing = true; 1649 View v = mVoiceInput.getView(); 1650 ViewParent p = v.getParent(); 1651 if (p != null && p instanceof ViewGroup) { 1652 ((ViewGroup)v.getParent()).removeView(v); 1653 } 1654 setInputView(v); 1655 updateInputViewShown(); 1656 if (configChanged) { 1657 mVoiceInput.onConfigurationChanged(); 1658 } 1659 }}); 1660 } 1661 1662 private void startListening(boolean swipe) { 1663 if (!mHasUsedVoiceInput || 1664 (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale)) { 1665 // Calls reallyStartListening if user clicks OK, does nothing if user clicks Cancel. 1666 showVoiceWarningDialog(swipe); 1667 } else { 1668 reallyStartListening(swipe); 1669 } 1670 } 1671 1672 private void reallyStartListening(boolean swipe) { 1673 if (!mHasUsedVoiceInput) { 1674 // The user has started a voice input, so remember that in the 1675 // future (so we don't show the warning dialog after the first run). 1676 SharedPreferences.Editor editor = 1677 PreferenceManager.getDefaultSharedPreferences(this).edit(); 1678 editor.putBoolean(PREF_HAS_USED_VOICE_INPUT, true); 1679 SharedPreferencesCompat.apply(editor); 1680 mHasUsedVoiceInput = true; 1681 } 1682 1683 if (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale) { 1684 // The user has started a voice input from an unsupported locale, so remember that 1685 // in the future (so we don't show the warning dialog the next time they do this). 1686 SharedPreferences.Editor editor = 1687 PreferenceManager.getDefaultSharedPreferences(this).edit(); 1688 editor.putBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, true); 1689 SharedPreferencesCompat.apply(editor); 1690 mHasUsedVoiceInputUnsupportedLocale = true; 1691 } 1692 1693 // Clear N-best suggestions 1694 clearSuggestions(); 1695 1696 FieldContext context = new FieldContext( 1697 getCurrentInputConnection(), 1698 getCurrentInputEditorInfo(), 1699 mLanguageSwitcher.getInputLanguage(), 1700 mLanguageSwitcher.getEnabledLanguages()); 1701 mVoiceInput.startListening(context, swipe); 1702 switchToRecognitionStatusView(); 1703 } 1704 1705 private void showVoiceWarningDialog(final boolean swipe) { 1706 AlertDialog.Builder builder = new AlertDialog.Builder(this); 1707 builder.setCancelable(true); 1708 builder.setIcon(R.drawable.ic_mic_dialog); 1709 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 1710 public void onClick(DialogInterface dialog, int whichButton) { 1711 mVoiceInput.logKeyboardWarningDialogOk(); 1712 reallyStartListening(swipe); 1713 } 1714 }); 1715 builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { 1716 public void onClick(DialogInterface dialog, int whichButton) { 1717 mVoiceInput.logKeyboardWarningDialogCancel(); 1718 } 1719 }); 1720 1721 if (mLocaleSupportedForVoiceInput) { 1722 String message = getString(R.string.voice_warning_may_not_understand) + "\n\n" + 1723 getString(R.string.voice_warning_how_to_turn_off); 1724 builder.setMessage(message); 1725 } else { 1726 String message = getString(R.string.voice_warning_locale_not_supported) + "\n\n" + 1727 getString(R.string.voice_warning_may_not_understand) + "\n\n" + 1728 getString(R.string.voice_warning_how_to_turn_off); 1729 builder.setMessage(message); 1730 } 1731 1732 builder.setTitle(R.string.voice_warning_title); 1733 mVoiceWarningDialog = builder.create(); 1734 1735 Window window = mVoiceWarningDialog.getWindow(); 1736 WindowManager.LayoutParams lp = window.getAttributes(); 1737 lp.token = mKeyboardSwitcher.getInputView().getWindowToken(); 1738 lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; 1739 window.setAttributes(lp); 1740 window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 1741 mVoiceInput.logKeyboardWarningDialogShown(); 1742 mVoiceWarningDialog.show(); 1743 } 1744 1745 public void onVoiceResults(List<String> candidates, 1746 Map<String, List<CharSequence>> alternatives) { 1747 if (!mRecognizing) { 1748 return; 1749 } 1750 mVoiceResults.candidates = candidates; 1751 mVoiceResults.alternatives = alternatives; 1752 mHandler.updateVoiceResults(); 1753 } 1754 1755 private void handleVoiceResults() { 1756 mAfterVoiceInput = true; 1757 mImmediatelyAfterVoiceInput = true; 1758 1759 InputConnection ic = getCurrentInputConnection(); 1760 if (!isFullscreenMode()) { 1761 // Start listening for updates to the text from typing, etc. 1762 if (ic != null) { 1763 ExtractedTextRequest req = new ExtractedTextRequest(); 1764 ic.getExtractedText(req, InputConnection.GET_EXTRACTED_TEXT_MONITOR); 1765 } 1766 } 1767 1768 vibrate(); 1769 switchToKeyboardView(); 1770 1771 final List<CharSequence> nBest = new ArrayList<CharSequence>(); 1772 KeyboardSwitcher switcher = mKeyboardSwitcher; 1773 boolean capitalizeFirstWord = preferCapitalization() 1774 || (switcher.isAlphabetMode() && switcher.isShifted()); 1775 for (String c : mVoiceResults.candidates) { 1776 if (capitalizeFirstWord) { 1777 c = Character.toUpperCase(c.charAt(0)) + c.substring(1, c.length()); 1778 } 1779 nBest.add(c); 1780 } 1781 1782 if (nBest.size() == 0) { 1783 return; 1784 } 1785 1786 String bestResult = nBest.get(0).toString(); 1787 1788 mVoiceInput.logVoiceInputDelivered(bestResult.length()); 1789 1790 mHints.registerVoiceResult(bestResult); 1791 1792 if (ic != null) ic.beginBatchEdit(); // To avoid extra updates on committing older text 1793 1794 commitTyped(ic); 1795 EditingUtil.appendText(ic, bestResult); 1796 1797 if (ic != null) ic.endBatchEdit(); 1798 1799 mVoiceInputHighlighted = true; 1800 mWordToSuggestions.putAll(mVoiceResults.alternatives); 1801 } 1802 1803 private void clearSuggestions() { 1804 setSuggestions(null, false, false, false); 1805 } 1806 1807 private void setSuggestions( 1808 List<CharSequence> suggestions, 1809 boolean completions, 1810 boolean typedWordValid, 1811 boolean haveMinimalSuggestion) { 1812 1813 if (mIsShowingHint) { 1814 setCandidatesView(mCandidateViewContainer); 1815 mIsShowingHint = false; 1816 } 1817 1818 if (mCandidateView != null) { 1819 mCandidateView.setSuggestions( 1820 suggestions, completions, typedWordValid, haveMinimalSuggestion); 1821 } 1822 } 1823 1824 private void updateSuggestions() { 1825 mKeyboardSwitcher.setPreferredLetters(null); 1826 1827 // Check if we have a suggestion engine attached. 1828 if ((mSuggest == null || !isPredictionOn()) && !mVoiceInputHighlighted) { 1829 return; 1830 } 1831 1832 if (!mPredicting) { 1833 setPunctuationSuggestions(); 1834 return; 1835 } 1836 showSuggestions(mWord); 1837 } 1838 1839 private List<CharSequence> getTypedSuggestions(WordComposer word) { 1840 List<CharSequence> stringList = mSuggest.getSuggestions( 1841 mKeyboardSwitcher.getInputView(), word, false, null); 1842 return stringList; 1843 } 1844 1845 private void showCorrections(WordAlternatives alternatives) { 1846 mKeyboardSwitcher.setPreferredLetters(null); 1847 List<CharSequence> stringList = alternatives.getAlternatives(); 1848 showSuggestions(stringList, alternatives.getOriginalWord(), false, false); 1849 } 1850 1851 private void showSuggestions(WordComposer word) { 1852 // long startTime = System.currentTimeMillis(); // TIME MEASUREMENT! 1853 // TODO Maybe need better way of retrieving previous word 1854 CharSequence prevWord = EditingUtil.getPreviousWord(getCurrentInputConnection(), 1855 mWordSeparators); 1856 List<CharSequence> stringList = mSuggest.getSuggestions( 1857 mKeyboardSwitcher.getInputView(), word, false, prevWord); 1858 // long stopTime = System.currentTimeMillis(); // TIME MEASUREMENT! 1859 // Log.d("LatinIME","Suggest Total Time - " + (stopTime - startTime)); 1860 1861 int[] nextLettersFrequencies = mSuggest.getNextLettersFrequencies(); 1862 mKeyboardSwitcher.setPreferredLetters(nextLettersFrequencies); 1863 1864 boolean correctionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasMinimalCorrection(); 1865 //|| mCorrectionMode == mSuggest.CORRECTION_FULL; 1866 CharSequence typedWord = word.getTypedWord(); 1867 // If we're in basic correct 1868 boolean typedWordValid = mSuggest.isValidWord(typedWord) || 1869 (preferCapitalization() 1870 && mSuggest.isValidWord(typedWord.toString().toLowerCase())); 1871 if (mCorrectionMode == Suggest.CORRECTION_FULL 1872 || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) { 1873 correctionAvailable |= typedWordValid; 1874 } 1875 // Don't auto-correct words with multiple capital letter 1876 correctionAvailable &= !word.isMostlyCaps(); 1877 correctionAvailable &= !TextEntryState.isCorrecting(); 1878 1879 showSuggestions(stringList, typedWord, typedWordValid, correctionAvailable); 1880 } 1881 1882 private void showSuggestions(List<CharSequence> stringList, CharSequence typedWord, 1883 boolean typedWordValid, boolean correctionAvailable) { 1884 setSuggestions(stringList, false, typedWordValid, correctionAvailable); 1885 if (stringList.size() > 0) { 1886 if (correctionAvailable && !typedWordValid && stringList.size() > 1) { 1887 mBestWord = stringList.get(1); 1888 } else { 1889 mBestWord = typedWord; 1890 } 1891 } else { 1892 mBestWord = null; 1893 } 1894 setCandidatesViewShown(isCandidateStripVisible()); 1895 } 1896 1897 private boolean pickDefaultSuggestion() { 1898 // Complete any pending candidate query first 1899 if (mHandler.hasPendingUpdateSuggestions()) { 1900 mHandler.cancelUpdateSuggestions(); 1901 updateSuggestions(); 1902 } 1903 if (mBestWord != null && mBestWord.length() > 0) { 1904 TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord); 1905 mJustAccepted = true; 1906 pickSuggestion(mBestWord, false); 1907 // Add the word to the auto dictionary if it's not a known word 1908 addToDictionaries(mBestWord, AutoDictionary.FREQUENCY_FOR_TYPED); 1909 return true; 1910 1911 } 1912 return false; 1913 } 1914 1915 public void pickSuggestionManually(int index, CharSequence suggestion) { 1916 List<CharSequence> suggestions = mCandidateView.getSuggestions(); 1917 if (mAfterVoiceInput && mShowingVoiceSuggestions) { 1918 mVoiceInput.flushAllTextModificationCounters(); 1919 // send this intent AFTER logging any prior aggregated edits. 1920 mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.toString(), index, 1921 mWordSeparators, 1922 getCurrentInputConnection()); 1923 } 1924 1925 final boolean correcting = TextEntryState.isCorrecting(); 1926 InputConnection ic = getCurrentInputConnection(); 1927 if (ic != null) { 1928 ic.beginBatchEdit(); 1929 } 1930 if (mCompletionOn && mCompletions != null && index >= 0 1931 && index < mCompletions.length) { 1932 CompletionInfo ci = mCompletions[index]; 1933 if (ic != null) { 1934 ic.commitCompletion(ci); 1935 } 1936 mCommittedLength = suggestion.length(); 1937 if (mCandidateView != null) { 1938 mCandidateView.clear(); 1939 } 1940 updateShiftKeyState(getCurrentInputEditorInfo()); 1941 if (ic != null) { 1942 ic.endBatchEdit(); 1943 } 1944 return; 1945 } 1946 1947 // If this is a punctuation, apply it through the normal key press 1948 if (suggestion.length() == 1 && (isWordSeparator(suggestion.charAt(0)) 1949 || isSuggestedPunctuation(suggestion.charAt(0)))) { 1950 // Word separators are suggested before the user inputs something. 1951 // So, LatinImeLogger logs "" as a user's input. 1952 LatinImeLogger.logOnManualSuggestion( 1953 "", suggestion.toString(), index, suggestions); 1954 final char primaryCode = suggestion.charAt(0); 1955 onKey(primaryCode, new int[]{primaryCode}, BaseKeyboardView.NOT_A_TOUCH_COORDINATE, 1956 BaseKeyboardView.NOT_A_TOUCH_COORDINATE); 1957 if (ic != null) { 1958 ic.endBatchEdit(); 1959 } 1960 return; 1961 } 1962 mJustAccepted = true; 1963 pickSuggestion(suggestion, correcting); 1964 // Add the word to the auto dictionary if it's not a known word 1965 if (index == 0) { 1966 addToDictionaries(suggestion, AutoDictionary.FREQUENCY_FOR_PICKED); 1967 } else { 1968 addToBigramDictionary(suggestion, 1); 1969 } 1970 LatinImeLogger.logOnManualSuggestion(mComposing.toString(), suggestion.toString(), 1971 index, suggestions); 1972 TextEntryState.acceptedSuggestion(mComposing.toString(), suggestion); 1973 // Follow it with a space 1974 if (mAutoSpace && !correcting) { 1975 sendSpace(); 1976 mJustAddedAutoSpace = true; 1977 } 1978 1979 final boolean showingAddToDictionaryHint = index == 0 && mCorrectionMode > 0 1980 && !mSuggest.isValidWord(suggestion) 1981 && !mSuggest.isValidWord(suggestion.toString().toLowerCase()); 1982 1983 if (!correcting) { 1984 // Fool the state watcher so that a subsequent backspace will not do a revert, unless 1985 // we just did a correction, in which case we need to stay in 1986 // TextEntryState.State.PICKED_SUGGESTION state. 1987 TextEntryState.typedCharacter((char) KEYCODE_SPACE, true); 1988 setPunctuationSuggestions(); 1989 } else if (!showingAddToDictionaryHint) { 1990 // If we're not showing the "Touch again to save", then show corrections again. 1991 // In case the cursor position doesn't change, make sure we show the suggestions again. 1992 clearSuggestions(); 1993 mHandler.postUpdateOldSuggestions(); 1994 } 1995 if (showingAddToDictionaryHint) { 1996 mCandidateView.showAddToDictionaryHint(suggestion); 1997 } 1998 if (ic != null) { 1999 ic.endBatchEdit(); 2000 } 2001 } 2002 2003 private void rememberReplacedWord(CharSequence suggestion) { 2004 if (mShowingVoiceSuggestions) { 2005 // Retain the replaced word in the alternatives array. 2006 EditingUtil.Range range = new EditingUtil.Range(); 2007 String wordToBeReplaced = EditingUtil.getWordAtCursor(getCurrentInputConnection(), 2008 mWordSeparators, range); 2009 if (!mWordToSuggestions.containsKey(wordToBeReplaced)) { 2010 wordToBeReplaced = wordToBeReplaced.toLowerCase(); 2011 } 2012 if (mWordToSuggestions.containsKey(wordToBeReplaced)) { 2013 List<CharSequence> suggestions = mWordToSuggestions.get(wordToBeReplaced); 2014 if (suggestions.contains(suggestion)) { 2015 suggestions.remove(suggestion); 2016 } 2017 suggestions.add(wordToBeReplaced); 2018 mWordToSuggestions.remove(wordToBeReplaced); 2019 mWordToSuggestions.put(suggestion.toString(), suggestions); 2020 } 2021 } 2022 } 2023 2024 /** 2025 * Commits the chosen word to the text field and saves it for later 2026 * retrieval. 2027 * @param suggestion the suggestion picked by the user to be committed to 2028 * the text field 2029 * @param correcting whether this is due to a correction of an existing 2030 * word. 2031 */ 2032 private void pickSuggestion(CharSequence suggestion, boolean correcting) { 2033 if (!mKeyboardSwitcher.isKeyboardAvailable()) 2034 return; 2035 InputConnection ic = getCurrentInputConnection(); 2036 if (ic != null) { 2037 rememberReplacedWord(suggestion); 2038 ic.commitText(suggestion, 1); 2039 } 2040 saveWordInHistory(suggestion); 2041 mPredicting = false; 2042 mCommittedLength = suggestion.length(); 2043 mKeyboardSwitcher.setPreferredLetters(null); 2044 // If we just corrected a word, then don't show punctuations 2045 if (!correcting) { 2046 setPunctuationSuggestions(); 2047 } 2048 updateShiftKeyState(getCurrentInputEditorInfo()); 2049 } 2050 2051 /** 2052 * Tries to apply any voice alternatives for the word if this was a spoken word and 2053 * there are voice alternatives. 2054 * @param touching The word that the cursor is touching, with position information 2055 * @return true if an alternative was found, false otherwise. 2056 */ 2057 private boolean applyVoiceAlternatives(EditingUtil.SelectedWord touching) { 2058 // Search for result in spoken word alternatives 2059 String selectedWord = touching.word.toString().trim(); 2060 if (!mWordToSuggestions.containsKey(selectedWord)) { 2061 selectedWord = selectedWord.toLowerCase(); 2062 } 2063 if (mWordToSuggestions.containsKey(selectedWord)) { 2064 mShowingVoiceSuggestions = true; 2065 List<CharSequence> suggestions = mWordToSuggestions.get(selectedWord); 2066 // If the first letter of touching is capitalized, make all the suggestions 2067 // start with a capital letter. 2068 if (Character.isUpperCase(touching.word.charAt(0))) { 2069 for (int i = 0; i < suggestions.size(); i++) { 2070 String origSugg = (String) suggestions.get(i); 2071 String capsSugg = origSugg.toUpperCase().charAt(0) 2072 + origSugg.subSequence(1, origSugg.length()).toString(); 2073 suggestions.set(i, capsSugg); 2074 } 2075 } 2076 setSuggestions(suggestions, false, true, true); 2077 setCandidatesViewShown(true); 2078 return true; 2079 } 2080 return false; 2081 } 2082 2083 /** 2084 * Tries to apply any typed alternatives for the word if we have any cached alternatives, 2085 * otherwise tries to find new corrections and completions for the word. 2086 * @param touching The word that the cursor is touching, with position information 2087 * @return true if an alternative was found, false otherwise. 2088 */ 2089 private boolean applyTypedAlternatives(EditingUtil.SelectedWord touching) { 2090 // If we didn't find a match, search for result in typed word history 2091 WordComposer foundWord = null; 2092 WordAlternatives alternatives = null; 2093 for (WordAlternatives entry : mWordHistory) { 2094 if (TextUtils.equals(entry.getChosenWord(), touching.word)) { 2095 if (entry instanceof TypedWordAlternatives) { 2096 foundWord = ((TypedWordAlternatives) entry).word; 2097 } 2098 alternatives = entry; 2099 break; 2100 } 2101 } 2102 // If we didn't find a match, at least suggest completions 2103 if (foundWord == null 2104 && (mSuggest.isValidWord(touching.word) 2105 || mSuggest.isValidWord(touching.word.toString().toLowerCase()))) { 2106 foundWord = new WordComposer(); 2107 for (int i = 0; i < touching.word.length(); i++) { 2108 foundWord.add(touching.word.charAt(i), new int[] { 2109 touching.word.charAt(i) 2110 }); 2111 } 2112 foundWord.setFirstCharCapitalized(Character.isUpperCase(touching.word.charAt(0))); 2113 } 2114 // Found a match, show suggestions 2115 if (foundWord != null || alternatives != null) { 2116 if (alternatives == null) { 2117 alternatives = new TypedWordAlternatives(touching.word, foundWord); 2118 } 2119 showCorrections(alternatives); 2120 if (foundWord != null) { 2121 mWord = new WordComposer(foundWord); 2122 } else { 2123 mWord.reset(); 2124 } 2125 return true; 2126 } 2127 return false; 2128 } 2129 2130 private void setOldSuggestions() { 2131 mShowingVoiceSuggestions = false; 2132 if (mCandidateView != null && mCandidateView.isShowingAddToDictionaryHint()) { 2133 return; 2134 } 2135 InputConnection ic = getCurrentInputConnection(); 2136 if (ic == null) return; 2137 if (!mPredicting) { 2138 // Extract the selected or touching text 2139 EditingUtil.SelectedWord touching = EditingUtil.getWordAtCursorOrSelection(ic, 2140 mLastSelectionStart, mLastSelectionEnd, mWordSeparators); 2141 2142 if (touching != null && touching.word.length() > 1) { 2143 ic.beginBatchEdit(); 2144 2145 if (!applyVoiceAlternatives(touching) && !applyTypedAlternatives(touching)) { 2146 abortCorrection(true); 2147 } else { 2148 TextEntryState.selectedForCorrection(); 2149 EditingUtil.underlineWord(ic, touching); 2150 } 2151 2152 ic.endBatchEdit(); 2153 } else { 2154 abortCorrection(true); 2155 setPunctuationSuggestions(); // Show the punctuation suggestions list 2156 } 2157 } else { 2158 abortCorrection(true); 2159 } 2160 } 2161 2162 private void setPunctuationSuggestions() { 2163 setCandidatesViewShown(isCandidateStripVisible()); 2164 setSuggestions(mSuggestPuncList, false, false, false); 2165 } 2166 2167 private void addToDictionaries(CharSequence suggestion, int frequencyDelta) { 2168 checkAddToDictionary(suggestion, frequencyDelta, false); 2169 } 2170 2171 private void addToBigramDictionary(CharSequence suggestion, int frequencyDelta) { 2172 checkAddToDictionary(suggestion, frequencyDelta, true); 2173 } 2174 2175 /** 2176 * Adds to the UserBigramDictionary and/or AutoDictionary 2177 * @param addToBigramDictionary true if it should be added to bigram dictionary if possible 2178 */ 2179 private void checkAddToDictionary(CharSequence suggestion, int frequencyDelta, 2180 boolean addToBigramDictionary) { 2181 if (suggestion == null || suggestion.length() < 1) return; 2182 // Only auto-add to dictionary if auto-correct is ON. Otherwise we'll be 2183 // adding words in situations where the user or application really didn't 2184 // want corrections enabled or learned. 2185 if (!(mCorrectionMode == Suggest.CORRECTION_FULL 2186 || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM)) { 2187 return; 2188 } 2189 if (suggestion != null) { 2190 if (!addToBigramDictionary && mAutoDictionary.isValidWord(suggestion) 2191 || (!mSuggest.isValidWord(suggestion.toString()) 2192 && !mSuggest.isValidWord(suggestion.toString().toLowerCase()))) { 2193 mAutoDictionary.addWord(suggestion.toString(), frequencyDelta); 2194 } 2195 2196 if (mUserBigramDictionary != null) { 2197 CharSequence prevWord = EditingUtil.getPreviousWord(getCurrentInputConnection(), 2198 mSentenceSeparators); 2199 if (!TextUtils.isEmpty(prevWord)) { 2200 mUserBigramDictionary.addBigrams(prevWord.toString(), suggestion.toString()); 2201 } 2202 } 2203 } 2204 } 2205 2206 private boolean isCursorTouchingWord() { 2207 InputConnection ic = getCurrentInputConnection(); 2208 if (ic == null) return false; 2209 CharSequence toLeft = ic.getTextBeforeCursor(1, 0); 2210 CharSequence toRight = ic.getTextAfterCursor(1, 0); 2211 if (!TextUtils.isEmpty(toLeft) 2212 && !isWordSeparator(toLeft.charAt(0)) 2213 && !isSuggestedPunctuation(toLeft.charAt(0))) { 2214 return true; 2215 } 2216 if (!TextUtils.isEmpty(toRight) 2217 && !isWordSeparator(toRight.charAt(0)) 2218 && !isSuggestedPunctuation(toRight.charAt(0))) { 2219 return true; 2220 } 2221 return false; 2222 } 2223 2224 private boolean sameAsTextBeforeCursor(InputConnection ic, CharSequence text) { 2225 CharSequence beforeText = ic.getTextBeforeCursor(text.length(), 0); 2226 return TextUtils.equals(text, beforeText); 2227 } 2228 2229 public void revertLastWord(boolean deleteChar) { 2230 final int length = mComposing.length(); 2231 if (!mPredicting && length > 0) { 2232 final InputConnection ic = getCurrentInputConnection(); 2233 mPredicting = true; 2234 mJustReverted = true; 2235 if (deleteChar) ic.deleteSurroundingText(1, 0); 2236 int toDelete = mCommittedLength; 2237 CharSequence toTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0); 2238 if (toTheLeft != null && toTheLeft.length() > 0 2239 && isWordSeparator(toTheLeft.charAt(0))) { 2240 toDelete--; 2241 } 2242 ic.deleteSurroundingText(toDelete, 0); 2243 ic.setComposingText(mComposing, 1); 2244 TextEntryState.backspace(); 2245 mHandler.postUpdateSuggestions(); 2246 } else { 2247 sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); 2248 mJustReverted = false; 2249 } 2250 } 2251 2252 protected String getWordSeparators() { 2253 return mWordSeparators; 2254 } 2255 2256 public boolean isWordSeparator(int code) { 2257 String separators = getWordSeparators(); 2258 return separators.contains(String.valueOf((char)code)); 2259 } 2260 2261 private boolean isSentenceSeparator(int code) { 2262 return mSentenceSeparators.contains(String.valueOf((char)code)); 2263 } 2264 2265 private void sendSpace() { 2266 sendKeyChar((char)KEYCODE_SPACE); 2267 updateShiftKeyState(getCurrentInputEditorInfo()); 2268 //onKey(KEY_SPACE[0], KEY_SPACE); 2269 } 2270 2271 public boolean preferCapitalization() { 2272 return mWord.isFirstCharCapitalized(); 2273 } 2274 2275 private void toggleLanguage(boolean reset, boolean next) { 2276 if (reset) { 2277 mLanguageSwitcher.reset(); 2278 } else { 2279 if (next) { 2280 mLanguageSwitcher.next(); 2281 } else { 2282 mLanguageSwitcher.prev(); 2283 } 2284 } 2285 final int mode = mKeyboardSwitcher.getKeyboardMode(); 2286 final EditorInfo attribute = getCurrentInputEditorInfo(); 2287 final int imeOptions = (attribute != null) ? attribute.imeOptions : 0; 2288 mKeyboardSwitcher.loadKeyboard(mode, imeOptions, mVoiceButtonEnabled, 2289 mVoiceButtonOnPrimary); 2290 initSuggest(mLanguageSwitcher.getInputLanguage()); 2291 mLanguageSwitcher.persist(); 2292 updateShiftKeyState(getCurrentInputEditorInfo()); 2293 } 2294 2295 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, 2296 String key) { 2297 if (PREF_SELECTED_LANGUAGES.equals(key)) { 2298 mLanguageSwitcher.loadLocales(sharedPreferences); 2299 mRefreshKeyboardRequired = true; 2300 } else if (PREF_RECORRECTION_ENABLED.equals(key)) { 2301 mReCorrectionEnabled = sharedPreferences.getBoolean(PREF_RECORRECTION_ENABLED, 2302 getResources().getBoolean(R.bool.default_recorrection_enabled)); 2303 } 2304 } 2305 2306 public void swipeRight() { 2307 if (LatinKeyboardView.DEBUG_AUTO_PLAY) { 2308 ClipboardManager cm = ((ClipboardManager)getSystemService(CLIPBOARD_SERVICE)); 2309 CharSequence text = cm.getText(); 2310 if (!TextUtils.isEmpty(text)) { 2311 mKeyboardSwitcher.getInputView().startPlaying(text.toString()); 2312 } 2313 } 2314 } 2315 2316 public void swipeLeft() { 2317 } 2318 2319 public void swipeDown() { 2320 handleClose(); 2321 } 2322 2323 public void swipeUp() { 2324 //launchSettings(); 2325 } 2326 2327 public void onPress(int primaryCode) { 2328 vibrate(); 2329 playKeyClick(primaryCode); 2330 KeyboardSwitcher switcher = mKeyboardSwitcher; 2331 if (!switcher.isKeyboardAvailable()) 2332 return; 2333 final boolean distinctMultiTouch = switcher.hasDistinctMultitouch(); 2334 if (distinctMultiTouch && primaryCode == BaseKeyboard.KEYCODE_SHIFT) { 2335 // In alphabet mode, we call handleShift() to go into the shifted mode in this 2336 // method, onPress(), only when we are in the small letter mode. 2337 if (switcher.isAlphabetMode() && switcher.isShifted()) { 2338 switcher.onPressShiftOnShifted(); 2339 } else { 2340 switcher.onPressShift(); 2341 handleShift(); 2342 } 2343 } else if (distinctMultiTouch && primaryCode == BaseKeyboard.KEYCODE_MODE_CHANGE) { 2344 switcher.onPressSymbol(); 2345 changeKeyboardMode(); 2346 } else { 2347 switcher.onOtherKeyPressed(); 2348 } 2349 } 2350 2351 public void onRelease(int primaryCode) { 2352 KeyboardSwitcher switcher = mKeyboardSwitcher; 2353 if (!switcher.isKeyboardAvailable()) 2354 return; 2355 // Reset any drag flags in the keyboard 2356 switcher.keyReleased(); 2357 final boolean distinctMultiTouch = switcher.hasDistinctMultitouch(); 2358 if (distinctMultiTouch && primaryCode == BaseKeyboard.KEYCODE_SHIFT) { 2359 if (switcher.isShiftMomentary()) { 2360 resetShift(); 2361 } 2362 if (switcher.isAlphabetMode()) { 2363 // In alphabet mode, we call handleShift() to go into the small letter mode in this 2364 // method, onRelease(), only when we are in the shifted modes -- temporary shifted 2365 // mode or caps lock mode. 2366 if (switcher.isShifted() && switcher.isShiftPressingOnShifted()) { 2367 handleShift(); 2368 } 2369 } 2370 switcher.onReleaseShift(); 2371 } else if (distinctMultiTouch && primaryCode == BaseKeyboard.KEYCODE_MODE_CHANGE) { 2372 if (switcher.isSymbolMomentary()) { 2373 changeKeyboardMode(); 2374 } 2375 switcher.onReleaseSymbol(); 2376 } 2377 } 2378 2379 private FieldContext makeFieldContext() { 2380 return new FieldContext( 2381 getCurrentInputConnection(), 2382 getCurrentInputEditorInfo(), 2383 mLanguageSwitcher.getInputLanguage(), 2384 mLanguageSwitcher.getEnabledLanguages()); 2385 } 2386 2387 private boolean fieldCanDoVoice(FieldContext fieldContext) { 2388 return !mPasswordText 2389 && mVoiceInput != null 2390 && !mVoiceInput.isBlacklistedField(fieldContext); 2391 } 2392 2393 private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) { 2394 return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext) 2395 && !(attribute != null 2396 && IME_OPTION_NO_MICROPHONE.equals(attribute.privateImeOptions)) 2397 && SpeechRecognizer.isRecognitionAvailable(this); 2398 } 2399 2400 // receive ringer mode changes to detect silent mode 2401 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 2402 @Override 2403 public void onReceive(Context context, Intent intent) { 2404 updateRingerMode(); 2405 } 2406 }; 2407 2408 // update flags for silent mode 2409 private void updateRingerMode() { 2410 if (mAudioManager == null) { 2411 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 2412 } 2413 if (mAudioManager != null) { 2414 mSilentMode = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL); 2415 } 2416 } 2417 2418 private void playKeyClick(int primaryCode) { 2419 // if mAudioManager is null, we don't have the ringer state yet 2420 // mAudioManager will be set by updateRingerMode 2421 if (mAudioManager == null) { 2422 if (mKeyboardSwitcher.getInputView() != null) { 2423 updateRingerMode(); 2424 } 2425 } 2426 if (mSoundOn && !mSilentMode) { 2427 // FIXME: Volume and enable should come from UI settings 2428 // FIXME: These should be triggered after auto-repeat logic 2429 int sound = AudioManager.FX_KEYPRESS_STANDARD; 2430 switch (primaryCode) { 2431 case BaseKeyboard.KEYCODE_DELETE: 2432 sound = AudioManager.FX_KEYPRESS_DELETE; 2433 break; 2434 case KEYCODE_ENTER: 2435 sound = AudioManager.FX_KEYPRESS_RETURN; 2436 break; 2437 case KEYCODE_SPACE: 2438 sound = AudioManager.FX_KEYPRESS_SPACEBAR; 2439 break; 2440 } 2441 mAudioManager.playSoundEffect(sound, FX_VOLUME); 2442 } 2443 } 2444 2445 private void vibrate() { 2446 if (!mVibrateOn) { 2447 return; 2448 } 2449 LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); 2450 if (inputView != null) { 2451 inputView.performHapticFeedback( 2452 HapticFeedbackConstants.KEYBOARD_TAP, 2453 HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); 2454 } 2455 } 2456 2457 private void checkTutorial(String privateImeOptions) { 2458 if (privateImeOptions == null) return; 2459 if (privateImeOptions.equals("com.android.setupwizard:ShowTutorial")) { 2460 if (mTutorial == null) mHandler.startTutorial(); 2461 } else if (privateImeOptions.equals("com.android.setupwizard:HideTutorial")) { 2462 if (mTutorial != null) { 2463 if (mTutorial.close()) { 2464 mTutorial = null; 2465 } 2466 } 2467 } 2468 } 2469 2470 // Tutorial.TutorialListener 2471 public void onTutorialDone() { 2472 sendDownUpKeyEvents(-1); // Inform the setupwizard that tutorial is in last bubble 2473 mTutorial = null; 2474 } 2475 2476 public void promoteToUserDictionary(String word, int frequency) { 2477 if (mUserDictionary.isValidWord(word)) return; 2478 mUserDictionary.addWord(word, frequency); 2479 } 2480 2481 public WordComposer getCurrentWord() { 2482 return mWord; 2483 } 2484 2485 public boolean getPopupOn() { 2486 return mPopupOn; 2487 } 2488 2489 private void updateCorrectionMode() { 2490 mHasDictionary = mSuggest != null ? mSuggest.hasMainDictionary() : false; 2491 mAutoCorrectOn = (mAutoCorrectEnabled || mQuickFixes) 2492 && !mInputTypeNoAutoCorrect && mHasDictionary; 2493 mCorrectionMode = (mAutoCorrectOn && mAutoCorrectEnabled) 2494 ? Suggest.CORRECTION_FULL 2495 : (mAutoCorrectOn ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE); 2496 mCorrectionMode = (mBigramSuggestionEnabled && mAutoCorrectOn && mAutoCorrectEnabled) 2497 ? Suggest.CORRECTION_FULL_BIGRAM : mCorrectionMode; 2498 if (mSuggest != null) { 2499 mSuggest.setCorrectionMode(mCorrectionMode); 2500 } 2501 } 2502 2503 private void updateAutoTextEnabled(Locale systemLocale) { 2504 if (mSuggest == null) return; 2505 boolean different = 2506 !systemLocale.getLanguage().equalsIgnoreCase(mInputLocale.substring(0, 2)); 2507 mSuggest.setAutoTextEnabled(!different && mQuickFixes); 2508 } 2509 2510 private void updateSuggestionVisibility(SharedPreferences prefs) { 2511 final String suggestionVisiblityStr = prefs.getString( 2512 PREF_SHOW_SUGGESTIONS_SETTING, mResources.getString( 2513 R.string.prefs_suggestion_visibility_default_value)); 2514 for (int visibility : SUGGESTION_VISIBILITY_VALUE_ARRAY) { 2515 if (suggestionVisiblityStr.equals(mResources.getString(visibility))) { 2516 mSuggestionVisibility = visibility; 2517 break; 2518 } 2519 } 2520 } 2521 2522 protected void launchSettings() { 2523 launchSettings(LatinIMESettings.class); 2524 } 2525 2526 public void launchDebugSettings() { 2527 launchSettings(LatinIMEDebugSettings.class); 2528 } 2529 2530 protected void launchSettings(Class<? extends PreferenceActivity> settingsClass) { 2531 handleClose(); 2532 Intent intent = new Intent(); 2533 intent.setClass(LatinIME.this, settingsClass); 2534 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 2535 startActivity(intent); 2536 } 2537 2538 private void loadSettings(EditorInfo attribute) { 2539 // Get the settings preferences 2540 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); 2541 Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); 2542 mVibrateOn = vibrator != null && vibrator.hasVibrator() 2543 && sp.getBoolean(LatinIMESettings.PREF_VIBRATE_ON, false); 2544 mSoundOn = sp.getBoolean(PREF_SOUND_ON, false); 2545 mPopupOn = sp.getBoolean(PREF_POPUP_ON, 2546 mResources.getBoolean(R.bool.default_popup_preview)); 2547 mAutoCap = sp.getBoolean(PREF_AUTO_CAP, true); 2548 mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true); 2549 mHasUsedVoiceInput = sp.getBoolean(PREF_HAS_USED_VOICE_INPUT, false); 2550 mHasUsedVoiceInputUnsupportedLocale = 2551 sp.getBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, false); 2552 2553 // Get the current list of supported locales and check the current locale against that 2554 // list. We cache this value so as not to check it every time the user starts a voice 2555 // input. Because this method is called by onStartInputView, this should mean that as 2556 // long as the locale doesn't change while the user is keeping the IME open, the 2557 // value should never be stale. 2558 String supportedLocalesString = SettingsUtil.getSettingsString( 2559 getContentResolver(), 2560 SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES, 2561 DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES); 2562 ArrayList<String> voiceInputSupportedLocales = 2563 newArrayList(supportedLocalesString.split("\\s+")); 2564 2565 mLocaleSupportedForVoiceInput = voiceInputSupportedLocales.contains(mInputLocale); 2566 2567 mAutoCorrectEnabled = isAutoCorrectEnabled(sp); 2568 mBigramSuggestionEnabled = mAutoCorrectEnabled && isBigramSuggestionEnabled(sp); 2569 loadAndSetAutoCompletionThreshold(sp); 2570 2571 if (VOICE_INSTALLED) { 2572 final String voiceMode = sp.getString(PREF_VOICE_MODE, 2573 getString(R.string.voice_mode_main)); 2574 mVoiceButtonEnabled = !voiceMode.equals(getString(R.string.voice_mode_off)) 2575 && shouldShowVoiceButton(makeFieldContext(), attribute); 2576 mVoiceButtonOnPrimary = voiceMode.equals(getString(R.string.voice_mode_main)); 2577 } 2578 updateCorrectionMode(); 2579 updateAutoTextEnabled(mResources.getConfiguration().locale); 2580 updateSuggestionVisibility(sp); 2581 mLanguageSwitcher.loadLocales(sp); 2582 } 2583 2584 /** 2585 * load Auto completion threshold from SharedPreferences, 2586 * and modify mSuggest's threshold. 2587 */ 2588 private void loadAndSetAutoCompletionThreshold(SharedPreferences sp) { 2589 // When mSuggest is not initialized, cannnot modify mSuggest's threshold. 2590 if (mSuggest == null) return; 2591 // When auto completion setting is turned off, the threshold is ignored. 2592 if (!isAutoCorrectEnabled(sp)) return; 2593 2594 final String currentAutoCompletionSetting = sp.getString(PREF_AUTO_COMPLETION_THRESHOLD, 2595 mResources.getString(R.string.auto_completion_threshold_mode_value_modest)); 2596 final String[] autoCompletionThresholdValues = mResources.getStringArray( 2597 R.array.auto_complete_threshold_values); 2598 // When autoCompletionThreshold is greater than 1.0, 2599 // auto completion is virtually turned off. 2600 double autoCompletionThreshold = Double.MAX_VALUE; 2601 try { 2602 final int arrayIndex = Integer.valueOf(currentAutoCompletionSetting); 2603 if (arrayIndex >= 0 && arrayIndex < autoCompletionThresholdValues.length) { 2604 autoCompletionThreshold = Double.parseDouble( 2605 autoCompletionThresholdValues[arrayIndex]); 2606 } 2607 } catch (NumberFormatException e) { 2608 // Whenever the threshold settings are correct, 2609 // never come here. 2610 autoCompletionThreshold = Double.MAX_VALUE; 2611 Log.w(TAG, "Cannot load auto completion threshold setting." 2612 + " currentAutoCompletionSetting: " + currentAutoCompletionSetting 2613 + ", autoCompletionThresholdValues: " 2614 + Arrays.toString(autoCompletionThresholdValues)); 2615 } 2616 // TODO: This should be refactored : 2617 // setAutoCompleteThreshold should be called outside of this method. 2618 mSuggest.setAutoCompleteThreshold(autoCompletionThreshold); 2619 } 2620 2621 private boolean isAutoCorrectEnabled(SharedPreferences sp) { 2622 final String currentAutoCompletionSetting = sp.getString(PREF_AUTO_COMPLETION_THRESHOLD, 2623 mResources.getString(R.string.auto_completion_threshold_mode_value_modest)); 2624 final String autoCompletionOff = mResources.getString( 2625 R.string.auto_completion_threshold_mode_value_off); 2626 return !currentAutoCompletionSetting.equals(autoCompletionOff); 2627 } 2628 2629 private boolean isBigramSuggestionEnabled(SharedPreferences sp) { 2630 // TODO: Define default value instead of 'true'. 2631 return sp.getBoolean(PREF_BIGRAM_SUGGESTIONS, true); 2632 } 2633 2634 private void initSuggestPuncList() { 2635 mSuggestPuncList = new ArrayList<CharSequence>(); 2636 mSuggestPuncs = mResources.getString(R.string.suggested_punctuations); 2637 if (mSuggestPuncs != null) { 2638 for (int i = 0; i < mSuggestPuncs.length(); i++) { 2639 mSuggestPuncList.add(mSuggestPuncs.subSequence(i, i + 1)); 2640 } 2641 } 2642 } 2643 2644 private boolean isSuggestedPunctuation(int code) { 2645 return mSuggestPuncs.contains(String.valueOf((char)code)); 2646 } 2647 2648 private void showOptionsMenu() { 2649 AlertDialog.Builder builder = new AlertDialog.Builder(this); 2650 builder.setCancelable(true); 2651 builder.setIcon(R.drawable.ic_dialog_keyboard); 2652 builder.setNegativeButton(android.R.string.cancel, null); 2653 CharSequence itemSettings = getString(R.string.english_ime_settings); 2654 CharSequence itemInputMethod = getString(R.string.selectInputMethod); 2655 builder.setItems(new CharSequence[] { 2656 itemInputMethod, itemSettings}, 2657 new DialogInterface.OnClickListener() { 2658 2659 public void onClick(DialogInterface di, int position) { 2660 di.dismiss(); 2661 switch (position) { 2662 case POS_SETTINGS: 2663 launchSettings(); 2664 break; 2665 case POS_METHOD: 2666 showInputMethodSubtypePicker(); 2667 break; 2668 } 2669 } 2670 }); 2671 builder.setTitle(mResources.getString(R.string.english_ime_input_options)); 2672 mOptionsDialog = builder.create(); 2673 Window window = mOptionsDialog.getWindow(); 2674 WindowManager.LayoutParams lp = window.getAttributes(); 2675 lp.token = mKeyboardSwitcher.getInputView().getWindowToken(); 2676 lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; 2677 window.setAttributes(lp); 2678 window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 2679 mOptionsDialog.show(); 2680 } 2681 2682 private void changeKeyboardMode() { 2683 KeyboardSwitcher switcher = mKeyboardSwitcher; 2684 switcher.toggleSymbols(); 2685 if (!switcher.isKeyboardAvailable()) 2686 return; 2687 if (switcher.isShiftLocked() && switcher.isAlphabetMode()) { 2688 switcher.setShiftLocked(true); 2689 } 2690 2691 updateShiftKeyState(getCurrentInputEditorInfo()); 2692 } 2693 2694 public static <E> ArrayList<E> newArrayList(E... elements) { 2695 int capacity = (elements.length * 110) / 100 + 5; 2696 ArrayList<E> list = new ArrayList<E>(capacity); 2697 Collections.addAll(list, elements); 2698 return list; 2699 } 2700 2701 @Override 2702 protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { 2703 super.dump(fd, fout, args); 2704 2705 final Printer p = new PrintWriterPrinter(fout); 2706 p.println("LatinIME state :"); 2707 p.println(" Keyboard mode = " + mKeyboardSwitcher.getKeyboardMode()); 2708 p.println(" mComposing=" + mComposing.toString()); 2709 p.println(" mPredictionOn=" + mPredictionOn); 2710 p.println(" mCorrectionMode=" + mCorrectionMode); 2711 p.println(" mPredicting=" + mPredicting); 2712 p.println(" mAutoCorrectOn=" + mAutoCorrectOn); 2713 p.println(" mAutoSpace=" + mAutoSpace); 2714 p.println(" mCompletionOn=" + mCompletionOn); 2715 p.println(" TextEntryState.state=" + TextEntryState.getState()); 2716 p.println(" mSoundOn=" + mSoundOn); 2717 p.println(" mVibrateOn=" + mVibrateOn); 2718 p.println(" mPopupOn=" + mPopupOn); 2719 } 2720 2721 // Characters per second measurement 2722 2723 private long mLastCpsTime; 2724 private static final int CPS_BUFFER_SIZE = 16; 2725 private long[] mCpsIntervals = new long[CPS_BUFFER_SIZE]; 2726 private int mCpsIndex; 2727 2728 private void measureCps() { 2729 long now = System.currentTimeMillis(); 2730 if (mLastCpsTime == 0) mLastCpsTime = now - 100; // Initial 2731 mCpsIntervals[mCpsIndex] = now - mLastCpsTime; 2732 mLastCpsTime = now; 2733 mCpsIndex = (mCpsIndex + 1) % CPS_BUFFER_SIZE; 2734 long total = 0; 2735 for (int i = 0; i < CPS_BUFFER_SIZE; i++) total += mCpsIntervals[i]; 2736 System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total)); 2737 } 2738 2739 public void onAutoCompletionStateChanged(boolean isAutoCompletion) { 2740 mKeyboardSwitcher.onAutoCompletionStateChanged(isAutoCompletion); 2741 } 2742} 2743