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