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