• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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