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