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 android.app.AlertDialog; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.DialogInterface; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.content.SharedPreferences; 26 import android.content.res.Configuration; 27 import android.inputmethodservice.InputMethodService; 28 import android.inputmethodservice.Keyboard; 29 import android.inputmethodservice.KeyboardView; 30 import android.media.AudioManager; 31 import android.os.Debug; 32 import android.os.Handler; 33 import android.os.Message; 34 import android.os.SystemClock; 35 import android.os.Vibrator; 36 import android.preference.PreferenceManager; 37 import android.text.AutoText; 38 import android.text.ClipboardManager; 39 import android.text.TextUtils; 40 import android.util.Log; 41 import android.util.PrintWriterPrinter; 42 import android.util.Printer; 43 import android.view.KeyEvent; 44 import android.view.View; 45 import android.view.Window; 46 import android.view.WindowManager; 47 import android.view.inputmethod.CompletionInfo; 48 import android.view.inputmethod.EditorInfo; 49 import android.view.inputmethod.InputConnection; 50 import android.view.inputmethod.InputMethodManager; 51 52 import java.io.FileDescriptor; 53 import java.io.PrintWriter; 54 import java.util.ArrayList; 55 import java.util.List; 56 57 /** 58 * Input method implementation for Qwerty'ish keyboard. 59 */ 60 public class LatinIME extends InputMethodService 61 implements KeyboardView.OnKeyboardActionListener { 62 static final boolean DEBUG = false; 63 static final boolean TRACE = false; 64 65 private static final String PREF_VIBRATE_ON = "vibrate_on"; 66 private static final String PREF_SOUND_ON = "sound_on"; 67 private static final String PREF_AUTO_CAP = "auto_cap"; 68 private static final String PREF_QUICK_FIXES = "quick_fixes"; 69 private static final String PREF_SHOW_SUGGESTIONS = "show_suggestions"; 70 private static final String PREF_AUTO_COMPLETE = "auto_complete"; 71 72 private static final int MSG_UPDATE_SUGGESTIONS = 0; 73 private static final int MSG_START_TUTORIAL = 1; 74 private static final int MSG_UPDATE_SHIFT_STATE = 2; 75 76 // How many continuous deletes at which to start deleting at a higher speed. 77 private static final int DELETE_ACCELERATE_AT = 20; 78 // Key events coming any faster than this are long-presses. 79 private static final int QUICK_PRESS = 200; 80 // Weight added to a user picking a new word from the suggestion strip 81 static final int FREQUENCY_FOR_PICKED = 3; 82 // Weight added to a user typing a new word that doesn't get corrected (or is reverted) 83 static final int FREQUENCY_FOR_TYPED = 1; 84 // A word that is frequently typed and get's promoted to the user dictionary, uses this 85 // frequency. 86 static final int FREQUENCY_FOR_AUTO_ADD = 250; 87 88 static final int KEYCODE_ENTER = '\n'; 89 static final int KEYCODE_SPACE = ' '; 90 91 // Contextual menu positions 92 private static final int POS_SETTINGS = 0; 93 private static final int POS_METHOD = 1; 94 95 private LatinKeyboardView mInputView; 96 private CandidateViewContainer mCandidateViewContainer; 97 private CandidateView mCandidateView; 98 private Suggest mSuggest; 99 private CompletionInfo[] mCompletions; 100 101 private AlertDialog mOptionsDialog; 102 103 KeyboardSwitcher mKeyboardSwitcher; 104 105 private UserDictionary mUserDictionary; 106 private ContactsDictionary mContactsDictionary; 107 private ExpandableDictionary mAutoDictionary; 108 109 private String mLocale; 110 111 private StringBuilder mComposing = new StringBuilder(); 112 private WordComposer mWord = new WordComposer(); 113 private int mCommittedLength; 114 private boolean mPredicting; 115 private CharSequence mBestWord; 116 private boolean mPredictionOn; 117 private boolean mCompletionOn; 118 private boolean mAutoSpace; 119 private boolean mAutoCorrectOn; 120 private boolean mCapsLock; 121 private boolean mVibrateOn; 122 private boolean mSoundOn; 123 private boolean mAutoCap; 124 private boolean mQuickFixes; 125 private boolean mShowSuggestions; 126 private int mCorrectionMode; 127 private int mOrientation; 128 129 // Indicates whether the suggestion strip is to be on in landscape 130 private boolean mJustAccepted; 131 private CharSequence mJustRevertedSeparator; 132 private int mDeleteCount; 133 private long mLastKeyTime; 134 135 private Tutorial mTutorial; 136 137 private Vibrator mVibrator; 138 private long mVibrateDuration; 139 140 private AudioManager mAudioManager; 141 // Align sound effect volume on music volume 142 private final float FX_VOLUME = -1.0f; 143 private boolean mSilentMode; 144 145 private String mWordSeparators; 146 private String mSentenceSeparators; 147 148 Handler mHandler = new Handler() { 149 @Override 150 public void handleMessage(Message msg) { 151 switch (msg.what) { 152 case MSG_UPDATE_SUGGESTIONS: 153 updateSuggestions(); 154 break; 155 case MSG_START_TUTORIAL: 156 if (mTutorial == null) { 157 if (mInputView.isShown()) { 158 mTutorial = new Tutorial(LatinIME.this, mInputView); 159 mTutorial.start(); 160 } else { 161 // Try again soon if the view is not yet showing 162 sendMessageDelayed(obtainMessage(MSG_START_TUTORIAL), 100); 163 } 164 } 165 break; 166 case MSG_UPDATE_SHIFT_STATE: 167 updateShiftKeyState(getCurrentInputEditorInfo()); 168 break; 169 } 170 } 171 }; 172 onCreate()173 @Override public void onCreate() { 174 super.onCreate(); 175 //setStatusIcon(R.drawable.ime_qwerty); 176 mKeyboardSwitcher = new KeyboardSwitcher(this); 177 final Configuration conf = getResources().getConfiguration(); 178 initSuggest(conf.locale.toString()); 179 mOrientation = conf.orientation; 180 181 mVibrateDuration = getResources().getInteger(R.integer.vibrate_duration_ms); 182 183 // register to receive ringer mode changes for silent mode 184 IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); 185 registerReceiver(mReceiver, filter); 186 } 187 initSuggest(String locale)188 private void initSuggest(String locale) { 189 mLocale = locale; 190 mSuggest = new Suggest(this, R.raw.main); 191 mSuggest.setCorrectionMode(mCorrectionMode); 192 mUserDictionary = new UserDictionary(this); 193 mContactsDictionary = new ContactsDictionary(this); 194 mAutoDictionary = new AutoDictionary(this); 195 mSuggest.setUserDictionary(mUserDictionary); 196 mSuggest.setContactsDictionary(mContactsDictionary); 197 mSuggest.setAutoDictionary(mAutoDictionary); 198 mWordSeparators = getResources().getString(R.string.word_separators); 199 mSentenceSeparators = getResources().getString(R.string.sentence_separators); 200 } 201 onDestroy()202 @Override public void onDestroy() { 203 mUserDictionary.close(); 204 mContactsDictionary.close(); 205 unregisterReceiver(mReceiver); 206 super.onDestroy(); 207 } 208 209 @Override onConfigurationChanged(Configuration conf)210 public void onConfigurationChanged(Configuration conf) { 211 if (!TextUtils.equals(conf.locale.toString(), mLocale)) { 212 initSuggest(conf.locale.toString()); 213 } 214 // If orientation changed while predicting, commit the change 215 if (conf.orientation != mOrientation) { 216 commitTyped(getCurrentInputConnection()); 217 mOrientation = conf.orientation; 218 } 219 if (mKeyboardSwitcher == null) { 220 mKeyboardSwitcher = new KeyboardSwitcher(this); 221 } 222 mKeyboardSwitcher.makeKeyboards(true); 223 super.onConfigurationChanged(conf); 224 } 225 226 @Override onCreateInputView()227 public View onCreateInputView() { 228 mInputView = (LatinKeyboardView) getLayoutInflater().inflate( 229 R.layout.input, null); 230 mKeyboardSwitcher.setInputView(mInputView); 231 mKeyboardSwitcher.makeKeyboards(true); 232 mInputView.setOnKeyboardActionListener(this); 233 mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT, 0); 234 return mInputView; 235 } 236 237 @Override onCreateCandidatesView()238 public View onCreateCandidatesView() { 239 mKeyboardSwitcher.makeKeyboards(true); 240 mCandidateViewContainer = (CandidateViewContainer) getLayoutInflater().inflate( 241 R.layout.candidates, null); 242 mCandidateViewContainer.initViews(); 243 mCandidateView = (CandidateView) mCandidateViewContainer.findViewById(R.id.candidates); 244 mCandidateView.setService(this); 245 setCandidatesViewShown(true); 246 return mCandidateViewContainer; 247 } 248 249 @Override onStartInputView(EditorInfo attribute, boolean restarting)250 public void onStartInputView(EditorInfo attribute, boolean restarting) { 251 // In landscape mode, this method gets called without the input view being created. 252 if (mInputView == null) { 253 return; 254 } 255 256 mKeyboardSwitcher.makeKeyboards(false); 257 258 TextEntryState.newSession(this); 259 260 boolean disableAutoCorrect = false; 261 mPredictionOn = false; 262 mCompletionOn = false; 263 mCompletions = null; 264 mCapsLock = false; 265 switch (attribute.inputType&EditorInfo.TYPE_MASK_CLASS) { 266 case EditorInfo.TYPE_CLASS_NUMBER: 267 case EditorInfo.TYPE_CLASS_DATETIME: 268 mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_SYMBOLS, 269 attribute.imeOptions); 270 break; 271 case EditorInfo.TYPE_CLASS_PHONE: 272 mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_PHONE, 273 attribute.imeOptions); 274 break; 275 case EditorInfo.TYPE_CLASS_TEXT: 276 mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT, 277 attribute.imeOptions); 278 //startPrediction(); 279 mPredictionOn = true; 280 // Make sure that passwords are not displayed in candidate view 281 int variation = attribute.inputType & EditorInfo.TYPE_MASK_VARIATION; 282 if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD || 283 variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD ) { 284 mPredictionOn = false; 285 } 286 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 287 || variation == EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME) { 288 mAutoSpace = false; 289 } else { 290 mAutoSpace = true; 291 } 292 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) { 293 mPredictionOn = false; 294 mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_EMAIL, 295 attribute.imeOptions); 296 } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_URI) { 297 mPredictionOn = false; 298 mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_URL, 299 attribute.imeOptions); 300 } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) { 301 mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_IM, 302 attribute.imeOptions); 303 } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_FILTER) { 304 mPredictionOn = false; 305 } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) { 306 // If it's a browser edit field and auto correct is not ON explicitly, then 307 // disable auto correction, but keep suggestions on. 308 if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) { 309 disableAutoCorrect = true; 310 } 311 } 312 313 // If NO_SUGGESTIONS is set, don't do prediction. 314 if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) { 315 mPredictionOn = false; 316 disableAutoCorrect = true; 317 } 318 // If it's not multiline and the autoCorrect flag is not set, then don't correct 319 if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0 && 320 (attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) == 0) { 321 disableAutoCorrect = true; 322 } 323 if ((attribute.inputType&EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) { 324 mPredictionOn = false; 325 mCompletionOn = true && isFullscreenMode(); 326 } 327 updateShiftKeyState(attribute); 328 break; 329 default: 330 mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT, 331 attribute.imeOptions); 332 updateShiftKeyState(attribute); 333 } 334 mInputView.closing(); 335 mComposing.setLength(0); 336 mPredicting = false; 337 mDeleteCount = 0; 338 setCandidatesViewShown(false); 339 if (mCandidateView != null) mCandidateView.setSuggestions(null, false, false, false); 340 loadSettings(); 341 // Override auto correct 342 if (disableAutoCorrect) { 343 mAutoCorrectOn = false; 344 if (mCorrectionMode == Suggest.CORRECTION_FULL) { 345 mCorrectionMode = Suggest.CORRECTION_BASIC; 346 } 347 } 348 mInputView.setProximityCorrectionEnabled(true); 349 if (mSuggest != null) { 350 mSuggest.setCorrectionMode(mCorrectionMode); 351 } 352 mPredictionOn = mPredictionOn && mCorrectionMode > 0; 353 checkTutorial(attribute.privateImeOptions); 354 if (TRACE) Debug.startMethodTracing("/data/trace/latinime"); 355 } 356 357 @Override onFinishInput()358 public void onFinishInput() { 359 super.onFinishInput(); 360 361 if (mInputView != null) { 362 mInputView.closing(); 363 } 364 } 365 366 @Override onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd)367 public void onUpdateSelection(int oldSelStart, int oldSelEnd, 368 int newSelStart, int newSelEnd, 369 int candidatesStart, int candidatesEnd) { 370 super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, 371 candidatesStart, candidatesEnd); 372 // If the current selection in the text view changes, we should 373 // clear whatever candidate text we have. 374 if (mComposing.length() > 0 && mPredicting && (newSelStart != candidatesEnd 375 || newSelEnd != candidatesEnd)) { 376 mComposing.setLength(0); 377 mPredicting = false; 378 updateSuggestions(); 379 TextEntryState.reset(); 380 InputConnection ic = getCurrentInputConnection(); 381 if (ic != null) { 382 ic.finishComposingText(); 383 } 384 } else if (!mPredicting && !mJustAccepted 385 && TextEntryState.getState() == TextEntryState.STATE_ACCEPTED_DEFAULT) { 386 TextEntryState.reset(); 387 } 388 mJustAccepted = false; 389 postUpdateShiftKeyState(); 390 } 391 392 @Override hideWindow()393 public void hideWindow() { 394 if (TRACE) Debug.stopMethodTracing(); 395 if (mOptionsDialog != null && mOptionsDialog.isShowing()) { 396 mOptionsDialog.dismiss(); 397 mOptionsDialog = null; 398 } 399 if (mTutorial != null) { 400 mTutorial.close(); 401 mTutorial = null; 402 } 403 super.hideWindow(); 404 TextEntryState.endSession(); 405 } 406 407 @Override onDisplayCompletions(CompletionInfo[] completions)408 public void onDisplayCompletions(CompletionInfo[] completions) { 409 if (false) { 410 Log.i("foo", "Received completions:"); 411 for (int i=0; i<(completions != null ? completions.length : 0); i++) { 412 Log.i("foo", " #" + i + ": " + completions[i]); 413 } 414 } 415 if (mCompletionOn) { 416 mCompletions = completions; 417 if (completions == null) { 418 mCandidateView.setSuggestions(null, false, false, false); 419 return; 420 } 421 422 List<CharSequence> stringList = new ArrayList<CharSequence>(); 423 for (int i=0; i<(completions != null ? completions.length : 0); i++) { 424 CompletionInfo ci = completions[i]; 425 if (ci != null) stringList.add(ci.getText()); 426 } 427 //CharSequence typedWord = mWord.getTypedWord(); 428 mCandidateView.setSuggestions(stringList, true, true, true); 429 mBestWord = null; 430 setCandidatesViewShown(isCandidateStripVisible() || mCompletionOn); 431 } 432 } 433 434 @Override setCandidatesViewShown(boolean shown)435 public void setCandidatesViewShown(boolean shown) { 436 // TODO: Remove this if we support candidates with hard keyboard 437 if (onEvaluateInputViewShown()) { 438 super.setCandidatesViewShown(shown); 439 } 440 } 441 442 @Override onComputeInsets(InputMethodService.Insets outInsets)443 public void onComputeInsets(InputMethodService.Insets outInsets) { 444 super.onComputeInsets(outInsets); 445 if (!isFullscreenMode()) { 446 outInsets.contentTopInsets = outInsets.visibleTopInsets; 447 } 448 } 449 450 @Override onKeyDown(int keyCode, KeyEvent event)451 public boolean onKeyDown(int keyCode, KeyEvent event) { 452 switch (keyCode) { 453 case KeyEvent.KEYCODE_BACK: 454 if (event.getRepeatCount() == 0 && mInputView != null) { 455 if (mInputView.handleBack()) { 456 return true; 457 } else if (mTutorial != null) { 458 mTutorial.close(); 459 mTutorial = null; 460 } 461 } 462 break; 463 case KeyEvent.KEYCODE_DPAD_DOWN: 464 case KeyEvent.KEYCODE_DPAD_UP: 465 case KeyEvent.KEYCODE_DPAD_LEFT: 466 case KeyEvent.KEYCODE_DPAD_RIGHT: 467 // If tutorial is visible, don't allow dpad to work 468 if (mTutorial != null) { 469 return true; 470 } 471 break; 472 } 473 return super.onKeyDown(keyCode, event); 474 } 475 476 @Override onKeyUp(int keyCode, KeyEvent event)477 public boolean onKeyUp(int keyCode, KeyEvent event) { 478 switch (keyCode) { 479 case KeyEvent.KEYCODE_DPAD_DOWN: 480 case KeyEvent.KEYCODE_DPAD_UP: 481 case KeyEvent.KEYCODE_DPAD_LEFT: 482 case KeyEvent.KEYCODE_DPAD_RIGHT: 483 // If tutorial is visible, don't allow dpad to work 484 if (mTutorial != null) { 485 return true; 486 } 487 // Enable shift key and DPAD to do selections 488 if (mInputView != null && mInputView.isShown() && mInputView.isShifted()) { 489 event = new KeyEvent(event.getDownTime(), event.getEventTime(), 490 event.getAction(), event.getKeyCode(), event.getRepeatCount(), 491 event.getDeviceId(), event.getScanCode(), 492 KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON); 493 InputConnection ic = getCurrentInputConnection(); 494 if (ic != null) ic.sendKeyEvent(event); 495 return true; 496 } 497 break; 498 } 499 return super.onKeyUp(keyCode, event); 500 } 501 commitTyped(InputConnection inputConnection)502 private void commitTyped(InputConnection inputConnection) { 503 if (mPredicting) { 504 mPredicting = false; 505 if (mComposing.length() > 0) { 506 if (inputConnection != null) { 507 inputConnection.commitText(mComposing, 1); 508 } 509 mCommittedLength = mComposing.length(); 510 TextEntryState.acceptedTyped(mComposing); 511 mAutoDictionary.addWord(mComposing.toString(), FREQUENCY_FOR_TYPED); 512 } 513 updateSuggestions(); 514 } 515 } 516 postUpdateShiftKeyState()517 private void postUpdateShiftKeyState() { 518 mHandler.removeMessages(MSG_UPDATE_SHIFT_STATE); 519 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SHIFT_STATE), 300); 520 } 521 updateShiftKeyState(EditorInfo attr)522 public void updateShiftKeyState(EditorInfo attr) { 523 InputConnection ic = getCurrentInputConnection(); 524 if (attr != null && mInputView != null && mKeyboardSwitcher.isAlphabetMode() 525 && ic != null) { 526 int caps = 0; 527 EditorInfo ei = getCurrentInputEditorInfo(); 528 if (mAutoCap && ei != null && ei.inputType != EditorInfo.TYPE_NULL) { 529 caps = ic.getCursorCapsMode(attr.inputType); 530 } 531 mInputView.setShifted(mCapsLock || caps != 0); 532 } 533 } 534 swapPunctuationAndSpace()535 private void swapPunctuationAndSpace() { 536 final InputConnection ic = getCurrentInputConnection(); 537 if (ic == null) return; 538 CharSequence lastTwo = ic.getTextBeforeCursor(2, 0); 539 if (lastTwo != null && lastTwo.length() == 2 540 && lastTwo.charAt(0) == KEYCODE_SPACE && isSentenceSeparator(lastTwo.charAt(1))) { 541 ic.beginBatchEdit(); 542 ic.deleteSurroundingText(2, 0); 543 ic.commitText(lastTwo.charAt(1) + " ", 1); 544 ic.endBatchEdit(); 545 updateShiftKeyState(getCurrentInputEditorInfo()); 546 } 547 } 548 doubleSpace()549 private void doubleSpace() { 550 //if (!mAutoPunctuate) return; 551 if (mCorrectionMode == Suggest.CORRECTION_NONE) return; 552 final InputConnection ic = getCurrentInputConnection(); 553 if (ic == null) return; 554 CharSequence lastThree = ic.getTextBeforeCursor(3, 0); 555 if (lastThree != null && lastThree.length() == 3 556 && Character.isLetterOrDigit(lastThree.charAt(0)) 557 && lastThree.charAt(1) == KEYCODE_SPACE && lastThree.charAt(2) == KEYCODE_SPACE) { 558 ic.beginBatchEdit(); 559 ic.deleteSurroundingText(2, 0); 560 ic.commitText(". ", 1); 561 ic.endBatchEdit(); 562 updateShiftKeyState(getCurrentInputEditorInfo()); 563 } 564 } 565 addWordToDictionary(String word)566 public boolean addWordToDictionary(String word) { 567 mUserDictionary.addWord(word, 128); 568 return true; 569 } 570 isAlphabet(int code)571 private boolean isAlphabet(int code) { 572 if (Character.isLetter(code)) { 573 return true; 574 } else { 575 return false; 576 } 577 } 578 579 // Implementation of KeyboardViewListener 580 onKey(int primaryCode, int[] keyCodes)581 public void onKey(int primaryCode, int[] keyCodes) { 582 long when = SystemClock.uptimeMillis(); 583 if (primaryCode != Keyboard.KEYCODE_DELETE || 584 when > mLastKeyTime + QUICK_PRESS) { 585 mDeleteCount = 0; 586 } 587 mLastKeyTime = when; 588 switch (primaryCode) { 589 case Keyboard.KEYCODE_DELETE: 590 handleBackspace(); 591 mDeleteCount++; 592 break; 593 case Keyboard.KEYCODE_SHIFT: 594 handleShift(); 595 break; 596 case Keyboard.KEYCODE_CANCEL: 597 if (mOptionsDialog == null || !mOptionsDialog.isShowing()) { 598 handleClose(); 599 } 600 break; 601 case LatinKeyboardView.KEYCODE_OPTIONS: 602 showOptionsMenu(); 603 break; 604 case LatinKeyboardView.KEYCODE_SHIFT_LONGPRESS: 605 if (mCapsLock) { 606 handleShift(); 607 } else { 608 toggleCapsLock(); 609 } 610 break; 611 case Keyboard.KEYCODE_MODE_CHANGE: 612 changeKeyboardMode(); 613 break; 614 default: 615 if (isWordSeparator(primaryCode)) { 616 handleSeparator(primaryCode); 617 } else { 618 handleCharacter(primaryCode, keyCodes); 619 } 620 // Cancel the just reverted state 621 mJustRevertedSeparator = null; 622 } 623 if (mKeyboardSwitcher.onKey(primaryCode)) { 624 changeKeyboardMode(); 625 } 626 } 627 onText(CharSequence text)628 public void onText(CharSequence text) { 629 InputConnection ic = getCurrentInputConnection(); 630 if (ic == null) return; 631 ic.beginBatchEdit(); 632 if (mPredicting) { 633 commitTyped(ic); 634 } 635 ic.commitText(text, 1); 636 ic.endBatchEdit(); 637 updateShiftKeyState(getCurrentInputEditorInfo()); 638 mJustRevertedSeparator = null; 639 } 640 handleBackspace()641 private void handleBackspace() { 642 boolean deleteChar = false; 643 InputConnection ic = getCurrentInputConnection(); 644 if (ic == null) return; 645 if (mPredicting) { 646 final int length = mComposing.length(); 647 if (length > 0) { 648 mComposing.delete(length - 1, length); 649 mWord.deleteLast(); 650 ic.setComposingText(mComposing, 1); 651 if (mComposing.length() == 0) { 652 mPredicting = false; 653 } 654 postUpdateSuggestions(); 655 } else { 656 ic.deleteSurroundingText(1, 0); 657 } 658 } else { 659 deleteChar = true; 660 } 661 postUpdateShiftKeyState(); 662 TextEntryState.backspace(); 663 if (TextEntryState.getState() == TextEntryState.STATE_UNDO_COMMIT) { 664 revertLastWord(deleteChar); 665 return; 666 } else if (deleteChar) { 667 sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); 668 if (mDeleteCount > DELETE_ACCELERATE_AT) { 669 sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); 670 } 671 } 672 mJustRevertedSeparator = null; 673 } 674 handleShift()675 private void handleShift() { 676 Keyboard currentKeyboard = mInputView.getKeyboard(); 677 if (mKeyboardSwitcher.isAlphabetMode()) { 678 // Alphabet keyboard 679 checkToggleCapsLock(); 680 mInputView.setShifted(mCapsLock || !mInputView.isShifted()); 681 } else { 682 mKeyboardSwitcher.toggleShift(); 683 } 684 } 685 handleCharacter(int primaryCode, int[] keyCodes)686 private void handleCharacter(int primaryCode, int[] keyCodes) { 687 if (isAlphabet(primaryCode) && isPredictionOn() && !isCursorTouchingWord()) { 688 if (!mPredicting) { 689 mPredicting = true; 690 mComposing.setLength(0); 691 mWord.reset(); 692 } 693 } 694 if (mInputView.isShifted()) { 695 // TODO: This doesn't work with ß, need to fix it in the next release. 696 if (keyCodes == null || keyCodes[0] < Character.MIN_CODE_POINT 697 || keyCodes[0] > Character.MAX_CODE_POINT) { 698 return; 699 } 700 primaryCode = new String(keyCodes, 0, 1).toUpperCase().charAt(0); 701 } 702 if (mPredicting) { 703 if (mInputView.isShifted() && mComposing.length() == 0) { 704 mWord.setCapitalized(true); 705 } 706 mComposing.append((char) primaryCode); 707 mWord.add(primaryCode, keyCodes); 708 InputConnection ic = getCurrentInputConnection(); 709 if (ic != null) { 710 ic.setComposingText(mComposing, 1); 711 } 712 postUpdateSuggestions(); 713 } else { 714 sendKeyChar((char)primaryCode); 715 } 716 updateShiftKeyState(getCurrentInputEditorInfo()); 717 measureCps(); 718 TextEntryState.typedCharacter((char) primaryCode, isWordSeparator(primaryCode)); 719 } 720 handleSeparator(int primaryCode)721 private void handleSeparator(int primaryCode) { 722 boolean pickedDefault = false; 723 // Handle separator 724 InputConnection ic = getCurrentInputConnection(); 725 if (ic != null) { 726 ic.beginBatchEdit(); 727 } 728 if (mPredicting) { 729 // In certain languages where single quote is a separator, it's better 730 // not to auto correct, but accept the typed word. For instance, 731 // in Italian dov' should not be expanded to dove' because the elision 732 // requires the last vowel to be removed. 733 if (mAutoCorrectOn && primaryCode != '\'' && 734 (mJustRevertedSeparator == null 735 || mJustRevertedSeparator.length() == 0 736 || mJustRevertedSeparator.charAt(0) != primaryCode)) { 737 pickDefaultSuggestion(); 738 pickedDefault = true; 739 } else { 740 commitTyped(ic); 741 } 742 } 743 sendKeyChar((char)primaryCode); 744 TextEntryState.typedCharacter((char) primaryCode, true); 745 if (TextEntryState.getState() == TextEntryState.STATE_PUNCTUATION_AFTER_ACCEPTED 746 && primaryCode != KEYCODE_ENTER) { 747 swapPunctuationAndSpace(); 748 } else if (isPredictionOn() && primaryCode == ' ') { 749 //else if (TextEntryState.STATE_SPACE_AFTER_ACCEPTED) { 750 doubleSpace(); 751 } 752 if (pickedDefault && mBestWord != null) { 753 TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord); 754 } 755 updateShiftKeyState(getCurrentInputEditorInfo()); 756 if (ic != null) { 757 ic.endBatchEdit(); 758 } 759 } 760 handleClose()761 private void handleClose() { 762 commitTyped(getCurrentInputConnection()); 763 requestHideSelf(0); 764 mInputView.closing(); 765 TextEntryState.endSession(); 766 } 767 checkToggleCapsLock()768 private void checkToggleCapsLock() { 769 if (mInputView.getKeyboard().isShifted()) { 770 toggleCapsLock(); 771 } 772 } 773 toggleCapsLock()774 private void toggleCapsLock() { 775 mCapsLock = !mCapsLock; 776 if (mKeyboardSwitcher.isAlphabetMode()) { 777 ((LatinKeyboard) mInputView.getKeyboard()).setShiftLocked(mCapsLock); 778 } 779 } 780 postUpdateSuggestions()781 private void postUpdateSuggestions() { 782 mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS); 783 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SUGGESTIONS), 100); 784 } 785 isPredictionOn()786 private boolean isPredictionOn() { 787 boolean predictionOn = mPredictionOn; 788 //if (isFullscreenMode()) predictionOn &= mPredictionLandscape; 789 return predictionOn; 790 } 791 isCandidateStripVisible()792 private boolean isCandidateStripVisible() { 793 return isPredictionOn() && mShowSuggestions; 794 } 795 updateSuggestions()796 private void updateSuggestions() { 797 // Check if we have a suggestion engine attached. 798 if (mSuggest == null || !isPredictionOn()) { 799 return; 800 } 801 802 if (!mPredicting) { 803 mCandidateView.setSuggestions(null, false, false, false); 804 return; 805 } 806 807 List<CharSequence> stringList = mSuggest.getSuggestions(mInputView, mWord, false); 808 boolean correctionAvailable = mSuggest.hasMinimalCorrection(); 809 //|| mCorrectionMode == mSuggest.CORRECTION_FULL; 810 CharSequence typedWord = mWord.getTypedWord(); 811 // If we're in basic correct 812 boolean typedWordValid = mSuggest.isValidWord(typedWord); 813 if (mCorrectionMode == Suggest.CORRECTION_FULL) { 814 correctionAvailable |= typedWordValid; 815 } 816 // Don't auto-correct words with multiple capital letter 817 correctionAvailable &= !mWord.isMostlyCaps(); 818 819 mCandidateView.setSuggestions(stringList, false, typedWordValid, correctionAvailable); 820 if (stringList.size() > 0) { 821 if (correctionAvailable && !typedWordValid && stringList.size() > 1) { 822 mBestWord = stringList.get(1); 823 } else { 824 mBestWord = typedWord; 825 } 826 } else { 827 mBestWord = null; 828 } 829 setCandidatesViewShown(isCandidateStripVisible() || mCompletionOn); 830 } 831 pickDefaultSuggestion()832 private void pickDefaultSuggestion() { 833 // Complete any pending candidate query first 834 if (mHandler.hasMessages(MSG_UPDATE_SUGGESTIONS)) { 835 mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS); 836 updateSuggestions(); 837 } 838 if (mBestWord != null) { 839 TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord); 840 mJustAccepted = true; 841 pickSuggestion(mBestWord); 842 } 843 } 844 pickSuggestionManually(int index, CharSequence suggestion)845 public void pickSuggestionManually(int index, CharSequence suggestion) { 846 if (mCompletionOn && mCompletions != null && index >= 0 847 && index < mCompletions.length) { 848 CompletionInfo ci = mCompletions[index]; 849 InputConnection ic = getCurrentInputConnection(); 850 if (ic != null) { 851 ic.commitCompletion(ci); 852 } 853 mCommittedLength = suggestion.length(); 854 if (mCandidateView != null) { 855 mCandidateView.clear(); 856 } 857 updateShiftKeyState(getCurrentInputEditorInfo()); 858 return; 859 } 860 pickSuggestion(suggestion); 861 TextEntryState.acceptedSuggestion(mComposing.toString(), suggestion); 862 // Follow it with a space 863 if (mAutoSpace) { 864 sendSpace(); 865 } 866 // Fool the state watcher so that a subsequent backspace will not do a revert 867 TextEntryState.typedCharacter((char) KEYCODE_SPACE, true); 868 } 869 pickSuggestion(CharSequence suggestion)870 private void pickSuggestion(CharSequence suggestion) { 871 if (mCapsLock) { 872 suggestion = suggestion.toString().toUpperCase(); 873 } else if (preferCapitalization() 874 || (mKeyboardSwitcher.isAlphabetMode() && mInputView.isShifted())) { 875 suggestion = suggestion.toString().toUpperCase().charAt(0) 876 + suggestion.subSequence(1, suggestion.length()).toString(); 877 } 878 InputConnection ic = getCurrentInputConnection(); 879 if (ic != null) { 880 ic.commitText(suggestion, 1); 881 } 882 // Add the word to the auto dictionary if it's not a known word 883 if (mAutoDictionary.isValidWord(suggestion) || !mSuggest.isValidWord(suggestion)) { 884 mAutoDictionary.addWord(suggestion.toString(), FREQUENCY_FOR_PICKED); 885 } 886 mPredicting = false; 887 mCommittedLength = suggestion.length(); 888 if (mCandidateView != null) { 889 mCandidateView.setSuggestions(null, false, false, false); 890 } 891 updateShiftKeyState(getCurrentInputEditorInfo()); 892 } 893 isCursorTouchingWord()894 private boolean isCursorTouchingWord() { 895 InputConnection ic = getCurrentInputConnection(); 896 if (ic == null) return false; 897 CharSequence toLeft = ic.getTextBeforeCursor(1, 0); 898 CharSequence toRight = ic.getTextAfterCursor(1, 0); 899 if (!TextUtils.isEmpty(toLeft) 900 && !isWordSeparator(toLeft.charAt(0))) { 901 return true; 902 } 903 if (!TextUtils.isEmpty(toRight) 904 && !isWordSeparator(toRight.charAt(0))) { 905 return true; 906 } 907 return false; 908 } 909 revertLastWord(boolean deleteChar)910 public void revertLastWord(boolean deleteChar) { 911 final int length = mComposing.length(); 912 if (!mPredicting && length > 0) { 913 final InputConnection ic = getCurrentInputConnection(); 914 mPredicting = true; 915 ic.beginBatchEdit(); 916 mJustRevertedSeparator = ic.getTextBeforeCursor(1, 0); 917 if (deleteChar) ic.deleteSurroundingText(1, 0); 918 int toDelete = mCommittedLength; 919 CharSequence toTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0); 920 if (toTheLeft != null && toTheLeft.length() > 0 921 && isWordSeparator(toTheLeft.charAt(0))) { 922 toDelete--; 923 } 924 ic.deleteSurroundingText(toDelete, 0); 925 ic.setComposingText(mComposing, 1); 926 TextEntryState.backspace(); 927 ic.endBatchEdit(); 928 postUpdateSuggestions(); 929 } else { 930 sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); 931 mJustRevertedSeparator = null; 932 } 933 } 934 getWordSeparators()935 protected String getWordSeparators() { 936 return mWordSeparators; 937 } 938 isWordSeparator(int code)939 public boolean isWordSeparator(int code) { 940 String separators = getWordSeparators(); 941 return separators.contains(String.valueOf((char)code)); 942 } 943 isSentenceSeparator(int code)944 public boolean isSentenceSeparator(int code) { 945 return mSentenceSeparators.contains(String.valueOf((char)code)); 946 } 947 sendSpace()948 private void sendSpace() { 949 sendKeyChar((char)KEYCODE_SPACE); 950 updateShiftKeyState(getCurrentInputEditorInfo()); 951 //onKey(KEY_SPACE[0], KEY_SPACE); 952 } 953 preferCapitalization()954 public boolean preferCapitalization() { 955 return mWord.isCapitalized(); 956 } 957 swipeRight()958 public void swipeRight() { 959 if (LatinKeyboardView.DEBUG_AUTO_PLAY) { 960 ClipboardManager cm = ((ClipboardManager)getSystemService(CLIPBOARD_SERVICE)); 961 CharSequence text = cm.getText(); 962 if (!TextUtils.isEmpty(text)) { 963 mInputView.startPlaying(text.toString()); 964 } 965 } 966 } 967 swipeLeft()968 public void swipeLeft() { 969 //handleBackspace(); 970 } 971 swipeDown()972 public void swipeDown() { 973 handleClose(); 974 } 975 swipeUp()976 public void swipeUp() { 977 //launchSettings(); 978 } 979 onPress(int primaryCode)980 public void onPress(int primaryCode) { 981 vibrate(); 982 playKeyClick(primaryCode); 983 } 984 onRelease(int primaryCode)985 public void onRelease(int primaryCode) { 986 //vibrate(); 987 } 988 989 // receive ringer mode changes to detect silent mode 990 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 991 @Override 992 public void onReceive(Context context, Intent intent) { 993 updateRingerMode(); 994 } 995 }; 996 997 // update flags for silent mode updateRingerMode()998 private void updateRingerMode() { 999 if (mAudioManager == null) { 1000 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 1001 } 1002 if (mAudioManager != null) { 1003 mSilentMode = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL); 1004 } 1005 } 1006 playKeyClick(int primaryCode)1007 private void playKeyClick(int primaryCode) { 1008 // if mAudioManager is null, we don't have the ringer state yet 1009 // mAudioManager will be set by updateRingerMode 1010 if (mAudioManager == null) { 1011 if (mInputView != null) { 1012 updateRingerMode(); 1013 } 1014 } 1015 if (mSoundOn && !mSilentMode) { 1016 // FIXME: Volume and enable should come from UI settings 1017 // FIXME: These should be triggered after auto-repeat logic 1018 int sound = AudioManager.FX_KEYPRESS_STANDARD; 1019 switch (primaryCode) { 1020 case Keyboard.KEYCODE_DELETE: 1021 sound = AudioManager.FX_KEYPRESS_DELETE; 1022 break; 1023 case KEYCODE_ENTER: 1024 sound = AudioManager.FX_KEYPRESS_RETURN; 1025 break; 1026 case KEYCODE_SPACE: 1027 sound = AudioManager.FX_KEYPRESS_SPACEBAR; 1028 break; 1029 } 1030 mAudioManager.playSoundEffect(sound, FX_VOLUME); 1031 } 1032 } 1033 vibrate()1034 private void vibrate() { 1035 if (!mVibrateOn) { 1036 return; 1037 } 1038 if (mVibrator == null) { 1039 mVibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE); 1040 } 1041 mVibrator.vibrate(mVibrateDuration); 1042 } 1043 checkTutorial(String privateImeOptions)1044 private void checkTutorial(String privateImeOptions) { 1045 if (privateImeOptions == null) return; 1046 if (privateImeOptions.equals("com.android.setupwizard:ShowTutorial")) { 1047 if (mTutorial == null) startTutorial(); 1048 } else if (privateImeOptions.equals("com.android.setupwizard:HideTutorial")) { 1049 if (mTutorial != null) { 1050 if (mTutorial.close()) { 1051 mTutorial = null; 1052 } 1053 } 1054 } 1055 } 1056 startTutorial()1057 private void startTutorial() { 1058 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_TUTORIAL), 500); 1059 } 1060 tutorialDone()1061 void tutorialDone() { 1062 mTutorial = null; 1063 } 1064 promoteToUserDictionary(String word, int frequency)1065 void promoteToUserDictionary(String word, int frequency) { 1066 if (mUserDictionary.isValidWord(word)) return; 1067 mUserDictionary.addWord(word, frequency); 1068 } 1069 launchSettings()1070 private void launchSettings() { 1071 handleClose(); 1072 Intent intent = new Intent(); 1073 intent.setClass(LatinIME.this, LatinIMESettings.class); 1074 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1075 startActivity(intent); 1076 } 1077 loadSettings()1078 private void loadSettings() { 1079 // Get the settings preferences 1080 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); 1081 mVibrateOn = sp.getBoolean(PREF_VIBRATE_ON, false); 1082 mSoundOn = sp.getBoolean(PREF_SOUND_ON, false); 1083 mAutoCap = sp.getBoolean(PREF_AUTO_CAP, true); 1084 mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true); 1085 // If there is no auto text data, then quickfix is forced to "on", so that the other options 1086 // will continue to work 1087 if (AutoText.getSize(mInputView) < 1) mQuickFixes = true; 1088 mShowSuggestions = sp.getBoolean(PREF_SHOW_SUGGESTIONS, true) & mQuickFixes; 1089 boolean autoComplete = sp.getBoolean(PREF_AUTO_COMPLETE, 1090 getResources().getBoolean(R.bool.enable_autocorrect)) & mShowSuggestions; 1091 mAutoCorrectOn = mSuggest != null && (autoComplete || mQuickFixes); 1092 mCorrectionMode = autoComplete 1093 ? Suggest.CORRECTION_FULL 1094 : (mQuickFixes ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE); 1095 } 1096 showOptionsMenu()1097 private void showOptionsMenu() { 1098 AlertDialog.Builder builder = new AlertDialog.Builder(this); 1099 builder.setCancelable(true); 1100 builder.setIcon(R.drawable.ic_dialog_keyboard); 1101 builder.setNegativeButton(android.R.string.cancel, null); 1102 CharSequence itemSettings = getString(R.string.english_ime_settings); 1103 CharSequence itemInputMethod = getString(com.android.internal.R.string.inputMethod); 1104 builder.setItems(new CharSequence[] { 1105 itemSettings, itemInputMethod}, 1106 new DialogInterface.OnClickListener() { 1107 1108 public void onClick(DialogInterface di, int position) { 1109 di.dismiss(); 1110 switch (position) { 1111 case POS_SETTINGS: 1112 launchSettings(); 1113 break; 1114 case POS_METHOD: 1115 ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)) 1116 .showInputMethodPicker(); 1117 break; 1118 } 1119 } 1120 }); 1121 builder.setTitle(getResources().getString(R.string.english_ime_name)); 1122 mOptionsDialog = builder.create(); 1123 Window window = mOptionsDialog.getWindow(); 1124 WindowManager.LayoutParams lp = window.getAttributes(); 1125 lp.token = mInputView.getWindowToken(); 1126 lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; 1127 window.setAttributes(lp); 1128 window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 1129 mOptionsDialog.show(); 1130 } 1131 changeKeyboardMode()1132 private void changeKeyboardMode() { 1133 mKeyboardSwitcher.toggleSymbols(); 1134 if (mCapsLock && mKeyboardSwitcher.isAlphabetMode()) { 1135 ((LatinKeyboard) mInputView.getKeyboard()).setShiftLocked(mCapsLock); 1136 } 1137 1138 updateShiftKeyState(getCurrentInputEditorInfo()); 1139 } 1140 dump(FileDescriptor fd, PrintWriter fout, String[] args)1141 @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { 1142 super.dump(fd, fout, args); 1143 1144 final Printer p = new PrintWriterPrinter(fout); 1145 p.println("LatinIME state :"); 1146 p.println(" Keyboard mode = " + mKeyboardSwitcher.getKeyboardMode()); 1147 p.println(" mCapsLock=" + mCapsLock); 1148 p.println(" mComposing=" + mComposing.toString()); 1149 p.println(" mPredictionOn=" + mPredictionOn); 1150 p.println(" mCorrectionMode=" + mCorrectionMode); 1151 p.println(" mPredicting=" + mPredicting); 1152 p.println(" mAutoCorrectOn=" + mAutoCorrectOn); 1153 p.println(" mAutoSpace=" + mAutoSpace); 1154 p.println(" mCompletionOn=" + mCompletionOn); 1155 p.println(" TextEntryState.state=" + TextEntryState.getState()); 1156 p.println(" mSoundOn=" + mSoundOn); 1157 p.println(" mVibrateOn=" + mVibrateOn); 1158 } 1159 1160 // Characters per second measurement 1161 1162 private static final boolean PERF_DEBUG = false; 1163 private long mLastCpsTime; 1164 private static final int CPS_BUFFER_SIZE = 16; 1165 private long[] mCpsIntervals = new long[CPS_BUFFER_SIZE]; 1166 private int mCpsIndex; 1167 measureCps()1168 private void measureCps() { 1169 if (!LatinIME.PERF_DEBUG) return; 1170 long now = System.currentTimeMillis(); 1171 if (mLastCpsTime == 0) mLastCpsTime = now - 100; // Initial 1172 mCpsIntervals[mCpsIndex] = now - mLastCpsTime; 1173 mLastCpsTime = now; 1174 mCpsIndex = (mCpsIndex + 1) % CPS_BUFFER_SIZE; 1175 long total = 0; 1176 for (int i = 0; i < CPS_BUFFER_SIZE; i++) total += mCpsIntervals[i]; 1177 System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total)); 1178 } 1179 1180 class AutoDictionary extends ExpandableDictionary { 1181 // If the user touches a typed word 2 times or more, it will become valid. 1182 private static final int VALIDITY_THRESHOLD = 2 * FREQUENCY_FOR_PICKED; 1183 // If the user touches a typed word 5 times or more, it will be added to the user dict. 1184 private static final int PROMOTION_THRESHOLD = 5 * FREQUENCY_FOR_PICKED; 1185 AutoDictionary(Context context)1186 public AutoDictionary(Context context) { 1187 super(context); 1188 } 1189 1190 @Override isValidWord(CharSequence word)1191 public boolean isValidWord(CharSequence word) { 1192 final int frequency = getWordFrequency(word); 1193 return frequency > VALIDITY_THRESHOLD; 1194 } 1195 1196 @Override addWord(String word, int addFrequency)1197 public void addWord(String word, int addFrequency) { 1198 final int length = word.length(); 1199 // Don't add very short or very long words. 1200 if (length < 2 || length > getMaxWordLength()) return; 1201 super.addWord(word, addFrequency); 1202 final int freq = getWordFrequency(word); 1203 if (freq > PROMOTION_THRESHOLD) { 1204 LatinIME.this.promoteToUserDictionary(word, FREQUENCY_FOR_AUTO_ADD); 1205 } 1206 } 1207 } 1208 } 1209 1210 1211 1212