LatinIME.java revision 14e427d5bb13d59d23fb317ef90a6c44ae279425
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.android.inputmethod.latin;
18
19import com.android.inputmethod.keyboard.Keyboard;
20import com.android.inputmethod.keyboard.KeyboardActionListener;
21import com.android.inputmethod.keyboard.KeyboardId;
22import com.android.inputmethod.keyboard.KeyboardSwitcher;
23import com.android.inputmethod.keyboard.KeyboardView;
24import com.android.inputmethod.keyboard.LatinKeyboard;
25import com.android.inputmethod.keyboard.LatinKeyboardView;
26import com.android.inputmethod.latin.Utils.RingCharBuffer;
27import com.android.inputmethod.voice.VoiceIMEConnector;
28
29import android.app.AlertDialog;
30import android.content.BroadcastReceiver;
31import android.content.Context;
32import android.content.DialogInterface;
33import android.content.Intent;
34import android.content.IntentFilter;
35import android.content.SharedPreferences;
36import android.content.res.Configuration;
37import android.content.res.Resources;
38import android.inputmethodservice.InputMethodService;
39import android.media.AudioManager;
40import android.net.ConnectivityManager;
41import android.os.Debug;
42import android.os.Handler;
43import android.os.Message;
44import android.os.SystemClock;
45import android.os.Vibrator;
46import android.preference.PreferenceActivity;
47import android.preference.PreferenceManager;
48import android.text.InputType;
49import android.text.TextUtils;
50import android.util.DisplayMetrics;
51import android.util.Log;
52import android.util.PrintWriterPrinter;
53import android.util.Printer;
54import android.view.Gravity;
55import android.view.HapticFeedbackConstants;
56import android.view.KeyEvent;
57import android.view.LayoutInflater;
58import android.view.View;
59import android.view.ViewGroup;
60import android.view.ViewParent;
61import android.view.Window;
62import android.view.WindowManager;
63import android.view.inputmethod.CompletionInfo;
64import android.view.inputmethod.CorrectionInfo;
65import android.view.inputmethod.EditorInfo;
66import android.view.inputmethod.ExtractedText;
67import android.view.inputmethod.ExtractedTextRequest;
68import android.view.inputmethod.InputConnection;
69import android.view.inputmethod.InputMethodManager;
70import android.view.inputmethod.InputMethodSubtype;
71import android.widget.FrameLayout;
72import android.widget.HorizontalScrollView;
73import android.widget.LinearLayout;
74
75import java.io.FileDescriptor;
76import java.io.PrintWriter;
77import java.util.ArrayList;
78import java.util.Arrays;
79import java.util.Locale;
80
81/**
82 * Input method implementation for Qwerty'ish keyboard.
83 */
84public class LatinIME extends InputMethodService implements KeyboardActionListener,
85        SharedPreferences.OnSharedPreferenceChangeListener {
86    private static final String TAG = "LatinIME";
87    private static final boolean PERF_DEBUG = false;
88    private static final boolean TRACE = false;
89    private static boolean DEBUG = LatinImeLogger.sDBG;
90
91    private static final int DELAY_UPDATE_SUGGESTIONS = 180;
92    private static final int DELAY_UPDATE_OLD_SUGGESTIONS = 300;
93    private static final int DELAY_UPDATE_SHIFT_STATE = 300;
94    private static final int EXTENDED_TOUCHABLE_REGION_HEIGHT = 100;
95
96    // How many continuous deletes at which to start deleting at a higher speed.
97    private static final int DELETE_ACCELERATE_AT = 20;
98    // Key events coming any faster than this are long-presses.
99    private static final int QUICK_PRESS = 200;
100
101    private int mSuggestionVisibility;
102    private static final int SUGGESTION_VISIBILILTY_SHOW_VALUE
103            = R.string.prefs_suggestion_visibility_show_value;
104    private static final int SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE
105            = R.string.prefs_suggestion_visibility_show_only_portrait_value;
106    private static final int SUGGESTION_VISIBILILTY_HIDE_VALUE
107            = R.string.prefs_suggestion_visibility_hide_value;
108
109    private static final int[] SUGGESTION_VISIBILITY_VALUE_ARRAY = new int[] {
110        SUGGESTION_VISIBILILTY_SHOW_VALUE,
111        SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE,
112        SUGGESTION_VISIBILILTY_HIDE_VALUE
113    };
114
115    private View mCandidateViewContainer;
116    private CandidateView mCandidateView;
117    private Suggest mSuggest;
118    private CompletionInfo[] mApplicationSpecifiedCompletions;
119
120    private AlertDialog mOptionsDialog;
121
122    private InputMethodManager mImm;
123    private Resources mResources;
124    private SharedPreferences mPrefs;
125    private String mInputMethodId;
126    private KeyboardSwitcher mKeyboardSwitcher;
127    private SubtypeSwitcher mSubtypeSwitcher;
128    private VoiceIMEConnector mVoiceConnector;
129
130    private UserDictionary mUserDictionary;
131    private UserBigramDictionary mUserBigramDictionary;
132    private ContactsDictionary mContactsDictionary;
133    private AutoDictionary mAutoDictionary;
134
135    // These variables are initialized according to the {@link EditorInfo#inputType}.
136    private boolean mAutoSpace;
137    private boolean mInputTypeNoAutoCorrect;
138    private boolean mIsSettingsSuggestionStripOn;
139    private boolean mApplicationSpecifiedCompletionOn;
140
141    private final StringBuilder mComposing = new StringBuilder();
142    private WordComposer mWord = new WordComposer();
143    private CharSequence mBestWord;
144    private boolean mHasValidSuggestions;
145    private boolean mHasDictionary;
146    private boolean mJustAddedAutoSpace;
147    private boolean mAutoCorrectEnabled;
148    private boolean mReCorrectionEnabled;
149    private boolean mBigramSuggestionEnabled;
150    private boolean mAutoCorrectOn;
151    private boolean mVibrateOn;
152    private boolean mSoundOn;
153    private boolean mPopupOn;
154    private boolean mAutoCap;
155    private boolean mQuickFixes;
156    private boolean mConfigEnableShowSubtypeSettings;
157    private boolean mConfigSwipeDownDismissKeyboardEnabled;
158    private int mConfigDelayBeforeFadeoutLanguageOnSpacebar;
159    private int mConfigDurationOfFadeoutLanguageOnSpacebar;
160    private float mConfigFinalFadeoutFactorOfLanguageOnSpacebar;
161
162    private int mCorrectionMode;
163    private int mCommittedLength;
164    private int mOrientation;
165    // Keep track of the last selection range to decide if we need to show word alternatives
166    private int mLastSelectionStart;
167    private int mLastSelectionEnd;
168    private SuggestedWords mSuggestPuncList;
169
170    // Indicates whether the suggestion strip is to be on in landscape
171    private boolean mJustAccepted;
172    private boolean mJustReverted;
173    private int mDeleteCount;
174    private long mLastKeyTime;
175
176    private AudioManager mAudioManager;
177    // Align sound effect volume on music volume
178    private static final float FX_VOLUME = -1.0f;
179    private boolean mSilentMode;
180
181    /* package */ String mWordSeparators;
182    private String mSentenceSeparators;
183    private String mSuggestPuncs;
184    // TODO: Move this flag to VoiceIMEConnector
185    private boolean mConfigurationChanging;
186
187    // Keeps track of most recently inserted text (multi-character key) for reverting
188    private CharSequence mEnteredText;
189    private boolean mRefreshKeyboardRequired;
190
191    private final ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>();
192
193    public abstract static class WordAlternatives {
194        protected CharSequence mChosenWord;
195
196        public WordAlternatives() {
197            // Nothing
198        }
199
200        public WordAlternatives(CharSequence chosenWord) {
201            mChosenWord = chosenWord;
202        }
203
204        @Override
205        public int hashCode() {
206            return mChosenWord.hashCode();
207        }
208
209        public abstract CharSequence getOriginalWord();
210
211        public CharSequence getChosenWord() {
212            return mChosenWord;
213        }
214
215        public abstract SuggestedWords.Builder getAlternatives();
216    }
217
218    public class TypedWordAlternatives extends WordAlternatives {
219        private WordComposer word;
220
221        public TypedWordAlternatives() {
222            // Nothing
223        }
224
225        public TypedWordAlternatives(CharSequence chosenWord, WordComposer wordComposer) {
226            super(chosenWord);
227            word = wordComposer;
228        }
229
230        @Override
231        public CharSequence getOriginalWord() {
232            return word.getTypedWord();
233        }
234
235        @Override
236        public SuggestedWords.Builder getAlternatives() {
237            return getTypedSuggestions(word);
238        }
239    }
240
241    public final UIHandler mHandler = new UIHandler();
242
243    public class UIHandler extends Handler {
244        private static final int MSG_UPDATE_SUGGESTIONS = 0;
245        private static final int MSG_UPDATE_OLD_SUGGESTIONS = 1;
246        private static final int MSG_UPDATE_SHIFT_STATE = 2;
247        private static final int MSG_VOICE_RESULTS = 3;
248        private static final int MSG_FADEOUT_LANGUAGE_ON_SPACEBAR = 4;
249        private static final int MSG_DISMISS_LANGUAGE_ON_SPACEBAR = 5;
250
251        @Override
252        public void handleMessage(Message msg) {
253            final KeyboardSwitcher switcher = mKeyboardSwitcher;
254            final LatinKeyboardView inputView = switcher.getInputView();
255            switch (msg.what) {
256            case MSG_UPDATE_SUGGESTIONS:
257                updateSuggestions();
258                break;
259            case MSG_UPDATE_OLD_SUGGESTIONS:
260                setOldSuggestions();
261                break;
262            case MSG_UPDATE_SHIFT_STATE:
263                switcher.updateShiftState();
264                break;
265            case MSG_VOICE_RESULTS:
266                mVoiceConnector.handleVoiceResults(preferCapitalization()
267                        || (switcher.isAlphabetMode() && switcher.isShiftedOrShiftLocked()));
268                break;
269            case MSG_FADEOUT_LANGUAGE_ON_SPACEBAR:
270                if (inputView != null)
271                    inputView.setSpacebarTextFadeFactor(
272                            (1.0f + mConfigFinalFadeoutFactorOfLanguageOnSpacebar) / 2,
273                            (LatinKeyboard)msg.obj);
274                sendMessageDelayed(obtainMessage(MSG_DISMISS_LANGUAGE_ON_SPACEBAR, msg.obj),
275                        mConfigDurationOfFadeoutLanguageOnSpacebar);
276                break;
277            case MSG_DISMISS_LANGUAGE_ON_SPACEBAR:
278                if (inputView != null)
279                    inputView.setSpacebarTextFadeFactor(
280                            mConfigFinalFadeoutFactorOfLanguageOnSpacebar, (LatinKeyboard)msg.obj);
281                break;
282            }
283        }
284
285        public void postUpdateSuggestions() {
286            removeMessages(MSG_UPDATE_SUGGESTIONS);
287            sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTIONS), DELAY_UPDATE_SUGGESTIONS);
288        }
289
290        public void cancelUpdateSuggestions() {
291            removeMessages(MSG_UPDATE_SUGGESTIONS);
292        }
293
294        public boolean hasPendingUpdateSuggestions() {
295            return hasMessages(MSG_UPDATE_SUGGESTIONS);
296        }
297
298        public void postUpdateOldSuggestions() {
299            removeMessages(MSG_UPDATE_OLD_SUGGESTIONS);
300            sendMessageDelayed(obtainMessage(MSG_UPDATE_OLD_SUGGESTIONS),
301                    DELAY_UPDATE_OLD_SUGGESTIONS);
302        }
303
304        public void cancelUpdateOldSuggestions() {
305            removeMessages(MSG_UPDATE_OLD_SUGGESTIONS);
306        }
307
308        public void postUpdateShiftKeyState() {
309            removeMessages(MSG_UPDATE_SHIFT_STATE);
310            sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), DELAY_UPDATE_SHIFT_STATE);
311        }
312
313        public void cancelUpdateShiftState() {
314            removeMessages(MSG_UPDATE_SHIFT_STATE);
315        }
316
317        public void updateVoiceResults() {
318            sendMessage(obtainMessage(MSG_VOICE_RESULTS));
319        }
320
321        public void startDisplayLanguageOnSpacebar(boolean localeChanged) {
322            removeMessages(MSG_FADEOUT_LANGUAGE_ON_SPACEBAR);
323            removeMessages(MSG_DISMISS_LANGUAGE_ON_SPACEBAR);
324            final LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
325            if (inputView != null) {
326                final LatinKeyboard keyboard = inputView.getLatinKeyboard();
327                // The language is never displayed when the delay is zero.
328                if (mConfigDelayBeforeFadeoutLanguageOnSpacebar != 0)
329                    inputView.setSpacebarTextFadeFactor(localeChanged ? 1.0f
330                            : mConfigFinalFadeoutFactorOfLanguageOnSpacebar, keyboard);
331                // The language is always displayed when the delay is negative.
332                if (localeChanged && mConfigDelayBeforeFadeoutLanguageOnSpacebar > 0) {
333                    sendMessageDelayed(obtainMessage(MSG_FADEOUT_LANGUAGE_ON_SPACEBAR, keyboard),
334                            mConfigDelayBeforeFadeoutLanguageOnSpacebar);
335                }
336            }
337        }
338    }
339
340    @Override
341    public void onCreate() {
342        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
343        mPrefs = prefs;
344        LatinImeLogger.init(this, prefs);
345        SubtypeSwitcher.init(this, prefs);
346        KeyboardSwitcher.init(this, prefs);
347
348        super.onCreate();
349
350        mImm = ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE));
351        mInputMethodId = Utils.getInputMethodId(mImm, getApplicationInfo().packageName);
352        mSubtypeSwitcher = SubtypeSwitcher.getInstance();
353        mKeyboardSwitcher = KeyboardSwitcher.getInstance();
354
355        final Resources res = getResources();
356        mResources = res;
357
358        // If the option should not be shown, do not read the recorrection preference
359        // but always use the default setting defined in the resources.
360        if (res.getBoolean(R.bool.config_enable_show_recorrection_option)) {
361            mReCorrectionEnabled = prefs.getBoolean(Settings.PREF_RECORRECTION_ENABLED,
362                    res.getBoolean(R.bool.default_recorrection_enabled));
363        } else {
364            mReCorrectionEnabled = res.getBoolean(R.bool.default_recorrection_enabled);
365        }
366
367        mConfigEnableShowSubtypeSettings = res.getBoolean(
368                R.bool.config_enable_show_subtype_settings);
369        mConfigSwipeDownDismissKeyboardEnabled = res.getBoolean(
370                R.bool.config_swipe_down_dismiss_keyboard_enabled);
371        mConfigDelayBeforeFadeoutLanguageOnSpacebar = res.getInteger(
372                R.integer.config_delay_before_fadeout_language_on_spacebar);
373        mConfigDurationOfFadeoutLanguageOnSpacebar = res.getInteger(
374                R.integer.config_duration_of_fadeout_language_on_spacebar);
375        mConfigFinalFadeoutFactorOfLanguageOnSpacebar = res.getInteger(
376                R.integer.config_final_fadeout_percentage_of_language_on_spacebar) / 100.0f;
377
378        Utils.GCUtils.getInstance().reset();
379        boolean tryGC = true;
380        for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
381            try {
382                initSuggest();
383                tryGC = false;
384            } catch (OutOfMemoryError e) {
385                tryGC = Utils.GCUtils.getInstance().tryGCOrWait("InitSuggest", e);
386            }
387        }
388
389        mOrientation = res.getConfiguration().orientation;
390        initSuggestPuncList();
391
392        // register to receive ringer mode change and network state change.
393        final IntentFilter filter = new IntentFilter();
394        filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
395        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
396        registerReceiver(mReceiver, filter);
397        mVoiceConnector = VoiceIMEConnector.init(this, prefs, mHandler);
398        prefs.registerOnSharedPreferenceChangeListener(this);
399    }
400
401    /**
402     * Returns a main dictionary resource id
403     * @return main dictionary resource id
404     */
405    public static int getMainDictionaryResourceId(Resources res) {
406        final String MAIN_DIC_NAME = "main";
407        String packageName = LatinIME.class.getPackage().getName();
408        return res.getIdentifier(MAIN_DIC_NAME, "raw", packageName);
409    }
410
411    private void initSuggest() {
412        updateAutoTextEnabled();
413        String locale = mSubtypeSwitcher.getInputLocaleStr();
414
415        Locale savedLocale = mSubtypeSwitcher.changeSystemLocale(new Locale(locale));
416        if (mSuggest != null) {
417            mSuggest.close();
418        }
419        final SharedPreferences prefs = mPrefs;
420        mQuickFixes = isQuickFixesEnabled(prefs);
421
422        final Resources res = mResources;
423        int mainDicResId = getMainDictionaryResourceId(res);
424        mSuggest = new Suggest(this, mainDicResId);
425        loadAndSetAutoCorrectionThreshold(prefs);
426
427        mUserDictionary = new UserDictionary(this, locale);
428        mSuggest.setUserDictionary(mUserDictionary);
429
430        mContactsDictionary = new ContactsDictionary(this, Suggest.DIC_CONTACTS);
431        mSuggest.setContactsDictionary(mContactsDictionary);
432
433        mAutoDictionary = new AutoDictionary(this, this, locale, Suggest.DIC_AUTO);
434        mSuggest.setAutoDictionary(mAutoDictionary);
435
436        mUserBigramDictionary = new UserBigramDictionary(this, this, locale, Suggest.DIC_USER);
437        mSuggest.setUserBigramDictionary(mUserBigramDictionary);
438
439        updateCorrectionMode();
440        mWordSeparators = res.getString(R.string.word_separators);
441        mSentenceSeparators = res.getString(R.string.sentence_separators);
442
443        mSubtypeSwitcher.changeSystemLocale(savedLocale);
444    }
445
446    @Override
447    public void onDestroy() {
448        if (mSuggest != null) {
449            mSuggest.close();
450            mSuggest = null;
451        }
452        unregisterReceiver(mReceiver);
453        mVoiceConnector.destroy();
454        LatinImeLogger.commit();
455        LatinImeLogger.onDestroy();
456        super.onDestroy();
457    }
458
459    @Override
460    public void onConfigurationChanged(Configuration conf) {
461        mSubtypeSwitcher.onConfigurationChanged(conf);
462        // If orientation changed while predicting, commit the change
463        if (conf.orientation != mOrientation) {
464            InputConnection ic = getCurrentInputConnection();
465            commitTyped(ic);
466            if (ic != null) ic.finishComposingText(); // For voice input
467            mOrientation = conf.orientation;
468            if (isShowingOptionDialog())
469                mOptionsDialog.dismiss();
470        }
471
472        mConfigurationChanging = true;
473        super.onConfigurationChanged(conf);
474        mVoiceConnector.onConfigurationChanged(conf);
475        mConfigurationChanging = false;
476    }
477
478    @Override
479    public View onCreateInputView() {
480        return mKeyboardSwitcher.onCreateInputView();
481    }
482
483    @Override
484    public View onCreateCandidatesView() {
485        LayoutInflater inflater = getLayoutInflater();
486        LinearLayout container = (LinearLayout)inflater.inflate(R.layout.candidates, null);
487        mCandidateViewContainer = container;
488        if (container.getPaddingRight() != 0) {
489            HorizontalScrollView scrollView =
490                    (HorizontalScrollView) container.findViewById(R.id.candidates_scroll_view);
491            scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER);
492            container.setGravity(Gravity.CENTER_HORIZONTAL);
493        }
494        mCandidateView = (CandidateView) container.findViewById(R.id.candidates);
495        mCandidateView.setService(this);
496        setCandidatesViewShown(true);
497        return container;
498    }
499
500    private static boolean isPasswordVariation(int variation) {
501        return variation == InputType.TYPE_TEXT_VARIATION_PASSWORD
502                || variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
503                || variation == InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD;
504    }
505
506    private static boolean isEmailVariation(int variation) {
507        return variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
508                || variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
509    }
510
511    @Override
512    public void onStartInputView(EditorInfo attribute, boolean restarting) {
513        final KeyboardSwitcher switcher = mKeyboardSwitcher;
514        LatinKeyboardView inputView = switcher.getInputView();
515
516        if(DEBUG) {
517            Log.d(TAG, "onStartInputView: " + inputView);
518        }
519        // In landscape mode, this method gets called without the input view being created.
520        if (inputView == null) {
521            return;
522        }
523
524        mSubtypeSwitcher.updateParametersOnStartInputView();
525
526        if (mRefreshKeyboardRequired) {
527            mRefreshKeyboardRequired = false;
528            onRefreshKeyboard();
529        }
530
531        TextEntryState.newSession(this);
532
533        // Most such things we decide below in initializeInputAttributesAndGetMode, but we need to
534        // know now whether this is a password text field, because we need to know now whether we
535        // want to enable the voice button.
536        mVoiceConnector.resetVoiceStates(isPasswordVariation(
537                attribute.inputType & InputType.TYPE_MASK_VARIATION));
538
539        final int mode = initializeInputAttributesAndGetMode(attribute.inputType);
540
541        inputView.closing();
542        mEnteredText = null;
543        mComposing.setLength(0);
544        mHasValidSuggestions = false;
545        mDeleteCount = 0;
546        mJustAddedAutoSpace = false;
547
548        loadSettings(attribute);
549        if (mSubtypeSwitcher.isKeyboardMode()) {
550            switcher.loadKeyboard(mode, attribute.imeOptions,
551                    mVoiceConnector.isVoiceButtonEnabled(),
552                    mVoiceConnector.isVoiceButtonOnPrimary());
553            switcher.updateShiftState();
554        }
555
556        setCandidatesViewShownInternal(isCandidateStripVisible(),
557                false /* needsInputViewShown */ );
558        // Delay updating suggestions because keyboard input view may not be shown at this point.
559        mHandler.postUpdateSuggestions();
560
561        updateCorrectionMode();
562
563        inputView.setPreviewEnabled(mPopupOn);
564        inputView.setProximityCorrectionEnabled(true);
565        // If we just entered a text field, maybe it has some old text that requires correction
566        checkReCorrectionOnStart();
567        inputView.setForeground(true);
568
569        mVoiceConnector.onStartInputView(inputView.getWindowToken());
570
571        if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
572    }
573
574    private int initializeInputAttributesAndGetMode(int inputType) {
575        final int variation = inputType & InputType.TYPE_MASK_VARIATION;
576        mAutoSpace = false;
577        mInputTypeNoAutoCorrect = false;
578        mIsSettingsSuggestionStripOn = false;
579        mApplicationSpecifiedCompletionOn = false;
580        mApplicationSpecifiedCompletions = null;
581
582        final int mode;
583        switch (inputType & InputType.TYPE_MASK_CLASS) {
584            case InputType.TYPE_CLASS_NUMBER:
585            case InputType.TYPE_CLASS_DATETIME:
586                mode = KeyboardId.MODE_NUMBER;
587                break;
588            case InputType.TYPE_CLASS_PHONE:
589                mode = KeyboardId.MODE_PHONE;
590                break;
591            case InputType.TYPE_CLASS_TEXT:
592                mIsSettingsSuggestionStripOn = true;
593                // Make sure that passwords are not displayed in candidate view
594                if (isPasswordVariation(variation)) {
595                    mIsSettingsSuggestionStripOn = false;
596                }
597                if (isEmailVariation(variation)
598                        || variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) {
599                    mAutoSpace = false;
600                } else {
601                    mAutoSpace = true;
602                }
603                if (isEmailVariation(variation)) {
604                    mIsSettingsSuggestionStripOn = false;
605                    mode = KeyboardId.MODE_EMAIL;
606                } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
607                    mIsSettingsSuggestionStripOn = false;
608                    mode = KeyboardId.MODE_URL;
609                } else if (variation == InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
610                    mode = KeyboardId.MODE_IM;
611                } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
612                    mIsSettingsSuggestionStripOn = false;
613                    mode = KeyboardId.MODE_TEXT;
614                } else if (variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) {
615                    mode = KeyboardId.MODE_WEB;
616                    // If it's a browser edit field and auto correct is not ON explicitly, then
617                    // disable auto correction, but keep suggestions on.
618                    if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) {
619                        mInputTypeNoAutoCorrect = true;
620                    }
621                } else {
622                    mode = KeyboardId.MODE_TEXT;
623                }
624
625                // If NO_SUGGESTIONS is set, don't do prediction.
626                if ((inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) {
627                    mIsSettingsSuggestionStripOn = false;
628                    mInputTypeNoAutoCorrect = true;
629                }
630                // If it's not multiline and the autoCorrect flag is not set, then don't correct
631                if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0 &&
632                        (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) == 0) {
633                    mInputTypeNoAutoCorrect = true;
634                }
635                if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
636                    mIsSettingsSuggestionStripOn = false;
637                    mApplicationSpecifiedCompletionOn = isFullscreenMode();
638                }
639                break;
640            default:
641                mode = KeyboardId.MODE_TEXT;
642                break;
643        }
644        return mode;
645    }
646
647    private void checkReCorrectionOnStart() {
648        if (!mReCorrectionEnabled) return;
649
650        final InputConnection ic = getCurrentInputConnection();
651        if (ic == null) return;
652        // There could be a pending composing span.  Clean it up first.
653        ic.finishComposingText();
654
655        if (isShowingSuggestionsStrip() && isSuggestionsRequested()) {
656            // First get the cursor position. This is required by setOldSuggestions(), so that
657            // it can pass the correct range to setComposingRegion(). At this point, we don't
658            // have valid values for mLastSelectionStart/End because onUpdateSelection() has
659            // not been called yet.
660            ExtractedTextRequest etr = new ExtractedTextRequest();
661            etr.token = 0; // anything is fine here
662            ExtractedText et = ic.getExtractedText(etr, 0);
663            if (et == null) return;
664
665            mLastSelectionStart = et.startOffset + et.selectionStart;
666            mLastSelectionEnd = et.startOffset + et.selectionEnd;
667
668            // Then look for possible corrections in a delayed fashion
669            if (!TextUtils.isEmpty(et.text) && isCursorTouchingWord()) {
670                mHandler.postUpdateOldSuggestions();
671            }
672        }
673    }
674
675    @Override
676    public void onFinishInput() {
677        super.onFinishInput();
678
679        LatinImeLogger.commit();
680        mKeyboardSwitcher.onAutoCorrectionStateChanged(false);
681
682        mVoiceConnector.flushVoiceInputLogs(mConfigurationChanging);
683
684        KeyboardView inputView = mKeyboardSwitcher.getInputView();
685        if (inputView != null) inputView.closing();
686        if (mAutoDictionary != null) mAutoDictionary.flushPendingWrites();
687        if (mUserBigramDictionary != null) mUserBigramDictionary.flushPendingWrites();
688    }
689
690    @Override
691    public void onFinishInputView(boolean finishingInput) {
692        super.onFinishInputView(finishingInput);
693        KeyboardView inputView = mKeyboardSwitcher.getInputView();
694        if (inputView != null) inputView.setForeground(false);
695        // Remove pending messages related to update suggestions
696        mHandler.cancelUpdateSuggestions();
697        mHandler.cancelUpdateOldSuggestions();
698    }
699
700    @Override
701    public void onUpdateExtractedText(int token, ExtractedText text) {
702        super.onUpdateExtractedText(token, text);
703        mVoiceConnector.showPunctuationHintIfNecessary();
704    }
705
706    @Override
707    public void onUpdateSelection(int oldSelStart, int oldSelEnd,
708            int newSelStart, int newSelEnd,
709            int candidatesStart, int candidatesEnd) {
710        super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
711                candidatesStart, candidatesEnd);
712
713        if (DEBUG) {
714            Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart
715                    + ", ose=" + oldSelEnd
716                    + ", nss=" + newSelStart
717                    + ", nse=" + newSelEnd
718                    + ", cs=" + candidatesStart
719                    + ", ce=" + candidatesEnd);
720        }
721
722        mVoiceConnector.setCursorAndSelection(newSelEnd, newSelStart);
723
724        // If the current selection in the text view changes, we should
725        // clear whatever candidate text we have.
726        if ((((mComposing.length() > 0 && mHasValidSuggestions)
727                || mVoiceConnector.isVoiceInputHighlighted()) && (newSelStart != candidatesEnd
728                        || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart)) {
729            mComposing.setLength(0);
730            mHasValidSuggestions = false;
731            mHandler.postUpdateSuggestions();
732            TextEntryState.reset();
733            InputConnection ic = getCurrentInputConnection();
734            if (ic != null) {
735                ic.finishComposingText();
736            }
737            mVoiceConnector.setVoiceInputHighlighted(false);
738        } else if (!mHasValidSuggestions && !mJustAccepted) {
739            switch (TextEntryState.getState()) {
740            case ACCEPTED_DEFAULT:
741                TextEntryState.reset();
742                // $FALL-THROUGH$
743            case SPACE_AFTER_PICKED:
744                mJustAddedAutoSpace = false; // The user moved the cursor.
745                break;
746            default:
747                break;
748            }
749        }
750        mJustAccepted = false;
751        mHandler.postUpdateShiftKeyState();
752
753        // Make a note of the cursor position
754        mLastSelectionStart = newSelStart;
755        mLastSelectionEnd = newSelEnd;
756
757        if (mReCorrectionEnabled && isShowingSuggestionsStrip()) {
758            // Don't look for corrections if the keyboard is not visible
759            if (mKeyboardSwitcher.isInputViewShown()) {
760                // Check if we should go in or out of correction mode.
761                if (isSuggestionsRequested() && !mJustReverted
762                        && (candidatesStart == candidatesEnd || newSelStart != oldSelStart
763                                || TextEntryState.isCorrecting())
764                                && (newSelStart < newSelEnd - 1 || !mHasValidSuggestions)) {
765                    if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) {
766                        mHandler.postUpdateOldSuggestions();
767                    } else {
768                        abortCorrection(false);
769                        // Show the punctuation suggestions list if the current one is not
770                        // and if not showing "Touch again to save".
771                        if (mCandidateView != null && !isShowingPunctuationList()
772                                && !mCandidateView.isShowingAddToDictionaryHint()) {
773                            setPunctuationSuggestions();
774                        }
775                    }
776                }
777            }
778        }
779    }
780
781    /**
782     * This is called when the user has clicked on the extracted text view,
783     * when running in fullscreen mode.  The default implementation hides
784     * the candidates view when this happens, but only if the extracted text
785     * editor has a vertical scroll bar because its text doesn't fit.
786     * Here we override the behavior due to the possibility that a re-correction could
787     * cause the candidate strip to disappear and re-appear.
788     */
789    @Override
790    public void onExtractedTextClicked() {
791        if (mReCorrectionEnabled && isSuggestionsRequested()) return;
792
793        super.onExtractedTextClicked();
794    }
795
796    /**
797     * This is called when the user has performed a cursor movement in the
798     * extracted text view, when it is running in fullscreen mode.  The default
799     * implementation hides the candidates view when a vertical movement
800     * happens, but only if the extracted text editor has a vertical scroll bar
801     * because its text doesn't fit.
802     * Here we override the behavior due to the possibility that a re-correction could
803     * cause the candidate strip to disappear and re-appear.
804     */
805    @Override
806    public void onExtractedCursorMovement(int dx, int dy) {
807        if (mReCorrectionEnabled && isSuggestionsRequested()) return;
808
809        super.onExtractedCursorMovement(dx, dy);
810    }
811
812    @Override
813    public void hideWindow() {
814        LatinImeLogger.commit();
815        mKeyboardSwitcher.onAutoCorrectionStateChanged(false);
816
817        if (TRACE) Debug.stopMethodTracing();
818        if (mOptionsDialog != null && mOptionsDialog.isShowing()) {
819            mOptionsDialog.dismiss();
820            mOptionsDialog = null;
821        }
822        mVoiceConnector.hideVoiceWindow(mConfigurationChanging);
823        mWordHistory.clear();
824        super.hideWindow();
825        TextEntryState.endSession();
826    }
827
828    @Override
829    public void onDisplayCompletions(CompletionInfo[] applicationSpecifiedCompletions) {
830        if (DEBUG) {
831            Log.i(TAG, "Received completions:");
832            final int count = (applicationSpecifiedCompletions != null)
833                    ? applicationSpecifiedCompletions.length : 0;
834            for (int i = 0; i < count; i++) {
835                Log.i(TAG, "  #" + i + ": " + applicationSpecifiedCompletions[i]);
836            }
837        }
838        if (mApplicationSpecifiedCompletionOn) {
839            mApplicationSpecifiedCompletions = applicationSpecifiedCompletions;
840            if (applicationSpecifiedCompletions == null) {
841                clearSuggestions();
842                return;
843            }
844
845            SuggestedWords.Builder builder = new SuggestedWords.Builder()
846                    .setApplicationSpecifiedCompletions(applicationSpecifiedCompletions)
847                    .setTypedWordValid(true)
848                    .setHasMinimalSuggestion(true);
849            // When in fullscreen mode, show completions generated by the application
850            setSuggestions(builder.build());
851            mBestWord = null;
852            setCandidatesViewShown(true);
853        }
854    }
855
856    private void setCandidatesViewShownInternal(boolean shown, boolean needsInputViewShown) {
857        // TODO: Remove this if we support candidates with hard keyboard
858        if (onEvaluateInputViewShown()) {
859            super.setCandidatesViewShown(shown
860                    && (needsInputViewShown ? mKeyboardSwitcher.isInputViewShown() : true));
861        }
862    }
863
864    @Override
865    public void setCandidatesViewShown(boolean shown) {
866        setCandidatesViewShownInternal(shown, true /* needsInputViewShown */ );
867    }
868
869    @Override
870    public void onComputeInsets(InputMethodService.Insets outInsets) {
871        super.onComputeInsets(outInsets);
872        if (!isFullscreenMode()) {
873            outInsets.contentTopInsets = outInsets.visibleTopInsets;
874        }
875        KeyboardView inputView = mKeyboardSwitcher.getInputView();
876        // Need to set touchable region only if input view is being shown
877        if (inputView != null && mKeyboardSwitcher.isInputViewShown()) {
878            final int x = 0;
879            int y = 0;
880            final int width = inputView.getWidth();
881            int height = inputView.getHeight() + EXTENDED_TOUCHABLE_REGION_HEIGHT;
882            if (mCandidateViewContainer != null) {
883                ViewParent candidateParent = mCandidateViewContainer.getParent();
884                if (candidateParent instanceof FrameLayout) {
885                    FrameLayout fl = (FrameLayout) candidateParent;
886                    if (fl != null) {
887                        // Check frame layout's visibility
888                        if (fl.getVisibility() == View.INVISIBLE) {
889                            y = fl.getHeight();
890                            height += y;
891                        } else if (fl.getVisibility() == View.VISIBLE) {
892                            height += fl.getHeight();
893                        }
894                    }
895                }
896            }
897            if (DEBUG) {
898                Log.d(TAG, "Touchable region " + x + ", " + y + ", " + width + ", " + height);
899            }
900            outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION;
901            outInsets.touchableRegion.set(x, y, width, height);
902        }
903    }
904
905    @Override
906    public boolean onEvaluateFullscreenMode() {
907        final Resources res = mResources;
908        DisplayMetrics dm = res.getDisplayMetrics();
909        float displayHeight = dm.heightPixels;
910        // If the display is more than X inches high, don't go to fullscreen mode
911        float dimen = res.getDimension(R.dimen.max_height_for_fullscreen);
912        if (displayHeight > dimen) {
913            return false;
914        } else {
915            return super.onEvaluateFullscreenMode();
916        }
917    }
918
919    @Override
920    public boolean onKeyDown(int keyCode, KeyEvent event) {
921        switch (keyCode) {
922        case KeyEvent.KEYCODE_BACK:
923            if (event.getRepeatCount() == 0 && mKeyboardSwitcher.getInputView() != null) {
924                if (mKeyboardSwitcher.getInputView().handleBack()) {
925                    return true;
926                }
927            }
928            break;
929        }
930        return super.onKeyDown(keyCode, event);
931    }
932
933    @Override
934    public boolean onKeyUp(int keyCode, KeyEvent event) {
935        switch (keyCode) {
936        case KeyEvent.KEYCODE_DPAD_DOWN:
937        case KeyEvent.KEYCODE_DPAD_UP:
938        case KeyEvent.KEYCODE_DPAD_LEFT:
939        case KeyEvent.KEYCODE_DPAD_RIGHT:
940            // Enable shift key and DPAD to do selections
941            if (mKeyboardSwitcher.isInputViewShown()
942                    && mKeyboardSwitcher.isShiftedOrShiftLocked()) {
943                KeyEvent newEvent = new KeyEvent(event.getDownTime(), event.getEventTime(),
944                        event.getAction(), event.getKeyCode(), event.getRepeatCount(),
945                        event.getDeviceId(), event.getScanCode(),
946                        KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON);
947                InputConnection ic = getCurrentInputConnection();
948                if (ic != null)
949                    ic.sendKeyEvent(newEvent);
950                return true;
951            }
952            break;
953        }
954        return super.onKeyUp(keyCode, event);
955    }
956
957    public void commitTyped(InputConnection inputConnection) {
958        if (mHasValidSuggestions) {
959            mHasValidSuggestions = false;
960            if (mComposing.length() > 0) {
961                if (inputConnection != null) {
962                    inputConnection.commitText(mComposing, 1);
963                }
964                mCommittedLength = mComposing.length();
965                TextEntryState.acceptedTyped(mComposing);
966                addToDictionaries(mComposing, AutoDictionary.FREQUENCY_FOR_TYPED);
967            }
968            updateSuggestions();
969        }
970    }
971
972    public boolean getCurrentAutoCapsState() {
973        InputConnection ic = getCurrentInputConnection();
974        EditorInfo ei = getCurrentInputEditorInfo();
975        if (mAutoCap && ic != null && ei != null && ei.inputType != InputType.TYPE_NULL) {
976            return ic.getCursorCapsMode(ei.inputType) != 0;
977        }
978        return false;
979    }
980
981    private void swapPunctuationAndSpace() {
982        final InputConnection ic = getCurrentInputConnection();
983        if (ic == null) return;
984        CharSequence lastTwo = ic.getTextBeforeCursor(2, 0);
985        if (lastTwo != null && lastTwo.length() == 2
986                && lastTwo.charAt(0) == Keyboard.CODE_SPACE
987                && isSentenceSeparator(lastTwo.charAt(1))) {
988            ic.beginBatchEdit();
989            ic.deleteSurroundingText(2, 0);
990            ic.commitText(lastTwo.charAt(1) + " ", 1);
991            ic.endBatchEdit();
992            mKeyboardSwitcher.updateShiftState();
993            mJustAddedAutoSpace = true;
994        }
995    }
996
997    private void reswapPeriodAndSpace() {
998        final InputConnection ic = getCurrentInputConnection();
999        if (ic == null) return;
1000        CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
1001        if (lastThree != null && lastThree.length() == 3
1002                && lastThree.charAt(0) == Keyboard.CODE_PERIOD
1003                && lastThree.charAt(1) == Keyboard.CODE_SPACE
1004                && lastThree.charAt(2) == Keyboard.CODE_PERIOD) {
1005            ic.beginBatchEdit();
1006            ic.deleteSurroundingText(3, 0);
1007            ic.commitText(" ..", 1);
1008            ic.endBatchEdit();
1009            mKeyboardSwitcher.updateShiftState();
1010        }
1011    }
1012
1013    private void doubleSpace() {
1014        //if (!mAutoPunctuate) return;
1015        if (mCorrectionMode == Suggest.CORRECTION_NONE) return;
1016        final InputConnection ic = getCurrentInputConnection();
1017        if (ic == null) return;
1018        CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
1019        if (lastThree != null && lastThree.length() == 3
1020                && Character.isLetterOrDigit(lastThree.charAt(0))
1021                && lastThree.charAt(1) == Keyboard.CODE_SPACE
1022                && lastThree.charAt(2) == Keyboard.CODE_SPACE) {
1023            ic.beginBatchEdit();
1024            ic.deleteSurroundingText(2, 0);
1025            ic.commitText(". ", 1);
1026            ic.endBatchEdit();
1027            mKeyboardSwitcher.updateShiftState();
1028            mJustAddedAutoSpace = true;
1029        }
1030    }
1031
1032    private void maybeRemovePreviousPeriod(CharSequence text) {
1033        final InputConnection ic = getCurrentInputConnection();
1034        if (ic == null) return;
1035
1036        // When the text's first character is '.', remove the previous period
1037        // if there is one.
1038        CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
1039        if (lastOne != null && lastOne.length() == 1
1040                && lastOne.charAt(0) == Keyboard.CODE_PERIOD
1041                && text.charAt(0) == Keyboard.CODE_PERIOD) {
1042            ic.deleteSurroundingText(1, 0);
1043        }
1044    }
1045
1046    private void removeTrailingSpace() {
1047        final InputConnection ic = getCurrentInputConnection();
1048        if (ic == null) return;
1049
1050        CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
1051        if (lastOne != null && lastOne.length() == 1
1052                && lastOne.charAt(0) == Keyboard.CODE_SPACE) {
1053            ic.deleteSurroundingText(1, 0);
1054        }
1055    }
1056
1057    public boolean addWordToDictionary(String word) {
1058        mUserDictionary.addWord(word, 128);
1059        // Suggestion strip should be updated after the operation of adding word to the
1060        // user dictionary
1061        mHandler.postUpdateSuggestions();
1062        return true;
1063    }
1064
1065    private boolean isAlphabet(int code) {
1066        if (Character.isLetter(code)) {
1067            return true;
1068        } else {
1069            return false;
1070        }
1071    }
1072
1073    private void onSettingsKeyPressed() {
1074        if (!isShowingOptionDialog()) {
1075            if (!mConfigEnableShowSubtypeSettings) {
1076                showSubtypeSelectorAndSettings();
1077            } else if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm)) {
1078                showOptionsMenu();
1079            } else {
1080                launchSettings();
1081            }
1082        }
1083    }
1084
1085    private void onSettingsKeyLongPressed() {
1086        if (!isShowingOptionDialog()) {
1087            if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm)) {
1088                mImm.showInputMethodPicker();
1089            } else {
1090                launchSettings();
1091            }
1092        }
1093    }
1094
1095    private boolean isShowingOptionDialog() {
1096        return mOptionsDialog != null && mOptionsDialog.isShowing();
1097    }
1098
1099    // Implementation of {@link KeyboardActionListener}.
1100    @Override
1101    public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {
1102        long when = SystemClock.uptimeMillis();
1103        if (primaryCode != Keyboard.CODE_DELETE || when > mLastKeyTime + QUICK_PRESS) {
1104            mDeleteCount = 0;
1105        }
1106        mLastKeyTime = when;
1107        KeyboardSwitcher switcher = mKeyboardSwitcher;
1108        final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
1109        switch (primaryCode) {
1110        case Keyboard.CODE_DELETE:
1111            handleBackspace();
1112            mDeleteCount++;
1113            LatinImeLogger.logOnDelete();
1114            break;
1115        case Keyboard.CODE_SHIFT:
1116            // Shift key is handled in onPress() when device has distinct multi-touch panel.
1117            if (!distinctMultiTouch)
1118                switcher.toggleShift();
1119            break;
1120        case Keyboard.CODE_SWITCH_ALPHA_SYMBOL:
1121            // Symbol key is handled in onPress() when device has distinct multi-touch panel.
1122            if (!distinctMultiTouch)
1123                switcher.changeKeyboardMode();
1124            break;
1125        case Keyboard.CODE_CANCEL:
1126            if (!isShowingOptionDialog()) {
1127                handleClose();
1128            }
1129            break;
1130        case Keyboard.CODE_SETTINGS:
1131            onSettingsKeyPressed();
1132            break;
1133        case Keyboard.CODE_SETTINGS_LONGPRESS:
1134            onSettingsKeyLongPressed();
1135            break;
1136        case Keyboard.CODE_NEXT_LANGUAGE:
1137            toggleLanguage(false, true);
1138            break;
1139        case Keyboard.CODE_PREV_LANGUAGE:
1140            toggleLanguage(false, false);
1141            break;
1142        case Keyboard.CODE_CAPSLOCK:
1143            switcher.toggleCapsLock();
1144            break;
1145        case Keyboard.CODE_VOICE:
1146            mSubtypeSwitcher.switchToShortcutIME();
1147            break;
1148        case Keyboard.CODE_TAB:
1149            handleTab();
1150            break;
1151        default:
1152            if (primaryCode != Keyboard.CODE_ENTER) {
1153                mJustAddedAutoSpace = false;
1154            }
1155            RingCharBuffer.getInstance().push((char)primaryCode, x, y);
1156            LatinImeLogger.logOnInputChar();
1157            if (isWordSeparator(primaryCode)) {
1158                handleSeparator(primaryCode);
1159            } else {
1160                handleCharacter(primaryCode, keyCodes);
1161            }
1162            // Cancel the just reverted state
1163            mJustReverted = false;
1164        }
1165        switcher.onKey(primaryCode);
1166        // Reset after any single keystroke
1167        mEnteredText = null;
1168    }
1169
1170    @Override
1171    public void onTextInput(CharSequence text) {
1172        mVoiceConnector.commitVoiceInput();
1173        InputConnection ic = getCurrentInputConnection();
1174        if (ic == null) return;
1175        abortCorrection(false);
1176        ic.beginBatchEdit();
1177        commitTyped(ic);
1178        maybeRemovePreviousPeriod(text);
1179        ic.commitText(text, 1);
1180        ic.endBatchEdit();
1181        mKeyboardSwitcher.updateShiftState();
1182        mKeyboardSwitcher.onKey(Keyboard.CODE_DUMMY);
1183        mJustReverted = false;
1184        mJustAddedAutoSpace = false;
1185        mEnteredText = text;
1186    }
1187
1188    @Override
1189    public void onCancelInput() {
1190        // User released a finger outside any key
1191        mKeyboardSwitcher.onCancelInput();
1192    }
1193
1194    private void handleBackspace() {
1195        if (mVoiceConnector.logAndRevertVoiceInput()) return;
1196
1197        final InputConnection ic = getCurrentInputConnection();
1198        if (ic == null) return;
1199        ic.beginBatchEdit();
1200
1201        mVoiceConnector.handleBackspace();
1202
1203        boolean deleteChar = false;
1204        if (mHasValidSuggestions) {
1205            final int length = mComposing.length();
1206            if (length > 0) {
1207                mComposing.delete(length - 1, length);
1208                mWord.deleteLast();
1209                ic.setComposingText(mComposing, 1);
1210                if (mComposing.length() == 0) {
1211                    mHasValidSuggestions = false;
1212                }
1213                mHandler.postUpdateSuggestions();
1214            } else {
1215                ic.deleteSurroundingText(1, 0);
1216            }
1217        } else {
1218            deleteChar = true;
1219        }
1220        mHandler.postUpdateShiftKeyState();
1221
1222        TextEntryState.backspace();
1223        if (TextEntryState.getState() == TextEntryState.State.UNDO_COMMIT) {
1224            revertLastWord(deleteChar);
1225            ic.endBatchEdit();
1226            return;
1227        }
1228
1229        if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) {
1230            ic.deleteSurroundingText(mEnteredText.length(), 0);
1231        } else if (deleteChar) {
1232            if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) {
1233                // Go back to the suggestion mode if the user canceled the
1234                // "Touch again to save".
1235                // NOTE: In gerenal, we don't revert the word when backspacing
1236                // from a manual suggestion pick.  We deliberately chose a
1237                // different behavior only in the case of picking the first
1238                // suggestion (typed word).  It's intentional to have made this
1239                // inconsistent with backspacing after selecting other suggestions.
1240                revertLastWord(deleteChar);
1241            } else {
1242                sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
1243                if (mDeleteCount > DELETE_ACCELERATE_AT) {
1244                    sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
1245                }
1246            }
1247        }
1248        mJustReverted = false;
1249        ic.endBatchEdit();
1250    }
1251
1252    private void handleTab() {
1253        final int imeOptions = getCurrentInputEditorInfo().imeOptions;
1254        final int navigationFlags =
1255                EditorInfo.IME_FLAG_NAVIGATE_NEXT | EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
1256        if ((imeOptions & navigationFlags) == 0) {
1257            sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB);
1258            return;
1259        }
1260
1261        final InputConnection ic = getCurrentInputConnection();
1262        if (ic == null)
1263            return;
1264
1265        // True if keyboard is in either chording shift or manual temporary upper case mode.
1266        final boolean isManualTemporaryUpperCase = mKeyboardSwitcher.isManualTemporaryUpperCase();
1267        if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0
1268                && !isManualTemporaryUpperCase) {
1269            ic.performEditorAction(EditorInfo.IME_ACTION_NEXT);
1270        } else if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS) != 0
1271                && isManualTemporaryUpperCase) {
1272            ic.performEditorAction(EditorInfo.IME_ACTION_PREVIOUS);
1273        }
1274    }
1275
1276    private void abortCorrection(boolean force) {
1277        if (force || TextEntryState.isCorrecting()) {
1278            TextEntryState.onAbortCorrection();
1279            setCandidatesViewShown(isCandidateStripVisible());
1280            getCurrentInputConnection().finishComposingText();
1281            clearSuggestions();
1282        }
1283    }
1284
1285    private void handleCharacter(int primaryCode, int[] keyCodes) {
1286        mVoiceConnector.handleCharacter();
1287
1288        if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isCorrecting()) {
1289            abortCorrection(false);
1290        }
1291
1292        int code = primaryCode;
1293        if (isAlphabet(code) && isSuggestionsRequested() && !isCursorTouchingWord()) {
1294            if (!mHasValidSuggestions) {
1295                mHasValidSuggestions = true;
1296                mComposing.setLength(0);
1297                saveWordInHistory(mBestWord);
1298                mWord.reset();
1299            }
1300        }
1301        KeyboardSwitcher switcher = mKeyboardSwitcher;
1302        if (switcher.isShiftedOrShiftLocked()) {
1303            if (keyCodes == null || keyCodes[0] < Character.MIN_CODE_POINT
1304                    || keyCodes[0] > Character.MAX_CODE_POINT) {
1305                return;
1306            }
1307            code = keyCodes[0];
1308            if (switcher.isAlphabetMode() && Character.isLowerCase(code)) {
1309                int upperCaseCode = Character.toUpperCase(code);
1310                if (upperCaseCode != code) {
1311                    code = upperCaseCode;
1312                } else {
1313                    // Some keys, such as [eszett], have upper case as multi-characters.
1314                    String upperCase = new String(new int[] {code}, 0, 1).toUpperCase();
1315                    onTextInput(upperCase);
1316                    return;
1317                }
1318            }
1319        }
1320        if (mHasValidSuggestions) {
1321            if (mComposing.length() == 0 && switcher.isAlphabetMode()
1322                    && switcher.isShiftedOrShiftLocked()) {
1323                mWord.setFirstCharCapitalized(true);
1324            }
1325            mComposing.append((char) code);
1326            mWord.add(code, keyCodes);
1327            InputConnection ic = getCurrentInputConnection();
1328            if (ic != null) {
1329                // If it's the first letter, make note of auto-caps state
1330                if (mWord.size() == 1) {
1331                    mWord.setAutoCapitalized(getCurrentAutoCapsState());
1332                }
1333                ic.setComposingText(mComposing, 1);
1334            }
1335            mHandler.postUpdateSuggestions();
1336        } else {
1337            sendKeyChar((char)code);
1338        }
1339        switcher.updateShiftState();
1340        if (LatinIME.PERF_DEBUG) measureCps();
1341        TextEntryState.typedCharacter((char) code, isWordSeparator(code));
1342    }
1343
1344    private void handleSeparator(int primaryCode) {
1345        mVoiceConnector.handleSeparator();
1346
1347        // Should dismiss the "Touch again to save" message when handling separator
1348        if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) {
1349            mHandler.postUpdateSuggestions();
1350        }
1351
1352        boolean pickedDefault = false;
1353        // Handle separator
1354        final InputConnection ic = getCurrentInputConnection();
1355        if (ic != null) {
1356            ic.beginBatchEdit();
1357            abortCorrection(false);
1358        }
1359        if (mHasValidSuggestions) {
1360            // In certain languages where single quote is a separator, it's better
1361            // not to auto correct, but accept the typed word. For instance,
1362            // in Italian dov' should not be expanded to dove' because the elision
1363            // requires the last vowel to be removed.
1364            if (mAutoCorrectOn && primaryCode != '\'' && !mJustReverted) {
1365                pickedDefault = pickDefaultSuggestion();
1366                // Picked the suggestion by the space key.  We consider this
1367                // as "added an auto space".
1368                if (primaryCode == Keyboard.CODE_SPACE) {
1369                    mJustAddedAutoSpace = true;
1370                }
1371            } else {
1372                commitTyped(ic);
1373            }
1374        }
1375        if (mJustAddedAutoSpace && primaryCode == Keyboard.CODE_ENTER) {
1376            removeTrailingSpace();
1377            mJustAddedAutoSpace = false;
1378        }
1379        sendKeyChar((char)primaryCode);
1380
1381        // Handle the case of ". ." -> " .." with auto-space if necessary
1382        // before changing the TextEntryState.
1383        if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED
1384                && primaryCode == Keyboard.CODE_PERIOD) {
1385            reswapPeriodAndSpace();
1386        }
1387
1388        TextEntryState.typedCharacter((char) primaryCode, true);
1389        if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED
1390                && primaryCode != Keyboard.CODE_ENTER) {
1391            swapPunctuationAndSpace();
1392        } else if (isSuggestionsRequested() && primaryCode == Keyboard.CODE_SPACE) {
1393            doubleSpace();
1394        }
1395        if (pickedDefault) {
1396            CharSequence typedWord = mWord.getTypedWord();
1397            TextEntryState.backToAcceptedDefault(typedWord);
1398            if (!TextUtils.isEmpty(typedWord) && !typedWord.equals(mBestWord)) {
1399                if (ic != null) {
1400                    CorrectionInfo correctionInfo = new CorrectionInfo(
1401                            mLastSelectionEnd - typedWord.length(), typedWord, mBestWord);
1402                    ic.commitCorrection(correctionInfo);
1403                }
1404                if (mCandidateView != null)
1405                    mCandidateView.onAutoCorrectionInverted(mBestWord);
1406            }
1407            setPunctuationSuggestions();
1408        }
1409        mKeyboardSwitcher.updateShiftState();
1410        if (ic != null) {
1411            ic.endBatchEdit();
1412        }
1413    }
1414
1415    private void handleClose() {
1416        commitTyped(getCurrentInputConnection());
1417        mVoiceConnector.handleClose();
1418        requestHideSelf(0);
1419        LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
1420        if (inputView != null)
1421            inputView.closing();
1422        TextEntryState.endSession();
1423    }
1424
1425    private void saveWordInHistory(CharSequence result) {
1426        if (mWord.size() <= 1) {
1427            mWord.reset();
1428            return;
1429        }
1430        // Skip if result is null. It happens in some edge case.
1431        if (TextUtils.isEmpty(result)) {
1432            return;
1433        }
1434
1435        // Make a copy of the CharSequence, since it is/could be a mutable CharSequence
1436        final String resultCopy = result.toString();
1437        TypedWordAlternatives entry = new TypedWordAlternatives(resultCopy,
1438                new WordComposer(mWord));
1439        mWordHistory.add(entry);
1440    }
1441
1442    private boolean isSuggestionsRequested() {
1443        return mIsSettingsSuggestionStripOn
1444                && (mCorrectionMode > 0 || isShowingSuggestionsStrip());
1445    }
1446
1447    private boolean isShowingPunctuationList() {
1448        return mSuggestPuncList == mCandidateView.getSuggestions();
1449    }
1450
1451    private boolean isShowingSuggestionsStrip() {
1452        return (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_VALUE)
1453                || (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE
1454                        && mOrientation == Configuration.ORIENTATION_PORTRAIT);
1455    }
1456
1457    private boolean isCandidateStripVisible() {
1458        if (mCandidateView == null)
1459            return false;
1460        if (mCandidateView.isShowingAddToDictionaryHint() || TextEntryState.isCorrecting())
1461            return true;
1462        if (!isShowingSuggestionsStrip())
1463            return false;
1464        if (mApplicationSpecifiedCompletionOn)
1465            return true;
1466        return isSuggestionsRequested();
1467    }
1468
1469    public void switchToKeyboardView() {
1470        mHandler.post(new Runnable() {
1471            @Override
1472            public void run() {
1473                if (DEBUG) {
1474                    Log.d(TAG, "Switch to keyboard view.");
1475                }
1476                View v = mKeyboardSwitcher.getInputView();
1477                if (v != null) {
1478                    // Confirms that the keyboard view doesn't have parent view.
1479                    ViewParent p = v.getParent();
1480                    if (p != null && p instanceof ViewGroup) {
1481                        ((ViewGroup) p).removeView(v);
1482                    }
1483                    setInputView(v);
1484                }
1485                setCandidatesViewShown(isCandidateStripVisible());
1486                updateInputViewShown();
1487                mHandler.postUpdateSuggestions();
1488            }
1489        });
1490    }
1491
1492    public void clearSuggestions() {
1493        setSuggestions(SuggestedWords.EMPTY);
1494    }
1495
1496    public void setSuggestions(SuggestedWords words) {
1497        if (mVoiceConnector.getAndResetIsShowingHint()) {
1498             setCandidatesView(mCandidateViewContainer);
1499        }
1500
1501        if (mCandidateView != null) {
1502            mCandidateView.setSuggestions(words);
1503            if (mCandidateView.isConfigCandidateHighlightFontColorEnabled()) {
1504                mKeyboardSwitcher.onAutoCorrectionStateChanged(
1505                        words.hasWordAboveAutoCorrectionScoreThreshold());
1506            }
1507        }
1508    }
1509
1510    public void updateSuggestions() {
1511        mKeyboardSwitcher.setPreferredLetters(null);
1512
1513        // Check if we have a suggestion engine attached.
1514        if ((mSuggest == null || !isSuggestionsRequested())
1515                && !mVoiceConnector.isVoiceInputHighlighted()) {
1516            return;
1517        }
1518
1519        if (!mHasValidSuggestions) {
1520            setPunctuationSuggestions();
1521            return;
1522        }
1523        showSuggestions(mWord);
1524    }
1525
1526    private SuggestedWords.Builder getTypedSuggestions(WordComposer word) {
1527        return mSuggest.getSuggestedWordBuilder(mKeyboardSwitcher.getInputView(), word, null);
1528    }
1529
1530    private void showCorrections(WordAlternatives alternatives) {
1531        mKeyboardSwitcher.setPreferredLetters(null);
1532        SuggestedWords.Builder builder = alternatives.getAlternatives();
1533        builder.setTypedWordValid(false).setHasMinimalSuggestion(false);
1534        showSuggestions(builder.build(), alternatives.getOriginalWord());
1535    }
1536
1537    private void showSuggestions(WordComposer word) {
1538        // TODO Maybe need better way of retrieving previous word
1539        CharSequence prevWord = EditingUtils.getPreviousWord(getCurrentInputConnection(),
1540                mWordSeparators);
1541        SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(
1542                mKeyboardSwitcher.getInputView(), word, prevWord);
1543
1544        int[] nextLettersFrequencies = mSuggest.getNextLettersFrequencies();
1545        mKeyboardSwitcher.setPreferredLetters(nextLettersFrequencies);
1546
1547        boolean correctionAvailable = !mInputTypeNoAutoCorrect && !mJustReverted
1548                && mSuggest.hasAutoCorrection();
1549        final CharSequence typedWord = word.getTypedWord();
1550        // If we're in basic correct
1551        final boolean typedWordValid = mSuggest.isValidWord(typedWord) ||
1552                (preferCapitalization()
1553                        && mSuggest.isValidWord(typedWord.toString().toLowerCase()));
1554        if (mCorrectionMode == Suggest.CORRECTION_FULL
1555                || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) {
1556            correctionAvailable |= typedWordValid;
1557        }
1558        // Don't auto-correct words with multiple capital letter
1559        correctionAvailable &= !word.isMostlyCaps();
1560        correctionAvailable &= !TextEntryState.isCorrecting();
1561
1562        // Basically, we update the suggestion strip only when suggestion count > 1.  However,
1563        // there is an exception: We update the suggestion strip whenever typed word's length
1564        // is 1 or typed word is found in dictionary, regardless of suggestion count.  Actually,
1565        // in most cases, suggestion count is 1 when typed word's length is 1, but we do always
1566        // need to clear the previous state when the user starts typing a word (i.e. typed word's
1567        // length == 1).
1568        if (builder.size() > 1 || typedWord.length() == 1 || typedWordValid
1569                || mCandidateView.isShowingAddToDictionaryHint()) {
1570            builder.setTypedWordValid(typedWordValid).setHasMinimalSuggestion(correctionAvailable);
1571        } else {
1572            final SuggestedWords previousSuggestions = mCandidateView.getSuggestions();
1573            if (previousSuggestions == mSuggestPuncList)
1574                return;
1575            builder.addTypedWordAndPreviousSuggestions(typedWord, previousSuggestions);
1576        }
1577        showSuggestions(builder.build(), typedWord);
1578    }
1579
1580    private void showSuggestions(SuggestedWords suggestedWords, CharSequence typedWord) {
1581        setSuggestions(suggestedWords);
1582        if (suggestedWords.size() > 0) {
1583            if (Utils.shouldBlockedBySafetyNetForAutoCorrection(suggestedWords, mSuggest)) {
1584                mBestWord = typedWord;
1585            } else if (suggestedWords.hasAutoCorrectionWord()) {
1586                mBestWord = suggestedWords.getWord(1);
1587            } else {
1588                mBestWord = typedWord;
1589            }
1590        } else {
1591            mBestWord = null;
1592        }
1593        setCandidatesViewShown(isCandidateStripVisible());
1594    }
1595
1596    private boolean pickDefaultSuggestion() {
1597        // Complete any pending candidate query first
1598        if (mHandler.hasPendingUpdateSuggestions()) {
1599            mHandler.cancelUpdateSuggestions();
1600            updateSuggestions();
1601        }
1602        if (mBestWord != null && mBestWord.length() > 0) {
1603            TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord);
1604            mJustAccepted = true;
1605            pickSuggestion(mBestWord);
1606            // Add the word to the auto dictionary if it's not a known word
1607            addToDictionaries(mBestWord, AutoDictionary.FREQUENCY_FOR_TYPED);
1608            return true;
1609
1610        }
1611        return false;
1612    }
1613
1614    public void pickSuggestionManually(int index, CharSequence suggestion) {
1615        SuggestedWords suggestions = mCandidateView.getSuggestions();
1616        mVoiceConnector.flushAndLogAllTextModificationCounters(index, suggestion, mWordSeparators);
1617
1618        final boolean correcting = TextEntryState.isCorrecting();
1619        InputConnection ic = getCurrentInputConnection();
1620        if (ic != null) {
1621            ic.beginBatchEdit();
1622        }
1623        if (mApplicationSpecifiedCompletionOn && mApplicationSpecifiedCompletions != null
1624                && index >= 0 && index < mApplicationSpecifiedCompletions.length) {
1625            CompletionInfo ci = mApplicationSpecifiedCompletions[index];
1626            if (ic != null) {
1627                ic.commitCompletion(ci);
1628            }
1629            mCommittedLength = suggestion.length();
1630            if (mCandidateView != null) {
1631                mCandidateView.clear();
1632            }
1633            mKeyboardSwitcher.updateShiftState();
1634            if (ic != null) {
1635                ic.endBatchEdit();
1636            }
1637            return;
1638        }
1639
1640        // If this is a punctuation, apply it through the normal key press
1641        if (suggestion.length() == 1 && (isWordSeparator(suggestion.charAt(0))
1642                || isSuggestedPunctuation(suggestion.charAt(0)))) {
1643            // Word separators are suggested before the user inputs something.
1644            // So, LatinImeLogger logs "" as a user's input.
1645            LatinImeLogger.logOnManualSuggestion(
1646                    "", suggestion.toString(), index, suggestions.mWords);
1647            final char primaryCode = suggestion.charAt(0);
1648            onCodeInput(primaryCode, new int[] { primaryCode },
1649                    KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
1650                    KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
1651            if (ic != null) {
1652                ic.endBatchEdit();
1653            }
1654            return;
1655        }
1656        mJustAccepted = true;
1657        pickSuggestion(suggestion);
1658        // Add the word to the auto dictionary if it's not a known word
1659        if (index == 0) {
1660            addToDictionaries(suggestion, AutoDictionary.FREQUENCY_FOR_PICKED);
1661        } else {
1662            addToBigramDictionary(suggestion, 1);
1663        }
1664        LatinImeLogger.logOnManualSuggestion(mComposing.toString(), suggestion.toString(),
1665                index, suggestions.mWords);
1666        TextEntryState.acceptedSuggestion(mComposing.toString(), suggestion);
1667        // Follow it with a space
1668        if (mAutoSpace && !correcting) {
1669            sendSpace();
1670            mJustAddedAutoSpace = true;
1671        }
1672
1673        final boolean showingAddToDictionaryHint = index == 0 && mCorrectionMode > 0
1674                && !mSuggest.isValidWord(suggestion)
1675                && !mSuggest.isValidWord(suggestion.toString().toLowerCase());
1676
1677        if (!correcting) {
1678            // Fool the state watcher so that a subsequent backspace will not do a revert, unless
1679            // we just did a correction, in which case we need to stay in
1680            // TextEntryState.State.PICKED_SUGGESTION state.
1681            TextEntryState.typedCharacter((char) Keyboard.CODE_SPACE, true);
1682            setPunctuationSuggestions();
1683        } else if (!showingAddToDictionaryHint) {
1684            // If we're not showing the "Touch again to save", then show corrections again.
1685            // In case the cursor position doesn't change, make sure we show the suggestions again.
1686            clearSuggestions();
1687            mHandler.postUpdateOldSuggestions();
1688        }
1689        if (showingAddToDictionaryHint) {
1690            mCandidateView.showAddToDictionaryHint(suggestion);
1691        }
1692        if (ic != null) {
1693            ic.endBatchEdit();
1694        }
1695    }
1696
1697    /**
1698     * Commits the chosen word to the text field and saves it for later
1699     * retrieval.
1700     * @param suggestion the suggestion picked by the user to be committed to
1701     *            the text field
1702     */
1703    private void pickSuggestion(CharSequence suggestion) {
1704        KeyboardSwitcher switcher = mKeyboardSwitcher;
1705        if (!switcher.isKeyboardAvailable())
1706            return;
1707        InputConnection ic = getCurrentInputConnection();
1708        if (ic != null) {
1709            mVoiceConnector.rememberReplacedWord(suggestion, mWordSeparators);
1710            ic.commitText(suggestion, 1);
1711        }
1712        saveWordInHistory(suggestion);
1713        mHasValidSuggestions = false;
1714        mCommittedLength = suggestion.length();
1715        switcher.setPreferredLetters(null);
1716    }
1717
1718    /**
1719     * Tries to apply any typed alternatives for the word if we have any cached alternatives,
1720     * otherwise tries to find new corrections and completions for the word.
1721     * @param touching The word that the cursor is touching, with position information
1722     * @return true if an alternative was found, false otherwise.
1723     */
1724    private boolean applyTypedAlternatives(EditingUtils.SelectedWord touching) {
1725        // If we didn't find a match, search for result in typed word history
1726        WordComposer foundWord = null;
1727        WordAlternatives alternatives = null;
1728        for (WordAlternatives entry : mWordHistory) {
1729            if (TextUtils.equals(entry.getChosenWord(), touching.mWord)) {
1730                if (entry instanceof TypedWordAlternatives) {
1731                    foundWord = ((TypedWordAlternatives) entry).word;
1732                }
1733                alternatives = entry;
1734                break;
1735            }
1736        }
1737        // If we didn't find a match, at least suggest corrections.
1738        if (foundWord == null
1739                && (mSuggest.isValidWord(touching.mWord)
1740                        || mSuggest.isValidWord(touching.mWord.toString().toLowerCase()))) {
1741            foundWord = new WordComposer();
1742            for (int i = 0; i < touching.mWord.length(); i++) {
1743                foundWord.add(touching.mWord.charAt(i), new int[] {
1744                    touching.mWord.charAt(i)
1745                });
1746            }
1747            foundWord.setFirstCharCapitalized(Character.isUpperCase(touching.mWord.charAt(0)));
1748        }
1749        // Found a match, show suggestions
1750        if (foundWord != null || alternatives != null) {
1751            if (alternatives == null) {
1752                alternatives = new TypedWordAlternatives(touching.mWord, foundWord);
1753            }
1754            showCorrections(alternatives);
1755            if (foundWord != null) {
1756                mWord = new WordComposer(foundWord);
1757            } else {
1758                mWord.reset();
1759            }
1760            return true;
1761        }
1762        return false;
1763    }
1764
1765    private void setOldSuggestions() {
1766        mVoiceConnector.setShowingVoiceSuggestions(false);
1767        if (mCandidateView != null && mCandidateView.isShowingAddToDictionaryHint()) {
1768            return;
1769        }
1770        InputConnection ic = getCurrentInputConnection();
1771        if (ic == null) return;
1772        if (!mHasValidSuggestions) {
1773            // Extract the selected or touching text
1774            EditingUtils.SelectedWord touching = EditingUtils.getWordAtCursorOrSelection(ic,
1775                    mLastSelectionStart, mLastSelectionEnd, mWordSeparators);
1776
1777            if (touching != null && touching.mWord.length() > 1) {
1778                ic.beginBatchEdit();
1779
1780                if (!mVoiceConnector.applyVoiceAlternatives(touching)
1781                        && !applyTypedAlternatives(touching)) {
1782                    abortCorrection(true);
1783                } else {
1784                    TextEntryState.selectedForCorrection();
1785                    EditingUtils.underlineWord(ic, touching);
1786                }
1787
1788                ic.endBatchEdit();
1789            } else {
1790                abortCorrection(true);
1791                setPunctuationSuggestions();  // Show the punctuation suggestions list
1792            }
1793        } else {
1794            abortCorrection(true);
1795        }
1796    }
1797
1798    private void setPunctuationSuggestions() {
1799        setSuggestions(mSuggestPuncList);
1800        setCandidatesViewShown(isCandidateStripVisible());
1801    }
1802
1803    private void addToDictionaries(CharSequence suggestion, int frequencyDelta) {
1804        checkAddToDictionary(suggestion, frequencyDelta, false);
1805    }
1806
1807    private void addToBigramDictionary(CharSequence suggestion, int frequencyDelta) {
1808        checkAddToDictionary(suggestion, frequencyDelta, true);
1809    }
1810
1811    /**
1812     * Adds to the UserBigramDictionary and/or AutoDictionary
1813     * @param addToBigramDictionary true if it should be added to bigram dictionary if possible
1814     */
1815    private void checkAddToDictionary(CharSequence suggestion, int frequencyDelta,
1816            boolean addToBigramDictionary) {
1817        if (suggestion == null || suggestion.length() < 1) return;
1818        // Only auto-add to dictionary if auto-correct is ON. Otherwise we'll be
1819        // adding words in situations where the user or application really didn't
1820        // want corrections enabled or learned.
1821        if (!(mCorrectionMode == Suggest.CORRECTION_FULL
1822                || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM)) {
1823            return;
1824        }
1825        if (!addToBigramDictionary && mAutoDictionary.isValidWord(suggestion)
1826                || (!mSuggest.isValidWord(suggestion.toString())
1827                        && !mSuggest.isValidWord(suggestion.toString().toLowerCase()))) {
1828            mAutoDictionary.addWord(suggestion.toString(), frequencyDelta);
1829        }
1830
1831        if (mUserBigramDictionary != null) {
1832            CharSequence prevWord = EditingUtils.getPreviousWord(getCurrentInputConnection(),
1833                    mSentenceSeparators);
1834            if (!TextUtils.isEmpty(prevWord)) {
1835                mUserBigramDictionary.addBigrams(prevWord.toString(), suggestion.toString());
1836            }
1837        }
1838    }
1839
1840    private boolean isCursorTouchingWord() {
1841        InputConnection ic = getCurrentInputConnection();
1842        if (ic == null) return false;
1843        CharSequence toLeft = ic.getTextBeforeCursor(1, 0);
1844        CharSequence toRight = ic.getTextAfterCursor(1, 0);
1845        if (!TextUtils.isEmpty(toLeft)
1846                && !isWordSeparator(toLeft.charAt(0))
1847                && !isSuggestedPunctuation(toLeft.charAt(0))) {
1848            return true;
1849        }
1850        if (!TextUtils.isEmpty(toRight)
1851                && !isWordSeparator(toRight.charAt(0))
1852                && !isSuggestedPunctuation(toRight.charAt(0))) {
1853            return true;
1854        }
1855        return false;
1856    }
1857
1858    private boolean sameAsTextBeforeCursor(InputConnection ic, CharSequence text) {
1859        CharSequence beforeText = ic.getTextBeforeCursor(text.length(), 0);
1860        return TextUtils.equals(text, beforeText);
1861    }
1862
1863    public void revertLastWord(boolean deleteChar) {
1864        final int length = mComposing.length();
1865        if (!mHasValidSuggestions && length > 0) {
1866            final InputConnection ic = getCurrentInputConnection();
1867            mJustReverted = true;
1868            final CharSequence punctuation = ic.getTextBeforeCursor(1, 0);
1869            if (deleteChar) ic.deleteSurroundingText(1, 0);
1870            int toDelete = mCommittedLength;
1871            final CharSequence toTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0);
1872            if (!TextUtils.isEmpty(toTheLeft) && isWordSeparator(toTheLeft.charAt(0))) {
1873                toDelete--;
1874            }
1875            ic.deleteSurroundingText(toDelete, 0);
1876            // Re-insert punctuation only when the deleted character was word separator and the
1877            // composing text wasn't equal to the auto-corrected text.
1878            if (deleteChar
1879                    && !TextUtils.isEmpty(punctuation) && isWordSeparator(punctuation.charAt(0))
1880                    && !TextUtils.equals(mComposing, toTheLeft)) {
1881                ic.commitText(mComposing, 1);
1882                TextEntryState.acceptedTyped(mComposing);
1883                ic.commitText(punctuation, 1);
1884                TextEntryState.typedCharacter(punctuation.charAt(0), true);
1885                // Clear composing text
1886                mComposing.setLength(0);
1887            } else {
1888                mHasValidSuggestions = true;
1889                ic.setComposingText(mComposing, 1);
1890                TextEntryState.backspace();
1891            }
1892            mHandler.postUpdateSuggestions();
1893        } else {
1894            sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
1895            mJustReverted = false;
1896        }
1897    }
1898
1899    protected String getWordSeparators() {
1900        return mWordSeparators;
1901    }
1902
1903    public boolean isWordSeparator(int code) {
1904        String separators = getWordSeparators();
1905        return separators.contains(String.valueOf((char)code));
1906    }
1907
1908    private boolean isSentenceSeparator(int code) {
1909        return mSentenceSeparators.contains(String.valueOf((char)code));
1910    }
1911
1912    private void sendSpace() {
1913        sendKeyChar((char)Keyboard.CODE_SPACE);
1914        mKeyboardSwitcher.updateShiftState();
1915    }
1916
1917    public boolean preferCapitalization() {
1918        return mWord.isFirstCharCapitalized();
1919    }
1920
1921    // Notify that language or mode have been changed and toggleLanguage will update KeyboaredID
1922    // according to new language or mode.
1923    public void onRefreshKeyboard() {
1924        toggleLanguage(true, true);
1925    }
1926
1927    // "reset" and "next" are used only for USE_SPACEBAR_LANGUAGE_SWITCHER.
1928    private void toggleLanguage(boolean reset, boolean next) {
1929        if (mSubtypeSwitcher.useSpacebarLanguageSwitcher()) {
1930            mSubtypeSwitcher.toggleLanguage(reset, next);
1931        }
1932        // Reload keyboard because the current language has been changed.
1933        KeyboardSwitcher switcher = mKeyboardSwitcher;
1934        final EditorInfo attribute = getCurrentInputEditorInfo();
1935        final int mode = initializeInputAttributesAndGetMode((attribute != null)
1936                ? attribute.inputType : 0);
1937        final int imeOptions = (attribute != null) ? attribute.imeOptions : 0;
1938        switcher.loadKeyboard(mode, imeOptions, mVoiceConnector.isVoiceButtonEnabled(),
1939                mVoiceConnector.isVoiceButtonOnPrimary());
1940        initSuggest();
1941        switcher.updateShiftState();
1942    }
1943
1944    @Override
1945    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
1946            String key) {
1947        mSubtypeSwitcher.onSharedPreferenceChanged(sharedPreferences, key);
1948        if (Settings.PREF_SELECTED_LANGUAGES.equals(key)) {
1949            mRefreshKeyboardRequired = true;
1950        } else if (Settings.PREF_RECORRECTION_ENABLED.equals(key)) {
1951            mReCorrectionEnabled = sharedPreferences.getBoolean(
1952                    Settings.PREF_RECORRECTION_ENABLED,
1953                    mResources.getBoolean(R.bool.default_recorrection_enabled));
1954        }
1955    }
1956
1957    @Override
1958    public void onSwipeDown() {
1959        if (mConfigSwipeDownDismissKeyboardEnabled)
1960            handleClose();
1961    }
1962
1963    @Override
1964    public void onPress(int primaryCode) {
1965        if (mKeyboardSwitcher.isVibrateAndSoundFeedbackRequired()) {
1966            vibrate();
1967            playKeyClick(primaryCode);
1968        }
1969        KeyboardSwitcher switcher = mKeyboardSwitcher;
1970        final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
1971        if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) {
1972            switcher.onPressShift();
1973        } else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
1974            switcher.onPressSymbol();
1975        } else {
1976            switcher.onOtherKeyPressed();
1977        }
1978    }
1979
1980    @Override
1981    public void onRelease(int primaryCode) {
1982        KeyboardSwitcher switcher = mKeyboardSwitcher;
1983        // Reset any drag flags in the keyboard
1984        switcher.keyReleased();
1985        final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
1986        if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) {
1987            switcher.onReleaseShift();
1988        } else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
1989            switcher.onReleaseSymbol();
1990        }
1991    }
1992
1993
1994    // receive ringer mode change and network state change.
1995    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
1996        @Override
1997        public void onReceive(Context context, Intent intent) {
1998            final String action = intent.getAction();
1999            if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
2000                updateRingerMode();
2001            } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
2002                mSubtypeSwitcher.onNetworkStateChanged(intent);
2003            }
2004        }
2005    };
2006
2007    // update flags for silent mode
2008    private void updateRingerMode() {
2009        if (mAudioManager == null) {
2010            mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
2011        }
2012        if (mAudioManager != null) {
2013            mSilentMode = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL);
2014        }
2015    }
2016
2017    private void playKeyClick(int primaryCode) {
2018        // if mAudioManager is null, we don't have the ringer state yet
2019        // mAudioManager will be set by updateRingerMode
2020        if (mAudioManager == null) {
2021            if (mKeyboardSwitcher.getInputView() != null) {
2022                updateRingerMode();
2023            }
2024        }
2025        if (mSoundOn && !mSilentMode) {
2026            // FIXME: Volume and enable should come from UI settings
2027            // FIXME: These should be triggered after auto-repeat logic
2028            int sound = AudioManager.FX_KEYPRESS_STANDARD;
2029            switch (primaryCode) {
2030                case Keyboard.CODE_DELETE:
2031                    sound = AudioManager.FX_KEYPRESS_DELETE;
2032                    break;
2033                case Keyboard.CODE_ENTER:
2034                    sound = AudioManager.FX_KEYPRESS_RETURN;
2035                    break;
2036                case Keyboard.CODE_SPACE:
2037                    sound = AudioManager.FX_KEYPRESS_SPACEBAR;
2038                    break;
2039            }
2040            mAudioManager.playSoundEffect(sound, FX_VOLUME);
2041        }
2042    }
2043
2044    public void vibrate() {
2045        if (!mVibrateOn) {
2046            return;
2047        }
2048        LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
2049        if (inputView != null) {
2050            inputView.performHapticFeedback(
2051                    HapticFeedbackConstants.KEYBOARD_TAP,
2052                    HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
2053        }
2054    }
2055
2056    public void promoteToUserDictionary(String word, int frequency) {
2057        if (mUserDictionary.isValidWord(word)) return;
2058        mUserDictionary.addWord(word, frequency);
2059    }
2060
2061    public WordComposer getCurrentWord() {
2062        return mWord;
2063    }
2064
2065    public boolean getPopupOn() {
2066        return mPopupOn;
2067    }
2068
2069    private void updateCorrectionMode() {
2070        mHasDictionary = mSuggest != null ? mSuggest.hasMainDictionary() : false;
2071        mAutoCorrectOn = (mAutoCorrectEnabled || mQuickFixes)
2072                && !mInputTypeNoAutoCorrect && mHasDictionary;
2073        mCorrectionMode = (mAutoCorrectOn && mAutoCorrectEnabled)
2074                ? Suggest.CORRECTION_FULL
2075                : (mAutoCorrectOn ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE);
2076        mCorrectionMode = (mBigramSuggestionEnabled && mAutoCorrectOn && mAutoCorrectEnabled)
2077                ? Suggest.CORRECTION_FULL_BIGRAM : mCorrectionMode;
2078        if (mSuggest != null) {
2079            mSuggest.setCorrectionMode(mCorrectionMode);
2080        }
2081    }
2082
2083    private void updateAutoTextEnabled() {
2084        if (mSuggest == null) return;
2085        mSuggest.setAutoTextEnabled(mQuickFixes
2086                && SubtypeSwitcher.getInstance().isSystemLanguageSameAsInputLanguage());
2087    }
2088
2089    private void updateSuggestionVisibility(SharedPreferences prefs) {
2090        final Resources res = mResources;
2091        final String suggestionVisiblityStr = prefs.getString(
2092                Settings.PREF_SHOW_SUGGESTIONS_SETTING,
2093                res.getString(R.string.prefs_suggestion_visibility_default_value));
2094        for (int visibility : SUGGESTION_VISIBILITY_VALUE_ARRAY) {
2095            if (suggestionVisiblityStr.equals(res.getString(visibility))) {
2096                mSuggestionVisibility = visibility;
2097                break;
2098            }
2099        }
2100    }
2101
2102    protected void launchSettings() {
2103        launchSettings(Settings.class);
2104    }
2105
2106    public void launchDebugSettings() {
2107        launchSettings(DebugSettings.class);
2108    }
2109
2110    protected void launchSettings(Class<? extends PreferenceActivity> settingsClass) {
2111        handleClose();
2112        Intent intent = new Intent();
2113        intent.setClass(LatinIME.this, settingsClass);
2114        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2115        startActivity(intent);
2116    }
2117
2118    private void loadSettings(EditorInfo attribute) {
2119        // Get the settings preferences
2120        final SharedPreferences prefs = mPrefs;
2121        Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
2122        mVibrateOn = vibrator != null && vibrator.hasVibrator()
2123                && prefs.getBoolean(Settings.PREF_VIBRATE_ON, false);
2124        mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON, false);
2125        mPopupOn = prefs.getBoolean(Settings.PREF_POPUP_ON,
2126                mResources.getBoolean(R.bool.config_default_popup_preview));
2127        mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true);
2128        mQuickFixes = isQuickFixesEnabled(prefs);
2129
2130        mAutoCorrectEnabled = isAutoCorrectEnabled(prefs);
2131        mBigramSuggestionEnabled = mAutoCorrectEnabled && isBigramSuggestionEnabled(prefs);
2132        loadAndSetAutoCorrectionThreshold(prefs);
2133
2134        mVoiceConnector.loadSettings(attribute, prefs);
2135
2136        updateCorrectionMode();
2137        updateAutoTextEnabled();
2138        updateSuggestionVisibility(prefs);
2139        SubtypeSwitcher.getInstance().loadSettings();
2140    }
2141
2142    /**
2143     *  Load Auto correction threshold from SharedPreferences, and modify mSuggest's threshold.
2144     */
2145    private void loadAndSetAutoCorrectionThreshold(SharedPreferences sp) {
2146        // When mSuggest is not initialized, cannnot modify mSuggest's threshold.
2147        if (mSuggest == null) return;
2148        // When auto correction setting is turned off, the threshold is ignored.
2149        if (!isAutoCorrectEnabled(sp)) return;
2150
2151        final String currentAutoCorrectionSetting = sp.getString(
2152                Settings.PREF_AUTO_CORRECTION_THRESHOLD,
2153                mResources.getString(R.string.auto_correction_threshold_mode_index_modest));
2154        final String[] autoCorrectionThresholdValues = mResources.getStringArray(
2155                R.array.auto_correction_threshold_values);
2156        // When autoCrrectionThreshold is greater than 1.0, auto correction is virtually turned off.
2157        double autoCorrectionThreshold = Double.MAX_VALUE;
2158        try {
2159            final int arrayIndex = Integer.valueOf(currentAutoCorrectionSetting);
2160            if (arrayIndex >= 0 && arrayIndex < autoCorrectionThresholdValues.length) {
2161                autoCorrectionThreshold = Double.parseDouble(
2162                        autoCorrectionThresholdValues[arrayIndex]);
2163            }
2164        } catch (NumberFormatException e) {
2165            // Whenever the threshold settings are correct, never come here.
2166            autoCorrectionThreshold = Double.MAX_VALUE;
2167            Log.w(TAG, "Cannot load auto correction threshold setting."
2168                    + " currentAutoCorrectionSetting: " + currentAutoCorrectionSetting
2169                    + ", autoCorrectionThresholdValues: "
2170                    + Arrays.toString(autoCorrectionThresholdValues));
2171        }
2172        // TODO: This should be refactored :
2173        //           setAutoCorrectionThreshold should be called outside of this method.
2174        mSuggest.setAutoCorrectionThreshold(autoCorrectionThreshold);
2175    }
2176
2177    private boolean isQuickFixesEnabled(SharedPreferences sp) {
2178        final boolean showQuickFixesOption = mResources.getBoolean(
2179                R.bool.config_enable_quick_fixes_option);
2180        if (!showQuickFixesOption) {
2181            return isAutoCorrectEnabled(sp);
2182        }
2183        return sp.getBoolean(Settings.PREF_QUICK_FIXES, mResources.getBoolean(
2184                R.bool.config_default_quick_fixes));
2185    }
2186
2187    private boolean isAutoCorrectEnabled(SharedPreferences sp) {
2188        final String currentAutoCorrectionSetting = sp.getString(
2189                Settings.PREF_AUTO_CORRECTION_THRESHOLD,
2190                mResources.getString(R.string.auto_correction_threshold_mode_index_modest));
2191        final String autoCorrectionOff = mResources.getString(
2192                R.string.auto_correction_threshold_mode_index_off);
2193        return !currentAutoCorrectionSetting.equals(autoCorrectionOff);
2194    }
2195
2196    private boolean isBigramSuggestionEnabled(SharedPreferences sp) {
2197        final boolean showBigramSuggestionsOption = mResources.getBoolean(
2198                R.bool.config_enable_bigram_suggestions_option);
2199        if (!showBigramSuggestionsOption) {
2200            return isAutoCorrectEnabled(sp);
2201        }
2202        return sp.getBoolean(Settings.PREF_BIGRAM_SUGGESTIONS, mResources.getBoolean(
2203                R.bool.config_default_bigram_suggestions));
2204    }
2205
2206    private void initSuggestPuncList() {
2207        if (mSuggestPuncs != null || mSuggestPuncList != null)
2208            return;
2209        SuggestedWords.Builder builder = new SuggestedWords.Builder();
2210        String puncs = mResources.getString(R.string.suggested_punctuations);
2211        if (puncs != null) {
2212            for (int i = 0; i < puncs.length(); i++) {
2213                builder.addWord(puncs.subSequence(i, i + 1));
2214            }
2215        }
2216        mSuggestPuncList = builder.build();
2217        mSuggestPuncs = puncs;
2218    }
2219
2220    private boolean isSuggestedPunctuation(int code) {
2221        return mSuggestPuncs.contains(String.valueOf((char)code));
2222    }
2223
2224    private void showSubtypeSelectorAndSettings() {
2225        final CharSequence title = getString(R.string.english_ime_input_options);
2226        final CharSequence[] items = new CharSequence[] {
2227                // TODO: Should use new string "Select active input modes".
2228                getString(R.string.language_selection_title),
2229                getString(R.string.english_ime_settings),
2230        };
2231        final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
2232            @Override
2233            public void onClick(DialogInterface di, int position) {
2234                di.dismiss();
2235                switch (position) {
2236                case 0:
2237                    Intent intent = new Intent(
2238                            android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
2239                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
2240                            | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
2241                            | Intent.FLAG_ACTIVITY_CLEAR_TOP);
2242                    intent.putExtra(android.provider.Settings.EXTRA_INPUT_METHOD_ID,
2243                            mInputMethodId);
2244                    startActivity(intent);
2245                    break;
2246                case 1:
2247                    launchSettings();
2248                    break;
2249                }
2250            }
2251        };
2252        showOptionsMenuInternal(title, items, listener);
2253    }
2254
2255    private void showOptionsMenu() {
2256        final CharSequence title = getString(R.string.english_ime_input_options);
2257        final CharSequence[] items = new CharSequence[] {
2258                getString(R.string.selectInputMethod),
2259                getString(R.string.english_ime_settings),
2260        };
2261        final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
2262            @Override
2263            public void onClick(DialogInterface di, int position) {
2264                di.dismiss();
2265                switch (position) {
2266                case 0:
2267                    launchSettings();
2268                    break;
2269                case 1:
2270                    mImm.showInputMethodPicker();
2271                    break;
2272                }
2273            }
2274        };
2275        showOptionsMenuInternal(title, items, listener);
2276    }
2277
2278    private void showOptionsMenuInternal(CharSequence title, CharSequence[] items,
2279            DialogInterface.OnClickListener listener) {
2280        AlertDialog.Builder builder = new AlertDialog.Builder(this);
2281        builder.setCancelable(true);
2282        builder.setIcon(R.drawable.ic_dialog_keyboard);
2283        builder.setNegativeButton(android.R.string.cancel, null);
2284        builder.setItems(items, listener);
2285        builder.setTitle(title);
2286        mOptionsDialog = builder.create();
2287        mOptionsDialog.setCanceledOnTouchOutside(true);
2288        Window window = mOptionsDialog.getWindow();
2289        WindowManager.LayoutParams lp = window.getAttributes();
2290        lp.token = mKeyboardSwitcher.getInputView().getWindowToken();
2291        lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
2292        window.setAttributes(lp);
2293        window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
2294        mOptionsDialog.show();
2295    }
2296
2297    @Override
2298    protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
2299        super.dump(fd, fout, args);
2300
2301        final Printer p = new PrintWriterPrinter(fout);
2302        p.println("LatinIME state :");
2303        p.println("  Keyboard mode = " + mKeyboardSwitcher.getKeyboardMode());
2304        p.println("  mComposing=" + mComposing.toString());
2305        p.println("  mIsSuggestionsRequested=" + mIsSettingsSuggestionStripOn);
2306        p.println("  mCorrectionMode=" + mCorrectionMode);
2307        p.println("  mHasValidSuggestions=" + mHasValidSuggestions);
2308        p.println("  mAutoCorrectOn=" + mAutoCorrectOn);
2309        p.println("  mAutoSpace=" + mAutoSpace);
2310        p.println("  mApplicationSpecifiedCompletionOn=" + mApplicationSpecifiedCompletionOn);
2311        p.println("  TextEntryState.state=" + TextEntryState.getState());
2312        p.println("  mSoundOn=" + mSoundOn);
2313        p.println("  mVibrateOn=" + mVibrateOn);
2314        p.println("  mPopupOn=" + mPopupOn);
2315    }
2316
2317    // Characters per second measurement
2318
2319    private long mLastCpsTime;
2320    private static final int CPS_BUFFER_SIZE = 16;
2321    private long[] mCpsIntervals = new long[CPS_BUFFER_SIZE];
2322    private int mCpsIndex;
2323
2324    private void measureCps() {
2325        long now = System.currentTimeMillis();
2326        if (mLastCpsTime == 0) mLastCpsTime = now - 100; // Initial
2327        mCpsIntervals[mCpsIndex] = now - mLastCpsTime;
2328        mLastCpsTime = now;
2329        mCpsIndex = (mCpsIndex + 1) % CPS_BUFFER_SIZE;
2330        long total = 0;
2331        for (int i = 0; i < CPS_BUFFER_SIZE; i++) total += mCpsIntervals[i];
2332        System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total));
2333    }
2334
2335    @Override
2336    public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) {
2337        SubtypeSwitcher.getInstance().updateSubtype(subtype);
2338    }
2339}
2340