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