LatinIME.java revision c1e2e827b5d1bba59eb99d322dc101657ec28768
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.latin.LatinIMEUtil.RingCharBuffer;
20import com.android.inputmethod.voice.FieldContext;
21import com.android.inputmethod.voice.SettingsUtil;
22import com.android.inputmethod.voice.VoiceInput;
23
24import org.xmlpull.v1.XmlPullParserException;
25
26import android.app.AlertDialog;
27import android.content.BroadcastReceiver;
28import android.content.Context;
29import android.content.DialogInterface;
30import android.content.Intent;
31import android.content.IntentFilter;
32import android.content.SharedPreferences;
33import android.content.res.Configuration;
34import android.content.res.Resources;
35import android.content.res.XmlResourceParser;
36import android.inputmethodservice.InputMethodService;
37import android.media.AudioManager;
38import android.os.Debug;
39import android.os.Handler;
40import android.os.Message;
41import android.os.SystemClock;
42import android.os.Vibrator;
43import android.preference.PreferenceActivity;
44import android.preference.PreferenceManager;
45import android.speech.SpeechRecognizer;
46import android.text.ClipboardManager;
47import android.text.TextUtils;
48import android.util.DisplayMetrics;
49import android.util.Log;
50import android.util.PrintWriterPrinter;
51import android.util.Printer;
52import android.view.HapticFeedbackConstants;
53import android.view.KeyEvent;
54import android.view.LayoutInflater;
55import android.view.View;
56import android.view.ViewGroup;
57import android.view.ViewParent;
58import android.view.Window;
59import android.view.WindowManager;
60import android.view.inputmethod.CompletionInfo;
61import android.view.inputmethod.EditorInfo;
62import android.view.inputmethod.ExtractedText;
63import android.view.inputmethod.ExtractedTextRequest;
64import android.view.inputmethod.InputConnection;
65import android.view.inputmethod.InputMethodManager;
66import android.widget.LinearLayout;
67
68import java.io.FileDescriptor;
69import java.io.IOException;
70import java.io.PrintWriter;
71import java.util.ArrayList;
72import java.util.Arrays;
73import java.util.Collections;
74import java.util.HashMap;
75import java.util.List;
76import java.util.Locale;
77import java.util.Map;
78
79/**
80 * Input method implementation for Qwerty'ish keyboard.
81 */
82public class LatinIME extends InputMethodService
83        implements BaseKeyboardView.OnKeyboardActionListener,
84        VoiceInput.UiListener,
85        SharedPreferences.OnSharedPreferenceChangeListener,
86        Tutorial.TutorialListener {
87    private static final String TAG = "LatinIME";
88    private static final boolean PERF_DEBUG = false;
89    static final boolean DEBUG = false;
90    static final boolean TRACE = false;
91    static final boolean VOICE_INSTALLED = true;
92    static final boolean ENABLE_VOICE_BUTTON = true;
93
94    private static final String PREF_SOUND_ON = "sound_on";
95    private static final String PREF_POPUP_ON = "popup_on";
96    private static final String PREF_AUTO_CAP = "auto_cap";
97    private static final String PREF_QUICK_FIXES = "quick_fixes";
98    private static final String PREF_SHOW_SUGGESTIONS_SETTING = "show_suggestions_setting";
99    private static final String PREF_AUTO_COMPLETION_THRESHOLD = "auto_completion_threshold";
100    private static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion";
101    private static final String PREF_VOICE_MODE = "voice_mode";
102
103    // Whether or not the user has used voice input before (and thus, whether to show the
104    // first-run warning dialog or not).
105    private static final String PREF_HAS_USED_VOICE_INPUT = "has_used_voice_input";
106
107    // Whether or not the user has used voice input from an unsupported locale UI before.
108    // For example, the user has a Chinese UI but activates voice input.
109    private static final String PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE =
110            "has_used_voice_input_unsupported_locale";
111
112    // A list of locales which are supported by default for voice input, unless we get a
113    // different list from Gservices.
114    public static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES =
115            "en " +
116            "en_US " +
117            "en_GB " +
118            "en_AU " +
119            "en_CA " +
120            "en_IE " +
121            "en_IN " +
122            "en_NZ " +
123            "en_SG " +
124            "en_ZA ";
125
126    // The private IME option used to indicate that no microphone should be shown for a
127    // given text field. For instance this is specified by the search dialog when the
128    // dialog is already showing a voice search button.
129    private static final String IME_OPTION_NO_MICROPHONE = "nm";
130
131    public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
132    public static final String PREF_INPUT_LANGUAGE = "input_language";
133    private static final String PREF_RECORRECTION_ENABLED = "recorrection_enabled";
134
135    private static final int DELAY_UPDATE_SUGGESTIONS = 180;
136    private static final int DELAY_UPDATE_OLD_SUGGESTIONS = 300;
137    private static final int DELAY_UPDATE_SHIFT_STATE = 300;
138    private static final int DELAY_START_TUTORIAL = 500;
139
140    // How many continuous deletes at which to start deleting at a higher speed.
141    private static final int DELETE_ACCELERATE_AT = 20;
142    // Key events coming any faster than this are long-presses.
143    private static final int QUICK_PRESS = 200;
144
145    public static final int KEYCODE_ENTER = '\n';
146    public static final int KEYCODE_TAB = '\t';
147    public static final int KEYCODE_SPACE = ' ';
148    public static final int KEYCODE_PERIOD = '.';
149
150    // Contextual menu positions
151    private static final int POS_METHOD = 0;
152    private static final int POS_SETTINGS = 1;
153
154    private int mSuggestionVisibility;
155    private static final int SUGGESTION_VISIBILILTY_SHOW_VALUE
156            = R.string.prefs_suggestion_visibility_show_value;
157    private static final int SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE
158            = R.string.prefs_suggestion_visibility_show_only_portrait_value;
159    private static final int SUGGESTION_VISIBILILTY_HIDE_VALUE
160            = R.string.prefs_suggestion_visibility_hide_value;
161
162    private static final int[] SUGGESTION_VISIBILITY_VALUE_ARRAY = new int[] {
163        SUGGESTION_VISIBILILTY_SHOW_VALUE,
164        SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE,
165        SUGGESTION_VISIBILILTY_HIDE_VALUE
166    };
167
168    private LinearLayout mCandidateViewContainer;
169    private CandidateView mCandidateView;
170    private Suggest mSuggest;
171    private CompletionInfo[] mCompletions;
172
173    private AlertDialog mOptionsDialog;
174    private AlertDialog mVoiceWarningDialog;
175
176    private KeyboardSwitcher mKeyboardSwitcher;
177
178    private UserDictionary mUserDictionary;
179    private UserBigramDictionary mUserBigramDictionary;
180    private ContactsDictionary mContactsDictionary;
181    private AutoDictionary mAutoDictionary;
182
183    private Hints mHints;
184
185    private Resources mResources;
186
187    private String mInputLocale;
188    private String mSystemLocale;
189    private LanguageSwitcher mLanguageSwitcher;
190
191    private final StringBuilder mComposing = new StringBuilder();
192    private WordComposer mWord = new WordComposer();
193    private int mCommittedLength;
194    private boolean mPredicting;
195    private boolean mRecognizing;
196    private boolean mAfterVoiceInput;
197    private boolean mImmediatelyAfterVoiceInput;
198    private boolean mShowingVoiceSuggestions;
199    private boolean mVoiceInputHighlighted;
200    private CharSequence mBestWord;
201    private boolean mPredictionOn;
202    private boolean mCompletionOn;
203    private boolean mHasDictionary;
204    private boolean mAutoSpace;
205    private boolean mJustAddedAutoSpace;
206    private boolean mAutoCorrectEnabled;
207    private boolean mReCorrectionEnabled;
208    private boolean mBigramSuggestionEnabled;
209    private boolean mAutoCorrectOn;
210    private boolean mPasswordText;
211    private boolean mVibrateOn;
212    private boolean mSoundOn;
213    private boolean mPopupOn;
214    private boolean mAutoCap;
215    private boolean mQuickFixes;
216    private boolean mHasUsedVoiceInput;
217    private boolean mHasUsedVoiceInputUnsupportedLocale;
218    private boolean mLocaleSupportedForVoiceInput;
219    private boolean mIsShowingHint;
220    private int     mCorrectionMode;
221    private boolean mVoiceButtonEnabled;
222    private boolean mVoiceButtonOnPrimary;
223    private int     mOrientation;
224    private List<CharSequence> mSuggestPuncList;
225    // Keep track of the last selection range to decide if we need to show word alternatives
226    private int     mLastSelectionStart;
227    private int     mLastSelectionEnd;
228
229    // Input type is such that we should not auto-correct
230    private boolean mInputTypeNoAutoCorrect;
231
232    // Indicates whether the suggestion strip is to be on in landscape
233    private boolean mJustAccepted;
234    private boolean mJustReverted;
235    private int mDeleteCount;
236    private long mLastKeyTime;
237
238    private Tutorial mTutorial;
239
240    private AudioManager mAudioManager;
241    // Align sound effect volume on music volume
242    private static final float FX_VOLUME = -1.0f;
243    private boolean mSilentMode;
244
245    /* package */ String mWordSeparators;
246    private String mSentenceSeparators;
247    private String mSuggestPuncs;
248    private VoiceInput mVoiceInput;
249    private final VoiceResults mVoiceResults = new VoiceResults();
250    private boolean mConfigurationChanging;
251
252    // Keeps track of most recently inserted text (multi-character key) for reverting
253    private CharSequence mEnteredText;
254    private boolean mRefreshKeyboardRequired;
255
256    // For each word, a list of potential replacements, usually from voice.
257    private final Map<String, List<CharSequence>> mWordToSuggestions =
258            new HashMap<String, List<CharSequence>>();
259
260    private final ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>();
261
262    private class VoiceResults {
263        List<String> candidates;
264        Map<String, List<CharSequence>> alternatives;
265    }
266
267    public abstract static class WordAlternatives {
268        protected CharSequence mChosenWord;
269
270        public WordAlternatives() {
271            // Nothing
272        }
273
274        public WordAlternatives(CharSequence chosenWord) {
275            mChosenWord = chosenWord;
276        }
277
278        @Override
279        public int hashCode() {
280            return mChosenWord.hashCode();
281        }
282
283        public abstract CharSequence getOriginalWord();
284
285        public CharSequence getChosenWord() {
286            return mChosenWord;
287        }
288
289        public abstract List<CharSequence> getAlternatives();
290    }
291
292    public class TypedWordAlternatives extends WordAlternatives {
293        private WordComposer word;
294
295        public TypedWordAlternatives() {
296            // Nothing
297        }
298
299        public TypedWordAlternatives(CharSequence chosenWord, WordComposer wordComposer) {
300            super(chosenWord);
301            word = wordComposer;
302        }
303
304        @Override
305        public CharSequence getOriginalWord() {
306            return word.getTypedWord();
307        }
308
309        @Override
310        public List<CharSequence> getAlternatives() {
311            return getTypedSuggestions(word);
312        }
313    }
314
315    /* package */ UIHandler mHandler = new UIHandler();
316
317    /* package */ class UIHandler extends Handler {
318        private static final int MSG_UPDATE_SUGGESTIONS = 0;
319        private static final int MSG_UPDATE_OLD_SUGGESTIONS = 1;
320        private static final int MSG_UPDATE_SHIFT_STATE = 2;
321        private static final int MSG_VOICE_RESULTS = 3;
322        private static final int MSG_START_TUTORIAL = 4;
323
324        @Override
325        public void handleMessage(Message msg) {
326            switch (msg.what) {
327            case MSG_UPDATE_SUGGESTIONS:
328                updateSuggestions();
329                break;
330            case MSG_UPDATE_OLD_SUGGESTIONS:
331                setOldSuggestions();
332                break;
333            case MSG_UPDATE_SHIFT_STATE:
334                updateShiftKeyState(getCurrentInputEditorInfo());
335                break;
336            case MSG_VOICE_RESULTS:
337                handleVoiceResults();
338                break;
339            case MSG_START_TUTORIAL:
340                if (mTutorial == null) {
341                    if (mKeyboardSwitcher.isInputViewShown()) {
342                        mTutorial = new Tutorial(LatinIME.this, mKeyboardSwitcher);
343                        mTutorial.start();
344                    } else {
345                        // Try again soon if the view is not yet showing
346                        sendMessageDelayed(obtainMessage(MSG_START_TUTORIAL), 100);
347                    }
348                }
349                break;
350            }
351        }
352
353        public void postUpdateSuggestions() {
354            removeMessages(MSG_UPDATE_SUGGESTIONS);
355            sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTIONS), DELAY_UPDATE_SUGGESTIONS);
356        }
357
358        public void cancelUpdateSuggestions() {
359            removeMessages(MSG_UPDATE_SUGGESTIONS);
360        }
361
362        public boolean hasPendingUpdateSuggestions() {
363            return hasMessages(MSG_UPDATE_SUGGESTIONS);
364        }
365
366        public void postUpdateOldSuggestions() {
367            removeMessages(MSG_UPDATE_OLD_SUGGESTIONS);
368            sendMessageDelayed(obtainMessage(MSG_UPDATE_OLD_SUGGESTIONS),
369                    DELAY_UPDATE_OLD_SUGGESTIONS);
370        }
371
372        public void cancelUpdateOldSuggestions() {
373            removeMessages(MSG_UPDATE_OLD_SUGGESTIONS);
374        }
375
376        public void postUpdateShiftKeyState() {
377            removeMessages(MSG_UPDATE_SHIFT_STATE);
378            sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), DELAY_UPDATE_SHIFT_STATE);
379        }
380
381        public void cancelUpdateShiftState() {
382            removeMessages(MSG_UPDATE_SHIFT_STATE);
383        }
384
385        public void updateVoiceResults() {
386            sendMessage(obtainMessage(MSG_VOICE_RESULTS));
387        }
388
389        public void startTutorial() {
390            sendMessageDelayed(obtainMessage(MSG_START_TUTORIAL), DELAY_START_TUTORIAL);
391        }
392    }
393
394    @Override
395    public void onCreate() {
396        LatinImeLogger.init(this);
397        super.onCreate();
398        //setStatusIcon(R.drawable.ime_qwerty);
399        mResources = getResources();
400        final Configuration conf = mResources.getConfiguration();
401        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
402        mLanguageSwitcher = new LanguageSwitcher(this);
403        mLanguageSwitcher.loadLocales(prefs);
404        mKeyboardSwitcher = new KeyboardSwitcher(this, mLanguageSwitcher);
405        mSystemLocale = conf.locale.toString();
406        mLanguageSwitcher.setSystemLocale(conf.locale);
407        String inputLanguage = mLanguageSwitcher.getInputLanguage();
408        if (inputLanguage == null) {
409            inputLanguage = conf.locale.toString();
410        }
411        mReCorrectionEnabled = prefs.getBoolean(PREF_RECORRECTION_ENABLED,
412                getResources().getBoolean(R.bool.default_recorrection_enabled));
413
414        LatinIMEUtil.GCUtils.getInstance().reset();
415        boolean tryGC = true;
416        for (int i = 0; i < LatinIMEUtil.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
417            try {
418                initSuggest(inputLanguage);
419                tryGC = false;
420            } catch (OutOfMemoryError e) {
421                tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait(inputLanguage, e);
422            }
423        }
424
425        mOrientation = conf.orientation;
426        initSuggestPuncList();
427
428        // register to receive ringer mode changes for silent mode
429        IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
430        registerReceiver(mReceiver, filter);
431        if (VOICE_INSTALLED) {
432            mVoiceInput = new VoiceInput(this, this);
433            mHints = new Hints(this, new Hints.Display() {
434                public void showHint(int viewResource) {
435                    LayoutInflater inflater = (LayoutInflater) getSystemService(
436                            Context.LAYOUT_INFLATER_SERVICE);
437                    View view = inflater.inflate(viewResource, null);
438                    setCandidatesView(view);
439                    setCandidatesViewShown(true);
440                    mIsShowingHint = true;
441                }
442              });
443        }
444        prefs.registerOnSharedPreferenceChangeListener(this);
445    }
446
447    /**
448     * Loads a dictionary or multiple separated dictionary
449     * @return returns array of dictionary resource ids
450     */
451    public static int[] getDictionary(Resources res) {
452        String packageName = LatinIME.class.getPackage().getName();
453        XmlResourceParser xrp = res.getXml(R.xml.dictionary);
454        ArrayList<Integer> dictionaries = new ArrayList<Integer>();
455
456        try {
457            int current = xrp.getEventType();
458            while (current != XmlResourceParser.END_DOCUMENT) {
459                if (current == XmlResourceParser.START_TAG) {
460                    String tag = xrp.getName();
461                    if (tag != null) {
462                        if (tag.equals("part")) {
463                            String dictFileName = xrp.getAttributeValue(null, "name");
464                            dictionaries.add(res.getIdentifier(dictFileName, "raw", packageName));
465                        }
466                    }
467                }
468                xrp.next();
469                current = xrp.getEventType();
470            }
471        } catch (XmlPullParserException e) {
472            Log.e(TAG, "Dictionary XML parsing failure");
473        } catch (IOException e) {
474            Log.e(TAG, "Dictionary XML IOException");
475        }
476
477        int count = dictionaries.size();
478        int[] dict = new int[count];
479        for (int i = 0; i < count; i++) {
480            dict[i] = dictionaries.get(i);
481        }
482
483        return dict;
484    }
485
486    private void initSuggest(String locale) {
487        mInputLocale = locale;
488
489        Resources orig = getResources();
490        Configuration conf = orig.getConfiguration();
491        Locale saveLocale = conf.locale;
492        conf.locale = new Locale(locale);
493        orig.updateConfiguration(conf, orig.getDisplayMetrics());
494        if (mSuggest != null) {
495            mSuggest.close();
496        }
497        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
498        mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true);
499
500        int[] dictionaries = getDictionary(orig);
501        mSuggest = new Suggest(this, dictionaries);
502        loadAndSetAutoCompletionThreshold(sp);
503        updateAutoTextEnabled(saveLocale);
504        if (mUserDictionary != null) mUserDictionary.close();
505        mUserDictionary = new UserDictionary(this, mInputLocale);
506        if (mContactsDictionary == null) {
507            mContactsDictionary = new ContactsDictionary(this, Suggest.DIC_CONTACTS);
508        }
509        if (mAutoDictionary != null) {
510            mAutoDictionary.close();
511        }
512        mAutoDictionary = new AutoDictionary(this, this, mInputLocale, Suggest.DIC_AUTO);
513        if (mUserBigramDictionary != null) {
514            mUserBigramDictionary.close();
515        }
516        mUserBigramDictionary = new UserBigramDictionary(this, this, mInputLocale,
517                Suggest.DIC_USER);
518        mSuggest.setUserBigramDictionary(mUserBigramDictionary);
519        mSuggest.setUserDictionary(mUserDictionary);
520        mSuggest.setContactsDictionary(mContactsDictionary);
521        mSuggest.setAutoDictionary(mAutoDictionary);
522        updateCorrectionMode();
523        mWordSeparators = mResources.getString(R.string.word_separators);
524        mSentenceSeparators = mResources.getString(R.string.sentence_separators);
525
526        conf.locale = saveLocale;
527        orig.updateConfiguration(conf, orig.getDisplayMetrics());
528    }
529
530    @Override
531    public void onDestroy() {
532        if (mUserDictionary != null) {
533            mUserDictionary.close();
534        }
535        if (mContactsDictionary != null) {
536            mContactsDictionary.close();
537        }
538        unregisterReceiver(mReceiver);
539        if (VOICE_INSTALLED && mVoiceInput != null) {
540            mVoiceInput.destroy();
541        }
542        LatinImeLogger.commit();
543        LatinImeLogger.onDestroy();
544        super.onDestroy();
545    }
546
547    @Override
548    public void onConfigurationChanged(Configuration conf) {
549        // If the system locale changes and is different from the saved
550        // locale (mSystemLocale), then reload the input locale list from the
551        // latin ime settings (shared prefs) and reset the input locale
552        // to the first one.
553        final String systemLocale = conf.locale.toString();
554        if (!TextUtils.equals(systemLocale, mSystemLocale)) {
555            mSystemLocale = systemLocale;
556            mLanguageSwitcher.loadLocales(
557                    PreferenceManager.getDefaultSharedPreferences(this));
558            mLanguageSwitcher.setSystemLocale(conf.locale);
559            toggleLanguage(true, true);
560        }
561        // If orientation changed while predicting, commit the change
562        if (conf.orientation != mOrientation) {
563            InputConnection ic = getCurrentInputConnection();
564            commitTyped(ic);
565            if (ic != null) ic.finishComposingText(); // For voice input
566            mOrientation = conf.orientation;
567            final int mode = mKeyboardSwitcher.getKeyboardMode();
568            final EditorInfo attribute = getCurrentInputEditorInfo();
569            final int imeOptions = (attribute != null) ? attribute.imeOptions : 0;
570            mKeyboardSwitcher.loadKeyboard(mode, imeOptions, mVoiceButtonEnabled,
571                    mVoiceButtonOnPrimary);
572        }
573
574        mConfigurationChanging = true;
575        super.onConfigurationChanged(conf);
576        if (mRecognizing) {
577            switchToRecognitionStatusView();
578        }
579        mConfigurationChanging = false;
580    }
581
582    @Override
583    public View onCreateInputView() {
584        mKeyboardSwitcher.loadKeyboardView();
585        return mKeyboardSwitcher.getInputView();
586    }
587
588    @Override
589    public View onCreateCandidatesView() {
590        mCandidateViewContainer = (LinearLayout) getLayoutInflater().inflate(
591                R.layout.candidates, null);
592        mCandidateView = (CandidateView) mCandidateViewContainer.findViewById(R.id.candidates);
593        mCandidateView.setService(this);
594        setCandidatesViewShown(true);
595        return mCandidateViewContainer;
596    }
597
598    private static boolean isPasswordVariation(int variation) {
599        return variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD
600                || variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
601                || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD;
602    }
603
604    private static boolean isEmailVariation(int variation) {
605        return variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
606                || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
607    }
608
609    @Override
610    public void onStartInputView(EditorInfo attribute, boolean restarting) {
611        LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
612        // In landscape mode, this method gets called without the input view being created.
613        if (inputView == null) {
614            return;
615        }
616
617        if (mRefreshKeyboardRequired) {
618            mRefreshKeyboardRequired = false;
619            toggleLanguage(true, true);
620        }
621
622        TextEntryState.newSession(this);
623
624        // Most such things we decide below in the switch statement, but we need to know
625        // now whether this is a password text field, because we need to know now (before
626        // the switch statement) whether we want to enable the voice button.
627        mPasswordText = false;
628        int variation = attribute.inputType & EditorInfo.TYPE_MASK_VARIATION;
629        if (isPasswordVariation(variation)) {
630            mPasswordText = true;
631        }
632
633        mAfterVoiceInput = false;
634        mImmediatelyAfterVoiceInput = false;
635        mShowingVoiceSuggestions = false;
636        mVoiceInputHighlighted = false;
637        mInputTypeNoAutoCorrect = false;
638        mPredictionOn = false;
639        mCompletionOn = false;
640        mCompletions = null;
641        mEnteredText = null;
642
643        final int mode;
644        switch (attribute.inputType & EditorInfo.TYPE_MASK_CLASS) {
645            case EditorInfo.TYPE_CLASS_NUMBER:
646            case EditorInfo.TYPE_CLASS_DATETIME:
647                // fall through
648                // NOTE: For now, we use the phone keyboard for NUMBER and DATETIME until we get
649                // a dedicated number entry keypad.
650                // TODO: Use a dedicated number entry keypad here when we get one.
651            case EditorInfo.TYPE_CLASS_PHONE:
652                mode = KeyboardSwitcher.MODE_PHONE;
653                break;
654            case EditorInfo.TYPE_CLASS_TEXT:
655                //startPrediction();
656                mPredictionOn = true;
657                // Make sure that passwords are not displayed in candidate view
658                if (isPasswordVariation(variation)) {
659                    mPredictionOn = false;
660                }
661                if (isEmailVariation(variation)
662                        || variation == EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME) {
663                    mAutoSpace = false;
664                } else {
665                    mAutoSpace = true;
666                }
667                if (isEmailVariation(variation)) {
668                    mPredictionOn = false;
669                    mode = KeyboardSwitcher.MODE_EMAIL;
670                } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_URI) {
671                    mPredictionOn = false;
672                    mode = KeyboardSwitcher.MODE_URL;
673                } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
674                    mode = KeyboardSwitcher.MODE_IM;
675                } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_FILTER) {
676                    mPredictionOn = false;
677                    mode = KeyboardSwitcher.MODE_TEXT;
678                } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) {
679                    mode = KeyboardSwitcher.MODE_WEB;
680                    // If it's a browser edit field and auto correct is not ON explicitly, then
681                    // disable auto correction, but keep suggestions on.
682                    if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) {
683                        mInputTypeNoAutoCorrect = true;
684                    }
685                } else {
686                    mode = KeyboardSwitcher.MODE_TEXT;
687                }
688
689                // If NO_SUGGESTIONS is set, don't do prediction.
690                if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) {
691                    mPredictionOn = false;
692                    mInputTypeNoAutoCorrect = true;
693                }
694                // If it's not multiline and the autoCorrect flag is not set, then don't correct
695                if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0 &&
696                        (attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) == 0) {
697                    mInputTypeNoAutoCorrect = true;
698                }
699                if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
700                    mPredictionOn = false;
701                    mCompletionOn = isFullscreenMode();
702                }
703                break;
704            default:
705                mode = KeyboardSwitcher.MODE_TEXT;
706                break;
707        }
708        inputView.closing();
709        mComposing.setLength(0);
710        mPredicting = false;
711        mDeleteCount = 0;
712        mJustAddedAutoSpace = false;
713
714        loadSettings(attribute);
715        mKeyboardSwitcher.loadKeyboard(mode, attribute.imeOptions, mVoiceButtonEnabled,
716                mVoiceButtonOnPrimary);
717        updateShiftKeyState(attribute);
718
719        setCandidatesViewShownInternal(isCandidateStripVisible(),
720                false /* needsInputViewShown */ );
721        updateSuggestions();
722
723        // If the dictionary is not big enough, don't auto correct
724        mHasDictionary = mSuggest.hasMainDictionary();
725
726        updateCorrectionMode();
727
728        inputView.setPreviewEnabled(mPopupOn);
729        inputView.setProximityCorrectionEnabled(true);
730        mPredictionOn = mPredictionOn && (mCorrectionMode > 0 || isSuggestionShown());
731        // If we just entered a text field, maybe it has some old text that requires correction
732        checkReCorrectionOnStart();
733        checkTutorial(attribute.privateImeOptions);
734        inputView.setForeground(true);
735        if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
736    }
737
738    private void checkReCorrectionOnStart() {
739        if (mReCorrectionEnabled && isPredictionOn()) {
740            // First get the cursor position. This is required by setOldSuggestions(), so that
741            // it can pass the correct range to setComposingRegion(). At this point, we don't
742            // have valid values for mLastSelectionStart/Stop because onUpdateSelection() has
743            // not been called yet.
744            InputConnection ic = getCurrentInputConnection();
745            if (ic == null) return;
746            ExtractedTextRequest etr = new ExtractedTextRequest();
747            etr.token = 0; // anything is fine here
748            ExtractedText et = ic.getExtractedText(etr, 0);
749            if (et == null) return;
750
751            mLastSelectionStart = et.startOffset + et.selectionStart;
752            mLastSelectionEnd = et.startOffset + et.selectionEnd;
753
754            // Then look for possible corrections in a delayed fashion
755            if (!TextUtils.isEmpty(et.text) && isCursorTouchingWord()) {
756                mHandler.postUpdateOldSuggestions();
757            }
758        }
759    }
760
761    @Override
762    public void onFinishInput() {
763        super.onFinishInput();
764
765        LatinImeLogger.commit();
766        onAutoCompletionStateChanged(false);
767
768        if (VOICE_INSTALLED && !mConfigurationChanging) {
769            if (mAfterVoiceInput) {
770                mVoiceInput.flushAllTextModificationCounters();
771                mVoiceInput.logInputEnded();
772            }
773            mVoiceInput.flushLogs();
774            mVoiceInput.cancel();
775        }
776        BaseKeyboardView inputView = mKeyboardSwitcher.getInputView();
777        if (inputView != null)
778            inputView.closing();
779        if (mAutoDictionary != null) mAutoDictionary.flushPendingWrites();
780        if (mUserBigramDictionary != null) mUserBigramDictionary.flushPendingWrites();
781    }
782
783    @Override
784    public void onFinishInputView(boolean finishingInput) {
785        super.onFinishInputView(finishingInput);
786        BaseKeyboardView inputView = mKeyboardSwitcher.getInputView();
787        if (inputView != null)
788            inputView.setForeground(false);
789        // Remove pending messages related to update suggestions
790        mHandler.cancelUpdateSuggestions();
791        mHandler.cancelUpdateOldSuggestions();
792    }
793
794    @Override
795    public void onUpdateExtractedText(int token, ExtractedText text) {
796        super.onUpdateExtractedText(token, text);
797        InputConnection ic = getCurrentInputConnection();
798        if (!mImmediatelyAfterVoiceInput && mAfterVoiceInput && ic != null) {
799            if (mHints.showPunctuationHintIfNecessary(ic)) {
800                mVoiceInput.logPunctuationHintDisplayed();
801            }
802        }
803        mImmediatelyAfterVoiceInput = false;
804    }
805
806    @Override
807    public void onUpdateSelection(int oldSelStart, int oldSelEnd,
808            int newSelStart, int newSelEnd,
809            int candidatesStart, int candidatesEnd) {
810        super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
811                candidatesStart, candidatesEnd);
812
813        if (DEBUG) {
814            Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart
815                    + ", ose=" + oldSelEnd
816                    + ", nss=" + newSelStart
817                    + ", nse=" + newSelEnd
818                    + ", cs=" + candidatesStart
819                    + ", ce=" + candidatesEnd);
820        }
821
822        if (mAfterVoiceInput) {
823            mVoiceInput.setCursorPos(newSelEnd);
824            mVoiceInput.setSelectionSpan(newSelEnd - newSelStart);
825        }
826
827        // If the current selection in the text view changes, we should
828        // clear whatever candidate text we have.
829        if ((((mComposing.length() > 0 && mPredicting) || mVoiceInputHighlighted)
830                && (newSelStart != candidatesEnd
831                    || newSelEnd != candidatesEnd)
832                && mLastSelectionStart != newSelStart)) {
833            mComposing.setLength(0);
834            mPredicting = false;
835            mHandler.postUpdateSuggestions();
836            TextEntryState.reset();
837            InputConnection ic = getCurrentInputConnection();
838            if (ic != null) {
839                ic.finishComposingText();
840            }
841            mVoiceInputHighlighted = false;
842        } else if (!mPredicting && !mJustAccepted) {
843            switch (TextEntryState.getState()) {
844                case ACCEPTED_DEFAULT:
845                    TextEntryState.reset();
846                    // fall through
847                case SPACE_AFTER_PICKED:
848                    mJustAddedAutoSpace = false;  // The user moved the cursor.
849                    break;
850            }
851        }
852        mJustAccepted = false;
853        mHandler.postUpdateShiftKeyState();
854
855        // Make a note of the cursor position
856        mLastSelectionStart = newSelStart;
857        mLastSelectionEnd = newSelEnd;
858
859        if (mReCorrectionEnabled) {
860            // Don't look for corrections if the keyboard is not visible
861            if (mKeyboardSwitcher.isInputViewShown()) {
862                // Check if we should go in or out of correction mode.
863                if (isPredictionOn() && !mJustReverted
864                        && (candidatesStart == candidatesEnd || newSelStart != oldSelStart
865                                || TextEntryState.isCorrecting())
866                                && (newSelStart < newSelEnd - 1 || (!mPredicting))
867                                && !mVoiceInputHighlighted) {
868                    if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) {
869                        mHandler.postUpdateOldSuggestions();
870                    } else {
871                        abortCorrection(false);
872                        // Show the punctuation suggestions list if the current one is not
873                        // and if not showing "Touch again to save".
874                        if (mCandidateView != null && !isShowingPunctuationList()
875                                && !mCandidateView.isShowingAddToDictionaryHint()) {
876                            setPunctuationSuggestions();
877                        }
878                    }
879                }
880            }
881        }
882    }
883
884    /**
885     * This is called when the user has clicked on the extracted text view,
886     * when running in fullscreen mode.  The default implementation hides
887     * the candidates view when this happens, but only if the extracted text
888     * editor has a vertical scroll bar because its text doesn't fit.
889     * Here we override the behavior due to the possibility that a re-correction could
890     * cause the candidate strip to disappear and re-appear.
891     */
892    @Override
893    public void onExtractedTextClicked() {
894        if (mReCorrectionEnabled && isPredictionOn()) return;
895
896        super.onExtractedTextClicked();
897    }
898
899    /**
900     * This is called when the user has performed a cursor movement in the
901     * extracted text view, when it is running in fullscreen mode.  The default
902     * implementation hides the candidates view when a vertical movement
903     * happens, but only if the extracted text editor has a vertical scroll bar
904     * because its text doesn't fit.
905     * Here we override the behavior due to the possibility that a re-correction could
906     * cause the candidate strip to disappear and re-appear.
907     */
908    @Override
909    public void onExtractedCursorMovement(int dx, int dy) {
910        if (mReCorrectionEnabled && isPredictionOn()) return;
911
912        super.onExtractedCursorMovement(dx, dy);
913    }
914
915    @Override
916    public void hideWindow() {
917        LatinImeLogger.commit();
918        onAutoCompletionStateChanged(false);
919
920        if (TRACE) Debug.stopMethodTracing();
921        if (mOptionsDialog != null && mOptionsDialog.isShowing()) {
922            mOptionsDialog.dismiss();
923            mOptionsDialog = null;
924        }
925        if (!mConfigurationChanging) {
926            if (mAfterVoiceInput) mVoiceInput.logInputEnded();
927            if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) {
928                mVoiceInput.logKeyboardWarningDialogDismissed();
929                mVoiceWarningDialog.dismiss();
930                mVoiceWarningDialog = null;
931            }
932            if (VOICE_INSTALLED & mRecognizing) {
933                mVoiceInput.cancel();
934            }
935        }
936        mWordToSuggestions.clear();
937        mWordHistory.clear();
938        super.hideWindow();
939        TextEntryState.endSession();
940    }
941
942    @Override
943    public void onDisplayCompletions(CompletionInfo[] completions) {
944        if (DEBUG) {
945            Log.i("foo", "Received completions:");
946            for (int i=0; i<(completions != null ? completions.length : 0); i++) {
947                Log.i("foo", "  #" + i + ": " + completions[i]);
948            }
949        }
950        if (mCompletionOn) {
951            mCompletions = completions;
952            if (completions == null) {
953                clearSuggestions();
954                return;
955            }
956
957            List<CharSequence> stringList = new ArrayList<CharSequence>();
958            for (int i=0; i<(completions != null ? completions.length : 0); i++) {
959                CompletionInfo ci = completions[i];
960                if (ci != null) stringList.add(ci.getText());
961            }
962            // When in fullscreen mode, show completions generated by the application
963            setSuggestions(stringList, true, true, true);
964            mBestWord = null;
965            setCandidatesViewShown(true);
966        }
967    }
968
969    private void setCandidatesViewShownInternal(boolean shown, boolean needsInputViewShown) {
970        // TODO: Remove this if we support candidates with hard keyboard
971        if (onEvaluateInputViewShown()) {
972            super.setCandidatesViewShown(shown
973                    && (needsInputViewShown ? mKeyboardSwitcher.isInputViewShown() : true));
974        }
975    }
976
977    @Override
978    public void setCandidatesViewShown(boolean shown) {
979        setCandidatesViewShownInternal(shown, true /* needsInputViewShown */ );
980    }
981
982    @Override
983    public void onComputeInsets(InputMethodService.Insets outInsets) {
984        super.onComputeInsets(outInsets);
985        if (!isFullscreenMode()) {
986            outInsets.contentTopInsets = outInsets.visibleTopInsets;
987        }
988    }
989
990    @Override
991    public boolean onEvaluateFullscreenMode() {
992        DisplayMetrics dm = getResources().getDisplayMetrics();
993        float displayHeight = dm.heightPixels;
994        // If the display is more than X inches high, don't go to fullscreen mode
995        float dimen = getResources().getDimension(R.dimen.max_height_for_fullscreen);
996        if (displayHeight > dimen) {
997            return false;
998        } else {
999            return super.onEvaluateFullscreenMode();
1000        }
1001    }
1002
1003    @Override
1004    public boolean onKeyDown(int keyCode, KeyEvent event) {
1005        switch (keyCode) {
1006            case KeyEvent.KEYCODE_BACK:
1007                if (event.getRepeatCount() == 0 && mKeyboardSwitcher.getInputView() != null) {
1008                    if (mKeyboardSwitcher.getInputView().handleBack()) {
1009                        return true;
1010                    } else if (mTutorial != null) {
1011                        mTutorial.close();
1012                        mTutorial = null;
1013                    }
1014                }
1015                break;
1016            case KeyEvent.KEYCODE_DPAD_DOWN:
1017            case KeyEvent.KEYCODE_DPAD_UP:
1018            case KeyEvent.KEYCODE_DPAD_LEFT:
1019            case KeyEvent.KEYCODE_DPAD_RIGHT:
1020                // If tutorial is visible, don't allow dpad to work
1021                if (mTutorial != null) {
1022                    return true;
1023                }
1024                break;
1025        }
1026        return super.onKeyDown(keyCode, event);
1027    }
1028
1029    @Override
1030    public boolean onKeyUp(int keyCode, KeyEvent event) {
1031        switch (keyCode) {
1032            case KeyEvent.KEYCODE_DPAD_DOWN:
1033            case KeyEvent.KEYCODE_DPAD_UP:
1034            case KeyEvent.KEYCODE_DPAD_LEFT:
1035            case KeyEvent.KEYCODE_DPAD_RIGHT:
1036                // If tutorial is visible, don't allow dpad to work
1037                if (mTutorial != null) {
1038                    return true;
1039                }
1040                // Enable shift key and DPAD to do selections
1041                if (mKeyboardSwitcher.isInputViewShown() && mKeyboardSwitcher.isShifted()) {
1042                    event = new KeyEvent(event.getDownTime(), event.getEventTime(),
1043                            event.getAction(), event.getKeyCode(), event.getRepeatCount(),
1044                            event.getDeviceId(), event.getScanCode(),
1045                            KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON);
1046                    InputConnection ic = getCurrentInputConnection();
1047                    if (ic != null) ic.sendKeyEvent(event);
1048                    return true;
1049                }
1050                break;
1051        }
1052        return super.onKeyUp(keyCode, event);
1053    }
1054
1055    private void revertVoiceInput() {
1056        InputConnection ic = getCurrentInputConnection();
1057        if (ic != null) ic.commitText("", 1);
1058        updateSuggestions();
1059        mVoiceInputHighlighted = false;
1060    }
1061
1062    private void commitVoiceInput() {
1063        InputConnection ic = getCurrentInputConnection();
1064        if (ic != null) ic.finishComposingText();
1065        updateSuggestions();
1066        mVoiceInputHighlighted = false;
1067    }
1068
1069    private void commitTyped(InputConnection inputConnection) {
1070        if (mPredicting) {
1071            mPredicting = false;
1072            if (mComposing.length() > 0) {
1073                if (inputConnection != null) {
1074                    inputConnection.commitText(mComposing, 1);
1075                }
1076                mCommittedLength = mComposing.length();
1077                TextEntryState.acceptedTyped(mComposing);
1078                addToDictionaries(mComposing, AutoDictionary.FREQUENCY_FOR_TYPED);
1079            }
1080            updateSuggestions();
1081        }
1082    }
1083
1084    public void updateShiftKeyState(EditorInfo attr) {
1085        InputConnection ic = getCurrentInputConnection();
1086        KeyboardSwitcher switcher = mKeyboardSwitcher;
1087        if (!switcher.isKeyboardAvailable())
1088            return;
1089        if (ic != null && attr != null && switcher.isAlphabetMode()
1090                && !switcher.isShiftIgnoring()) {
1091            switcher.setShifted(switcher.isShiftMomentary()
1092                    || switcher.isShiftLocked() || getCursorCapsMode(ic, attr) != 0);
1093        }
1094    }
1095
1096    private int getCursorCapsMode(InputConnection ic, EditorInfo attr) {
1097        int caps = 0;
1098        EditorInfo ei = getCurrentInputEditorInfo();
1099        if (mAutoCap && ei != null && ei.inputType != EditorInfo.TYPE_NULL) {
1100            caps = ic.getCursorCapsMode(attr.inputType);
1101        }
1102        return caps;
1103    }
1104
1105    private void swapPunctuationAndSpace() {
1106        final InputConnection ic = getCurrentInputConnection();
1107        if (ic == null) return;
1108        CharSequence lastTwo = ic.getTextBeforeCursor(2, 0);
1109        if (lastTwo != null && lastTwo.length() == 2
1110                && lastTwo.charAt(0) == KEYCODE_SPACE && isSentenceSeparator(lastTwo.charAt(1))) {
1111            ic.beginBatchEdit();
1112            ic.deleteSurroundingText(2, 0);
1113            ic.commitText(lastTwo.charAt(1) + " ", 1);
1114            ic.endBatchEdit();
1115            updateShiftKeyState(getCurrentInputEditorInfo());
1116            mJustAddedAutoSpace = true;
1117        }
1118    }
1119
1120    private void reswapPeriodAndSpace() {
1121        final InputConnection ic = getCurrentInputConnection();
1122        if (ic == null) return;
1123        CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
1124        if (lastThree != null && lastThree.length() == 3
1125                && lastThree.charAt(0) == KEYCODE_PERIOD
1126                && lastThree.charAt(1) == KEYCODE_SPACE
1127                && lastThree.charAt(2) == KEYCODE_PERIOD) {
1128            ic.beginBatchEdit();
1129            ic.deleteSurroundingText(3, 0);
1130            ic.commitText(" ..", 1);
1131            ic.endBatchEdit();
1132            updateShiftKeyState(getCurrentInputEditorInfo());
1133        }
1134    }
1135
1136    private void doubleSpace() {
1137        //if (!mAutoPunctuate) return;
1138        if (mCorrectionMode == Suggest.CORRECTION_NONE) return;
1139        final InputConnection ic = getCurrentInputConnection();
1140        if (ic == null) return;
1141        CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
1142        if (lastThree != null && lastThree.length() == 3
1143                && Character.isLetterOrDigit(lastThree.charAt(0))
1144                && lastThree.charAt(1) == KEYCODE_SPACE && lastThree.charAt(2) == KEYCODE_SPACE) {
1145            ic.beginBatchEdit();
1146            ic.deleteSurroundingText(2, 0);
1147            ic.commitText(". ", 1);
1148            ic.endBatchEdit();
1149            updateShiftKeyState(getCurrentInputEditorInfo());
1150            mJustAddedAutoSpace = true;
1151        }
1152    }
1153
1154    private void maybeRemovePreviousPeriod(CharSequence text) {
1155        final InputConnection ic = getCurrentInputConnection();
1156        if (ic == null) return;
1157
1158        // When the text's first character is '.', remove the previous period
1159        // if there is one.
1160        CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
1161        if (lastOne != null && lastOne.length() == 1
1162                && lastOne.charAt(0) == KEYCODE_PERIOD
1163                && text.charAt(0) == KEYCODE_PERIOD) {
1164            ic.deleteSurroundingText(1, 0);
1165        }
1166    }
1167
1168    private void removeTrailingSpace() {
1169        final InputConnection ic = getCurrentInputConnection();
1170        if (ic == null) return;
1171
1172        CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
1173        if (lastOne != null && lastOne.length() == 1
1174                && lastOne.charAt(0) == KEYCODE_SPACE) {
1175            ic.deleteSurroundingText(1, 0);
1176        }
1177    }
1178
1179    public boolean addWordToDictionary(String word) {
1180        mUserDictionary.addWord(word, 128);
1181        // Suggestion strip should be updated after the operation of adding word to the
1182        // user dictionary
1183        mHandler.postUpdateSuggestions();
1184        return true;
1185    }
1186
1187    private boolean isAlphabet(int code) {
1188        if (Character.isLetter(code)) {
1189            return true;
1190        } else {
1191            return false;
1192        }
1193    }
1194
1195    private void showInputMethodSubtypePicker() {
1196        ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE))
1197                .showInputMethodSubtypePicker();
1198    }
1199
1200    private void onOptionKeyPressed() {
1201        if (!isShowingOptionDialog()) {
1202            if (LatinIMEUtil.hasMultipleEnabledIMEs(this)) {
1203                showOptionsMenu();
1204            } else {
1205                launchSettings();
1206            }
1207        }
1208    }
1209
1210    private void onOptionKeyLongPressed() {
1211        if (!isShowingOptionDialog()) {
1212            if (LatinIMEUtil.hasMultipleEnabledIMEs(this)) {
1213                showInputMethodSubtypePicker();
1214            } else {
1215                launchSettings();
1216            }
1217        }
1218    }
1219
1220    private boolean isShowingOptionDialog() {
1221        return mOptionsDialog != null && mOptionsDialog.isShowing();
1222    }
1223
1224    // Implementation of KeyboardViewListener
1225
1226    public void onKey(int primaryCode, int[] keyCodes, int x, int y) {
1227        long when = SystemClock.uptimeMillis();
1228        if (primaryCode != BaseKeyboard.KEYCODE_DELETE || when > mLastKeyTime + QUICK_PRESS) {
1229            mDeleteCount = 0;
1230        }
1231        mLastKeyTime = when;
1232        final boolean distinctMultiTouch = mKeyboardSwitcher.hasDistinctMultitouch();
1233        switch (primaryCode) {
1234        case BaseKeyboard.KEYCODE_DELETE:
1235            handleBackspace();
1236            mDeleteCount++;
1237            LatinImeLogger.logOnDelete();
1238            break;
1239        case BaseKeyboard.KEYCODE_SHIFT:
1240            // Shift key is handled in onPress() when device has distinct multi-touch panel.
1241            if (!distinctMultiTouch)
1242                handleShift();
1243            break;
1244        case BaseKeyboard.KEYCODE_MODE_CHANGE:
1245            // Symbol key is handled in onPress() when device has distinct multi-touch panel.
1246            if (!distinctMultiTouch)
1247                changeKeyboardMode();
1248            break;
1249        case BaseKeyboard.KEYCODE_CANCEL:
1250            if (!isShowingOptionDialog()) {
1251                handleClose();
1252            }
1253            break;
1254        case LatinKeyboardView.KEYCODE_OPTIONS:
1255            onOptionKeyPressed();
1256            break;
1257        case LatinKeyboardView.KEYCODE_OPTIONS_LONGPRESS:
1258            onOptionKeyLongPressed();
1259            break;
1260        case LatinKeyboardView.KEYCODE_NEXT_LANGUAGE:
1261            toggleLanguage(false, true);
1262            break;
1263        case LatinKeyboardView.KEYCODE_PREV_LANGUAGE:
1264            toggleLanguage(false, false);
1265            break;
1266        case LatinKeyboardView.KEYCODE_CAPSLOCK:
1267            handleCapsLock();
1268            break;
1269        case LatinKeyboardView.KEYCODE_VOICE:
1270            if (VOICE_INSTALLED) {
1271                startListening(false /* was a button press, was not a swipe */);
1272            }
1273            break;
1274        case KEYCODE_TAB:
1275            sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB);
1276            break;
1277        default:
1278            if (primaryCode != KEYCODE_ENTER) {
1279                mJustAddedAutoSpace = false;
1280            }
1281            RingCharBuffer.getInstance().push((char)primaryCode, x, y);
1282            LatinImeLogger.logOnInputChar();
1283            if (isWordSeparator(primaryCode)) {
1284                handleSeparator(primaryCode);
1285            } else {
1286                handleCharacter(primaryCode, keyCodes);
1287            }
1288            // Cancel the just reverted state
1289            mJustReverted = false;
1290        }
1291        if (mKeyboardSwitcher.onKey(primaryCode)) {
1292            changeKeyboardMode();
1293        }
1294        // Reset after any single keystroke
1295        mEnteredText = null;
1296    }
1297
1298    public void onText(CharSequence text) {
1299        if (VOICE_INSTALLED && mVoiceInputHighlighted) {
1300            commitVoiceInput();
1301        }
1302        InputConnection ic = getCurrentInputConnection();
1303        if (ic == null) return;
1304        abortCorrection(false);
1305        ic.beginBatchEdit();
1306        if (mPredicting) {
1307            commitTyped(ic);
1308        }
1309        maybeRemovePreviousPeriod(text);
1310        ic.commitText(text, 1);
1311        ic.endBatchEdit();
1312        updateShiftKeyState(getCurrentInputEditorInfo());
1313        mJustReverted = false;
1314        mJustAddedAutoSpace = false;
1315        mEnteredText = text;
1316    }
1317
1318    public void onCancel() {
1319        // User released a finger outside any key
1320    }
1321
1322    private void handleBackspace() {
1323        if (VOICE_INSTALLED && mVoiceInputHighlighted) {
1324            mVoiceInput.incrementTextModificationDeleteCount(
1325                    mVoiceResults.candidates.get(0).toString().length());
1326            revertVoiceInput();
1327            return;
1328        }
1329        boolean deleteChar = false;
1330        InputConnection ic = getCurrentInputConnection();
1331        if (ic == null) return;
1332
1333        ic.beginBatchEdit();
1334
1335        if (mAfterVoiceInput) {
1336            // Don't log delete if the user is pressing delete at
1337            // the beginning of the text box (hence not deleting anything)
1338            if (mVoiceInput.getCursorPos() > 0) {
1339                // If anything was selected before the delete was pressed, increment the
1340                // delete count by the length of the selection
1341                int deleteLen  =  mVoiceInput.getSelectionSpan() > 0 ?
1342                        mVoiceInput.getSelectionSpan() : 1;
1343                mVoiceInput.incrementTextModificationDeleteCount(deleteLen);
1344            }
1345        }
1346
1347        if (mPredicting) {
1348            final int length = mComposing.length();
1349            if (length > 0) {
1350                mComposing.delete(length - 1, length);
1351                mWord.deleteLast();
1352                ic.setComposingText(mComposing, 1);
1353                if (mComposing.length() == 0) {
1354                    mPredicting = false;
1355                }
1356                mHandler.postUpdateSuggestions();
1357            } else {
1358                ic.deleteSurroundingText(1, 0);
1359            }
1360        } else {
1361            deleteChar = true;
1362        }
1363        mHandler.postUpdateShiftKeyState();
1364        TextEntryState.backspace();
1365        if (TextEntryState.getState() == TextEntryState.State.UNDO_COMMIT) {
1366            revertLastWord(deleteChar);
1367            ic.endBatchEdit();
1368            return;
1369        } else if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) {
1370            ic.deleteSurroundingText(mEnteredText.length(), 0);
1371        } else if (deleteChar) {
1372            if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) {
1373                // Go back to the suggestion mode if the user canceled the
1374                // "Touch again to save".
1375                // NOTE: In gerenal, we don't revert the word when backspacing
1376                // from a manual suggestion pick.  We deliberately chose a
1377                // different behavior only in the case of picking the first
1378                // suggestion (typed word).  It's intentional to have made this
1379                // inconsistent with backspacing after selecting other suggestions.
1380                revertLastWord(deleteChar);
1381            } else {
1382                sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
1383                if (mDeleteCount > DELETE_ACCELERATE_AT) {
1384                    sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
1385                }
1386            }
1387        }
1388        mJustReverted = false;
1389        ic.endBatchEdit();
1390    }
1391
1392    private void resetShift() {
1393        handleShiftInternal(true);
1394    }
1395
1396    private void handleShift() {
1397        handleShiftInternal(false);
1398    }
1399
1400    private void handleShiftInternal(boolean forceNormal) {
1401        mHandler.cancelUpdateShiftState();
1402        KeyboardSwitcher switcher = mKeyboardSwitcher;
1403        if (switcher.isAlphabetMode()) {
1404            if (!switcher.isKeyboardAvailable())
1405                return;
1406            if (switcher.isShiftLocked() || forceNormal) {
1407                switcher.setShifted(false);
1408            } else {
1409                switcher.setShifted(!switcher.isShifted());
1410            }
1411        } else {
1412            switcher.toggleShift();
1413        }
1414    }
1415
1416    private void handleCapsLock() {
1417        mHandler.cancelUpdateShiftState();
1418        KeyboardSwitcher switcher = mKeyboardSwitcher;
1419        if (switcher.isAlphabetMode()) {
1420            if (!switcher.isKeyboardAvailable())
1421                return;
1422            if (switcher.isShiftLocked()) {
1423                // KeyboardSwitcher.setShifted(false) also disable shift locked state.
1424                // Note: Caps lock LED is off when Key.on is false.
1425                switcher.setShifted(false);
1426            } else {
1427                // KeyboardSwitcher.setShiftLocked(true) enable shift state too.
1428                // Note: Caps lock LED is on when Key.on is true.
1429                switcher.setShiftLocked(true);
1430            }
1431        }
1432    }
1433
1434    private void abortCorrection(boolean force) {
1435        if (force || TextEntryState.isCorrecting()) {
1436            TextEntryState.onAbortCorrection();
1437            setCandidatesViewShown(isCandidateStripVisible());
1438            getCurrentInputConnection().finishComposingText();
1439            clearSuggestions();
1440        }
1441    }
1442
1443    private void handleCharacter(int primaryCode, int[] keyCodes) {
1444        if (VOICE_INSTALLED && mVoiceInputHighlighted) {
1445            commitVoiceInput();
1446        }
1447
1448        if (mAfterVoiceInput) {
1449            // Assume input length is 1. This assumption fails for smiley face insertions.
1450            mVoiceInput.incrementTextModificationInsertCount(1);
1451        }
1452        if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isCorrecting()) {
1453            abortCorrection(false);
1454        }
1455
1456        if (isAlphabet(primaryCode) && isPredictionOn() && !isCursorTouchingWord()) {
1457            if (!mPredicting) {
1458                mPredicting = true;
1459                mComposing.setLength(0);
1460                saveWordInHistory(mBestWord);
1461                mWord.reset();
1462            }
1463        }
1464        KeyboardSwitcher switcher = mKeyboardSwitcher;
1465        if (switcher.isShifted()) {
1466            if (keyCodes == null || keyCodes[0] < Character.MIN_CODE_POINT
1467                    || keyCodes[0] > Character.MAX_CODE_POINT) {
1468                return;
1469            }
1470            primaryCode = keyCodes[0];
1471            if (switcher.isAlphabetMode() && Character.isLowerCase(primaryCode)) {
1472                int upperCaseCode = Character.toUpperCase(primaryCode);
1473                if (upperCaseCode != primaryCode) {
1474                    primaryCode = upperCaseCode;
1475                } else {
1476                    // Some keys, such as [eszett], have upper case as multi-characters.
1477                    String upperCase = new String(new int[] {primaryCode}, 0, 1).toUpperCase();
1478                    onText(upperCase);
1479                    return;
1480                }
1481            }
1482        }
1483        if (mPredicting) {
1484            if (mComposing.length() == 0 && switcher.isAlphabetMode() && switcher.isShifted()) {
1485                mWord.setFirstCharCapitalized(true);
1486            }
1487            mComposing.append((char) primaryCode);
1488            mWord.add(primaryCode, keyCodes);
1489            InputConnection ic = getCurrentInputConnection();
1490            if (ic != null) {
1491                // If it's the first letter, make note of auto-caps state
1492                if (mWord.size() == 1) {
1493                    mWord.setAutoCapitalized(
1494                            getCursorCapsMode(ic, getCurrentInputEditorInfo()) != 0);
1495                }
1496                ic.setComposingText(mComposing, 1);
1497            }
1498            mHandler.postUpdateSuggestions();
1499        } else {
1500            sendKeyChar((char)primaryCode);
1501        }
1502        updateShiftKeyState(getCurrentInputEditorInfo());
1503        if (LatinIME.PERF_DEBUG) measureCps();
1504        TextEntryState.typedCharacter((char) primaryCode, isWordSeparator(primaryCode));
1505    }
1506
1507    private void handleSeparator(int primaryCode) {
1508        if (VOICE_INSTALLED && mVoiceInputHighlighted) {
1509            commitVoiceInput();
1510        }
1511
1512        if (mAfterVoiceInput){
1513            // Assume input length is 1. This assumption fails for smiley face insertions.
1514            mVoiceInput.incrementTextModificationInsertPunctuationCount(1);
1515        }
1516
1517        // Should dismiss the "Touch again to save" message when handling separator
1518        if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) {
1519            mHandler.postUpdateSuggestions();
1520        }
1521
1522        boolean pickedDefault = false;
1523        // Handle separator
1524        InputConnection ic = getCurrentInputConnection();
1525        if (ic != null) {
1526            ic.beginBatchEdit();
1527            abortCorrection(false);
1528        }
1529        if (mPredicting) {
1530            // In certain languages where single quote is a separator, it's better
1531            // not to auto correct, but accept the typed word. For instance,
1532            // in Italian dov' should not be expanded to dove' because the elision
1533            // requires the last vowel to be removed.
1534            if (mAutoCorrectOn && primaryCode != '\'' && !mJustReverted) {
1535                pickedDefault = pickDefaultSuggestion();
1536                // Picked the suggestion by the space key.  We consider this
1537                // as "added an auto space".
1538                if (primaryCode == KEYCODE_SPACE) {
1539                    mJustAddedAutoSpace = true;
1540                }
1541            } else {
1542                commitTyped(ic);
1543            }
1544        }
1545        if (mJustAddedAutoSpace && primaryCode == KEYCODE_ENTER) {
1546            removeTrailingSpace();
1547            mJustAddedAutoSpace = false;
1548        }
1549        sendKeyChar((char)primaryCode);
1550
1551        // Handle the case of ". ." -> " .." with auto-space if necessary
1552        // before changing the TextEntryState.
1553        if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED
1554                && primaryCode == KEYCODE_PERIOD) {
1555            reswapPeriodAndSpace();
1556        }
1557
1558        TextEntryState.typedCharacter((char) primaryCode, true);
1559        if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED
1560                && primaryCode != KEYCODE_ENTER) {
1561            swapPunctuationAndSpace();
1562        } else if (isPredictionOn() && primaryCode == KEYCODE_SPACE) {
1563            doubleSpace();
1564        }
1565        if (pickedDefault) {
1566            TextEntryState.backToAcceptedDefault(mWord.getTypedWord());
1567        }
1568        updateShiftKeyState(getCurrentInputEditorInfo());
1569        if (ic != null) {
1570            ic.endBatchEdit();
1571        }
1572    }
1573
1574    private void handleClose() {
1575        commitTyped(getCurrentInputConnection());
1576        if (VOICE_INSTALLED & mRecognizing) {
1577            mVoiceInput.cancel();
1578        }
1579        requestHideSelf(0);
1580        LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
1581        if (inputView != null)
1582            inputView.closing();
1583        TextEntryState.endSession();
1584    }
1585
1586    private void saveWordInHistory(CharSequence result) {
1587        if (mWord.size() <= 1) {
1588            mWord.reset();
1589            return;
1590        }
1591        // Skip if result is null. It happens in some edge case.
1592        if (TextUtils.isEmpty(result)) {
1593            return;
1594        }
1595
1596        // Make a copy of the CharSequence, since it is/could be a mutable CharSequence
1597        final String resultCopy = result.toString();
1598        TypedWordAlternatives entry = new TypedWordAlternatives(resultCopy,
1599                new WordComposer(mWord));
1600        mWordHistory.add(entry);
1601    }
1602
1603    private boolean isPredictionOn() {
1604        return mPredictionOn;
1605    }
1606
1607    private boolean isShowingPunctuationList() {
1608        return mSuggestPuncList.equals(mCandidateView.getSuggestions());
1609    }
1610
1611    private boolean isSuggestionShown() {
1612        return (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_VALUE)
1613                || (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE
1614                        && mOrientation == Configuration.ORIENTATION_PORTRAIT);
1615    }
1616
1617    private boolean isCandidateStripVisible() {
1618        boolean forceVisible = mCandidateView.isShowingAddToDictionaryHint()
1619                || TextEntryState.isCorrecting();
1620        return forceVisible || (isSuggestionShown()
1621                && (isPredictionOn() || mCompletionOn || isShowingPunctuationList()));
1622    }
1623
1624    public void onCancelVoice() {
1625        if (mRecognizing) {
1626            switchToKeyboardView();
1627        }
1628    }
1629
1630    private void switchToKeyboardView() {
1631      mHandler.post(new Runnable() {
1632          public void run() {
1633              mRecognizing = false;
1634              if (mKeyboardSwitcher.getInputView() != null) {
1635                  setInputView(mKeyboardSwitcher.getInputView());
1636              }
1637              setCandidatesViewShown(isCandidateStripVisible());
1638              updateInputViewShown();
1639              mHandler.postUpdateSuggestions();
1640          }});
1641    }
1642
1643    private void switchToRecognitionStatusView() {
1644        final boolean configChanged = mConfigurationChanging;
1645        mHandler.post(new Runnable() {
1646            public void run() {
1647                setCandidatesViewShown(false);
1648                mRecognizing = true;
1649                View v = mVoiceInput.getView();
1650                ViewParent p = v.getParent();
1651                if (p != null && p instanceof ViewGroup) {
1652                    ((ViewGroup)v.getParent()).removeView(v);
1653                }
1654                setInputView(v);
1655                updateInputViewShown();
1656                if (configChanged) {
1657                    mVoiceInput.onConfigurationChanged();
1658                }
1659        }});
1660    }
1661
1662    private void startListening(boolean swipe) {
1663        if (!mHasUsedVoiceInput ||
1664                (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale)) {
1665            // Calls reallyStartListening if user clicks OK, does nothing if user clicks Cancel.
1666            showVoiceWarningDialog(swipe);
1667        } else {
1668            reallyStartListening(swipe);
1669        }
1670    }
1671
1672    private void reallyStartListening(boolean swipe) {
1673        if (!mHasUsedVoiceInput) {
1674            // The user has started a voice input, so remember that in the
1675            // future (so we don't show the warning dialog after the first run).
1676            SharedPreferences.Editor editor =
1677                    PreferenceManager.getDefaultSharedPreferences(this).edit();
1678            editor.putBoolean(PREF_HAS_USED_VOICE_INPUT, true);
1679            SharedPreferencesCompat.apply(editor);
1680            mHasUsedVoiceInput = true;
1681        }
1682
1683        if (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale) {
1684            // The user has started a voice input from an unsupported locale, so remember that
1685            // in the future (so we don't show the warning dialog the next time they do this).
1686            SharedPreferences.Editor editor =
1687                    PreferenceManager.getDefaultSharedPreferences(this).edit();
1688            editor.putBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, true);
1689            SharedPreferencesCompat.apply(editor);
1690            mHasUsedVoiceInputUnsupportedLocale = true;
1691        }
1692
1693        // Clear N-best suggestions
1694        clearSuggestions();
1695
1696        FieldContext context = new FieldContext(
1697            getCurrentInputConnection(),
1698            getCurrentInputEditorInfo(),
1699            mLanguageSwitcher.getInputLanguage(),
1700            mLanguageSwitcher.getEnabledLanguages());
1701        mVoiceInput.startListening(context, swipe);
1702        switchToRecognitionStatusView();
1703    }
1704
1705    private void showVoiceWarningDialog(final boolean swipe) {
1706        AlertDialog.Builder builder = new AlertDialog.Builder(this);
1707        builder.setCancelable(true);
1708        builder.setIcon(R.drawable.ic_mic_dialog);
1709        builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
1710            public void onClick(DialogInterface dialog, int whichButton) {
1711                mVoiceInput.logKeyboardWarningDialogOk();
1712                reallyStartListening(swipe);
1713            }
1714        });
1715        builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
1716            public void onClick(DialogInterface dialog, int whichButton) {
1717                mVoiceInput.logKeyboardWarningDialogCancel();
1718            }
1719        });
1720
1721        if (mLocaleSupportedForVoiceInput) {
1722            String message = getString(R.string.voice_warning_may_not_understand) + "\n\n" +
1723                    getString(R.string.voice_warning_how_to_turn_off);
1724            builder.setMessage(message);
1725        } else {
1726            String message = getString(R.string.voice_warning_locale_not_supported) + "\n\n" +
1727                    getString(R.string.voice_warning_may_not_understand) + "\n\n" +
1728                    getString(R.string.voice_warning_how_to_turn_off);
1729            builder.setMessage(message);
1730        }
1731
1732        builder.setTitle(R.string.voice_warning_title);
1733        mVoiceWarningDialog = builder.create();
1734
1735        Window window = mVoiceWarningDialog.getWindow();
1736        WindowManager.LayoutParams lp = window.getAttributes();
1737        lp.token = mKeyboardSwitcher.getInputView().getWindowToken();
1738        lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
1739        window.setAttributes(lp);
1740        window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
1741        mVoiceInput.logKeyboardWarningDialogShown();
1742        mVoiceWarningDialog.show();
1743    }
1744
1745    public void onVoiceResults(List<String> candidates,
1746            Map<String, List<CharSequence>> alternatives) {
1747        if (!mRecognizing) {
1748            return;
1749        }
1750        mVoiceResults.candidates = candidates;
1751        mVoiceResults.alternatives = alternatives;
1752        mHandler.updateVoiceResults();
1753    }
1754
1755    private void handleVoiceResults() {
1756        mAfterVoiceInput = true;
1757        mImmediatelyAfterVoiceInput = true;
1758
1759        InputConnection ic = getCurrentInputConnection();
1760        if (!isFullscreenMode()) {
1761            // Start listening for updates to the text from typing, etc.
1762            if (ic != null) {
1763                ExtractedTextRequest req = new ExtractedTextRequest();
1764                ic.getExtractedText(req, InputConnection.GET_EXTRACTED_TEXT_MONITOR);
1765            }
1766        }
1767
1768        vibrate();
1769        switchToKeyboardView();
1770
1771        final List<CharSequence> nBest = new ArrayList<CharSequence>();
1772        KeyboardSwitcher switcher = mKeyboardSwitcher;
1773        boolean capitalizeFirstWord = preferCapitalization()
1774                || (switcher.isAlphabetMode() && switcher.isShifted());
1775        for (String c : mVoiceResults.candidates) {
1776            if (capitalizeFirstWord) {
1777                c = Character.toUpperCase(c.charAt(0)) + c.substring(1, c.length());
1778            }
1779            nBest.add(c);
1780        }
1781
1782        if (nBest.size() == 0) {
1783            return;
1784        }
1785
1786        String bestResult = nBest.get(0).toString();
1787
1788        mVoiceInput.logVoiceInputDelivered(bestResult.length());
1789
1790        mHints.registerVoiceResult(bestResult);
1791
1792        if (ic != null) ic.beginBatchEdit(); // To avoid extra updates on committing older text
1793
1794        commitTyped(ic);
1795        EditingUtil.appendText(ic, bestResult);
1796
1797        if (ic != null) ic.endBatchEdit();
1798
1799        mVoiceInputHighlighted = true;
1800        mWordToSuggestions.putAll(mVoiceResults.alternatives);
1801    }
1802
1803    private void clearSuggestions() {
1804        setSuggestions(null, false, false, false);
1805    }
1806
1807    private void setSuggestions(
1808            List<CharSequence> suggestions,
1809            boolean completions,
1810            boolean typedWordValid,
1811            boolean haveMinimalSuggestion) {
1812
1813        if (mIsShowingHint) {
1814             setCandidatesView(mCandidateViewContainer);
1815             mIsShowingHint = false;
1816        }
1817
1818        if (mCandidateView != null) {
1819            mCandidateView.setSuggestions(
1820                    suggestions, completions, typedWordValid, haveMinimalSuggestion);
1821        }
1822    }
1823
1824    private void updateSuggestions() {
1825        mKeyboardSwitcher.setPreferredLetters(null);
1826
1827        // Check if we have a suggestion engine attached.
1828        if ((mSuggest == null || !isPredictionOn()) && !mVoiceInputHighlighted) {
1829            return;
1830        }
1831
1832        if (!mPredicting) {
1833            setPunctuationSuggestions();
1834            return;
1835        }
1836        showSuggestions(mWord);
1837    }
1838
1839    private List<CharSequence> getTypedSuggestions(WordComposer word) {
1840        List<CharSequence> stringList = mSuggest.getSuggestions(
1841                mKeyboardSwitcher.getInputView(), word, false, null);
1842        return stringList;
1843    }
1844
1845    private void showCorrections(WordAlternatives alternatives) {
1846        mKeyboardSwitcher.setPreferredLetters(null);
1847        List<CharSequence> stringList = alternatives.getAlternatives();
1848        showSuggestions(stringList, alternatives.getOriginalWord(), false, false);
1849    }
1850
1851    private void showSuggestions(WordComposer word) {
1852        // long startTime = System.currentTimeMillis(); // TIME MEASUREMENT!
1853        // TODO Maybe need better way of retrieving previous word
1854        CharSequence prevWord = EditingUtil.getPreviousWord(getCurrentInputConnection(),
1855                mWordSeparators);
1856        List<CharSequence> stringList = mSuggest.getSuggestions(
1857                mKeyboardSwitcher.getInputView(), word, false, prevWord);
1858        // long stopTime = System.currentTimeMillis(); // TIME MEASUREMENT!
1859        // Log.d("LatinIME","Suggest Total Time - " + (stopTime - startTime));
1860
1861        int[] nextLettersFrequencies = mSuggest.getNextLettersFrequencies();
1862        mKeyboardSwitcher.setPreferredLetters(nextLettersFrequencies);
1863
1864        boolean correctionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasMinimalCorrection();
1865        //|| mCorrectionMode == mSuggest.CORRECTION_FULL;
1866        CharSequence typedWord = word.getTypedWord();
1867        // If we're in basic correct
1868        boolean typedWordValid = mSuggest.isValidWord(typedWord) ||
1869                (preferCapitalization()
1870                        && mSuggest.isValidWord(typedWord.toString().toLowerCase()));
1871        if (mCorrectionMode == Suggest.CORRECTION_FULL
1872                || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) {
1873            correctionAvailable |= typedWordValid;
1874        }
1875        // Don't auto-correct words with multiple capital letter
1876        correctionAvailable &= !word.isMostlyCaps();
1877        correctionAvailable &= !TextEntryState.isCorrecting();
1878
1879        showSuggestions(stringList, typedWord, typedWordValid, correctionAvailable);
1880    }
1881
1882    private void showSuggestions(List<CharSequence> stringList, CharSequence typedWord,
1883            boolean typedWordValid, boolean correctionAvailable) {
1884        setSuggestions(stringList, false, typedWordValid, correctionAvailable);
1885        if (stringList.size() > 0) {
1886            if (correctionAvailable && !typedWordValid && stringList.size() > 1) {
1887                mBestWord = stringList.get(1);
1888            } else {
1889                mBestWord = typedWord;
1890            }
1891        } else {
1892            mBestWord = null;
1893        }
1894        setCandidatesViewShown(isCandidateStripVisible());
1895    }
1896
1897    private boolean pickDefaultSuggestion() {
1898        // Complete any pending candidate query first
1899        if (mHandler.hasPendingUpdateSuggestions()) {
1900            mHandler.cancelUpdateSuggestions();
1901            updateSuggestions();
1902        }
1903        if (mBestWord != null && mBestWord.length() > 0) {
1904            TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord);
1905            mJustAccepted = true;
1906            pickSuggestion(mBestWord, false);
1907            // Add the word to the auto dictionary if it's not a known word
1908            addToDictionaries(mBestWord, AutoDictionary.FREQUENCY_FOR_TYPED);
1909            return true;
1910
1911        }
1912        return false;
1913    }
1914
1915    public void pickSuggestionManually(int index, CharSequence suggestion) {
1916        List<CharSequence> suggestions = mCandidateView.getSuggestions();
1917        if (mAfterVoiceInput && mShowingVoiceSuggestions) {
1918            mVoiceInput.flushAllTextModificationCounters();
1919            // send this intent AFTER logging any prior aggregated edits.
1920            mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.toString(), index,
1921                                                          mWordSeparators,
1922                                                          getCurrentInputConnection());
1923        }
1924
1925        final boolean correcting = TextEntryState.isCorrecting();
1926        InputConnection ic = getCurrentInputConnection();
1927        if (ic != null) {
1928            ic.beginBatchEdit();
1929        }
1930        if (mCompletionOn && mCompletions != null && index >= 0
1931                && index < mCompletions.length) {
1932            CompletionInfo ci = mCompletions[index];
1933            if (ic != null) {
1934                ic.commitCompletion(ci);
1935            }
1936            mCommittedLength = suggestion.length();
1937            if (mCandidateView != null) {
1938                mCandidateView.clear();
1939            }
1940            updateShiftKeyState(getCurrentInputEditorInfo());
1941            if (ic != null) {
1942                ic.endBatchEdit();
1943            }
1944            return;
1945        }
1946
1947        // If this is a punctuation, apply it through the normal key press
1948        if (suggestion.length() == 1 && (isWordSeparator(suggestion.charAt(0))
1949                || isSuggestedPunctuation(suggestion.charAt(0)))) {
1950            // Word separators are suggested before the user inputs something.
1951            // So, LatinImeLogger logs "" as a user's input.
1952            LatinImeLogger.logOnManualSuggestion(
1953                    "", suggestion.toString(), index, suggestions);
1954            final char primaryCode = suggestion.charAt(0);
1955            onKey(primaryCode, new int[]{primaryCode}, BaseKeyboardView.NOT_A_TOUCH_COORDINATE,
1956                    BaseKeyboardView.NOT_A_TOUCH_COORDINATE);
1957            if (ic != null) {
1958                ic.endBatchEdit();
1959            }
1960            return;
1961        }
1962        mJustAccepted = true;
1963        pickSuggestion(suggestion, correcting);
1964        // Add the word to the auto dictionary if it's not a known word
1965        if (index == 0) {
1966            addToDictionaries(suggestion, AutoDictionary.FREQUENCY_FOR_PICKED);
1967        } else {
1968            addToBigramDictionary(suggestion, 1);
1969        }
1970        LatinImeLogger.logOnManualSuggestion(mComposing.toString(), suggestion.toString(),
1971                index, suggestions);
1972        TextEntryState.acceptedSuggestion(mComposing.toString(), suggestion);
1973        // Follow it with a space
1974        if (mAutoSpace && !correcting) {
1975            sendSpace();
1976            mJustAddedAutoSpace = true;
1977        }
1978
1979        final boolean showingAddToDictionaryHint = index == 0 && mCorrectionMode > 0
1980                && !mSuggest.isValidWord(suggestion)
1981                && !mSuggest.isValidWord(suggestion.toString().toLowerCase());
1982
1983        if (!correcting) {
1984            // Fool the state watcher so that a subsequent backspace will not do a revert, unless
1985            // we just did a correction, in which case we need to stay in
1986            // TextEntryState.State.PICKED_SUGGESTION state.
1987            TextEntryState.typedCharacter((char) KEYCODE_SPACE, true);
1988            setPunctuationSuggestions();
1989        } else if (!showingAddToDictionaryHint) {
1990            // If we're not showing the "Touch again to save", then show corrections again.
1991            // In case the cursor position doesn't change, make sure we show the suggestions again.
1992            clearSuggestions();
1993            mHandler.postUpdateOldSuggestions();
1994        }
1995        if (showingAddToDictionaryHint) {
1996            mCandidateView.showAddToDictionaryHint(suggestion);
1997        }
1998        if (ic != null) {
1999            ic.endBatchEdit();
2000        }
2001    }
2002
2003    private void rememberReplacedWord(CharSequence suggestion) {
2004        if (mShowingVoiceSuggestions) {
2005            // Retain the replaced word in the alternatives array.
2006            EditingUtil.Range range = new EditingUtil.Range();
2007            String wordToBeReplaced = EditingUtil.getWordAtCursor(getCurrentInputConnection(),
2008                    mWordSeparators, range);
2009            if (!mWordToSuggestions.containsKey(wordToBeReplaced)) {
2010                wordToBeReplaced = wordToBeReplaced.toLowerCase();
2011            }
2012            if (mWordToSuggestions.containsKey(wordToBeReplaced)) {
2013                List<CharSequence> suggestions = mWordToSuggestions.get(wordToBeReplaced);
2014                if (suggestions.contains(suggestion)) {
2015                    suggestions.remove(suggestion);
2016                }
2017                suggestions.add(wordToBeReplaced);
2018                mWordToSuggestions.remove(wordToBeReplaced);
2019                mWordToSuggestions.put(suggestion.toString(), suggestions);
2020            }
2021        }
2022    }
2023
2024    /**
2025     * Commits the chosen word to the text field and saves it for later
2026     * retrieval.
2027     * @param suggestion the suggestion picked by the user to be committed to
2028     *            the text field
2029     * @param correcting whether this is due to a correction of an existing
2030     *            word.
2031     */
2032    private void pickSuggestion(CharSequence suggestion, boolean correcting) {
2033        if (!mKeyboardSwitcher.isKeyboardAvailable())
2034            return;
2035        InputConnection ic = getCurrentInputConnection();
2036        if (ic != null) {
2037            rememberReplacedWord(suggestion);
2038            ic.commitText(suggestion, 1);
2039        }
2040        saveWordInHistory(suggestion);
2041        mPredicting = false;
2042        mCommittedLength = suggestion.length();
2043        mKeyboardSwitcher.setPreferredLetters(null);
2044        // If we just corrected a word, then don't show punctuations
2045        if (!correcting) {
2046            setPunctuationSuggestions();
2047        }
2048        updateShiftKeyState(getCurrentInputEditorInfo());
2049    }
2050
2051    /**
2052     * Tries to apply any voice alternatives for the word if this was a spoken word and
2053     * there are voice alternatives.
2054     * @param touching The word that the cursor is touching, with position information
2055     * @return true if an alternative was found, false otherwise.
2056     */
2057    private boolean applyVoiceAlternatives(EditingUtil.SelectedWord touching) {
2058        // Search for result in spoken word alternatives
2059        String selectedWord = touching.word.toString().trim();
2060        if (!mWordToSuggestions.containsKey(selectedWord)) {
2061            selectedWord = selectedWord.toLowerCase();
2062        }
2063        if (mWordToSuggestions.containsKey(selectedWord)) {
2064            mShowingVoiceSuggestions = true;
2065            List<CharSequence> suggestions = mWordToSuggestions.get(selectedWord);
2066            // If the first letter of touching is capitalized, make all the suggestions
2067            // start with a capital letter.
2068            if (Character.isUpperCase(touching.word.charAt(0))) {
2069                for (int i = 0; i < suggestions.size(); i++) {
2070                    String origSugg = (String) suggestions.get(i);
2071                    String capsSugg = origSugg.toUpperCase().charAt(0)
2072                            + origSugg.subSequence(1, origSugg.length()).toString();
2073                    suggestions.set(i, capsSugg);
2074                }
2075            }
2076            setSuggestions(suggestions, false, true, true);
2077            setCandidatesViewShown(true);
2078            return true;
2079        }
2080        return false;
2081    }
2082
2083    /**
2084     * Tries to apply any typed alternatives for the word if we have any cached alternatives,
2085     * otherwise tries to find new corrections and completions for the word.
2086     * @param touching The word that the cursor is touching, with position information
2087     * @return true if an alternative was found, false otherwise.
2088     */
2089    private boolean applyTypedAlternatives(EditingUtil.SelectedWord touching) {
2090        // If we didn't find a match, search for result in typed word history
2091        WordComposer foundWord = null;
2092        WordAlternatives alternatives = null;
2093        for (WordAlternatives entry : mWordHistory) {
2094            if (TextUtils.equals(entry.getChosenWord(), touching.word)) {
2095                if (entry instanceof TypedWordAlternatives) {
2096                    foundWord = ((TypedWordAlternatives) entry).word;
2097                }
2098                alternatives = entry;
2099                break;
2100            }
2101        }
2102        // If we didn't find a match, at least suggest completions
2103        if (foundWord == null
2104                && (mSuggest.isValidWord(touching.word)
2105                        || mSuggest.isValidWord(touching.word.toString().toLowerCase()))) {
2106            foundWord = new WordComposer();
2107            for (int i = 0; i < touching.word.length(); i++) {
2108                foundWord.add(touching.word.charAt(i), new int[] {
2109                    touching.word.charAt(i)
2110                });
2111            }
2112            foundWord.setFirstCharCapitalized(Character.isUpperCase(touching.word.charAt(0)));
2113        }
2114        // Found a match, show suggestions
2115        if (foundWord != null || alternatives != null) {
2116            if (alternatives == null) {
2117                alternatives = new TypedWordAlternatives(touching.word, foundWord);
2118            }
2119            showCorrections(alternatives);
2120            if (foundWord != null) {
2121                mWord = new WordComposer(foundWord);
2122            } else {
2123                mWord.reset();
2124            }
2125            return true;
2126        }
2127        return false;
2128    }
2129
2130    private void setOldSuggestions() {
2131        mShowingVoiceSuggestions = false;
2132        if (mCandidateView != null && mCandidateView.isShowingAddToDictionaryHint()) {
2133            return;
2134        }
2135        InputConnection ic = getCurrentInputConnection();
2136        if (ic == null) return;
2137        if (!mPredicting) {
2138            // Extract the selected or touching text
2139            EditingUtil.SelectedWord touching = EditingUtil.getWordAtCursorOrSelection(ic,
2140                    mLastSelectionStart, mLastSelectionEnd, mWordSeparators);
2141
2142            if (touching != null && touching.word.length() > 1) {
2143                ic.beginBatchEdit();
2144
2145                if (!applyVoiceAlternatives(touching) && !applyTypedAlternatives(touching)) {
2146                    abortCorrection(true);
2147                } else {
2148                    TextEntryState.selectedForCorrection();
2149                    EditingUtil.underlineWord(ic, touching);
2150                }
2151
2152                ic.endBatchEdit();
2153            } else {
2154                abortCorrection(true);
2155                setPunctuationSuggestions();  // Show the punctuation suggestions list
2156            }
2157        } else {
2158            abortCorrection(true);
2159        }
2160    }
2161
2162    private void setPunctuationSuggestions() {
2163        setCandidatesViewShown(isCandidateStripVisible());
2164        setSuggestions(mSuggestPuncList, false, false, false);
2165    }
2166
2167    private void addToDictionaries(CharSequence suggestion, int frequencyDelta) {
2168        checkAddToDictionary(suggestion, frequencyDelta, false);
2169    }
2170
2171    private void addToBigramDictionary(CharSequence suggestion, int frequencyDelta) {
2172        checkAddToDictionary(suggestion, frequencyDelta, true);
2173    }
2174
2175    /**
2176     * Adds to the UserBigramDictionary and/or AutoDictionary
2177     * @param addToBigramDictionary true if it should be added to bigram dictionary if possible
2178     */
2179    private void checkAddToDictionary(CharSequence suggestion, int frequencyDelta,
2180            boolean addToBigramDictionary) {
2181        if (suggestion == null || suggestion.length() < 1) return;
2182        // Only auto-add to dictionary if auto-correct is ON. Otherwise we'll be
2183        // adding words in situations where the user or application really didn't
2184        // want corrections enabled or learned.
2185        if (!(mCorrectionMode == Suggest.CORRECTION_FULL
2186                || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM)) {
2187            return;
2188        }
2189        if (suggestion != null) {
2190            if (!addToBigramDictionary && mAutoDictionary.isValidWord(suggestion)
2191                    || (!mSuggest.isValidWord(suggestion.toString())
2192                    && !mSuggest.isValidWord(suggestion.toString().toLowerCase()))) {
2193                mAutoDictionary.addWord(suggestion.toString(), frequencyDelta);
2194            }
2195
2196            if (mUserBigramDictionary != null) {
2197                CharSequence prevWord = EditingUtil.getPreviousWord(getCurrentInputConnection(),
2198                        mSentenceSeparators);
2199                if (!TextUtils.isEmpty(prevWord)) {
2200                    mUserBigramDictionary.addBigrams(prevWord.toString(), suggestion.toString());
2201                }
2202            }
2203        }
2204    }
2205
2206    private boolean isCursorTouchingWord() {
2207        InputConnection ic = getCurrentInputConnection();
2208        if (ic == null) return false;
2209        CharSequence toLeft = ic.getTextBeforeCursor(1, 0);
2210        CharSequence toRight = ic.getTextAfterCursor(1, 0);
2211        if (!TextUtils.isEmpty(toLeft)
2212                && !isWordSeparator(toLeft.charAt(0))
2213                && !isSuggestedPunctuation(toLeft.charAt(0))) {
2214            return true;
2215        }
2216        if (!TextUtils.isEmpty(toRight)
2217                && !isWordSeparator(toRight.charAt(0))
2218                && !isSuggestedPunctuation(toRight.charAt(0))) {
2219            return true;
2220        }
2221        return false;
2222    }
2223
2224    private boolean sameAsTextBeforeCursor(InputConnection ic, CharSequence text) {
2225        CharSequence beforeText = ic.getTextBeforeCursor(text.length(), 0);
2226        return TextUtils.equals(text, beforeText);
2227    }
2228
2229    public void revertLastWord(boolean deleteChar) {
2230        final int length = mComposing.length();
2231        if (!mPredicting && length > 0) {
2232            final InputConnection ic = getCurrentInputConnection();
2233            mPredicting = true;
2234            mJustReverted = true;
2235            if (deleteChar) ic.deleteSurroundingText(1, 0);
2236            int toDelete = mCommittedLength;
2237            CharSequence toTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0);
2238            if (toTheLeft != null && toTheLeft.length() > 0
2239                    && isWordSeparator(toTheLeft.charAt(0))) {
2240                toDelete--;
2241            }
2242            ic.deleteSurroundingText(toDelete, 0);
2243            ic.setComposingText(mComposing, 1);
2244            TextEntryState.backspace();
2245            mHandler.postUpdateSuggestions();
2246        } else {
2247            sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
2248            mJustReverted = false;
2249        }
2250    }
2251
2252    protected String getWordSeparators() {
2253        return mWordSeparators;
2254    }
2255
2256    public boolean isWordSeparator(int code) {
2257        String separators = getWordSeparators();
2258        return separators.contains(String.valueOf((char)code));
2259    }
2260
2261    private boolean isSentenceSeparator(int code) {
2262        return mSentenceSeparators.contains(String.valueOf((char)code));
2263    }
2264
2265    private void sendSpace() {
2266        sendKeyChar((char)KEYCODE_SPACE);
2267        updateShiftKeyState(getCurrentInputEditorInfo());
2268        //onKey(KEY_SPACE[0], KEY_SPACE);
2269    }
2270
2271    public boolean preferCapitalization() {
2272        return mWord.isFirstCharCapitalized();
2273    }
2274
2275    private void toggleLanguage(boolean reset, boolean next) {
2276        if (reset) {
2277            mLanguageSwitcher.reset();
2278        } else {
2279            if (next) {
2280                mLanguageSwitcher.next();
2281            } else {
2282                mLanguageSwitcher.prev();
2283            }
2284        }
2285        final int mode = mKeyboardSwitcher.getKeyboardMode();
2286        final EditorInfo attribute = getCurrentInputEditorInfo();
2287        final int imeOptions = (attribute != null) ? attribute.imeOptions : 0;
2288        mKeyboardSwitcher.loadKeyboard(mode, imeOptions, mVoiceButtonEnabled,
2289                mVoiceButtonOnPrimary);
2290        initSuggest(mLanguageSwitcher.getInputLanguage());
2291        mLanguageSwitcher.persist();
2292        updateShiftKeyState(getCurrentInputEditorInfo());
2293    }
2294
2295    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
2296            String key) {
2297        if (PREF_SELECTED_LANGUAGES.equals(key)) {
2298            mLanguageSwitcher.loadLocales(sharedPreferences);
2299            mRefreshKeyboardRequired = true;
2300        } else if (PREF_RECORRECTION_ENABLED.equals(key)) {
2301            mReCorrectionEnabled = sharedPreferences.getBoolean(PREF_RECORRECTION_ENABLED,
2302                    getResources().getBoolean(R.bool.default_recorrection_enabled));
2303        }
2304    }
2305
2306    public void swipeRight() {
2307        if (LatinKeyboardView.DEBUG_AUTO_PLAY) {
2308            ClipboardManager cm = ((ClipboardManager)getSystemService(CLIPBOARD_SERVICE));
2309            CharSequence text = cm.getText();
2310            if (!TextUtils.isEmpty(text)) {
2311                mKeyboardSwitcher.getInputView().startPlaying(text.toString());
2312            }
2313        }
2314    }
2315
2316    public void swipeLeft() {
2317    }
2318
2319    public void swipeDown() {
2320        handleClose();
2321    }
2322
2323    public void swipeUp() {
2324        //launchSettings();
2325    }
2326
2327    public void onPress(int primaryCode) {
2328        vibrate();
2329        playKeyClick(primaryCode);
2330        KeyboardSwitcher switcher = mKeyboardSwitcher;
2331        if (!switcher.isKeyboardAvailable())
2332            return;
2333        final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
2334        if (distinctMultiTouch && primaryCode == BaseKeyboard.KEYCODE_SHIFT) {
2335            // In alphabet mode, we call handleShift() to go into the shifted mode in this
2336            // method, onPress(), only when we are in the small letter mode.
2337            if (switcher.isAlphabetMode() && switcher.isShifted()) {
2338                switcher.onPressShiftOnShifted();
2339            } else {
2340                switcher.onPressShift();
2341                handleShift();
2342            }
2343        } else if (distinctMultiTouch && primaryCode == BaseKeyboard.KEYCODE_MODE_CHANGE) {
2344            switcher.onPressSymbol();
2345            changeKeyboardMode();
2346        } else {
2347            switcher.onOtherKeyPressed();
2348        }
2349    }
2350
2351    public void onRelease(int primaryCode) {
2352        KeyboardSwitcher switcher = mKeyboardSwitcher;
2353        if (!switcher.isKeyboardAvailable())
2354            return;
2355        // Reset any drag flags in the keyboard
2356        switcher.keyReleased();
2357        final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
2358        if (distinctMultiTouch && primaryCode == BaseKeyboard.KEYCODE_SHIFT) {
2359            if (switcher.isShiftMomentary()) {
2360                resetShift();
2361            }
2362            if (switcher.isAlphabetMode()) {
2363                // In alphabet mode, we call handleShift() to go into the small letter mode in this
2364                // method, onRelease(), only when we are in the shifted modes -- temporary shifted
2365                // mode or caps lock mode.
2366                if (switcher.isShifted() && switcher.isShiftPressingOnShifted()) {
2367                    handleShift();
2368                }
2369            }
2370            switcher.onReleaseShift();
2371        } else if (distinctMultiTouch && primaryCode == BaseKeyboard.KEYCODE_MODE_CHANGE) {
2372            if (switcher.isSymbolMomentary()) {
2373                changeKeyboardMode();
2374            }
2375            switcher.onReleaseSymbol();
2376        }
2377    }
2378
2379    private FieldContext makeFieldContext() {
2380        return new FieldContext(
2381                getCurrentInputConnection(),
2382                getCurrentInputEditorInfo(),
2383                mLanguageSwitcher.getInputLanguage(),
2384                mLanguageSwitcher.getEnabledLanguages());
2385    }
2386
2387    private boolean fieldCanDoVoice(FieldContext fieldContext) {
2388        return !mPasswordText
2389                && mVoiceInput != null
2390                && !mVoiceInput.isBlacklistedField(fieldContext);
2391    }
2392
2393    private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) {
2394        return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext)
2395                && !(attribute != null
2396                        && IME_OPTION_NO_MICROPHONE.equals(attribute.privateImeOptions))
2397                && SpeechRecognizer.isRecognitionAvailable(this);
2398    }
2399
2400    // receive ringer mode changes to detect silent mode
2401    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
2402        @Override
2403        public void onReceive(Context context, Intent intent) {
2404            updateRingerMode();
2405        }
2406    };
2407
2408    // update flags for silent mode
2409    private void updateRingerMode() {
2410        if (mAudioManager == null) {
2411            mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
2412        }
2413        if (mAudioManager != null) {
2414            mSilentMode = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL);
2415        }
2416    }
2417
2418    private void playKeyClick(int primaryCode) {
2419        // if mAudioManager is null, we don't have the ringer state yet
2420        // mAudioManager will be set by updateRingerMode
2421        if (mAudioManager == null) {
2422            if (mKeyboardSwitcher.getInputView() != null) {
2423                updateRingerMode();
2424            }
2425        }
2426        if (mSoundOn && !mSilentMode) {
2427            // FIXME: Volume and enable should come from UI settings
2428            // FIXME: These should be triggered after auto-repeat logic
2429            int sound = AudioManager.FX_KEYPRESS_STANDARD;
2430            switch (primaryCode) {
2431                case BaseKeyboard.KEYCODE_DELETE:
2432                    sound = AudioManager.FX_KEYPRESS_DELETE;
2433                    break;
2434                case KEYCODE_ENTER:
2435                    sound = AudioManager.FX_KEYPRESS_RETURN;
2436                    break;
2437                case KEYCODE_SPACE:
2438                    sound = AudioManager.FX_KEYPRESS_SPACEBAR;
2439                    break;
2440            }
2441            mAudioManager.playSoundEffect(sound, FX_VOLUME);
2442        }
2443    }
2444
2445    private void vibrate() {
2446        if (!mVibrateOn) {
2447            return;
2448        }
2449        LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
2450        if (inputView != null) {
2451            inputView.performHapticFeedback(
2452                    HapticFeedbackConstants.KEYBOARD_TAP,
2453                    HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
2454        }
2455    }
2456
2457    private void checkTutorial(String privateImeOptions) {
2458        if (privateImeOptions == null) return;
2459        if (privateImeOptions.equals("com.android.setupwizard:ShowTutorial")) {
2460            if (mTutorial == null) mHandler.startTutorial();
2461        } else if (privateImeOptions.equals("com.android.setupwizard:HideTutorial")) {
2462            if (mTutorial != null) {
2463                if (mTutorial.close()) {
2464                    mTutorial = null;
2465                }
2466            }
2467        }
2468    }
2469
2470    // Tutorial.TutorialListener
2471    public void onTutorialDone() {
2472        sendDownUpKeyEvents(-1); // Inform the setupwizard that tutorial is in last bubble
2473        mTutorial = null;
2474    }
2475
2476    public void promoteToUserDictionary(String word, int frequency) {
2477        if (mUserDictionary.isValidWord(word)) return;
2478        mUserDictionary.addWord(word, frequency);
2479    }
2480
2481    public WordComposer getCurrentWord() {
2482        return mWord;
2483    }
2484
2485    public boolean getPopupOn() {
2486        return mPopupOn;
2487    }
2488
2489    private void updateCorrectionMode() {
2490        mHasDictionary = mSuggest != null ? mSuggest.hasMainDictionary() : false;
2491        mAutoCorrectOn = (mAutoCorrectEnabled || mQuickFixes)
2492                && !mInputTypeNoAutoCorrect && mHasDictionary;
2493        mCorrectionMode = (mAutoCorrectOn && mAutoCorrectEnabled)
2494                ? Suggest.CORRECTION_FULL
2495                : (mAutoCorrectOn ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE);
2496        mCorrectionMode = (mBigramSuggestionEnabled && mAutoCorrectOn && mAutoCorrectEnabled)
2497                ? Suggest.CORRECTION_FULL_BIGRAM : mCorrectionMode;
2498        if (mSuggest != null) {
2499            mSuggest.setCorrectionMode(mCorrectionMode);
2500        }
2501    }
2502
2503    private void updateAutoTextEnabled(Locale systemLocale) {
2504        if (mSuggest == null) return;
2505        boolean different =
2506                !systemLocale.getLanguage().equalsIgnoreCase(mInputLocale.substring(0, 2));
2507        mSuggest.setAutoTextEnabled(!different && mQuickFixes);
2508    }
2509
2510    private void updateSuggestionVisibility(SharedPreferences prefs) {
2511        final String suggestionVisiblityStr = prefs.getString(
2512                PREF_SHOW_SUGGESTIONS_SETTING, mResources.getString(
2513                        R.string.prefs_suggestion_visibility_default_value));
2514        for (int visibility : SUGGESTION_VISIBILITY_VALUE_ARRAY) {
2515            if (suggestionVisiblityStr.equals(mResources.getString(visibility))) {
2516                mSuggestionVisibility = visibility;
2517                break;
2518            }
2519        }
2520    }
2521
2522    protected void launchSettings() {
2523        launchSettings(LatinIMESettings.class);
2524    }
2525
2526    public void launchDebugSettings() {
2527        launchSettings(LatinIMEDebugSettings.class);
2528    }
2529
2530    protected void launchSettings(Class<? extends PreferenceActivity> settingsClass) {
2531        handleClose();
2532        Intent intent = new Intent();
2533        intent.setClass(LatinIME.this, settingsClass);
2534        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2535        startActivity(intent);
2536    }
2537
2538    private void loadSettings(EditorInfo attribute) {
2539        // Get the settings preferences
2540        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
2541        Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
2542        mVibrateOn = vibrator != null && vibrator.hasVibrator()
2543                && sp.getBoolean(LatinIMESettings.PREF_VIBRATE_ON, false);
2544        mSoundOn = sp.getBoolean(PREF_SOUND_ON, false);
2545        mPopupOn = sp.getBoolean(PREF_POPUP_ON,
2546                mResources.getBoolean(R.bool.default_popup_preview));
2547        mAutoCap = sp.getBoolean(PREF_AUTO_CAP, true);
2548        mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true);
2549        mHasUsedVoiceInput = sp.getBoolean(PREF_HAS_USED_VOICE_INPUT, false);
2550        mHasUsedVoiceInputUnsupportedLocale =
2551                sp.getBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, false);
2552
2553        // Get the current list of supported locales and check the current locale against that
2554        // list. We cache this value so as not to check it every time the user starts a voice
2555        // input. Because this method is called by onStartInputView, this should mean that as
2556        // long as the locale doesn't change while the user is keeping the IME open, the
2557        // value should never be stale.
2558        String supportedLocalesString = SettingsUtil.getSettingsString(
2559                getContentResolver(),
2560                SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES,
2561                DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES);
2562        ArrayList<String> voiceInputSupportedLocales =
2563                newArrayList(supportedLocalesString.split("\\s+"));
2564
2565        mLocaleSupportedForVoiceInput = voiceInputSupportedLocales.contains(mInputLocale);
2566
2567        mAutoCorrectEnabled = isAutoCorrectEnabled(sp);
2568        mBigramSuggestionEnabled = mAutoCorrectEnabled && isBigramSuggestionEnabled(sp);
2569        loadAndSetAutoCompletionThreshold(sp);
2570
2571        if (VOICE_INSTALLED) {
2572            final String voiceMode = sp.getString(PREF_VOICE_MODE,
2573                    getString(R.string.voice_mode_main));
2574            mVoiceButtonEnabled = !voiceMode.equals(getString(R.string.voice_mode_off))
2575                    && shouldShowVoiceButton(makeFieldContext(), attribute);
2576            mVoiceButtonOnPrimary = voiceMode.equals(getString(R.string.voice_mode_main));
2577        }
2578        updateCorrectionMode();
2579        updateAutoTextEnabled(mResources.getConfiguration().locale);
2580        updateSuggestionVisibility(sp);
2581        mLanguageSwitcher.loadLocales(sp);
2582    }
2583
2584    /**
2585     *  load Auto completion threshold from SharedPreferences,
2586     *  and modify mSuggest's threshold.
2587     */
2588    private void loadAndSetAutoCompletionThreshold(SharedPreferences sp) {
2589        // When mSuggest is not initialized, cannnot modify mSuggest's threshold.
2590        if (mSuggest == null) return;
2591        // When auto completion setting is turned off, the threshold is ignored.
2592        if (!isAutoCorrectEnabled(sp)) return;
2593
2594        final String currentAutoCompletionSetting = sp.getString(PREF_AUTO_COMPLETION_THRESHOLD,
2595                mResources.getString(R.string.auto_completion_threshold_mode_value_modest));
2596        final String[] autoCompletionThresholdValues = mResources.getStringArray(
2597                R.array.auto_complete_threshold_values);
2598        // When autoCompletionThreshold is greater than 1.0,
2599        // auto completion is virtually turned off.
2600        double autoCompletionThreshold = Double.MAX_VALUE;
2601        try {
2602            final int arrayIndex = Integer.valueOf(currentAutoCompletionSetting);
2603            if (arrayIndex >= 0 && arrayIndex < autoCompletionThresholdValues.length) {
2604                autoCompletionThreshold = Double.parseDouble(
2605                        autoCompletionThresholdValues[arrayIndex]);
2606            }
2607        } catch (NumberFormatException e) {
2608            // Whenever the threshold settings are correct,
2609            // never come here.
2610            autoCompletionThreshold = Double.MAX_VALUE;
2611            Log.w(TAG, "Cannot load auto completion threshold setting."
2612                    + " currentAutoCompletionSetting: " + currentAutoCompletionSetting
2613                    + ", autoCompletionThresholdValues: "
2614                    + Arrays.toString(autoCompletionThresholdValues));
2615        }
2616        // TODO: This should be refactored :
2617        //           setAutoCompleteThreshold should be called outside of this method.
2618        mSuggest.setAutoCompleteThreshold(autoCompletionThreshold);
2619    }
2620
2621    private boolean isAutoCorrectEnabled(SharedPreferences sp) {
2622        final String currentAutoCompletionSetting = sp.getString(PREF_AUTO_COMPLETION_THRESHOLD,
2623                mResources.getString(R.string.auto_completion_threshold_mode_value_modest));
2624        final String autoCompletionOff = mResources.getString(
2625                R.string.auto_completion_threshold_mode_value_off);
2626        return !currentAutoCompletionSetting.equals(autoCompletionOff);
2627    }
2628
2629    private boolean isBigramSuggestionEnabled(SharedPreferences sp) {
2630       // TODO: Define default value instead of 'true'.
2631       return sp.getBoolean(PREF_BIGRAM_SUGGESTIONS, true);
2632    }
2633
2634    private void initSuggestPuncList() {
2635        mSuggestPuncList = new ArrayList<CharSequence>();
2636        mSuggestPuncs = mResources.getString(R.string.suggested_punctuations);
2637        if (mSuggestPuncs != null) {
2638            for (int i = 0; i < mSuggestPuncs.length(); i++) {
2639                mSuggestPuncList.add(mSuggestPuncs.subSequence(i, i + 1));
2640            }
2641        }
2642    }
2643
2644    private boolean isSuggestedPunctuation(int code) {
2645        return mSuggestPuncs.contains(String.valueOf((char)code));
2646    }
2647
2648    private void showOptionsMenu() {
2649        AlertDialog.Builder builder = new AlertDialog.Builder(this);
2650        builder.setCancelable(true);
2651        builder.setIcon(R.drawable.ic_dialog_keyboard);
2652        builder.setNegativeButton(android.R.string.cancel, null);
2653        CharSequence itemSettings = getString(R.string.english_ime_settings);
2654        CharSequence itemInputMethod = getString(R.string.selectInputMethod);
2655        builder.setItems(new CharSequence[] {
2656                itemInputMethod, itemSettings},
2657                new DialogInterface.OnClickListener() {
2658
2659            public void onClick(DialogInterface di, int position) {
2660                di.dismiss();
2661                switch (position) {
2662                    case POS_SETTINGS:
2663                        launchSettings();
2664                        break;
2665                    case POS_METHOD:
2666                        showInputMethodSubtypePicker();
2667                        break;
2668                }
2669            }
2670        });
2671        builder.setTitle(mResources.getString(R.string.english_ime_input_options));
2672        mOptionsDialog = builder.create();
2673        Window window = mOptionsDialog.getWindow();
2674        WindowManager.LayoutParams lp = window.getAttributes();
2675        lp.token = mKeyboardSwitcher.getInputView().getWindowToken();
2676        lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
2677        window.setAttributes(lp);
2678        window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
2679        mOptionsDialog.show();
2680    }
2681
2682    private void changeKeyboardMode() {
2683        KeyboardSwitcher switcher = mKeyboardSwitcher;
2684        switcher.toggleSymbols();
2685        if (!switcher.isKeyboardAvailable())
2686            return;
2687        if (switcher.isShiftLocked() && switcher.isAlphabetMode()) {
2688            switcher.setShiftLocked(true);
2689        }
2690
2691        updateShiftKeyState(getCurrentInputEditorInfo());
2692    }
2693
2694    public static <E> ArrayList<E> newArrayList(E... elements) {
2695        int capacity = (elements.length * 110) / 100 + 5;
2696        ArrayList<E> list = new ArrayList<E>(capacity);
2697        Collections.addAll(list, elements);
2698        return list;
2699    }
2700
2701    @Override
2702    protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
2703        super.dump(fd, fout, args);
2704
2705        final Printer p = new PrintWriterPrinter(fout);
2706        p.println("LatinIME state :");
2707        p.println("  Keyboard mode = " + mKeyboardSwitcher.getKeyboardMode());
2708        p.println("  mComposing=" + mComposing.toString());
2709        p.println("  mPredictionOn=" + mPredictionOn);
2710        p.println("  mCorrectionMode=" + mCorrectionMode);
2711        p.println("  mPredicting=" + mPredicting);
2712        p.println("  mAutoCorrectOn=" + mAutoCorrectOn);
2713        p.println("  mAutoSpace=" + mAutoSpace);
2714        p.println("  mCompletionOn=" + mCompletionOn);
2715        p.println("  TextEntryState.state=" + TextEntryState.getState());
2716        p.println("  mSoundOn=" + mSoundOn);
2717        p.println("  mVibrateOn=" + mVibrateOn);
2718        p.println("  mPopupOn=" + mPopupOn);
2719    }
2720
2721    // Characters per second measurement
2722
2723    private long mLastCpsTime;
2724    private static final int CPS_BUFFER_SIZE = 16;
2725    private long[] mCpsIntervals = new long[CPS_BUFFER_SIZE];
2726    private int mCpsIndex;
2727
2728    private void measureCps() {
2729        long now = System.currentTimeMillis();
2730        if (mLastCpsTime == 0) mLastCpsTime = now - 100; // Initial
2731        mCpsIntervals[mCpsIndex] = now - mLastCpsTime;
2732        mLastCpsTime = now;
2733        mCpsIndex = (mCpsIndex + 1) % CPS_BUFFER_SIZE;
2734        long total = 0;
2735        for (int i = 0; i < CPS_BUFFER_SIZE; i++) total += mCpsIntervals[i];
2736        System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total));
2737    }
2738
2739    public void onAutoCompletionStateChanged(boolean isAutoCompletion) {
2740        mKeyboardSwitcher.onAutoCompletionStateChanged(isAutoCompletion);
2741    }
2742}
2743