LatinIME.java revision fa12d86cb5162595b0b4791376be430ee1faffcc
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_METHOD = 0; 154 private static final int POS_SETTINGS = 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 public void onCancel() { 1216 // User released a finger outside any key 1217 } 1218 1219 private void handleBackspace() { 1220 if (VOICE_INSTALLED && mVoiceInputHighlighted) { 1221 mVoiceInput.incrementTextModificationDeleteCount( 1222 mVoiceResults.candidates.get(0).toString().length()); 1223 revertVoiceInput(); 1224 return; 1225 } 1226 boolean deleteChar = false; 1227 InputConnection ic = getCurrentInputConnection(); 1228 if (ic == null) return; 1229 1230 ic.beginBatchEdit(); 1231 1232 if (mAfterVoiceInput) { 1233 // Don't log delete if the user is pressing delete at 1234 // the beginning of the text box (hence not deleting anything) 1235 if (mVoiceInput.getCursorPos() > 0) { 1236 // If anything was selected before the delete was pressed, increment the 1237 // delete count by the length of the selection 1238 int deleteLen = mVoiceInput.getSelectionSpan() > 0 ? 1239 mVoiceInput.getSelectionSpan() : 1; 1240 mVoiceInput.incrementTextModificationDeleteCount(deleteLen); 1241 } 1242 } 1243 1244 if (mPredicting) { 1245 final int length = mComposing.length(); 1246 if (length > 0) { 1247 mComposing.delete(length - 1, length); 1248 mWord.deleteLast(); 1249 ic.setComposingText(mComposing, 1); 1250 if (mComposing.length() == 0) { 1251 mPredicting = false; 1252 } 1253 postUpdateSuggestions(); 1254 } else { 1255 ic.deleteSurroundingText(1, 0); 1256 } 1257 } else { 1258 deleteChar = true; 1259 } 1260 postUpdateShiftKeyState(); 1261 TextEntryState.backspace(); 1262 if (TextEntryState.getState() == TextEntryState.State.UNDO_COMMIT) { 1263 revertLastWord(deleteChar); 1264 ic.endBatchEdit(); 1265 return; 1266 } else if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) { 1267 ic.deleteSurroundingText(mEnteredText.length(), 0); 1268 } else if (deleteChar) { 1269 if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) { 1270 // Go back to the suggestion mode if the user canceled the 1271 // "Tap again to save". 1272 // NOTE: In gerenal, we don't revert the word when backspacing 1273 // from a manual suggestion pick. We deliberately chose a 1274 // different behavior only in the case of picking the first 1275 // suggestion (typed word). It's intentional to have made this 1276 // inconsistent with backspacing after selecting other suggestions. 1277 revertLastWord(deleteChar); 1278 } else { 1279 sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); 1280 if (mDeleteCount > DELETE_ACCELERATE_AT) { 1281 sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); 1282 } 1283 } 1284 } 1285 mJustRevertedSeparator = null; 1286 ic.endBatchEdit(); 1287 } 1288 1289 private void resetShift() { 1290 handleShiftInternal(true); 1291 } 1292 1293 private void handleShift() { 1294 handleShiftInternal(false); 1295 } 1296 1297 private void handleShiftInternal(boolean forceNormal) { 1298 mHandler.removeMessages(MSG_UPDATE_SHIFT_STATE); 1299 KeyboardSwitcher switcher = mKeyboardSwitcher; 1300 LatinKeyboardView inputView = switcher.getInputView(); 1301 if (switcher.isAlphabetMode()) { 1302 if (mCapsLock || forceNormal) { 1303 mCapsLock = false; 1304 switcher.setShifted(false); 1305 } else if (inputView != null) { 1306 if (inputView.isShifted()) { 1307 mCapsLock = true; 1308 switcher.setShiftLocked(true); 1309 } else { 1310 switcher.setShifted(true); 1311 } 1312 } 1313 } else { 1314 switcher.toggleShift(); 1315 } 1316 } 1317 1318 private void abortCorrection(boolean force) { 1319 if (force || TextEntryState.isCorrecting()) { 1320 getCurrentInputConnection().finishComposingText(); 1321 clearSuggestions(); 1322 } 1323 } 1324 1325 private void handleCharacter(int primaryCode, int[] keyCodes) { 1326 if (VOICE_INSTALLED && mVoiceInputHighlighted) { 1327 commitVoiceInput(); 1328 } 1329 1330 if (mAfterVoiceInput) { 1331 // Assume input length is 1. This assumption fails for smiley face insertions. 1332 mVoiceInput.incrementTextModificationInsertCount(1); 1333 } 1334 if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isCorrecting()) { 1335 abortCorrection(false); 1336 } 1337 1338 if (isAlphabet(primaryCode) && isPredictionOn() && !isCursorTouchingWord()) { 1339 if (!mPredicting) { 1340 mPredicting = true; 1341 mComposing.setLength(0); 1342 saveWordInHistory(mBestWord); 1343 mWord.reset(); 1344 } 1345 } 1346 if (mKeyboardSwitcher.getInputView().isShifted()) { 1347 // TODO: This doesn't work with [beta], need to fix it in the next release. 1348 if (keyCodes == null || keyCodes[0] < Character.MIN_CODE_POINT 1349 || keyCodes[0] > Character.MAX_CODE_POINT) { 1350 return; 1351 } 1352 primaryCode = keyCodes[0]; 1353 if (mKeyboardSwitcher.isAlphabetMode()) { 1354 primaryCode = Character.toUpperCase(primaryCode); 1355 } 1356 } 1357 if (mPredicting) { 1358 if (mKeyboardSwitcher.getInputView().isShifted() 1359 && mKeyboardSwitcher.isAlphabetMode() 1360 && mComposing.length() == 0) { 1361 mWord.setCapitalized(true); 1362 } 1363 mComposing.append((char) primaryCode); 1364 mWord.add(primaryCode, keyCodes); 1365 InputConnection ic = getCurrentInputConnection(); 1366 if (ic != null) { 1367 // If it's the first letter, make note of auto-caps state 1368 if (mWord.size() == 1) { 1369 mWord.setAutoCapitalized( 1370 getCursorCapsMode(ic, getCurrentInputEditorInfo()) != 0); 1371 } 1372 ic.setComposingText(mComposing, 1); 1373 } 1374 postUpdateSuggestions(); 1375 } else { 1376 sendKeyChar((char)primaryCode); 1377 } 1378 updateShiftKeyState(getCurrentInputEditorInfo()); 1379 if (LatinIME.PERF_DEBUG) measureCps(); 1380 TextEntryState.typedCharacter((char) primaryCode, isWordSeparator(primaryCode)); 1381 } 1382 1383 private void handleSeparator(int primaryCode) { 1384 if (VOICE_INSTALLED && mVoiceInputHighlighted) { 1385 commitVoiceInput(); 1386 } 1387 1388 if (mAfterVoiceInput){ 1389 // Assume input length is 1. This assumption fails for smiley face insertions. 1390 mVoiceInput.incrementTextModificationInsertPunctuationCount(1); 1391 } 1392 1393 // Should dismiss the "Tap again to save" message when handling separator 1394 if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) { 1395 postUpdateSuggestions(); 1396 } 1397 1398 boolean pickedDefault = false; 1399 // Handle separator 1400 InputConnection ic = getCurrentInputConnection(); 1401 if (ic != null) { 1402 ic.beginBatchEdit(); 1403 abortCorrection(false); 1404 } 1405 if (mPredicting) { 1406 // In certain languages where single quote is a separator, it's better 1407 // not to auto correct, but accept the typed word. For instance, 1408 // in Italian dov' should not be expanded to dove' because the elision 1409 // requires the last vowel to be removed. 1410 if (mAutoCorrectOn && primaryCode != '\'' && 1411 (mJustRevertedSeparator == null 1412 || mJustRevertedSeparator.length() == 0 1413 || mJustRevertedSeparator.charAt(0) != primaryCode)) { 1414 pickedDefault = pickDefaultSuggestion(); 1415 // Picked the suggestion by the space key. We consider this 1416 // as "added an auto space". 1417 if (primaryCode == KEYCODE_SPACE) { 1418 mJustAddedAutoSpace = true; 1419 } 1420 } else { 1421 commitTyped(ic); 1422 } 1423 } 1424 if (mJustAddedAutoSpace && primaryCode == KEYCODE_ENTER) { 1425 removeTrailingSpace(); 1426 mJustAddedAutoSpace = false; 1427 } 1428 sendKeyChar((char)primaryCode); 1429 1430 // Handle the case of ". ." -> " .." with auto-space if necessary 1431 // before changing the TextEntryState. 1432 if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED 1433 && primaryCode == KEYCODE_PERIOD) { 1434 reswapPeriodAndSpace(); 1435 } 1436 1437 TextEntryState.typedCharacter((char) primaryCode, true); 1438 if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED 1439 && primaryCode != KEYCODE_ENTER) { 1440 swapPunctuationAndSpace(); 1441 } else if (isPredictionOn() && primaryCode == KEYCODE_SPACE) { 1442 doubleSpace(); 1443 } 1444 if (pickedDefault) { 1445 TextEntryState.backToAcceptedDefault(mWord.getTypedWord()); 1446 } 1447 updateShiftKeyState(getCurrentInputEditorInfo()); 1448 if (ic != null) { 1449 ic.endBatchEdit(); 1450 } 1451 } 1452 1453 private void handleClose() { 1454 commitTyped(getCurrentInputConnection()); 1455 if (VOICE_INSTALLED & mRecognizing) { 1456 mVoiceInput.cancel(); 1457 } 1458 requestHideSelf(0); 1459 mKeyboardSwitcher.getInputView().closing(); 1460 TextEntryState.endSession(); 1461 } 1462 1463 private void saveWordInHistory(CharSequence result) { 1464 if (mWord.size() <= 1) { 1465 mWord.reset(); 1466 return; 1467 } 1468 // Skip if result is null. It happens in some edge case. 1469 if (TextUtils.isEmpty(result)) { 1470 return; 1471 } 1472 1473 // Make a copy of the CharSequence, since it is/could be a mutable CharSequence 1474 final String resultCopy = result.toString(); 1475 TypedWordAlternatives entry = new TypedWordAlternatives(resultCopy, 1476 new WordComposer(mWord)); 1477 mWordHistory.add(entry); 1478 } 1479 1480 private void postUpdateSuggestions() { 1481 mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS); 1482 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SUGGESTIONS), 100); 1483 } 1484 1485 private void postUpdateOldSuggestions() { 1486 mHandler.removeMessages(MSG_UPDATE_OLD_SUGGESTIONS); 1487 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_OLD_SUGGESTIONS), 300); 1488 } 1489 1490 private boolean isPredictionOn() { 1491 return mPredictionOn; 1492 } 1493 1494 private boolean isCandidateStripVisible() { 1495 return isPredictionOn() && mShowSuggestions; 1496 } 1497 1498 public void onCancelVoice() { 1499 if (mRecognizing) { 1500 switchToKeyboardView(); 1501 } 1502 } 1503 1504 private void switchToKeyboardView() { 1505 mHandler.post(new Runnable() { 1506 public void run() { 1507 mRecognizing = false; 1508 if (mKeyboardSwitcher.getInputView() != null) { 1509 setInputView(mKeyboardSwitcher.getInputView()); 1510 } 1511 updateInputViewShown(); 1512 }}); 1513 } 1514 1515 private void switchToRecognitionStatusView() { 1516 final boolean configChanged = mConfigurationChanging; 1517 mHandler.post(new Runnable() { 1518 public void run() { 1519 mRecognizing = true; 1520 View v = mVoiceInput.getView(); 1521 ViewParent p = v.getParent(); 1522 if (p != null && p instanceof ViewGroup) { 1523 ((ViewGroup)v.getParent()).removeView(v); 1524 } 1525 setInputView(v); 1526 updateInputViewShown(); 1527 if (configChanged) { 1528 mVoiceInput.onConfigurationChanged(); 1529 } 1530 }}); 1531 } 1532 1533 private void startListening(boolean swipe) { 1534 if (!mHasUsedVoiceInput || 1535 (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale)) { 1536 // Calls reallyStartListening if user clicks OK, does nothing if user clicks Cancel. 1537 showVoiceWarningDialog(swipe); 1538 } else { 1539 reallyStartListening(swipe); 1540 } 1541 } 1542 1543 private void reallyStartListening(boolean swipe) { 1544 if (!mHasUsedVoiceInput) { 1545 // The user has started a voice input, so remember that in the 1546 // future (so we don't show the warning dialog after the first run). 1547 SharedPreferences.Editor editor = 1548 PreferenceManager.getDefaultSharedPreferences(this).edit(); 1549 editor.putBoolean(PREF_HAS_USED_VOICE_INPUT, true); 1550 editor.commit(); 1551 mHasUsedVoiceInput = true; 1552 } 1553 1554 if (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale) { 1555 // The user has started a voice input from an unsupported locale, so remember that 1556 // in the future (so we don't show the warning dialog the next time they do this). 1557 SharedPreferences.Editor editor = 1558 PreferenceManager.getDefaultSharedPreferences(this).edit(); 1559 editor.putBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, true); 1560 editor.commit(); 1561 mHasUsedVoiceInputUnsupportedLocale = true; 1562 } 1563 1564 // Clear N-best suggestions 1565 clearSuggestions(); 1566 1567 FieldContext context = new FieldContext( 1568 getCurrentInputConnection(), 1569 getCurrentInputEditorInfo(), 1570 mLanguageSwitcher.getInputLanguage(), 1571 mLanguageSwitcher.getEnabledLanguages()); 1572 mVoiceInput.startListening(context, swipe); 1573 switchToRecognitionStatusView(); 1574 } 1575 1576 private void showVoiceWarningDialog(final boolean swipe) { 1577 AlertDialog.Builder builder = new AlertDialog.Builder(this); 1578 builder.setCancelable(true); 1579 builder.setIcon(R.drawable.ic_mic_dialog); 1580 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 1581 public void onClick(DialogInterface dialog, int whichButton) { 1582 mVoiceInput.logKeyboardWarningDialogOk(); 1583 reallyStartListening(swipe); 1584 } 1585 }); 1586 builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { 1587 public void onClick(DialogInterface dialog, int whichButton) { 1588 mVoiceInput.logKeyboardWarningDialogCancel(); 1589 } 1590 }); 1591 1592 if (mLocaleSupportedForVoiceInput) { 1593 String message = getString(R.string.voice_warning_may_not_understand) + "\n\n" + 1594 getString(R.string.voice_warning_how_to_turn_off); 1595 builder.setMessage(message); 1596 } else { 1597 String message = getString(R.string.voice_warning_locale_not_supported) + "\n\n" + 1598 getString(R.string.voice_warning_may_not_understand) + "\n\n" + 1599 getString(R.string.voice_warning_how_to_turn_off); 1600 builder.setMessage(message); 1601 } 1602 1603 builder.setTitle(R.string.voice_warning_title); 1604 mVoiceWarningDialog = builder.create(); 1605 1606 Window window = mVoiceWarningDialog.getWindow(); 1607 WindowManager.LayoutParams lp = window.getAttributes(); 1608 lp.token = mKeyboardSwitcher.getInputView().getWindowToken(); 1609 lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; 1610 window.setAttributes(lp); 1611 window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 1612 mVoiceInput.logKeyboardWarningDialogShown(); 1613 mVoiceWarningDialog.show(); 1614 } 1615 1616 public void onVoiceResults(List<String> candidates, 1617 Map<String, List<CharSequence>> alternatives) { 1618 if (!mRecognizing) { 1619 return; 1620 } 1621 mVoiceResults.candidates = candidates; 1622 mVoiceResults.alternatives = alternatives; 1623 mHandler.sendMessage(mHandler.obtainMessage(MSG_VOICE_RESULTS)); 1624 } 1625 1626 private void handleVoiceResults() { 1627 mAfterVoiceInput = true; 1628 mImmediatelyAfterVoiceInput = true; 1629 1630 InputConnection ic = getCurrentInputConnection(); 1631 if (!isFullscreenMode()) { 1632 // Start listening for updates to the text from typing, etc. 1633 if (ic != null) { 1634 ExtractedTextRequest req = new ExtractedTextRequest(); 1635 ic.getExtractedText(req, InputConnection.GET_EXTRACTED_TEXT_MONITOR); 1636 } 1637 } 1638 1639 vibrate(); 1640 switchToKeyboardView(); 1641 1642 final List<CharSequence> nBest = new ArrayList<CharSequence>(); 1643 boolean capitalizeFirstWord = preferCapitalization() 1644 || (mKeyboardSwitcher.isAlphabetMode() 1645 && mKeyboardSwitcher.getInputView().isShifted()); 1646 for (String c : mVoiceResults.candidates) { 1647 if (capitalizeFirstWord) { 1648 c = Character.toUpperCase(c.charAt(0)) + c.substring(1, c.length()); 1649 } 1650 nBest.add(c); 1651 } 1652 1653 if (nBest.size() == 0) { 1654 return; 1655 } 1656 1657 String bestResult = nBest.get(0).toString(); 1658 1659 mVoiceInput.logVoiceInputDelivered(bestResult.length()); 1660 1661 mHints.registerVoiceResult(bestResult); 1662 1663 if (ic != null) ic.beginBatchEdit(); // To avoid extra updates on committing older text 1664 1665 commitTyped(ic); 1666 EditingUtil.appendText(ic, bestResult); 1667 1668 if (ic != null) ic.endBatchEdit(); 1669 1670 mVoiceInputHighlighted = true; 1671 mWordToSuggestions.putAll(mVoiceResults.alternatives); 1672 } 1673 1674 private void clearSuggestions() { 1675 setSuggestions(null, false, false, false); 1676 } 1677 1678 private void setSuggestions( 1679 List<CharSequence> suggestions, 1680 boolean completions, 1681 boolean typedWordValid, 1682 boolean haveMinimalSuggestion) { 1683 1684 if (mIsShowingHint) { 1685 setCandidatesView(mCandidateViewContainer); 1686 mIsShowingHint = false; 1687 } 1688 1689 if (mCandidateView != null) { 1690 mCandidateView.setSuggestions( 1691 suggestions, completions, typedWordValid, haveMinimalSuggestion); 1692 } 1693 } 1694 1695 private void updateSuggestions() { 1696 LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); 1697 ((LatinKeyboard) inputView.getKeyboard()).setPreferredLetters(null); 1698 1699 // Check if we have a suggestion engine attached. 1700 if ((mSuggest == null || !isPredictionOn()) && !mVoiceInputHighlighted) { 1701 return; 1702 } 1703 1704 if (!mPredicting) { 1705 setNextSuggestions(); 1706 return; 1707 } 1708 showSuggestions(mWord); 1709 } 1710 1711 private List<CharSequence> getTypedSuggestions(WordComposer word) { 1712 List<CharSequence> stringList = mSuggest.getSuggestions( 1713 mKeyboardSwitcher.getInputView(), word, false, null); 1714 return stringList; 1715 } 1716 1717 private void showCorrections(WordAlternatives alternatives) { 1718 List<CharSequence> stringList = alternatives.getAlternatives(); 1719 ((LatinKeyboard) mKeyboardSwitcher.getInputView().getKeyboard()).setPreferredLetters(null); 1720 showSuggestions(stringList, alternatives.getOriginalWord(), false, false); 1721 } 1722 1723 private void showSuggestions(WordComposer word) { 1724 // long startTime = System.currentTimeMillis(); // TIME MEASUREMENT! 1725 // TODO Maybe need better way of retrieving previous word 1726 CharSequence prevWord = EditingUtil.getPreviousWord(getCurrentInputConnection(), 1727 mWordSeparators); 1728 List<CharSequence> stringList = mSuggest.getSuggestions( 1729 mKeyboardSwitcher.getInputView(), word, false, prevWord); 1730 // long stopTime = System.currentTimeMillis(); // TIME MEASUREMENT! 1731 // Log.d("LatinIME","Suggest Total Time - " + (stopTime - startTime)); 1732 1733 int[] nextLettersFrequencies = mSuggest.getNextLettersFrequencies(); 1734 1735 ((LatinKeyboard) mKeyboardSwitcher.getInputView().getKeyboard()).setPreferredLetters( 1736 nextLettersFrequencies); 1737 1738 boolean correctionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasMinimalCorrection(); 1739 //|| mCorrectionMode == mSuggest.CORRECTION_FULL; 1740 CharSequence typedWord = word.getTypedWord(); 1741 // If we're in basic correct 1742 boolean typedWordValid = mSuggest.isValidWord(typedWord) || 1743 (preferCapitalization() 1744 && mSuggest.isValidWord(typedWord.toString().toLowerCase())); 1745 if (mCorrectionMode == Suggest.CORRECTION_FULL 1746 || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) { 1747 correctionAvailable |= typedWordValid; 1748 } 1749 // Don't auto-correct words with multiple capital letter 1750 correctionAvailable &= !word.isMostlyCaps(); 1751 correctionAvailable &= !TextEntryState.isCorrecting(); 1752 1753 showSuggestions(stringList, typedWord, typedWordValid, correctionAvailable); 1754 } 1755 1756 private void showSuggestions(List<CharSequence> stringList, CharSequence typedWord, 1757 boolean typedWordValid, boolean correctionAvailable) { 1758 setSuggestions(stringList, false, typedWordValid, correctionAvailable); 1759 if (stringList.size() > 0) { 1760 if (correctionAvailable && !typedWordValid && stringList.size() > 1) { 1761 mBestWord = stringList.get(1); 1762 } else { 1763 mBestWord = typedWord; 1764 } 1765 } else { 1766 mBestWord = null; 1767 } 1768 setCandidatesViewShown(isCandidateStripVisible() || mCompletionOn); 1769 } 1770 1771 private boolean pickDefaultSuggestion() { 1772 // Complete any pending candidate query first 1773 if (mHandler.hasMessages(MSG_UPDATE_SUGGESTIONS)) { 1774 mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS); 1775 updateSuggestions(); 1776 } 1777 if (mBestWord != null && mBestWord.length() > 0) { 1778 TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord); 1779 mJustAccepted = true; 1780 pickSuggestion(mBestWord, false); 1781 // Add the word to the auto dictionary if it's not a known word 1782 addToDictionaries(mBestWord, AutoDictionary.FREQUENCY_FOR_TYPED); 1783 return true; 1784 1785 } 1786 return false; 1787 } 1788 1789 public void pickSuggestionManually(int index, CharSequence suggestion) { 1790 List<CharSequence> suggestions = mCandidateView.getSuggestions(); 1791 if (mAfterVoiceInput && mShowingVoiceSuggestions) { 1792 mVoiceInput.flushAllTextModificationCounters(); 1793 // send this intent AFTER logging any prior aggregated edits. 1794 mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.toString(), index, 1795 mWordSeparators, 1796 getCurrentInputConnection()); 1797 } 1798 1799 final boolean correcting = TextEntryState.isCorrecting(); 1800 InputConnection ic = getCurrentInputConnection(); 1801 if (ic != null) { 1802 ic.beginBatchEdit(); 1803 } 1804 if (mCompletionOn && mCompletions != null && index >= 0 1805 && index < mCompletions.length) { 1806 CompletionInfo ci = mCompletions[index]; 1807 if (ic != null) { 1808 ic.commitCompletion(ci); 1809 } 1810 mCommittedLength = suggestion.length(); 1811 if (mCandidateView != null) { 1812 mCandidateView.clear(); 1813 } 1814 updateShiftKeyState(getCurrentInputEditorInfo()); 1815 if (ic != null) { 1816 ic.endBatchEdit(); 1817 } 1818 return; 1819 } 1820 1821 // If this is a punctuation, apply it through the normal key press 1822 if (suggestion.length() == 1 && (isWordSeparator(suggestion.charAt(0)) 1823 || isSuggestedPunctuation(suggestion.charAt(0)))) { 1824 // Word separators are suggested before the user inputs something. 1825 // So, LatinImeLogger logs "" as a user's input. 1826 LatinImeLogger.logOnManualSuggestion( 1827 "", suggestion.toString(), index, suggestions); 1828 onKey(suggestion.charAt(0), null, LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE, 1829 LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE); 1830 if (ic != null) { 1831 ic.endBatchEdit(); 1832 } 1833 return; 1834 } 1835 mJustAccepted = true; 1836 pickSuggestion(suggestion, correcting); 1837 // Add the word to the auto dictionary if it's not a known word 1838 if (index == 0) { 1839 addToDictionaries(suggestion, AutoDictionary.FREQUENCY_FOR_PICKED); 1840 } else { 1841 addToBigramDictionary(suggestion, 1); 1842 } 1843 LatinImeLogger.logOnManualSuggestion(mComposing.toString(), suggestion.toString(), 1844 index, suggestions); 1845 TextEntryState.acceptedSuggestion(mComposing.toString(), suggestion); 1846 // Follow it with a space 1847 if (mAutoSpace && !correcting) { 1848 sendSpace(); 1849 mJustAddedAutoSpace = true; 1850 } 1851 1852 final boolean showingAddToDictionaryHint = index == 0 && mCorrectionMode > 0 1853 && !mSuggest.isValidWord(suggestion) 1854 && !mSuggest.isValidWord(suggestion.toString().toLowerCase()); 1855 1856 if (!correcting) { 1857 // Fool the state watcher so that a subsequent backspace will not do a revert, unless 1858 // we just did a correction, in which case we need to stay in 1859 // TextEntryState.State.PICKED_SUGGESTION state. 1860 TextEntryState.typedCharacter((char) KEYCODE_SPACE, true); 1861 setNextSuggestions(); 1862 } else if (!showingAddToDictionaryHint) { 1863 // If we're not showing the "Tap again to save hint", then show corrections again. 1864 // In case the cursor position doesn't change, make sure we show the suggestions again. 1865 clearSuggestions(); 1866 postUpdateOldSuggestions(); 1867 } 1868 if (showingAddToDictionaryHint) { 1869 mCandidateView.showAddToDictionaryHint(suggestion); 1870 } 1871 if (ic != null) { 1872 ic.endBatchEdit(); 1873 } 1874 } 1875 1876 private void rememberReplacedWord(CharSequence suggestion) { 1877 if (mShowingVoiceSuggestions) { 1878 // Retain the replaced word in the alternatives array. 1879 EditingUtil.Range range = new EditingUtil.Range(); 1880 String wordToBeReplaced = EditingUtil.getWordAtCursor(getCurrentInputConnection(), 1881 mWordSeparators, range); 1882 if (!mWordToSuggestions.containsKey(wordToBeReplaced)) { 1883 wordToBeReplaced = wordToBeReplaced.toLowerCase(); 1884 } 1885 if (mWordToSuggestions.containsKey(wordToBeReplaced)) { 1886 List<CharSequence> suggestions = mWordToSuggestions.get(wordToBeReplaced); 1887 if (suggestions.contains(suggestion)) { 1888 suggestions.remove(suggestion); 1889 } 1890 suggestions.add(wordToBeReplaced); 1891 mWordToSuggestions.remove(wordToBeReplaced); 1892 mWordToSuggestions.put(suggestion.toString(), suggestions); 1893 } 1894 } 1895 } 1896 1897 /** 1898 * Commits the chosen word to the text field and saves it for later 1899 * retrieval. 1900 * @param suggestion the suggestion picked by the user to be committed to 1901 * the text field 1902 * @param correcting whether this is due to a correction of an existing 1903 * word. 1904 */ 1905 private void pickSuggestion(CharSequence suggestion, boolean correcting) { 1906 LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); 1907 if (mCapsLock) { 1908 suggestion = suggestion.toString().toUpperCase(); 1909 } else if (preferCapitalization() 1910 || (mKeyboardSwitcher.isAlphabetMode() 1911 && inputView.isShifted())) { 1912 suggestion = suggestion.toString().toUpperCase().charAt(0) 1913 + suggestion.subSequence(1, suggestion.length()).toString(); 1914 } 1915 InputConnection ic = getCurrentInputConnection(); 1916 if (ic != null) { 1917 rememberReplacedWord(suggestion); 1918 ic.commitText(suggestion, 1); 1919 } 1920 saveWordInHistory(suggestion); 1921 mPredicting = false; 1922 mCommittedLength = suggestion.length(); 1923 ((LatinKeyboard) inputView.getKeyboard()).setPreferredLetters(null); 1924 // If we just corrected a word, then don't show punctuations 1925 if (!correcting) { 1926 setNextSuggestions(); 1927 } 1928 updateShiftKeyState(getCurrentInputEditorInfo()); 1929 } 1930 1931 /** 1932 * Tries to apply any voice alternatives for the word if this was a spoken word and 1933 * there are voice alternatives. 1934 * @param touching The word that the cursor is touching, with position information 1935 * @return true if an alternative was found, false otherwise. 1936 */ 1937 private boolean applyVoiceAlternatives(EditingUtil.SelectedWord touching) { 1938 // Search for result in spoken word alternatives 1939 String selectedWord = touching.word.toString().trim(); 1940 if (!mWordToSuggestions.containsKey(selectedWord)) { 1941 selectedWord = selectedWord.toLowerCase(); 1942 } 1943 if (mWordToSuggestions.containsKey(selectedWord)) { 1944 mShowingVoiceSuggestions = true; 1945 List<CharSequence> suggestions = mWordToSuggestions.get(selectedWord); 1946 // If the first letter of touching is capitalized, make all the suggestions 1947 // start with a capital letter. 1948 if (Character.isUpperCase(touching.word.charAt(0))) { 1949 for (int i = 0; i < suggestions.size(); i++) { 1950 String origSugg = (String) suggestions.get(i); 1951 String capsSugg = origSugg.toUpperCase().charAt(0) 1952 + origSugg.subSequence(1, origSugg.length()).toString(); 1953 suggestions.set(i, capsSugg); 1954 } 1955 } 1956 setSuggestions(suggestions, false, true, true); 1957 setCandidatesViewShown(true); 1958 return true; 1959 } 1960 return false; 1961 } 1962 1963 /** 1964 * Tries to apply any typed alternatives for the word if we have any cached alternatives, 1965 * otherwise tries to find new corrections and completions for the word. 1966 * @param touching The word that the cursor is touching, with position information 1967 * @return true if an alternative was found, false otherwise. 1968 */ 1969 private boolean applyTypedAlternatives(EditingUtil.SelectedWord touching) { 1970 // If we didn't find a match, search for result in typed word history 1971 WordComposer foundWord = null; 1972 WordAlternatives alternatives = null; 1973 for (WordAlternatives entry : mWordHistory) { 1974 if (TextUtils.equals(entry.getChosenWord(), touching.word)) { 1975 if (entry instanceof TypedWordAlternatives) { 1976 foundWord = ((TypedWordAlternatives) entry).word; 1977 } 1978 alternatives = entry; 1979 break; 1980 } 1981 } 1982 // If we didn't find a match, at least suggest completions 1983 if (foundWord == null 1984 && (mSuggest.isValidWord(touching.word) 1985 || mSuggest.isValidWord(touching.word.toString().toLowerCase()))) { 1986 foundWord = new WordComposer(); 1987 for (int i = 0; i < touching.word.length(); i++) { 1988 foundWord.add(touching.word.charAt(i), new int[] { 1989 touching.word.charAt(i) 1990 }); 1991 } 1992 foundWord.setCapitalized(Character.isUpperCase(touching.word.charAt(0))); 1993 } 1994 // Found a match, show suggestions 1995 if (foundWord != null || alternatives != null) { 1996 if (alternatives == null) { 1997 alternatives = new TypedWordAlternatives(touching.word, foundWord); 1998 } 1999 showCorrections(alternatives); 2000 if (foundWord != null) { 2001 mWord = new WordComposer(foundWord); 2002 } else { 2003 mWord.reset(); 2004 } 2005 return true; 2006 } 2007 return false; 2008 } 2009 2010 private void setOldSuggestions() { 2011 mShowingVoiceSuggestions = false; 2012 if (mCandidateView != null && mCandidateView.isShowingAddToDictionaryHint()) { 2013 return; 2014 } 2015 InputConnection ic = getCurrentInputConnection(); 2016 if (ic == null) return; 2017 if (!mPredicting) { 2018 // Extract the selected or touching text 2019 EditingUtil.SelectedWord touching = EditingUtil.getWordAtCursorOrSelection(ic, 2020 mLastSelectionStart, mLastSelectionEnd, mWordSeparators); 2021 2022 if (touching != null && touching.word.length() > 1) { 2023 ic.beginBatchEdit(); 2024 2025 if (!applyVoiceAlternatives(touching) && !applyTypedAlternatives(touching)) { 2026 abortCorrection(true); 2027 } else { 2028 TextEntryState.selectedForCorrection(); 2029 EditingUtil.underlineWord(ic, touching); 2030 } 2031 2032 ic.endBatchEdit(); 2033 } else { 2034 abortCorrection(true); 2035 setNextSuggestions(); 2036 } 2037 } else { 2038 abortCorrection(true); 2039 } 2040 } 2041 2042 private void setNextSuggestions() { 2043 setSuggestions(mSuggestPuncList, false, false, false); 2044 } 2045 2046 private void addToDictionaries(CharSequence suggestion, int frequencyDelta) { 2047 checkAddToDictionary(suggestion, frequencyDelta, false); 2048 } 2049 2050 private void addToBigramDictionary(CharSequence suggestion, int frequencyDelta) { 2051 checkAddToDictionary(suggestion, frequencyDelta, true); 2052 } 2053 2054 /** 2055 * Adds to the UserBigramDictionary and/or AutoDictionary 2056 * @param addToBigramDictionary true if it should be added to bigram dictionary if possible 2057 */ 2058 private void checkAddToDictionary(CharSequence suggestion, int frequencyDelta, 2059 boolean addToBigramDictionary) { 2060 if (suggestion == null || suggestion.length() < 1) return; 2061 // Only auto-add to dictionary if auto-correct is ON. Otherwise we'll be 2062 // adding words in situations where the user or application really didn't 2063 // want corrections enabled or learned. 2064 if (!(mCorrectionMode == Suggest.CORRECTION_FULL 2065 || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM)) { 2066 return; 2067 } 2068 if (suggestion != null) { 2069 if (!addToBigramDictionary && mAutoDictionary.isValidWord(suggestion) 2070 || (!mSuggest.isValidWord(suggestion.toString()) 2071 && !mSuggest.isValidWord(suggestion.toString().toLowerCase()))) { 2072 mAutoDictionary.addWord(suggestion.toString(), frequencyDelta); 2073 } 2074 2075 if (mUserBigramDictionary != null) { 2076 CharSequence prevWord = EditingUtil.getPreviousWord(getCurrentInputConnection(), 2077 mSentenceSeparators); 2078 if (!TextUtils.isEmpty(prevWord)) { 2079 mUserBigramDictionary.addBigrams(prevWord.toString(), suggestion.toString()); 2080 } 2081 } 2082 } 2083 } 2084 2085 private boolean isCursorTouchingWord() { 2086 InputConnection ic = getCurrentInputConnection(); 2087 if (ic == null) return false; 2088 CharSequence toLeft = ic.getTextBeforeCursor(1, 0); 2089 CharSequence toRight = ic.getTextAfterCursor(1, 0); 2090 if (!TextUtils.isEmpty(toLeft) 2091 && !isWordSeparator(toLeft.charAt(0))) { 2092 return true; 2093 } 2094 if (!TextUtils.isEmpty(toRight) 2095 && !isWordSeparator(toRight.charAt(0))) { 2096 return true; 2097 } 2098 return false; 2099 } 2100 2101 private boolean sameAsTextBeforeCursor(InputConnection ic, CharSequence text) { 2102 CharSequence beforeText = ic.getTextBeforeCursor(text.length(), 0); 2103 return TextUtils.equals(text, beforeText); 2104 } 2105 2106 public void revertLastWord(boolean deleteChar) { 2107 final int length = mComposing.length(); 2108 if (!mPredicting && length > 0) { 2109 final InputConnection ic = getCurrentInputConnection(); 2110 mPredicting = true; 2111 mJustRevertedSeparator = ic.getTextBeforeCursor(1, 0); 2112 if (deleteChar) ic.deleteSurroundingText(1, 0); 2113 int toDelete = mCommittedLength; 2114 CharSequence toTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0); 2115 if (toTheLeft != null && toTheLeft.length() > 0 2116 && isWordSeparator(toTheLeft.charAt(0))) { 2117 toDelete--; 2118 } 2119 ic.deleteSurroundingText(toDelete, 0); 2120 ic.setComposingText(mComposing, 1); 2121 TextEntryState.backspace(); 2122 postUpdateSuggestions(); 2123 } else { 2124 sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); 2125 mJustRevertedSeparator = null; 2126 } 2127 } 2128 2129 protected String getWordSeparators() { 2130 return mWordSeparators; 2131 } 2132 2133 public boolean isWordSeparator(int code) { 2134 String separators = getWordSeparators(); 2135 return separators.contains(String.valueOf((char)code)); 2136 } 2137 2138 private boolean isSentenceSeparator(int code) { 2139 return mSentenceSeparators.contains(String.valueOf((char)code)); 2140 } 2141 2142 private void sendSpace() { 2143 sendKeyChar((char)KEYCODE_SPACE); 2144 updateShiftKeyState(getCurrentInputEditorInfo()); 2145 //onKey(KEY_SPACE[0], KEY_SPACE); 2146 } 2147 2148 public boolean preferCapitalization() { 2149 return mWord.isCapitalized(); 2150 } 2151 2152 public void swipeRight() { 2153 if (userHasNotTypedRecently() && VOICE_INSTALLED && mEnableVoice && 2154 fieldCanDoVoice(makeFieldContext())) { 2155 startListening(true /* was a swipe */); 2156 } 2157 2158 if (LatinKeyboardView.DEBUG_AUTO_PLAY) { 2159 ClipboardManager cm = ((ClipboardManager)getSystemService(CLIPBOARD_SERVICE)); 2160 CharSequence text = cm.getText(); 2161 if (!TextUtils.isEmpty(text)) { 2162 mKeyboardSwitcher.getInputView().startPlaying(text.toString()); 2163 } 2164 } 2165 } 2166 2167 private void toggleLanguage(boolean reset, boolean next) { 2168 if (reset) { 2169 mLanguageSwitcher.reset(); 2170 } else { 2171 if (next) { 2172 mLanguageSwitcher.next(); 2173 } else { 2174 mLanguageSwitcher.prev(); 2175 } 2176 } 2177 int currentKeyboardMode = mKeyboardSwitcher.getKeyboardMode(); 2178 reloadKeyboards(); 2179 mKeyboardSwitcher.makeKeyboards(true); 2180 mKeyboardSwitcher.setKeyboardMode(currentKeyboardMode, 0, 2181 mEnableVoiceButton && mEnableVoice); 2182 initSuggest(mLanguageSwitcher.getInputLanguage()); 2183 mLanguageSwitcher.persist(); 2184 updateShiftKeyState(getCurrentInputEditorInfo()); 2185 } 2186 2187 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, 2188 String key) { 2189 if (PREF_SELECTED_LANGUAGES.equals(key)) { 2190 mLanguageSwitcher.loadLocales(sharedPreferences); 2191 mRefreshKeyboardRequired = true; 2192 } else if (PREF_RECORRECTION_ENABLED.equals(key)) { 2193 mReCorrectionEnabled = sharedPreferences.getBoolean(PREF_RECORRECTION_ENABLED, 2194 getResources().getBoolean(R.bool.default_recorrection_enabled)); 2195 } 2196 } 2197 2198 public void swipeLeft() { 2199 } 2200 2201 public void swipeDown() { 2202 handleClose(); 2203 } 2204 2205 public void swipeUp() { 2206 //launchSettings(); 2207 } 2208 2209 public void onPress(int primaryCode) { 2210 vibrate(); 2211 playKeyClick(primaryCode); 2212 if (mKeyboardSwitcher.hasDistinctMultitouch() && primaryCode == Keyboard.KEYCODE_SHIFT) { 2213 mShiftKeyState.onPress(); 2214 handleShift(); 2215 } else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE) { 2216 // TODO: We should handle KEYCODE_MODE_CHANGE (symbol) here as well. 2217 } else { 2218 mShiftKeyState.onOtherKeyPressed(); 2219 } 2220 } 2221 2222 public void onRelease(int primaryCode) { 2223 // Reset any drag flags in the keyboard 2224 ((LatinKeyboard) mKeyboardSwitcher.getInputView().getKeyboard()).keyReleased(); 2225 //vibrate(); 2226 if (mKeyboardSwitcher.hasDistinctMultitouch() && primaryCode == Keyboard.KEYCODE_SHIFT) { 2227 if (mShiftKeyState.isMomentary()) 2228 resetShift(); 2229 mShiftKeyState.onRelease(); 2230 } else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE) { 2231 // TODO: We should handle KEYCODE_MODE_CHANGE (symbol) here as well. 2232 } 2233 } 2234 2235 private FieldContext makeFieldContext() { 2236 return new FieldContext( 2237 getCurrentInputConnection(), 2238 getCurrentInputEditorInfo(), 2239 mLanguageSwitcher.getInputLanguage(), 2240 mLanguageSwitcher.getEnabledLanguages()); 2241 } 2242 2243 private boolean fieldCanDoVoice(FieldContext fieldContext) { 2244 return !mPasswordText 2245 && mVoiceInput != null 2246 && !mVoiceInput.isBlacklistedField(fieldContext); 2247 } 2248 2249 private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) { 2250 return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext) 2251 && !(attribute != null && attribute.privateImeOptions != null 2252 && attribute.privateImeOptions.equals(IME_OPTION_NO_MICROPHONE)) 2253 && SpeechRecognizer.isRecognitionAvailable(this); 2254 } 2255 2256 // receive ringer mode changes to detect silent mode 2257 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 2258 @Override 2259 public void onReceive(Context context, Intent intent) { 2260 updateRingerMode(); 2261 } 2262 }; 2263 2264 // update flags for silent mode 2265 private void updateRingerMode() { 2266 if (mAudioManager == null) { 2267 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 2268 } 2269 if (mAudioManager != null) { 2270 mSilentMode = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL); 2271 } 2272 } 2273 2274 private boolean userHasNotTypedRecently() { 2275 return (SystemClock.uptimeMillis() - mLastKeyTime) 2276 > MIN_MILLIS_AFTER_TYPING_BEFORE_SWIPE; 2277 } 2278 2279 private void playKeyClick(int primaryCode) { 2280 // if mAudioManager is null, we don't have the ringer state yet 2281 // mAudioManager will be set by updateRingerMode 2282 if (mAudioManager == null) { 2283 if (mKeyboardSwitcher.getInputView() != null) { 2284 updateRingerMode(); 2285 } 2286 } 2287 if (mSoundOn && !mSilentMode) { 2288 // FIXME: Volume and enable should come from UI settings 2289 // FIXME: These should be triggered after auto-repeat logic 2290 int sound = AudioManager.FX_KEYPRESS_STANDARD; 2291 switch (primaryCode) { 2292 case Keyboard.KEYCODE_DELETE: 2293 sound = AudioManager.FX_KEYPRESS_DELETE; 2294 break; 2295 case KEYCODE_ENTER: 2296 sound = AudioManager.FX_KEYPRESS_RETURN; 2297 break; 2298 case KEYCODE_SPACE: 2299 sound = AudioManager.FX_KEYPRESS_SPACEBAR; 2300 break; 2301 } 2302 mAudioManager.playSoundEffect(sound, FX_VOLUME); 2303 } 2304 } 2305 2306 private void vibrate() { 2307 if (!mVibrateOn) { 2308 return; 2309 } 2310 if (mKeyboardSwitcher.getInputView() != null) { 2311 mKeyboardSwitcher.getInputView().performHapticFeedback( 2312 HapticFeedbackConstants.KEYBOARD_TAP, 2313 HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); 2314 } 2315 } 2316 2317 private void checkTutorial(String privateImeOptions) { 2318 if (privateImeOptions == null) return; 2319 if (privateImeOptions.equals("com.android.setupwizard:ShowTutorial")) { 2320 if (mTutorial == null) startTutorial(); 2321 } else if (privateImeOptions.equals("com.android.setupwizard:HideTutorial")) { 2322 if (mTutorial != null) { 2323 if (mTutorial.close()) { 2324 mTutorial = null; 2325 } 2326 } 2327 } 2328 } 2329 2330 private void startTutorial() { 2331 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_TUTORIAL), 500); 2332 } 2333 2334 void tutorialDone() { 2335 mTutorial = null; 2336 } 2337 2338 void promoteToUserDictionary(String word, int frequency) { 2339 if (mUserDictionary.isValidWord(word)) return; 2340 mUserDictionary.addWord(word, frequency); 2341 } 2342 2343 WordComposer getCurrentWord() { 2344 return mWord; 2345 } 2346 2347 boolean getPopupOn() { 2348 return mPopupOn; 2349 } 2350 2351 private void updateCorrectionMode() { 2352 mHasDictionary = mSuggest != null ? mSuggest.hasMainDictionary() : false; 2353 mAutoCorrectOn = (mAutoCorrectEnabled || mQuickFixes) 2354 && !mInputTypeNoAutoCorrect && mHasDictionary; 2355 mCorrectionMode = (mAutoCorrectOn && mAutoCorrectEnabled) 2356 ? Suggest.CORRECTION_FULL 2357 : (mAutoCorrectOn ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE); 2358 mCorrectionMode = (mBigramSuggestionEnabled && mAutoCorrectOn && mAutoCorrectEnabled) 2359 ? Suggest.CORRECTION_FULL_BIGRAM : mCorrectionMode; 2360 if (mSuggest != null) { 2361 mSuggest.setCorrectionMode(mCorrectionMode); 2362 } 2363 } 2364 2365 private void updateAutoTextEnabled(Locale systemLocale) { 2366 if (mSuggest == null) return; 2367 boolean different = 2368 !systemLocale.getLanguage().equalsIgnoreCase(mInputLocale.substring(0, 2)); 2369 mSuggest.setAutoTextEnabled(!different && mQuickFixes); 2370 } 2371 2372 protected void launchSettings() { 2373 launchSettings(LatinIMESettings.class); 2374 } 2375 2376 protected void launchSettings(Class<LatinIMESettings> settingsClass) { 2377 handleClose(); 2378 Intent intent = new Intent(); 2379 intent.setClass(LatinIME.this, settingsClass); 2380 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 2381 startActivity(intent); 2382 } 2383 2384 private void loadSettings() { 2385 // Get the settings preferences 2386 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); 2387 mVibrateOn = sp.getBoolean(PREF_VIBRATE_ON, false); 2388 mSoundOn = sp.getBoolean(PREF_SOUND_ON, false); 2389 mPopupOn = sp.getBoolean(PREF_POPUP_ON, 2390 mResources.getBoolean(R.bool.default_popup_preview)); 2391 mAutoCap = sp.getBoolean(PREF_AUTO_CAP, true); 2392 mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true); 2393 mHasUsedVoiceInput = sp.getBoolean(PREF_HAS_USED_VOICE_INPUT, false); 2394 mHasUsedVoiceInputUnsupportedLocale = 2395 sp.getBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, false); 2396 2397 // Get the current list of supported locales and check the current locale against that 2398 // list. We cache this value so as not to check it every time the user starts a voice 2399 // input. Because this method is called by onStartInputView, this should mean that as 2400 // long as the locale doesn't change while the user is keeping the IME open, the 2401 // value should never be stale. 2402 String supportedLocalesString = SettingsUtil.getSettingsString( 2403 getContentResolver(), 2404 SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES, 2405 DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES); 2406 ArrayList<String> voiceInputSupportedLocales = 2407 newArrayList(supportedLocalesString.split("\\s+")); 2408 2409 mLocaleSupportedForVoiceInput = voiceInputSupportedLocales.contains(mInputLocale); 2410 2411 mShowSuggestions = sp.getBoolean(PREF_SHOW_SUGGESTIONS, true); 2412 2413 if (VOICE_INSTALLED) { 2414 final String voiceMode = sp.getString(PREF_VOICE_MODE, 2415 getString(R.string.voice_mode_main)); 2416 boolean enableVoice = !voiceMode.equals(getString(R.string.voice_mode_off)) 2417 && mEnableVoiceButton; 2418 boolean voiceOnPrimary = voiceMode.equals(getString(R.string.voice_mode_main)); 2419 if (mKeyboardSwitcher != null && 2420 (enableVoice != mEnableVoice || voiceOnPrimary != mVoiceOnPrimary)) { 2421 mKeyboardSwitcher.setVoiceMode(enableVoice, voiceOnPrimary); 2422 } 2423 mEnableVoice = enableVoice; 2424 mVoiceOnPrimary = voiceOnPrimary; 2425 } 2426 mAutoCorrectEnabled = sp.getBoolean(PREF_AUTO_COMPLETE, 2427 mResources.getBoolean(R.bool.enable_autocorrect)) & mShowSuggestions; 2428 mBigramSuggestionEnabled = sp.getBoolean(PREF_BIGRAM_SUGGESTIONS, true) & mShowSuggestions; 2429 updateCorrectionMode(); 2430 updateAutoTextEnabled(mResources.getConfiguration().locale); 2431 mLanguageSwitcher.loadLocales(sp); 2432 } 2433 2434 private void initSuggestPuncList() { 2435 mSuggestPuncList = new ArrayList<CharSequence>(); 2436 mSuggestPuncs = mResources.getString(R.string.suggested_punctuations); 2437 if (mSuggestPuncs != null) { 2438 for (int i = 0; i < mSuggestPuncs.length(); i++) { 2439 mSuggestPuncList.add(mSuggestPuncs.subSequence(i, i + 1)); 2440 } 2441 } 2442 } 2443 2444 private boolean isSuggestedPunctuation(int code) { 2445 return mSuggestPuncs.contains(String.valueOf((char)code)); 2446 } 2447 2448 private void showOptionsMenu() { 2449 AlertDialog.Builder builder = new AlertDialog.Builder(this); 2450 builder.setCancelable(true); 2451 builder.setIcon(R.drawable.ic_dialog_keyboard); 2452 builder.setNegativeButton(android.R.string.cancel, null); 2453 CharSequence itemSettings = getString(R.string.english_ime_settings); 2454 CharSequence itemInputMethod = getString(R.string.selectInputMethod); 2455 builder.setItems(new CharSequence[] { 2456 itemInputMethod, itemSettings}, 2457 new DialogInterface.OnClickListener() { 2458 2459 public void onClick(DialogInterface di, int position) { 2460 di.dismiss(); 2461 switch (position) { 2462 case POS_SETTINGS: 2463 launchSettings(); 2464 break; 2465 case POS_METHOD: 2466 ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)) 2467 .showInputMethodPicker(); 2468 break; 2469 } 2470 } 2471 }); 2472 builder.setTitle(mResources.getString(R.string.english_ime_input_options)); 2473 mOptionsDialog = builder.create(); 2474 Window window = mOptionsDialog.getWindow(); 2475 WindowManager.LayoutParams lp = window.getAttributes(); 2476 lp.token = mKeyboardSwitcher.getInputView().getWindowToken(); 2477 lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; 2478 window.setAttributes(lp); 2479 window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 2480 mOptionsDialog.show(); 2481 } 2482 2483 private void changeKeyboardMode() { 2484 mKeyboardSwitcher.toggleSymbols(); 2485 if (mCapsLock && mKeyboardSwitcher.isAlphabetMode()) { 2486 mKeyboardSwitcher.setShiftLocked(mCapsLock); 2487 } 2488 2489 updateShiftKeyState(getCurrentInputEditorInfo()); 2490 } 2491 2492 public static <E> ArrayList<E> newArrayList(E... elements) { 2493 int capacity = (elements.length * 110) / 100 + 5; 2494 ArrayList<E> list = new ArrayList<E>(capacity); 2495 Collections.addAll(list, elements); 2496 return list; 2497 } 2498 2499 @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { 2500 super.dump(fd, fout, args); 2501 2502 final Printer p = new PrintWriterPrinter(fout); 2503 p.println("LatinIME state :"); 2504 p.println(" Keyboard mode = " + mKeyboardSwitcher.getKeyboardMode()); 2505 p.println(" mCapsLock=" + mCapsLock); 2506 p.println(" mComposing=" + mComposing.toString()); 2507 p.println(" mPredictionOn=" + mPredictionOn); 2508 p.println(" mCorrectionMode=" + mCorrectionMode); 2509 p.println(" mPredicting=" + mPredicting); 2510 p.println(" mAutoCorrectOn=" + mAutoCorrectOn); 2511 p.println(" mAutoSpace=" + mAutoSpace); 2512 p.println(" mCompletionOn=" + mCompletionOn); 2513 p.println(" TextEntryState.state=" + TextEntryState.getState()); 2514 p.println(" mSoundOn=" + mSoundOn); 2515 p.println(" mVibrateOn=" + mVibrateOn); 2516 p.println(" mPopupOn=" + mPopupOn); 2517 } 2518 2519 // Characters per second measurement 2520 2521 private long mLastCpsTime; 2522 private static final int CPS_BUFFER_SIZE = 16; 2523 private long[] mCpsIntervals = new long[CPS_BUFFER_SIZE]; 2524 private int mCpsIndex; 2525 2526 private void measureCps() { 2527 long now = System.currentTimeMillis(); 2528 if (mLastCpsTime == 0) mLastCpsTime = now - 100; // Initial 2529 mCpsIntervals[mCpsIndex] = now - mLastCpsTime; 2530 mLastCpsTime = now; 2531 mCpsIndex = (mCpsIndex + 1) % CPS_BUFFER_SIZE; 2532 long total = 0; 2533 for (int i = 0; i < CPS_BUFFER_SIZE; i++) total += mCpsIntervals[i]; 2534 System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total)); 2535 } 2536 2537 public void onAutoCompletionStateChanged(boolean isAutoCompletion) { 2538 mKeyboardSwitcher.onAutoCompletionStateChanged(isAutoCompletion); 2539 } 2540} 2541