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