1 /* 2 * Copyright (C) 2008-2009 Google Inc. 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.voice.EditingUtil; 20 import com.android.inputmethod.voice.FieldContext; 21 import com.android.inputmethod.voice.SettingsUtil; 22 import com.android.inputmethod.voice.VoiceInput; 23 24 import android.app.AlertDialog; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.DialogInterface; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.content.SharedPreferences; 31 import android.content.res.Configuration; 32 import android.content.res.Resources; 33 import android.inputmethodservice.InputMethodService; 34 import android.inputmethodservice.Keyboard; 35 import android.inputmethodservice.KeyboardView; 36 import android.media.AudioManager; 37 import android.os.Debug; 38 import android.os.Handler; 39 import android.os.Message; 40 import android.os.SystemClock; 41 import android.preference.PreferenceManager; 42 import android.speech.SpeechRecognizer; 43 import android.text.AutoText; 44 import android.text.ClipboardManager; 45 import android.text.TextUtils; 46 import android.util.Log; 47 import android.util.PrintWriterPrinter; 48 import android.util.Printer; 49 import android.view.HapticFeedbackConstants; 50 import android.view.KeyEvent; 51 import android.view.LayoutInflater; 52 import android.view.View; 53 import android.view.ViewParent; 54 import android.view.ViewGroup; 55 import android.view.Window; 56 import android.view.WindowManager; 57 import android.view.inputmethod.CompletionInfo; 58 import android.view.inputmethod.EditorInfo; 59 import android.view.inputmethod.ExtractedText; 60 import android.view.inputmethod.ExtractedTextRequest; 61 import android.view.inputmethod.InputConnection; 62 import android.view.inputmethod.InputMethodManager; 63 64 import java.io.FileDescriptor; 65 import java.io.PrintWriter; 66 import java.util.ArrayList; 67 import java.util.Collections; 68 import java.util.HashMap; 69 import java.util.List; 70 import java.util.Locale; 71 import java.util.Map; 72 73 /** 74 * Input method implementation for Qwerty'ish keyboard. 75 */ 76 public class LatinIME extends InputMethodService 77 implements KeyboardView.OnKeyboardActionListener, 78 VoiceInput.UiListener, 79 SharedPreferences.OnSharedPreferenceChangeListener { 80 private static final String TAG = "LatinIME"; 81 static final boolean DEBUG = false; 82 static final boolean TRACE = false; 83 static final boolean VOICE_INSTALLED = true; 84 static final boolean ENABLE_VOICE_BUTTON = true; 85 86 private static final String PREF_VIBRATE_ON = "vibrate_on"; 87 private static final String PREF_SOUND_ON = "sound_on"; 88 private static final String PREF_AUTO_CAP = "auto_cap"; 89 private static final String PREF_QUICK_FIXES = "quick_fixes"; 90 private static final String PREF_SHOW_SUGGESTIONS = "show_suggestions"; 91 private static final String PREF_AUTO_COMPLETE = "auto_complete"; 92 private static final String PREF_VOICE_MODE = "voice_mode"; 93 94 // Whether or not the user has used voice input before (and thus, whether to show the 95 // first-run warning dialog or not). 96 private static final String PREF_HAS_USED_VOICE_INPUT = "has_used_voice_input"; 97 98 // Whether or not the user has used voice input from an unsupported locale UI before. 99 // For example, the user has a Chinese UI but activates voice input. 100 private static final String PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE = 101 "has_used_voice_input_unsupported_locale"; 102 103 // A list of locales which are supported by default for voice input, unless we get a 104 // different list from Gservices. 105 public static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES = 106 "en " + 107 "en_US " + 108 "en_GB " + 109 "en_AU " + 110 "en_CA " + 111 "en_IE " + 112 "en_IN " + 113 "en_NZ " + 114 "en_SG " + 115 "en_ZA "; 116 117 // The private IME option used to indicate that no microphone should be shown for a 118 // given text field. For instance this is specified by the search dialog when the 119 // dialog is already showing a voice search button. 120 private static final String IME_OPTION_NO_MICROPHONE = "nm"; 121 122 public static final String PREF_SELECTED_LANGUAGES = "selected_languages"; 123 public static final String PREF_INPUT_LANGUAGE = "input_language"; 124 125 private static final int MSG_UPDATE_SUGGESTIONS = 0; 126 private static final int MSG_START_TUTORIAL = 1; 127 private static final int MSG_UPDATE_SHIFT_STATE = 2; 128 private static final int MSG_VOICE_RESULTS = 3; 129 private static final int MSG_START_LISTENING_AFTER_SWIPE = 4; 130 131 // If we detect a swipe gesture within N ms of typing, then swipe is 132 // ignored, since it may in fact be two key presses in quick succession. 133 private static final long MIN_MILLIS_AFTER_TYPING_BEFORE_SWIPE = 1000; 134 135 // How many continuous deletes at which to start deleting at a higher speed. 136 private static final int DELETE_ACCELERATE_AT = 20; 137 // Key events coming any faster than this are long-presses. 138 private static final int QUICK_PRESS = 200; 139 140 static final int KEYCODE_ENTER = '\n'; 141 static final int KEYCODE_SPACE = ' '; 142 static final int KEYCODE_PERIOD = '.'; 143 144 // Contextual menu positions 145 private static final int POS_SETTINGS = 0; 146 private static final int POS_METHOD = 1; 147 148 private LatinKeyboardView mInputView; 149 private CandidateViewContainer mCandidateViewContainer; 150 private CandidateView mCandidateView; 151 private Suggest mSuggest; 152 private CompletionInfo[] mCompletions; 153 154 private AlertDialog mOptionsDialog; 155 private AlertDialog mVoiceWarningDialog; 156 157 KeyboardSwitcher mKeyboardSwitcher; 158 159 private UserDictionary mUserDictionary; 160 private ContactsDictionary mContactsDictionary; 161 private AutoDictionary mAutoDictionary; 162 163 private Hints mHints; 164 165 Resources mResources; 166 167 private String mInputLocale; 168 private String mSystemLocale; 169 private LanguageSwitcher mLanguageSwitcher; 170 171 private StringBuilder mComposing = new StringBuilder(); 172 private WordComposer mWord = new WordComposer(); 173 private int mCommittedLength; 174 private boolean mPredicting; 175 private boolean mRecognizing; 176 private boolean mAfterVoiceInput; 177 private boolean mImmediatelyAfterVoiceInput; 178 private boolean mShowingVoiceSuggestions; 179 private boolean mImmediatelyAfterVoiceSuggestions; 180 private boolean mVoiceInputHighlighted; 181 private boolean mEnableVoiceButton; 182 private CharSequence mBestWord; 183 private boolean mPredictionOn; 184 private boolean mCompletionOn; 185 private boolean mHasDictionary; 186 private boolean mAutoSpace; 187 private boolean mJustAddedAutoSpace; 188 private boolean mAutoCorrectEnabled; 189 private boolean mAutoCorrectOn; 190 private boolean mCapsLock; 191 private boolean mPasswordText; 192 private boolean mEmailText; 193 private boolean mVibrateOn; 194 private boolean mSoundOn; 195 private boolean mAutoCap; 196 private boolean mQuickFixes; 197 private boolean mHasUsedVoiceInput; 198 private boolean mHasUsedVoiceInputUnsupportedLocale; 199 private boolean mLocaleSupportedForVoiceInput; 200 private boolean mShowSuggestions; 201 private boolean mSuggestionShouldReplaceCurrentWord; 202 private boolean mIsShowingHint; 203 private int mCorrectionMode; 204 private boolean mEnableVoice = true; 205 private boolean mVoiceOnPrimary; 206 private int mOrientation; 207 private List<CharSequence> mSuggestPuncList; 208 209 // Indicates whether the suggestion strip is to be on in landscape 210 private boolean mJustAccepted; 211 private CharSequence mJustRevertedSeparator; 212 private int mDeleteCount; 213 private long mLastKeyTime; 214 215 private Tutorial mTutorial; 216 217 private AudioManager mAudioManager; 218 // Align sound effect volume on music volume 219 private final float FX_VOLUME = -1.0f; 220 private boolean mSilentMode; 221 222 private String mWordSeparators; 223 private String mSentenceSeparators; 224 private VoiceInput mVoiceInput; 225 private VoiceResults mVoiceResults = new VoiceResults(); 226 private long mSwipeTriggerTimeMillis; 227 private boolean mConfigurationChanging; 228 229 // Keeps track of most recently inserted text (multi-character key) for reverting 230 private CharSequence mEnteredText; 231 232 // For each word, a list of potential replacements, usually from voice. 233 private Map<String, List<CharSequence>> mWordToSuggestions = 234 new HashMap<String, List<CharSequence>>(); 235 236 private class VoiceResults { 237 List<String> candidates; 238 Map<String, List<CharSequence>> alternatives; 239 } 240 241 private boolean mRefreshKeyboardRequired; 242 243 Handler mHandler = new Handler() { 244 @Override 245 public void handleMessage(Message msg) { 246 switch (msg.what) { 247 case MSG_UPDATE_SUGGESTIONS: 248 updateSuggestions(); 249 break; 250 case MSG_START_TUTORIAL: 251 if (mTutorial == null) { 252 if (mInputView.isShown()) { 253 mTutorial = new Tutorial(LatinIME.this, mInputView); 254 mTutorial.start(); 255 } else { 256 // Try again soon if the view is not yet showing 257 sendMessageDelayed(obtainMessage(MSG_START_TUTORIAL), 100); 258 } 259 } 260 break; 261 case MSG_UPDATE_SHIFT_STATE: 262 updateShiftKeyState(getCurrentInputEditorInfo()); 263 break; 264 case MSG_VOICE_RESULTS: 265 handleVoiceResults(); 266 break; 267 case MSG_START_LISTENING_AFTER_SWIPE: 268 if (mLastKeyTime < mSwipeTriggerTimeMillis) { 269 startListening(true); 270 } 271 } 272 } 273 }; 274 275 @Override public void onCreate() { 276 super.onCreate(); 277 //setStatusIcon(R.drawable.ime_qwerty); 278 mResources = getResources(); 279 final Configuration conf = mResources.getConfiguration(); 280 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 281 mLanguageSwitcher = new LanguageSwitcher(this); 282 mLanguageSwitcher.loadLocales(prefs); 283 mKeyboardSwitcher = new KeyboardSwitcher(this, this); 284 mKeyboardSwitcher.setLanguageSwitcher(mLanguageSwitcher); 285 mSystemLocale = conf.locale.toString(); 286 mLanguageSwitcher.setSystemLocale(conf.locale); 287 String inputLanguage = mLanguageSwitcher.getInputLanguage(); 288 if (inputLanguage == null) { 289 inputLanguage = conf.locale.toString(); 290 } 291 initSuggest(inputLanguage); 292 mOrientation = conf.orientation; 293 initSuggestPuncList(); 294 295 // register to receive ringer mode changes for silent mode 296 IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); 297 registerReceiver(mReceiver, filter); 298 if (VOICE_INSTALLED) { 299 mVoiceInput = new VoiceInput(this, this); 300 mHints = new Hints(this, new Hints.Display() { 301 public void showHint(int viewResource) { 302 LayoutInflater inflater = (LayoutInflater) getSystemService( 303 Context.LAYOUT_INFLATER_SERVICE); 304 View view = inflater.inflate(viewResource, null); 305 setCandidatesView(view); 306 setCandidatesViewShown(true); 307 mIsShowingHint = true; 308 } 309 }); 310 } 311 prefs.registerOnSharedPreferenceChangeListener(this); 312 } 313 314 private void initSuggest(String locale) { 315 mInputLocale = locale; 316 317 Resources orig = getResources(); 318 Configuration conf = orig.getConfiguration(); 319 Locale saveLocale = conf.locale; 320 conf.locale = new Locale(locale); 321 orig.updateConfiguration(conf, orig.getDisplayMetrics()); 322 if (mSuggest != null) { 323 mSuggest.close(); 324 } 325 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); 326 mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true); 327 mSuggest = new Suggest(this, R.raw.main); 328 updateAutoTextEnabled(saveLocale); 329 if (mUserDictionary != null) mUserDictionary.close(); 330 mUserDictionary = new UserDictionary(this, mInputLocale); 331 if (mContactsDictionary == null) { 332 mContactsDictionary = new ContactsDictionary(this); 333 } 334 if (mAutoDictionary != null) { 335 mAutoDictionary.close(); 336 } 337 mAutoDictionary = new AutoDictionary(this, this, mInputLocale); 338 mSuggest.setUserDictionary(mUserDictionary); 339 mSuggest.setContactsDictionary(mContactsDictionary); 340 mSuggest.setAutoDictionary(mAutoDictionary); 341 updateCorrectionMode(); 342 mWordSeparators = mResources.getString(R.string.word_separators); 343 mSentenceSeparators = mResources.getString(R.string.sentence_separators); 344 345 conf.locale = saveLocale; 346 orig.updateConfiguration(conf, orig.getDisplayMetrics()); 347 } 348 349 @Override 350 public void onDestroy() { 351 mUserDictionary.close(); 352 mContactsDictionary.close(); 353 unregisterReceiver(mReceiver); 354 if (VOICE_INSTALLED) { 355 mVoiceInput.destroy(); 356 } 357 super.onDestroy(); 358 } 359 360 @Override 361 public void onConfigurationChanged(Configuration conf) { 362 // If the system locale changes and is different from the saved 363 // locale (mSystemLocale), then reload the input locale list from the 364 // latin ime settings (shared prefs) and reset the input locale 365 // to the first one. 366 final String systemLocale = conf.locale.toString(); 367 if (!TextUtils.equals(systemLocale, mSystemLocale)) { 368 mSystemLocale = systemLocale; 369 if (mLanguageSwitcher != null) { 370 mLanguageSwitcher.loadLocales( 371 PreferenceManager.getDefaultSharedPreferences(this)); 372 mLanguageSwitcher.setSystemLocale(conf.locale); 373 toggleLanguage(true, true); 374 } else { 375 reloadKeyboards(); 376 } 377 } 378 // If orientation changed while predicting, commit the change 379 if (conf.orientation != mOrientation) { 380 InputConnection ic = getCurrentInputConnection(); 381 commitTyped(ic); 382 if (ic != null) ic.finishComposingText(); // For voice input 383 mOrientation = conf.orientation; 384 reloadKeyboards(); 385 } 386 mConfigurationChanging = true; 387 super.onConfigurationChanged(conf); 388 if (mRecognizing) { 389 switchToRecognitionStatusView(); 390 } 391 mConfigurationChanging = false; 392 } 393 394 @Override 395 public View onCreateInputView() { 396 mInputView = (LatinKeyboardView) getLayoutInflater().inflate( 397 R.layout.input, null); 398 mKeyboardSwitcher.setInputView(mInputView); 399 mKeyboardSwitcher.makeKeyboards(true); 400 mInputView.setOnKeyboardActionListener(this); 401 mKeyboardSwitcher.setKeyboardMode( 402 KeyboardSwitcher.MODE_TEXT, 0, 403 shouldShowVoiceButton(makeFieldContext(), getCurrentInputEditorInfo())); 404 return mInputView; 405 } 406 407 @Override 408 public View onCreateCandidatesView() { 409 mKeyboardSwitcher.makeKeyboards(true); 410 mCandidateViewContainer = (CandidateViewContainer) getLayoutInflater().inflate( 411 R.layout.candidates, null); 412 mCandidateViewContainer.initViews(); 413 mCandidateView = (CandidateView) mCandidateViewContainer.findViewById(R.id.candidates); 414 mCandidateView.setService(this); 415 setCandidatesViewShown(true); 416 return mCandidateViewContainer; 417 } 418 419 @Override 420 public void onStartInputView(EditorInfo attribute, boolean restarting) { 421 // In landscape mode, this method gets called without the input view being created. 422 if (mInputView == null) { 423 return; 424 } 425 426 if (mRefreshKeyboardRequired) { 427 mRefreshKeyboardRequired = false; 428 toggleLanguage(true, true); 429 } 430 431 mKeyboardSwitcher.makeKeyboards(false); 432 433 TextEntryState.newSession(this); 434 435 // Most such things we decide below in the switch statement, but we need to know 436 // now whether this is a password text field, because we need to know now (before 437 // the switch statement) whether we want to enable the voice button. 438 mPasswordText = false; 439 int variation = attribute.inputType & EditorInfo.TYPE_MASK_VARIATION; 440 if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD || 441 variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) { 442 mPasswordText = true; 443 } 444 445 mEnableVoiceButton = shouldShowVoiceButton(makeFieldContext(), attribute); 446 final boolean enableVoiceButton = mEnableVoiceButton && mEnableVoice; 447 448 mAfterVoiceInput = false; 449 mImmediatelyAfterVoiceInput = false; 450 mShowingVoiceSuggestions = false; 451 mImmediatelyAfterVoiceSuggestions = false; 452 mVoiceInputHighlighted = false; 453 mWordToSuggestions.clear(); 454 mInputTypeNoAutoCorrect = false; 455 mPredictionOn = false; 456 mCompletionOn = false; 457 mCompletions = null; 458 mCapsLock = false; 459 mEmailText = false; 460 mEnteredText = null; 461 462 switch (attribute.inputType & EditorInfo.TYPE_MASK_CLASS) { 463 case EditorInfo.TYPE_CLASS_NUMBER: 464 case EditorInfo.TYPE_CLASS_DATETIME: 465 mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_SYMBOLS, 466 attribute.imeOptions, enableVoiceButton); 467 break; 468 case EditorInfo.TYPE_CLASS_PHONE: 469 mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_PHONE, 470 attribute.imeOptions, enableVoiceButton); 471 break; 472 case EditorInfo.TYPE_CLASS_TEXT: 473 mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT, 474 attribute.imeOptions, enableVoiceButton); 475 //startPrediction(); 476 mPredictionOn = true; 477 // Make sure that passwords are not displayed in candidate view 478 if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD || 479 variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD ) { 480 mPredictionOn = false; 481 } 482 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) { 483 mEmailText = true; 484 } 485 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 486 || variation == EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME) { 487 mAutoSpace = false; 488 } else { 489 mAutoSpace = true; 490 } 491 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) { 492 mPredictionOn = false; 493 mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_EMAIL, 494 attribute.imeOptions, enableVoiceButton); 495 } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_URI) { 496 mPredictionOn = false; 497 mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_URL, 498 attribute.imeOptions, enableVoiceButton); 499 } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) { 500 mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_IM, 501 attribute.imeOptions, enableVoiceButton); 502 } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_FILTER) { 503 mPredictionOn = false; 504 } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) { 505 mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_WEB, 506 attribute.imeOptions, enableVoiceButton); 507 // If it's a browser edit field and auto correct is not ON explicitly, then 508 // disable auto correction, but keep suggestions on. 509 if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) { 510 mInputTypeNoAutoCorrect = true; 511 } 512 } 513 514 // If NO_SUGGESTIONS is set, don't do prediction. 515 if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) { 516 mPredictionOn = false; 517 mInputTypeNoAutoCorrect = true; 518 } 519 // If it's not multiline and the autoCorrect flag is not set, then don't correct 520 if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0 && 521 (attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) == 0) { 522 mInputTypeNoAutoCorrect = true; 523 } 524 if ((attribute.inputType&EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) { 525 mPredictionOn = false; 526 mCompletionOn = true && isFullscreenMode(); 527 } 528 updateShiftKeyState(attribute); 529 break; 530 default: 531 mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT, 532 attribute.imeOptions, enableVoiceButton); 533 updateShiftKeyState(attribute); 534 } 535 mInputView.closing(); 536 mComposing.setLength(0); 537 mPredicting = false; 538 mDeleteCount = 0; 539 mJustAddedAutoSpace = false; 540 loadSettings(); 541 updateShiftKeyState(attribute); 542 543 setCandidatesViewShown(false); 544 setSuggestions(null, false, false, false); 545 546 // If the dictionary is not big enough, don't auto correct 547 mHasDictionary = mSuggest.hasMainDictionary(); 548 549 updateCorrectionMode(); 550 551 mInputView.setProximityCorrectionEnabled(true); 552 mPredictionOn = mPredictionOn && (mCorrectionMode > 0 || mShowSuggestions); 553 checkTutorial(attribute.privateImeOptions); 554 if (TRACE) Debug.startMethodTracing("/data/trace/latinime"); 555 } 556 557 @Override 558 public void onFinishInput() { 559 super.onFinishInput(); 560 561 if (VOICE_INSTALLED && !mConfigurationChanging) { 562 if (mAfterVoiceInput) { 563 mVoiceInput.flushAllTextModificationCounters(); 564 mVoiceInput.logInputEnded(); 565 } 566 mVoiceInput.flushLogs(); 567 mVoiceInput.cancel(); 568 } 569 if (mInputView != null) { 570 mInputView.closing(); 571 } 572 if (mAutoDictionary != null) mAutoDictionary.flushPendingWrites(); 573 } 574 575 @Override 576 public void onUpdateExtractedText(int token, ExtractedText text) { 577 super.onUpdateExtractedText(token, text); 578 InputConnection ic = getCurrentInputConnection(); 579 if (!mImmediatelyAfterVoiceInput && mAfterVoiceInput && ic != null) { 580 if (mHints.showPunctuationHintIfNecessary(ic)) { 581 mVoiceInput.logPunctuationHintDisplayed(); 582 } 583 } 584 mImmediatelyAfterVoiceInput = false; 585 } 586 587 @Override 588 public void onUpdateSelection(int oldSelStart, int oldSelEnd, 589 int newSelStart, int newSelEnd, 590 int candidatesStart, int candidatesEnd) { 591 super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, 592 candidatesStart, candidatesEnd); 593 594 if (DEBUG) { 595 Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart 596 + ", ose=" + oldSelEnd 597 + ", nss=" + newSelStart 598 + ", nse=" + newSelEnd 599 + ", cs=" + candidatesStart 600 + ", ce=" + candidatesEnd); 601 } 602 603 if (mAfterVoiceInput) { 604 mVoiceInput.setCursorPos(newSelEnd); 605 mVoiceInput.setSelectionSpan(newSelEnd - newSelStart); 606 } 607 608 mSuggestionShouldReplaceCurrentWord = false; 609 // If the current selection in the text view changes, we should 610 // clear whatever candidate text we have. 611 if ((((mComposing.length() > 0 && mPredicting) || mVoiceInputHighlighted) 612 && (newSelStart != candidatesEnd 613 || newSelEnd != candidatesEnd))) { 614 mComposing.setLength(0); 615 mPredicting = false; 616 updateSuggestions(); 617 TextEntryState.reset(); 618 InputConnection ic = getCurrentInputConnection(); 619 if (ic != null) { 620 ic.finishComposingText(); 621 } 622 mVoiceInputHighlighted = false; 623 } else if (!mPredicting && !mJustAccepted) { 624 switch (TextEntryState.getState()) { 625 case TextEntryState.STATE_ACCEPTED_DEFAULT: 626 TextEntryState.reset(); 627 // fall through 628 case TextEntryState.STATE_SPACE_AFTER_PICKED: 629 mJustAddedAutoSpace = false; // The user moved the cursor. 630 break; 631 } 632 } 633 mJustAccepted = false; 634 postUpdateShiftKeyState(); 635 636 if (VOICE_INSTALLED) { 637 if (mShowingVoiceSuggestions) { 638 if (mImmediatelyAfterVoiceSuggestions) { 639 mImmediatelyAfterVoiceSuggestions = false; 640 } else { 641 updateSuggestions(); 642 mShowingVoiceSuggestions = false; 643 } 644 } 645 if (VoiceInput.ENABLE_WORD_CORRECTIONS) { 646 // If we have alternatives for the current word, then show them. 647 String word = EditingUtil.getWordAtCursor( 648 getCurrentInputConnection(), getWordSeparators()); 649 if (word != null && mWordToSuggestions.containsKey(word.trim())) { 650 mSuggestionShouldReplaceCurrentWord = true; 651 final List<CharSequence> suggestions = mWordToSuggestions.get(word.trim()); 652 653 setSuggestions(suggestions, false, true, true); 654 setCandidatesViewShown(true); 655 } 656 } 657 } 658 } 659 660 @Override 661 public void hideWindow() { 662 if (TRACE) Debug.stopMethodTracing(); 663 if (mOptionsDialog != null && mOptionsDialog.isShowing()) { 664 mOptionsDialog.dismiss(); 665 mOptionsDialog = null; 666 } 667 if (!mConfigurationChanging) { 668 if (mAfterVoiceInput) mVoiceInput.logInputEnded(); 669 if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) { 670 mVoiceInput.logKeyboardWarningDialogDismissed(); 671 mVoiceWarningDialog.dismiss(); 672 mVoiceWarningDialog = null; 673 } 674 if (VOICE_INSTALLED & mRecognizing) { 675 mVoiceInput.cancel(); 676 } 677 } 678 super.hideWindow(); 679 TextEntryState.endSession(); 680 } 681 682 @Override 683 public void onDisplayCompletions(CompletionInfo[] completions) { 684 if (false) { 685 Log.i("foo", "Received completions:"); 686 for (int i=0; i<(completions != null ? completions.length : 0); i++) { 687 Log.i("foo", " #" + i + ": " + completions[i]); 688 } 689 } 690 if (mCompletionOn) { 691 mCompletions = completions; 692 if (completions == null) { 693 setSuggestions(null, false, false, false); 694 return; 695 } 696 697 List<CharSequence> stringList = new ArrayList<CharSequence>(); 698 for (int i=0; i<(completions != null ? completions.length : 0); i++) { 699 CompletionInfo ci = completions[i]; 700 if (ci != null) stringList.add(ci.getText()); 701 } 702 //CharSequence typedWord = mWord.getTypedWord(); 703 setSuggestions(stringList, true, true, true); 704 mBestWord = null; 705 setCandidatesViewShown(isCandidateStripVisible() || mCompletionOn); 706 } 707 } 708 709 @Override 710 public void setCandidatesViewShown(boolean shown) { 711 // TODO: Remove this if we support candidates with hard keyboard 712 if (onEvaluateInputViewShown()) { 713 // Show the candidates view only if input view is showing 714 super.setCandidatesViewShown(shown && mInputView != null && mInputView.isShown()); 715 } 716 } 717 718 @Override 719 public void onComputeInsets(InputMethodService.Insets outInsets) { 720 super.onComputeInsets(outInsets); 721 if (!isFullscreenMode()) { 722 outInsets.contentTopInsets = outInsets.visibleTopInsets; 723 } 724 } 725 726 @Override 727 public boolean onKeyDown(int keyCode, KeyEvent event) { 728 switch (keyCode) { 729 case KeyEvent.KEYCODE_BACK: 730 if (event.getRepeatCount() == 0 && mInputView != null) { 731 if (mInputView.handleBack()) { 732 return true; 733 } else if (mTutorial != null) { 734 mTutorial.close(); 735 mTutorial = null; 736 } 737 } 738 break; 739 case KeyEvent.KEYCODE_DPAD_DOWN: 740 case KeyEvent.KEYCODE_DPAD_UP: 741 case KeyEvent.KEYCODE_DPAD_LEFT: 742 case KeyEvent.KEYCODE_DPAD_RIGHT: 743 // If tutorial is visible, don't allow dpad to work 744 if (mTutorial != null) { 745 return true; 746 } 747 break; 748 } 749 return super.onKeyDown(keyCode, event); 750 } 751 752 @Override 753 public boolean onKeyUp(int keyCode, KeyEvent event) { 754 switch (keyCode) { 755 case KeyEvent.KEYCODE_DPAD_DOWN: 756 case KeyEvent.KEYCODE_DPAD_UP: 757 case KeyEvent.KEYCODE_DPAD_LEFT: 758 case KeyEvent.KEYCODE_DPAD_RIGHT: 759 // If tutorial is visible, don't allow dpad to work 760 if (mTutorial != null) { 761 return true; 762 } 763 // Enable shift key and DPAD to do selections 764 if (mInputView != null && mInputView.isShown() && mInputView.isShifted()) { 765 event = new KeyEvent(event.getDownTime(), event.getEventTime(), 766 event.getAction(), event.getKeyCode(), event.getRepeatCount(), 767 event.getDeviceId(), event.getScanCode(), 768 KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON); 769 InputConnection ic = getCurrentInputConnection(); 770 if (ic != null) ic.sendKeyEvent(event); 771 return true; 772 } 773 break; 774 } 775 return super.onKeyUp(keyCode, event); 776 } 777 778 private void revertVoiceInput() { 779 InputConnection ic = getCurrentInputConnection(); 780 if (ic != null) ic.commitText("", 1); 781 updateSuggestions(); 782 mVoiceInputHighlighted = false; 783 } 784 785 private void commitVoiceInput() { 786 InputConnection ic = getCurrentInputConnection(); 787 if (ic != null) ic.finishComposingText(); 788 updateSuggestions(); 789 mVoiceInputHighlighted = false; 790 } 791 792 private void reloadKeyboards() { 793 if (mKeyboardSwitcher == null) { 794 mKeyboardSwitcher = new KeyboardSwitcher(this, this); 795 } 796 mKeyboardSwitcher.setLanguageSwitcher(mLanguageSwitcher); 797 if (mInputView != null) { 798 mKeyboardSwitcher.setVoiceMode(mEnableVoice && mEnableVoiceButton, mVoiceOnPrimary); 799 } 800 mKeyboardSwitcher.makeKeyboards(true); 801 } 802 803 private void commitTyped(InputConnection inputConnection) { 804 if (mPredicting) { 805 mPredicting = false; 806 if (mComposing.length() > 0) { 807 if (inputConnection != null) { 808 inputConnection.commitText(mComposing, 1); 809 } 810 mCommittedLength = mComposing.length(); 811 TextEntryState.acceptedTyped(mComposing); 812 checkAddToDictionary(mComposing, AutoDictionary.FREQUENCY_FOR_TYPED); 813 } 814 updateSuggestions(); 815 } 816 } 817 818 private void postUpdateShiftKeyState() { 819 mHandler.removeMessages(MSG_UPDATE_SHIFT_STATE); 820 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SHIFT_STATE), 300); 821 } 822 823 public void updateShiftKeyState(EditorInfo attr) { 824 InputConnection ic = getCurrentInputConnection(); 825 if (attr != null && mInputView != null && mKeyboardSwitcher.isAlphabetMode() 826 && ic != null) { 827 mInputView.setShifted(mCapsLock || getCursorCapsMode(ic, attr) != 0); 828 } 829 } 830 831 private int getCursorCapsMode(InputConnection ic, EditorInfo attr) { 832 int caps = 0; 833 EditorInfo ei = getCurrentInputEditorInfo(); 834 if (mAutoCap && ei != null && ei.inputType != EditorInfo.TYPE_NULL) { 835 caps = ic.getCursorCapsMode(attr.inputType); 836 } 837 return caps; 838 } 839 840 private void swapPunctuationAndSpace() { 841 final InputConnection ic = getCurrentInputConnection(); 842 if (ic == null) return; 843 CharSequence lastTwo = ic.getTextBeforeCursor(2, 0); 844 if (lastTwo != null && lastTwo.length() == 2 845 && lastTwo.charAt(0) == KEYCODE_SPACE && isSentenceSeparator(lastTwo.charAt(1))) { 846 ic.beginBatchEdit(); 847 ic.deleteSurroundingText(2, 0); 848 ic.commitText(lastTwo.charAt(1) + " ", 1); 849 ic.endBatchEdit(); 850 updateShiftKeyState(getCurrentInputEditorInfo()); 851 mJustAddedAutoSpace = true; 852 } 853 } 854 855 private void reswapPeriodAndSpace() { 856 final InputConnection ic = getCurrentInputConnection(); 857 if (ic == null) return; 858 CharSequence lastThree = ic.getTextBeforeCursor(3, 0); 859 if (lastThree != null && lastThree.length() == 3 860 && lastThree.charAt(0) == KEYCODE_PERIOD 861 && lastThree.charAt(1) == KEYCODE_SPACE 862 && lastThree.charAt(2) == KEYCODE_PERIOD) { 863 ic.beginBatchEdit(); 864 ic.deleteSurroundingText(3, 0); 865 ic.commitText(" ..", 1); 866 ic.endBatchEdit(); 867 updateShiftKeyState(getCurrentInputEditorInfo()); 868 } 869 } 870 871 private void doubleSpace() { 872 //if (!mAutoPunctuate) return; 873 if (mCorrectionMode == Suggest.CORRECTION_NONE) return; 874 final InputConnection ic = getCurrentInputConnection(); 875 if (ic == null) return; 876 CharSequence lastThree = ic.getTextBeforeCursor(3, 0); 877 if (lastThree != null && lastThree.length() == 3 878 && Character.isLetterOrDigit(lastThree.charAt(0)) 879 && lastThree.charAt(1) == KEYCODE_SPACE && lastThree.charAt(2) == KEYCODE_SPACE) { 880 ic.beginBatchEdit(); 881 ic.deleteSurroundingText(2, 0); 882 ic.commitText(". ", 1); 883 ic.endBatchEdit(); 884 updateShiftKeyState(getCurrentInputEditorInfo()); 885 mJustAddedAutoSpace = true; 886 } 887 } 888 889 private void maybeRemovePreviousPeriod(CharSequence text) { 890 final InputConnection ic = getCurrentInputConnection(); 891 if (ic == null) return; 892 893 // When the text's first character is '.', remove the previous period 894 // if there is one. 895 CharSequence lastOne = ic.getTextBeforeCursor(1, 0); 896 if (lastOne != null && lastOne.length() == 1 897 && lastOne.charAt(0) == KEYCODE_PERIOD 898 && text.charAt(0) == KEYCODE_PERIOD) { 899 ic.deleteSurroundingText(1, 0); 900 } 901 } 902 903 private void removeTrailingSpace() { 904 final InputConnection ic = getCurrentInputConnection(); 905 if (ic == null) return; 906 907 CharSequence lastOne = ic.getTextBeforeCursor(1, 0); 908 if (lastOne != null && lastOne.length() == 1 909 && lastOne.charAt(0) == KEYCODE_SPACE) { 910 ic.deleteSurroundingText(1, 0); 911 } 912 } 913 914 public boolean addWordToDictionary(String word) { 915 mUserDictionary.addWord(word, 128); 916 return true; 917 } 918 919 private boolean isAlphabet(int code) { 920 if (Character.isLetter(code)) { 921 return true; 922 } else { 923 return false; 924 } 925 } 926 927 // Implementation of KeyboardViewListener 928 929 public void onKey(int primaryCode, int[] keyCodes) { 930 long when = SystemClock.uptimeMillis(); 931 if (primaryCode != Keyboard.KEYCODE_DELETE || 932 when > mLastKeyTime + QUICK_PRESS) { 933 mDeleteCount = 0; 934 } 935 mLastKeyTime = when; 936 switch (primaryCode) { 937 case Keyboard.KEYCODE_DELETE: 938 handleBackspace(); 939 mDeleteCount++; 940 break; 941 case Keyboard.KEYCODE_SHIFT: 942 handleShift(); 943 break; 944 case Keyboard.KEYCODE_CANCEL: 945 if (mOptionsDialog == null || !mOptionsDialog.isShowing()) { 946 handleClose(); 947 } 948 break; 949 case LatinKeyboardView.KEYCODE_OPTIONS: 950 showOptionsMenu(); 951 break; 952 case LatinKeyboardView.KEYCODE_NEXT_LANGUAGE: 953 toggleLanguage(false, true); 954 break; 955 case LatinKeyboardView.KEYCODE_PREV_LANGUAGE: 956 toggleLanguage(false, false); 957 break; 958 case LatinKeyboardView.KEYCODE_SHIFT_LONGPRESS: 959 if (mCapsLock) { 960 handleShift(); 961 } else { 962 toggleCapsLock(); 963 } 964 break; 965 case Keyboard.KEYCODE_MODE_CHANGE: 966 changeKeyboardMode(); 967 break; 968 case LatinKeyboardView.KEYCODE_VOICE: 969 if (VOICE_INSTALLED) { 970 startListening(false /* was a button press, was not a swipe */); 971 } 972 break; 973 case 9 /*Tab*/: 974 sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB); 975 break; 976 default: 977 if (primaryCode != KEYCODE_ENTER) { 978 mJustAddedAutoSpace = false; 979 } 980 if (isWordSeparator(primaryCode)) { 981 handleSeparator(primaryCode); 982 } else { 983 handleCharacter(primaryCode, keyCodes); 984 } 985 // Cancel the just reverted state 986 mJustRevertedSeparator = null; 987 } 988 if (mKeyboardSwitcher.onKey(primaryCode)) { 989 changeKeyboardMode(); 990 } 991 // Reset after any single keystroke 992 mEnteredText = null; 993 } 994 995 public void onText(CharSequence text) { 996 if (VOICE_INSTALLED && mVoiceInputHighlighted) { 997 commitVoiceInput(); 998 } 999 InputConnection ic = getCurrentInputConnection(); 1000 if (ic == null) return; 1001 ic.beginBatchEdit(); 1002 if (mPredicting) { 1003 commitTyped(ic); 1004 } 1005 maybeRemovePreviousPeriod(text); 1006 ic.commitText(text, 1); 1007 ic.endBatchEdit(); 1008 updateShiftKeyState(getCurrentInputEditorInfo()); 1009 mJustRevertedSeparator = null; 1010 mJustAddedAutoSpace = false; 1011 mEnteredText = text; 1012 } 1013 1014 private void handleBackspace() { 1015 if (VOICE_INSTALLED && mVoiceInputHighlighted) { 1016 mVoiceInput.incrementTextModificationDeleteCount( 1017 mVoiceResults.candidates.get(0).toString().length()); 1018 revertVoiceInput(); 1019 return; 1020 } 1021 boolean deleteChar = false; 1022 InputConnection ic = getCurrentInputConnection(); 1023 if (ic == null) return; 1024 1025 if (mAfterVoiceInput) { 1026 // Don't log delete if the user is pressing delete at 1027 // the beginning of the text box (hence not deleting anything) 1028 if (mVoiceInput.getCursorPos() > 0) { 1029 // If anything was selected before the delete was pressed, increment the 1030 // delete count by the length of the selection 1031 int deleteLen = mVoiceInput.getSelectionSpan() > 0 ? 1032 mVoiceInput.getSelectionSpan() : 1; 1033 mVoiceInput.incrementTextModificationDeleteCount(deleteLen); 1034 } 1035 } 1036 1037 if (mPredicting) { 1038 final int length = mComposing.length(); 1039 if (length > 0) { 1040 mComposing.delete(length - 1, length); 1041 mWord.deleteLast(); 1042 ic.setComposingText(mComposing, 1); 1043 if (mComposing.length() == 0) { 1044 mPredicting = false; 1045 } 1046 postUpdateSuggestions(); 1047 } else { 1048 ic.deleteSurroundingText(1, 0); 1049 } 1050 } else { 1051 deleteChar = true; 1052 } 1053 postUpdateShiftKeyState(); 1054 TextEntryState.backspace(); 1055 if (TextEntryState.getState() == TextEntryState.STATE_UNDO_COMMIT) { 1056 revertLastWord(deleteChar); 1057 return; 1058 } else if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) { 1059 ic.deleteSurroundingText(mEnteredText.length(), 0); 1060 } else if (deleteChar) { 1061 sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); 1062 if (mDeleteCount > DELETE_ACCELERATE_AT) { 1063 sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); 1064 } 1065 } 1066 mJustRevertedSeparator = null; 1067 } 1068 1069 private void handleShift() { 1070 mHandler.removeMessages(MSG_UPDATE_SHIFT_STATE); 1071 if (mKeyboardSwitcher.isAlphabetMode()) { 1072 // Alphabet keyboard 1073 checkToggleCapsLock(); 1074 mInputView.setShifted(mCapsLock || !mInputView.isShifted()); 1075 } else { 1076 mKeyboardSwitcher.toggleShift(); 1077 } 1078 } 1079 1080 private void handleCharacter(int primaryCode, int[] keyCodes) { 1081 if (VOICE_INSTALLED && mVoiceInputHighlighted) { 1082 commitVoiceInput(); 1083 } 1084 1085 if (mAfterVoiceInput) { 1086 // Assume input length is 1. This assumption fails for smiley face insertions. 1087 mVoiceInput.incrementTextModificationInsertCount(1); 1088 } 1089 1090 if (isAlphabet(primaryCode) && isPredictionOn() && !isCursorTouchingWord()) { 1091 if (!mPredicting) { 1092 mPredicting = true; 1093 mComposing.setLength(0); 1094 mWord.reset(); 1095 } 1096 } 1097 if (mInputView.isShifted()) { 1098 // TODO: This doesn't work with , need to fix it in the next release. 1099 if (keyCodes == null || keyCodes[0] < Character.MIN_CODE_POINT 1100 || keyCodes[0] > Character.MAX_CODE_POINT) { 1101 return; 1102 } 1103 primaryCode = new String(keyCodes, 0, 1).toUpperCase().charAt(0); 1104 } 1105 if (mPredicting) { 1106 if (mInputView.isShifted() && mComposing.length() == 0) { 1107 mWord.setCapitalized(true); 1108 } 1109 mComposing.append((char) primaryCode); 1110 mWord.add(primaryCode, keyCodes); 1111 InputConnection ic = getCurrentInputConnection(); 1112 if (ic != null) { 1113 // If it's the first letter, make note of auto-caps state 1114 if (mWord.size() == 1) { 1115 mWord.setAutoCapitalized( 1116 getCursorCapsMode(ic, getCurrentInputEditorInfo()) != 0); 1117 } 1118 ic.setComposingText(mComposing, 1); 1119 } 1120 postUpdateSuggestions(); 1121 } else { 1122 sendKeyChar((char)primaryCode); 1123 } 1124 updateShiftKeyState(getCurrentInputEditorInfo()); 1125 measureCps(); 1126 TextEntryState.typedCharacter((char) primaryCode, isWordSeparator(primaryCode)); 1127 } 1128 1129 private void handleSeparator(int primaryCode) { 1130 if (VOICE_INSTALLED && mVoiceInputHighlighted) { 1131 commitVoiceInput(); 1132 } 1133 1134 if (mAfterVoiceInput){ 1135 // Assume input length is 1. This assumption fails for smiley face insertions. 1136 mVoiceInput.incrementTextModificationInsertPunctuationCount(1); 1137 } 1138 1139 boolean pickedDefault = false; 1140 // Handle separator 1141 InputConnection ic = getCurrentInputConnection(); 1142 if (ic != null) { 1143 ic.beginBatchEdit(); 1144 } 1145 if (mPredicting) { 1146 // In certain languages where single quote is a separator, it's better 1147 // not to auto correct, but accept the typed word. For instance, 1148 // in Italian dov' should not be expanded to dove' because the elision 1149 // requires the last vowel to be removed. 1150 if (mAutoCorrectOn && primaryCode != '\'' && 1151 (mJustRevertedSeparator == null 1152 || mJustRevertedSeparator.length() == 0 1153 || mJustRevertedSeparator.charAt(0) != primaryCode)) { 1154 pickDefaultSuggestion(); 1155 pickedDefault = true; 1156 // Picked the suggestion by the space key. We consider this 1157 // as "added an auto space". 1158 if (primaryCode == KEYCODE_SPACE) { 1159 mJustAddedAutoSpace = true; 1160 } 1161 } else { 1162 commitTyped(ic); 1163 } 1164 } 1165 if (mJustAddedAutoSpace && primaryCode == KEYCODE_ENTER) { 1166 removeTrailingSpace(); 1167 mJustAddedAutoSpace = false; 1168 } 1169 sendKeyChar((char)primaryCode); 1170 1171 // Handle the case of ". ." -> " .." with auto-space if necessary 1172 // before changing the TextEntryState. 1173 if (TextEntryState.getState() == TextEntryState.STATE_PUNCTUATION_AFTER_ACCEPTED 1174 && primaryCode == KEYCODE_PERIOD) { 1175 reswapPeriodAndSpace(); 1176 } 1177 1178 TextEntryState.typedCharacter((char) primaryCode, true); 1179 if (TextEntryState.getState() == TextEntryState.STATE_PUNCTUATION_AFTER_ACCEPTED 1180 && primaryCode != KEYCODE_ENTER) { 1181 swapPunctuationAndSpace(); 1182 } else if (isPredictionOn() && primaryCode == KEYCODE_SPACE) { 1183 //else if (TextEntryState.STATE_SPACE_AFTER_ACCEPTED) { 1184 doubleSpace(); 1185 } 1186 if (pickedDefault && mBestWord != null) { 1187 TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord); 1188 } 1189 updateShiftKeyState(getCurrentInputEditorInfo()); 1190 if (ic != null) { 1191 ic.endBatchEdit(); 1192 } 1193 } 1194 1195 private void handleClose() { 1196 commitTyped(getCurrentInputConnection()); 1197 if (VOICE_INSTALLED & mRecognizing) { 1198 mVoiceInput.cancel(); 1199 } 1200 requestHideSelf(0); 1201 mInputView.closing(); 1202 TextEntryState.endSession(); 1203 } 1204 1205 private void checkToggleCapsLock() { 1206 if (mInputView.getKeyboard().isShifted()) { 1207 toggleCapsLock(); 1208 } 1209 } 1210 1211 private void toggleCapsLock() { 1212 mCapsLock = !mCapsLock; 1213 if (mKeyboardSwitcher.isAlphabetMode()) { 1214 ((LatinKeyboard) mInputView.getKeyboard()).setShiftLocked(mCapsLock); 1215 } 1216 } 1217 1218 private void postUpdateSuggestions() { 1219 mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS); 1220 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SUGGESTIONS), 100); 1221 } 1222 1223 private boolean isPredictionOn() { 1224 boolean predictionOn = mPredictionOn; 1225 return predictionOn; 1226 } 1227 1228 private boolean isCandidateStripVisible() { 1229 return isPredictionOn() && mShowSuggestions; 1230 } 1231 1232 public void onCancelVoice() { 1233 if (mRecognizing) { 1234 switchToKeyboardView(); 1235 } 1236 } 1237 1238 private void switchToKeyboardView() { 1239 mHandler.post(new Runnable() { 1240 public void run() { 1241 mRecognizing = false; 1242 if (mInputView != null) { 1243 setInputView(mInputView); 1244 } 1245 updateInputViewShown(); 1246 }}); 1247 } 1248 1249 private void switchToRecognitionStatusView() { 1250 final boolean configChanged = mConfigurationChanging; 1251 mHandler.post(new Runnable() { 1252 public void run() { 1253 mRecognizing = true; 1254 View v = mVoiceInput.getView(); 1255 ViewParent p = v.getParent(); 1256 if (p != null && p instanceof ViewGroup) { 1257 ((ViewGroup)v.getParent()).removeView(v); 1258 } 1259 setInputView(v); 1260 updateInputViewShown(); 1261 if (configChanged) { 1262 mVoiceInput.onConfigurationChanged(); 1263 } 1264 }}); 1265 } 1266 1267 private void startListening(boolean swipe) { 1268 if (!mHasUsedVoiceInput || 1269 (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale)) { 1270 // Calls reallyStartListening if user clicks OK, does nothing if user clicks Cancel. 1271 showVoiceWarningDialog(swipe); 1272 } else { 1273 reallyStartListening(swipe); 1274 } 1275 } 1276 1277 private void reallyStartListening(boolean swipe) { 1278 if (!mHasUsedVoiceInput) { 1279 // The user has started a voice input, so remember that in the 1280 // future (so we don't show the warning dialog after the first run). 1281 SharedPreferences.Editor editor = 1282 PreferenceManager.getDefaultSharedPreferences(this).edit(); 1283 editor.putBoolean(PREF_HAS_USED_VOICE_INPUT, true); 1284 editor.commit(); 1285 mHasUsedVoiceInput = true; 1286 } 1287 1288 if (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale) { 1289 // The user has started a voice input from an unsupported locale, so remember that 1290 // in the future (so we don't show the warning dialog the next time they do this). 1291 SharedPreferences.Editor editor = 1292 PreferenceManager.getDefaultSharedPreferences(this).edit(); 1293 editor.putBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, true); 1294 editor.commit(); 1295 mHasUsedVoiceInputUnsupportedLocale = true; 1296 } 1297 1298 // Clear N-best suggestions 1299 setSuggestions(null, false, false, true); 1300 1301 FieldContext context = new FieldContext( 1302 getCurrentInputConnection(), 1303 getCurrentInputEditorInfo(), 1304 mLanguageSwitcher.getInputLanguage(), 1305 mLanguageSwitcher.getEnabledLanguages()); 1306 mVoiceInput.startListening(context, swipe); 1307 switchToRecognitionStatusView(); 1308 } 1309 1310 private void showVoiceWarningDialog(final boolean swipe) { 1311 AlertDialog.Builder builder = new AlertDialog.Builder(this); 1312 builder.setCancelable(true); 1313 builder.setIcon(R.drawable.ic_mic_dialog); 1314 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 1315 public void onClick(DialogInterface dialog, int whichButton) { 1316 mVoiceInput.logKeyboardWarningDialogOk(); 1317 reallyStartListening(swipe); 1318 } 1319 }); 1320 builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { 1321 public void onClick(DialogInterface dialog, int whichButton) { 1322 mVoiceInput.logKeyboardWarningDialogCancel(); 1323 } 1324 }); 1325 1326 if (mLocaleSupportedForVoiceInput) { 1327 String message = getString(R.string.voice_warning_may_not_understand) + "\n\n" + 1328 getString(R.string.voice_warning_how_to_turn_off); 1329 builder.setMessage(message); 1330 } else { 1331 String message = getString(R.string.voice_warning_locale_not_supported) + "\n\n" + 1332 getString(R.string.voice_warning_may_not_understand) + "\n\n" + 1333 getString(R.string.voice_warning_how_to_turn_off); 1334 builder.setMessage(message); 1335 } 1336 1337 builder.setTitle(R.string.voice_warning_title); 1338 mVoiceWarningDialog = builder.create(); 1339 1340 Window window = mVoiceWarningDialog.getWindow(); 1341 WindowManager.LayoutParams lp = window.getAttributes(); 1342 lp.token = mInputView.getWindowToken(); 1343 lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; 1344 window.setAttributes(lp); 1345 window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 1346 mVoiceInput.logKeyboardWarningDialogShown(); 1347 mVoiceWarningDialog.show(); 1348 } 1349 1350 public void onVoiceResults(List<String> candidates, 1351 Map<String, List<CharSequence>> alternatives) { 1352 if (!mRecognizing) { 1353 return; 1354 } 1355 mVoiceResults.candidates = candidates; 1356 mVoiceResults.alternatives = alternatives; 1357 mHandler.sendMessage(mHandler.obtainMessage(MSG_VOICE_RESULTS)); 1358 } 1359 1360 private void handleVoiceResults() { 1361 mAfterVoiceInput = true; 1362 mImmediatelyAfterVoiceInput = true; 1363 1364 InputConnection ic = getCurrentInputConnection(); 1365 if (!isFullscreenMode()) { 1366 // Start listening for updates to the text from typing, etc. 1367 if (ic != null) { 1368 ExtractedTextRequest req = new ExtractedTextRequest(); 1369 ic.getExtractedText(req, InputConnection.GET_EXTRACTED_TEXT_MONITOR); 1370 } 1371 } 1372 1373 vibrate(); 1374 switchToKeyboardView(); 1375 1376 final List<CharSequence> nBest = new ArrayList<CharSequence>(); 1377 boolean capitalizeFirstWord = preferCapitalization() 1378 || (mKeyboardSwitcher.isAlphabetMode() && mInputView.isShifted()); 1379 for (String c : mVoiceResults.candidates) { 1380 if (capitalizeFirstWord) { 1381 c = Character.toUpperCase(c.charAt(0)) + c.substring(1, c.length()); 1382 } 1383 nBest.add(c); 1384 } 1385 1386 if (nBest.size() == 0) { 1387 return; 1388 } 1389 1390 String bestResult = nBest.get(0).toString(); 1391 1392 mVoiceInput.logVoiceInputDelivered(bestResult.length()); 1393 1394 mHints.registerVoiceResult(bestResult); 1395 1396 if (ic != null) ic.beginBatchEdit(); // To avoid extra updates on committing older text 1397 1398 commitTyped(ic); 1399 EditingUtil.appendText(ic, bestResult); 1400 1401 if (ic != null) ic.endBatchEdit(); 1402 1403 // Show N-Best alternates, if there is more than one choice. 1404 if (nBest.size() > 1) { 1405 mImmediatelyAfterVoiceSuggestions = true; 1406 mShowingVoiceSuggestions = true; 1407 setSuggestions(nBest.subList(1, nBest.size()), false, true, true); 1408 setCandidatesViewShown(true); 1409 } 1410 mVoiceInputHighlighted = true; 1411 mWordToSuggestions.putAll(mVoiceResults.alternatives); 1412 1413 } 1414 1415 private void setSuggestions( 1416 List<CharSequence> suggestions, 1417 boolean completions, 1418 1419 boolean typedWordValid, 1420 boolean haveMinimalSuggestion) { 1421 1422 if (mIsShowingHint) { 1423 setCandidatesView(mCandidateViewContainer); 1424 mIsShowingHint = false; 1425 } 1426 1427 if (mCandidateView != null) { 1428 mCandidateView.setSuggestions( 1429 suggestions, completions, typedWordValid, haveMinimalSuggestion); 1430 } 1431 } 1432 1433 private void updateSuggestions() { 1434 mSuggestionShouldReplaceCurrentWord = false; 1435 1436 ((LatinKeyboard) mInputView.getKeyboard()).setPreferredLetters(null); 1437 1438 // Check if we have a suggestion engine attached. 1439 if ((mSuggest == null || !isPredictionOn()) && !mVoiceInputHighlighted) { 1440 return; 1441 } 1442 1443 if (!mPredicting) { 1444 setNextSuggestions(); 1445 return; 1446 } 1447 1448 List<CharSequence> stringList = mSuggest.getSuggestions(mInputView, mWord, false); 1449 int[] nextLettersFrequencies = mSuggest.getNextLettersFrequencies(); 1450 1451 ((LatinKeyboard) mInputView.getKeyboard()).setPreferredLetters(nextLettersFrequencies); 1452 1453 boolean correctionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasMinimalCorrection(); 1454 //|| mCorrectionMode == mSuggest.CORRECTION_FULL; 1455 CharSequence typedWord = mWord.getTypedWord(); 1456 // If we're in basic correct 1457 boolean typedWordValid = mSuggest.isValidWord(typedWord) || 1458 (preferCapitalization() && mSuggest.isValidWord(typedWord.toString().toLowerCase())); 1459 if (mCorrectionMode == Suggest.CORRECTION_FULL) { 1460 correctionAvailable |= typedWordValid; 1461 } 1462 // Don't auto-correct words with multiple capital letter 1463 correctionAvailable &= !mWord.isMostlyCaps(); 1464 1465 setSuggestions(stringList, false, typedWordValid, correctionAvailable); 1466 if (stringList.size() > 0) { 1467 if (correctionAvailable && !typedWordValid && stringList.size() > 1) { 1468 mBestWord = stringList.get(1); 1469 } else { 1470 mBestWord = typedWord; 1471 } 1472 } else { 1473 mBestWord = null; 1474 } 1475 setCandidatesViewShown(isCandidateStripVisible() || mCompletionOn); 1476 } 1477 1478 private void pickDefaultSuggestion() { 1479 // Complete any pending candidate query first 1480 if (mHandler.hasMessages(MSG_UPDATE_SUGGESTIONS)) { 1481 mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS); 1482 updateSuggestions(); 1483 } 1484 if (mBestWord != null && mBestWord.length() > 0) { 1485 TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord); 1486 mJustAccepted = true; 1487 pickSuggestion(mBestWord); 1488 // Add the word to the auto dictionary if it's not a known word 1489 checkAddToDictionary(mBestWord, AutoDictionary.FREQUENCY_FOR_TYPED); 1490 } 1491 } 1492 1493 public void pickSuggestionManually(int index, CharSequence suggestion) { 1494 if (mAfterVoiceInput && mShowingVoiceSuggestions) mVoiceInput.logNBestChoose(index); 1495 1496 if (mAfterVoiceInput && !mShowingVoiceSuggestions) { 1497 mVoiceInput.flushAllTextModificationCounters(); 1498 // send this intent AFTER logging any prior aggregated edits. 1499 mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.length()); 1500 } 1501 1502 InputConnection ic = getCurrentInputConnection(); 1503 if (ic != null) { 1504 ic.beginBatchEdit(); 1505 } 1506 if (mCompletionOn && mCompletions != null && index >= 0 1507 && index < mCompletions.length) { 1508 CompletionInfo ci = mCompletions[index]; 1509 if (ic != null) { 1510 ic.commitCompletion(ci); 1511 } 1512 mCommittedLength = suggestion.length(); 1513 if (mCandidateView != null) { 1514 mCandidateView.clear(); 1515 } 1516 updateShiftKeyState(getCurrentInputEditorInfo()); 1517 if (ic != null) { 1518 ic.endBatchEdit(); 1519 } 1520 return; 1521 } 1522 1523 // If this is a punctuation, apply it through the normal key press 1524 if (suggestion.length() == 1 && isWordSeparator(suggestion.charAt(0))) { 1525 onKey(suggestion.charAt(0), null); 1526 if (ic != null) { 1527 ic.endBatchEdit(); 1528 } 1529 return; 1530 } 1531 mJustAccepted = true; 1532 pickSuggestion(suggestion); 1533 // Add the word to the auto dictionary if it's not a known word 1534 if (index == 0) { 1535 checkAddToDictionary(suggestion, AutoDictionary.FREQUENCY_FOR_PICKED); 1536 } 1537 TextEntryState.acceptedSuggestion(mComposing.toString(), suggestion); 1538 // Follow it with a space 1539 if (mAutoSpace) { 1540 sendSpace(); 1541 mJustAddedAutoSpace = true; 1542 } 1543 // Fool the state watcher so that a subsequent backspace will not do a revert 1544 TextEntryState.typedCharacter((char) KEYCODE_SPACE, true); 1545 if (index == 0 && mCorrectionMode > 0 && !mSuggest.isValidWord(suggestion)) { 1546 mCandidateView.showAddToDictionaryHint(suggestion); 1547 } 1548 if (ic != null) { 1549 ic.endBatchEdit(); 1550 } 1551 } 1552 1553 private void pickSuggestion(CharSequence suggestion) { 1554 if (mCapsLock) { 1555 suggestion = suggestion.toString().toUpperCase(); 1556 } else if (preferCapitalization() 1557 || (mKeyboardSwitcher.isAlphabetMode() && mInputView.isShifted())) { 1558 suggestion = suggestion.toString().toUpperCase().charAt(0) 1559 + suggestion.subSequence(1, suggestion.length()).toString(); 1560 } 1561 InputConnection ic = getCurrentInputConnection(); 1562 if (ic != null) { 1563 if (mSuggestionShouldReplaceCurrentWord) { 1564 EditingUtil.deleteWordAtCursor(ic, getWordSeparators()); 1565 } 1566 if (!VoiceInput.DELETE_SYMBOL.equals(suggestion)) { 1567 ic.commitText(suggestion, 1); 1568 } 1569 } 1570 mPredicting = false; 1571 mCommittedLength = suggestion.length(); 1572 ((LatinKeyboard) mInputView.getKeyboard()).setPreferredLetters(null); 1573 setNextSuggestions(); 1574 updateShiftKeyState(getCurrentInputEditorInfo()); 1575 } 1576 1577 private void setNextSuggestions() { 1578 setSuggestions(mSuggestPuncList, false, false, false); 1579 } 1580 1581 private void checkAddToDictionary(CharSequence suggestion, int frequencyDelta) { 1582 // Only auto-add to dictionary if auto-correct is ON. Otherwise we'll be 1583 // adding words in situations where the user or application really didn't 1584 // want corrections enabled or learned. 1585 if (!(mCorrectionMode == Suggest.CORRECTION_FULL)) return; 1586 if (mAutoDictionary.isValidWord(suggestion) 1587 || (!mSuggest.isValidWord(suggestion.toString()) 1588 && !mSuggest.isValidWord(suggestion.toString().toLowerCase()))) { 1589 mAutoDictionary.addWord(suggestion.toString(), frequencyDelta); 1590 } 1591 } 1592 1593 private boolean isCursorTouchingWord() { 1594 InputConnection ic = getCurrentInputConnection(); 1595 if (ic == null) return false; 1596 CharSequence toLeft = ic.getTextBeforeCursor(1, 0); 1597 CharSequence toRight = ic.getTextAfterCursor(1, 0); 1598 if (!TextUtils.isEmpty(toLeft) 1599 && !isWordSeparator(toLeft.charAt(0))) { 1600 return true; 1601 } 1602 if (!TextUtils.isEmpty(toRight) 1603 && !isWordSeparator(toRight.charAt(0))) { 1604 return true; 1605 } 1606 return false; 1607 } 1608 1609 private boolean sameAsTextBeforeCursor(InputConnection ic, CharSequence text) { 1610 CharSequence beforeText = ic.getTextBeforeCursor(text.length(), 0); 1611 return TextUtils.equals(text, beforeText); 1612 } 1613 1614 public void revertLastWord(boolean deleteChar) { 1615 final int length = mComposing.length(); 1616 if (!mPredicting && length > 0) { 1617 final InputConnection ic = getCurrentInputConnection(); 1618 mPredicting = true; 1619 ic.beginBatchEdit(); 1620 mJustRevertedSeparator = ic.getTextBeforeCursor(1, 0); 1621 if (deleteChar) ic.deleteSurroundingText(1, 0); 1622 int toDelete = mCommittedLength; 1623 CharSequence toTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0); 1624 if (toTheLeft != null && toTheLeft.length() > 0 1625 && isWordSeparator(toTheLeft.charAt(0))) { 1626 toDelete--; 1627 } 1628 ic.deleteSurroundingText(toDelete, 0); 1629 ic.setComposingText(mComposing, 1); 1630 TextEntryState.backspace(); 1631 ic.endBatchEdit(); 1632 postUpdateSuggestions(); 1633 } else { 1634 sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); 1635 mJustRevertedSeparator = null; 1636 } 1637 } 1638 1639 protected String getWordSeparators() { 1640 return mWordSeparators; 1641 } 1642 1643 public boolean isWordSeparator(int code) { 1644 String separators = getWordSeparators(); 1645 return separators.contains(String.valueOf((char)code)); 1646 } 1647 1648 public boolean isSentenceSeparator(int code) { 1649 return mSentenceSeparators.contains(String.valueOf((char)code)); 1650 } 1651 1652 private void sendSpace() { 1653 sendKeyChar((char)KEYCODE_SPACE); 1654 updateShiftKeyState(getCurrentInputEditorInfo()); 1655 //onKey(KEY_SPACE[0], KEY_SPACE); 1656 } 1657 1658 public boolean preferCapitalization() { 1659 return mWord.isCapitalized(); 1660 } 1661 1662 public void swipeRight() { 1663 if (userHasNotTypedRecently() && VOICE_INSTALLED && mEnableVoice && 1664 fieldCanDoVoice(makeFieldContext())) { 1665 startListening(true /* was a swipe */); 1666 } 1667 1668 if (LatinKeyboardView.DEBUG_AUTO_PLAY) { 1669 ClipboardManager cm = ((ClipboardManager)getSystemService(CLIPBOARD_SERVICE)); 1670 CharSequence text = cm.getText(); 1671 if (!TextUtils.isEmpty(text)) { 1672 mInputView.startPlaying(text.toString()); 1673 } 1674 } 1675 } 1676 1677 private void toggleLanguage(boolean reset, boolean next) { 1678 if (reset) { 1679 mLanguageSwitcher.reset(); 1680 } else { 1681 if (next) { 1682 mLanguageSwitcher.next(); 1683 } else { 1684 mLanguageSwitcher.prev(); 1685 } 1686 } 1687 int currentKeyboardMode = mKeyboardSwitcher.getKeyboardMode(); 1688 reloadKeyboards(); 1689 mKeyboardSwitcher.makeKeyboards(true); 1690 mKeyboardSwitcher.setKeyboardMode(currentKeyboardMode, 0, 1691 mEnableVoiceButton && mEnableVoice); 1692 initSuggest(mLanguageSwitcher.getInputLanguage()); 1693 mLanguageSwitcher.persist(); 1694 updateShiftKeyState(getCurrentInputEditorInfo()); 1695 } 1696 1697 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, 1698 String key) { 1699 if (PREF_SELECTED_LANGUAGES.equals(key)) { 1700 mLanguageSwitcher.loadLocales(sharedPreferences); 1701 mRefreshKeyboardRequired = true; 1702 } 1703 } 1704 1705 public void swipeLeft() { 1706 } 1707 1708 public void swipeDown() { 1709 handleClose(); 1710 } 1711 1712 public void swipeUp() { 1713 //launchSettings(); 1714 } 1715 1716 public void onPress(int primaryCode) { 1717 vibrate(); 1718 playKeyClick(primaryCode); 1719 } 1720 1721 public void onRelease(int primaryCode) { 1722 // Reset any drag flags in the keyboard 1723 ((LatinKeyboard) mInputView.getKeyboard()).keyReleased(); 1724 //vibrate(); 1725 } 1726 1727 private FieldContext makeFieldContext() { 1728 return new FieldContext( 1729 getCurrentInputConnection(), 1730 getCurrentInputEditorInfo(), 1731 mLanguageSwitcher.getInputLanguage(), 1732 mLanguageSwitcher.getEnabledLanguages()); 1733 } 1734 1735 private boolean fieldCanDoVoice(FieldContext fieldContext) { 1736 return !mPasswordText 1737 && mVoiceInput != null 1738 && !mVoiceInput.isBlacklistedField(fieldContext); 1739 } 1740 1741 private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) { 1742 return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext) 1743 && !(attribute != null && attribute.privateImeOptions != null 1744 && attribute.privateImeOptions.equals(IME_OPTION_NO_MICROPHONE)) 1745 && SpeechRecognizer.isRecognitionAvailable(this); 1746 } 1747 1748 // receive ringer mode changes to detect silent mode 1749 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 1750 @Override 1751 public void onReceive(Context context, Intent intent) { 1752 updateRingerMode(); 1753 } 1754 }; 1755 1756 // update flags for silent mode 1757 private void updateRingerMode() { 1758 if (mAudioManager == null) { 1759 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 1760 } 1761 if (mAudioManager != null) { 1762 mSilentMode = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL); 1763 } 1764 } 1765 1766 private boolean userHasNotTypedRecently() { 1767 return (SystemClock.uptimeMillis() - mLastKeyTime) 1768 > MIN_MILLIS_AFTER_TYPING_BEFORE_SWIPE; 1769 } 1770 1771 private void playKeyClick(int primaryCode) { 1772 // if mAudioManager is null, we don't have the ringer state yet 1773 // mAudioManager will be set by updateRingerMode 1774 if (mAudioManager == null) { 1775 if (mInputView != null) { 1776 updateRingerMode(); 1777 } 1778 } 1779 if (mSoundOn && !mSilentMode) { 1780 // FIXME: Volume and enable should come from UI settings 1781 // FIXME: These should be triggered after auto-repeat logic 1782 int sound = AudioManager.FX_KEYPRESS_STANDARD; 1783 switch (primaryCode) { 1784 case Keyboard.KEYCODE_DELETE: 1785 sound = AudioManager.FX_KEYPRESS_DELETE; 1786 break; 1787 case KEYCODE_ENTER: 1788 sound = AudioManager.FX_KEYPRESS_RETURN; 1789 break; 1790 case KEYCODE_SPACE: 1791 sound = AudioManager.FX_KEYPRESS_SPACEBAR; 1792 break; 1793 } 1794 mAudioManager.playSoundEffect(sound, FX_VOLUME); 1795 } 1796 } 1797 1798 private void vibrate() { 1799 if (!mVibrateOn) { 1800 return; 1801 } 1802 if (mInputView != null) { 1803 mInputView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, 1804 HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); 1805 } 1806 } 1807 1808 private void checkTutorial(String privateImeOptions) { 1809 if (privateImeOptions == null) return; 1810 if (privateImeOptions.equals("com.android.setupwizard:ShowTutorial")) { 1811 if (mTutorial == null) startTutorial(); 1812 } else if (privateImeOptions.equals("com.android.setupwizard:HideTutorial")) { 1813 if (mTutorial != null) { 1814 if (mTutorial.close()) { 1815 mTutorial = null; 1816 } 1817 } 1818 } 1819 } 1820 1821 private void startTutorial() { 1822 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_TUTORIAL), 500); 1823 } 1824 1825 void tutorialDone() { 1826 mTutorial = null; 1827 } 1828 1829 void promoteToUserDictionary(String word, int frequency) { 1830 if (mUserDictionary.isValidWord(word)) return; 1831 mUserDictionary.addWord(word, frequency); 1832 } 1833 1834 WordComposer getCurrentWord() { 1835 return mWord; 1836 } 1837 1838 private void updateCorrectionMode() { 1839 mHasDictionary = mSuggest != null ? mSuggest.hasMainDictionary() : false; 1840 mAutoCorrectOn = (mAutoCorrectEnabled || mQuickFixes) 1841 && !mInputTypeNoAutoCorrect && mHasDictionary; 1842 mCorrectionMode = (mAutoCorrectOn && mAutoCorrectEnabled) 1843 ? Suggest.CORRECTION_FULL 1844 : (mAutoCorrectOn ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE); 1845 if (mSuggest != null) { 1846 mSuggest.setCorrectionMode(mCorrectionMode); 1847 } 1848 } 1849 1850 private void updateAutoTextEnabled(Locale systemLocale) { 1851 if (mSuggest == null) return; 1852 boolean different = 1853 !systemLocale.getLanguage().equalsIgnoreCase(mInputLocale.substring(0, 2)); 1854 mSuggest.setAutoTextEnabled(!different && mQuickFixes); 1855 } 1856 1857 protected void launchSettings() { 1858 launchSettings(LatinIMESettings.class); 1859 } 1860 1861 protected void launchSettings(Class settingsClass) { 1862 handleClose(); 1863 Intent intent = new Intent(); 1864 intent.setClass(LatinIME.this, settingsClass); 1865 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1866 startActivity(intent); 1867 } 1868 1869 private void loadSettings() { 1870 // Get the settings preferences 1871 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); 1872 mVibrateOn = sp.getBoolean(PREF_VIBRATE_ON, false); 1873 mSoundOn = sp.getBoolean(PREF_SOUND_ON, false); 1874 mAutoCap = sp.getBoolean(PREF_AUTO_CAP, true); 1875 mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true); 1876 mHasUsedVoiceInput = sp.getBoolean(PREF_HAS_USED_VOICE_INPUT, false); 1877 mHasUsedVoiceInputUnsupportedLocale = 1878 sp.getBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, false); 1879 1880 // Get the current list of supported locales and check the current locale against that 1881 // list. We cache this value so as not to check it every time the user starts a voice 1882 // input. Because this method is called by onStartInputView, this should mean that as 1883 // long as the locale doesn't change while the user is keeping the IME open, the 1884 // value should never be stale. 1885 String supportedLocalesString = SettingsUtil.getSettingsString( 1886 getContentResolver(), 1887 SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES, 1888 DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES); 1889 ArrayList<String> voiceInputSupportedLocales = 1890 newArrayList(supportedLocalesString.split("\\s+")); 1891 1892 mLocaleSupportedForVoiceInput = voiceInputSupportedLocales.contains(mInputLocale); 1893 1894 mShowSuggestions = sp.getBoolean(PREF_SHOW_SUGGESTIONS, true); 1895 1896 if (VOICE_INSTALLED) { 1897 final String voiceMode = sp.getString(PREF_VOICE_MODE, 1898 getString(R.string.voice_mode_main)); 1899 boolean enableVoice = !voiceMode.equals(getString(R.string.voice_mode_off)) 1900 && mEnableVoiceButton; 1901 boolean voiceOnPrimary = voiceMode.equals(getString(R.string.voice_mode_main)); 1902 if (mKeyboardSwitcher != null && 1903 (enableVoice != mEnableVoice || voiceOnPrimary != mVoiceOnPrimary)) { 1904 mKeyboardSwitcher.setVoiceMode(enableVoice, voiceOnPrimary); 1905 } 1906 mEnableVoice = enableVoice; 1907 mVoiceOnPrimary = voiceOnPrimary; 1908 } 1909 mAutoCorrectEnabled = sp.getBoolean(PREF_AUTO_COMPLETE, 1910 mResources.getBoolean(R.bool.enable_autocorrect)) & mShowSuggestions; 1911 updateCorrectionMode(); 1912 updateAutoTextEnabled(mResources.getConfiguration().locale); 1913 mLanguageSwitcher.loadLocales(sp); 1914 } 1915 1916 private void initSuggestPuncList() { 1917 mSuggestPuncList = new ArrayList<CharSequence>(); 1918 String suggestPuncs = mResources.getString(R.string.suggested_punctuations); 1919 if (suggestPuncs != null) { 1920 for (int i = 0; i < suggestPuncs.length(); i++) { 1921 mSuggestPuncList.add(suggestPuncs.subSequence(i, i + 1)); 1922 } 1923 } 1924 } 1925 1926 private void showOptionsMenu() { 1927 AlertDialog.Builder builder = new AlertDialog.Builder(this); 1928 builder.setCancelable(true); 1929 builder.setIcon(R.drawable.ic_dialog_keyboard); 1930 builder.setNegativeButton(android.R.string.cancel, null); 1931 CharSequence itemSettings = getString(R.string.english_ime_settings); 1932 CharSequence itemInputMethod = getString(R.string.inputMethod); 1933 builder.setItems(new CharSequence[] { 1934 itemSettings, itemInputMethod}, 1935 new DialogInterface.OnClickListener() { 1936 1937 public void onClick(DialogInterface di, int position) { 1938 di.dismiss(); 1939 switch (position) { 1940 case POS_SETTINGS: 1941 launchSettings(); 1942 break; 1943 case POS_METHOD: 1944 ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)) 1945 .showInputMethodPicker(); 1946 break; 1947 } 1948 } 1949 }); 1950 builder.setTitle(mResources.getString(R.string.english_ime_name)); 1951 mOptionsDialog = builder.create(); 1952 Window window = mOptionsDialog.getWindow(); 1953 WindowManager.LayoutParams lp = window.getAttributes(); 1954 lp.token = mInputView.getWindowToken(); 1955 lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; 1956 window.setAttributes(lp); 1957 window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 1958 mOptionsDialog.show(); 1959 } 1960 1961 private void changeKeyboardMode() { 1962 mKeyboardSwitcher.toggleSymbols(); 1963 if (mCapsLock && mKeyboardSwitcher.isAlphabetMode()) { 1964 ((LatinKeyboard) mInputView.getKeyboard()).setShiftLocked(mCapsLock); 1965 } 1966 1967 updateShiftKeyState(getCurrentInputEditorInfo()); 1968 } 1969 1970 public static <E> ArrayList<E> newArrayList(E... elements) { 1971 int capacity = (elements.length * 110) / 100 + 5; 1972 ArrayList<E> list = new ArrayList<E>(capacity); 1973 Collections.addAll(list, elements); 1974 return list; 1975 } 1976 1977 @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { 1978 super.dump(fd, fout, args); 1979 1980 final Printer p = new PrintWriterPrinter(fout); 1981 p.println("LatinIME state :"); 1982 p.println(" Keyboard mode = " + mKeyboardSwitcher.getKeyboardMode()); 1983 p.println(" mCapsLock=" + mCapsLock); 1984 p.println(" mComposing=" + mComposing.toString()); 1985 p.println(" mPredictionOn=" + mPredictionOn); 1986 p.println(" mCorrectionMode=" + mCorrectionMode); 1987 p.println(" mPredicting=" + mPredicting); 1988 p.println(" mAutoCorrectOn=" + mAutoCorrectOn); 1989 p.println(" mAutoSpace=" + mAutoSpace); 1990 p.println(" mCompletionOn=" + mCompletionOn); 1991 p.println(" TextEntryState.state=" + TextEntryState.getState()); 1992 p.println(" mSoundOn=" + mSoundOn); 1993 p.println(" mVibrateOn=" + mVibrateOn); 1994 } 1995 1996 // Characters per second measurement 1997 1998 private static final boolean PERF_DEBUG = false; 1999 private long mLastCpsTime; 2000 private static final int CPS_BUFFER_SIZE = 16; 2001 private long[] mCpsIntervals = new long[CPS_BUFFER_SIZE]; 2002 private int mCpsIndex; 2003 private boolean mInputTypeNoAutoCorrect; 2004 2005 private void measureCps() { 2006 if (!LatinIME.PERF_DEBUG) return; 2007 long now = System.currentTimeMillis(); 2008 if (mLastCpsTime == 0) mLastCpsTime = now - 100; // Initial 2009 mCpsIntervals[mCpsIndex] = now - mLastCpsTime; 2010 mLastCpsTime = now; 2011 mCpsIndex = (mCpsIndex + 1) % CPS_BUFFER_SIZE; 2012 long total = 0; 2013 for (int i = 0; i < CPS_BUFFER_SIZE; i++) total += mCpsIntervals[i]; 2014 System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total)); 2015 } 2016 2017 } 2018