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