LatinIME.java revision 6c2f9f5ba7afedc183086d4ee3a7aa50b3866edc
1/*
2 * Copyright (C) 2008-2009 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.android.inputmethod.latin;
18
19import com.android.inputmethod.voice.EditingUtil;
20import com.android.inputmethod.voice.FieldContext;
21import com.android.inputmethod.voice.SettingsUtil;
22import com.android.inputmethod.voice.VoiceInput;
23
24import android.app.AlertDialog;
25import android.content.BroadcastReceiver;
26import android.content.Context;
27import android.content.DialogInterface;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.SharedPreferences;
31import android.content.res.Configuration;
32import android.content.res.Resources;
33import android.inputmethodservice.InputMethodService;
34import android.inputmethodservice.Keyboard;
35import android.inputmethodservice.KeyboardView;
36import android.media.AudioManager;
37import android.os.Debug;
38import android.os.Handler;
39import android.os.Message;
40import android.os.SystemClock;
41import android.preference.PreferenceManager;
42import android.speech.RecognitionManager;
43import android.text.AutoText;
44import android.text.ClipboardManager;
45import android.text.TextUtils;
46import android.util.Log;
47import android.util.PrintWriterPrinter;
48import android.util.Printer;
49import android.view.HapticFeedbackConstants;
50import android.view.KeyEvent;
51import android.view.LayoutInflater;
52import android.view.View;
53import android.view.ViewParent;
54import android.view.ViewGroup;
55import android.view.Window;
56import android.view.WindowManager;
57import android.view.inputmethod.CompletionInfo;
58import android.view.inputmethod.EditorInfo;
59import android.view.inputmethod.ExtractedText;
60import android.view.inputmethod.ExtractedTextRequest;
61import android.view.inputmethod.InputConnection;
62import android.view.inputmethod.InputMethodManager;
63
64import java.io.FileDescriptor;
65import java.io.PrintWriter;
66import java.util.ArrayList;
67import java.util.Collections;
68import java.util.HashMap;
69import java.util.List;
70import java.util.Locale;
71import java.util.Map;
72
73/**
74 * Input method implementation for Qwerty'ish keyboard.
75 */
76public class LatinIME extends InputMethodService
77        implements KeyboardView.OnKeyboardActionListener,
78        VoiceInput.UiListener,
79        SharedPreferences.OnSharedPreferenceChangeListener {
80    private static final String TAG = "LatinIME";
81    static final boolean DEBUG = false;
82    static final boolean TRACE = false;
83    static final boolean VOICE_INSTALLED = true;
84    static final boolean ENABLE_VOICE_BUTTON = true;
85
86    private static final String PREF_VIBRATE_ON = "vibrate_on";
87    private static final String PREF_SOUND_ON = "sound_on";
88    private static final String PREF_AUTO_CAP = "auto_cap";
89    private static final String PREF_QUICK_FIXES = "quick_fixes";
90    private static final String PREF_SHOW_SUGGESTIONS = "show_suggestions";
91    private static final String PREF_AUTO_COMPLETE = "auto_complete";
92    private static final String PREF_VOICE_MODE = "voice_mode";
93
94    // Whether or not the user has used voice input before (and thus, whether to show the
95    // first-run warning dialog or not).
96    private static final String PREF_HAS_USED_VOICE_INPUT = "has_used_voice_input";
97
98    // Whether or not the user has used voice input from an unsupported locale UI before.
99    // For example, the user has a Chinese UI but activates voice input.
100    private static final String PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE =
101            "has_used_voice_input_unsupported_locale";
102
103    // A list of locales which are supported by default for voice input, unless we get a
104    // different list from Gservices.
105    public static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES =
106            "en " +
107            "en_US " +
108            "en_GB " +
109            "en_AU " +
110            "en_CA " +
111            "en_IE " +
112            "en_IN " +
113            "en_NZ " +
114            "en_SG " +
115            "en_ZA ";
116
117    // The private IME option used to indicate that no microphone should be shown for a
118    // given text field. For instance this is specified by the search dialog when the
119    // dialog is already showing a voice search button.
120    private static final String IME_OPTION_NO_MICROPHONE = "nm";
121
122    public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
123    public static final String PREF_INPUT_LANGUAGE = "input_language";
124
125    private static final int MSG_UPDATE_SUGGESTIONS = 0;
126    private static final int MSG_START_TUTORIAL = 1;
127    private static final int MSG_UPDATE_SHIFT_STATE = 2;
128    private static final int MSG_VOICE_RESULTS = 3;
129    private static final int MSG_START_LISTENING_AFTER_SWIPE = 4;
130
131    // If we detect a swipe gesture within N ms of typing, then swipe is
132    // ignored, since it may in fact be two key presses in quick succession.
133    private static final long MIN_MILLIS_AFTER_TYPING_BEFORE_SWIPE = 1000;
134
135    // How many continuous deletes at which to start deleting at a higher speed.
136    private static final int DELETE_ACCELERATE_AT = 20;
137    // Key events coming any faster than this are long-presses.
138    private static final int QUICK_PRESS = 200;
139
140    static final int KEYCODE_ENTER = '\n';
141    static final int KEYCODE_SPACE = ' ';
142    static final int KEYCODE_PERIOD = '.';
143
144    // Contextual menu positions
145    private static final int POS_SETTINGS = 0;
146    private static final int POS_METHOD = 1;
147
148    private LatinKeyboardView mInputView;
149    private CandidateViewContainer mCandidateViewContainer;
150    private CandidateView mCandidateView;
151    private Suggest mSuggest;
152    private CompletionInfo[] mCompletions;
153
154    private AlertDialog mOptionsDialog;
155    private AlertDialog mVoiceWarningDialog;
156
157    KeyboardSwitcher mKeyboardSwitcher;
158
159    private UserDictionary mUserDictionary;
160    private ContactsDictionary mContactsDictionary;
161    private ExpandableDictionary mAutoDictionary;
162
163    private Hints mHints;
164
165    Resources mResources;
166
167    private String mInputLocale;
168    private String mSystemLocale;
169    private LanguageSwitcher mLanguageSwitcher;
170
171    private StringBuilder mComposing = new StringBuilder();
172    private WordComposer mWord = new WordComposer();
173    private int mCommittedLength;
174    private boolean mPredicting;
175    private boolean mRecognizing;
176    private boolean mAfterVoiceInput;
177    private boolean mImmediatelyAfterVoiceInput;
178    private boolean mShowingVoiceSuggestions;
179    private boolean mImmediatelyAfterVoiceSuggestions;
180    private boolean mVoiceInputHighlighted;
181    private boolean mEnableVoiceButton;
182    private CharSequence mBestWord;
183    private boolean mPredictionOn;
184    private boolean mCompletionOn;
185    private boolean mHasDictionary;
186    private boolean mAutoSpace;
187    private boolean mJustAddedAutoSpace;
188    private boolean mAutoCorrectEnabled;
189    private boolean mAutoCorrectOn;
190    private boolean mCapsLock;
191    private boolean mPasswordText;
192    private boolean mEmailText;
193    private boolean mVibrateOn;
194    private boolean mSoundOn;
195    private boolean mAutoCap;
196    private boolean mQuickFixes;
197    private boolean mHasUsedVoiceInput;
198    private boolean mHasUsedVoiceInputUnsupportedLocale;
199    private boolean mLocaleSupportedForVoiceInput;
200    private boolean mShowSuggestions;
201    private boolean mSuggestionShouldReplaceCurrentWord;
202    private boolean mIsShowingHint;
203    private int     mCorrectionMode;
204    private boolean mEnableVoice = true;
205    private boolean mVoiceOnPrimary;
206    private int     mOrientation;
207    private List<CharSequence> mSuggestPuncList;
208
209    // Indicates whether the suggestion strip is to be on in landscape
210    private boolean mJustAccepted;
211    private CharSequence mJustRevertedSeparator;
212    private int mDeleteCount;
213    private long mLastKeyTime;
214
215    private Tutorial mTutorial;
216
217    private AudioManager mAudioManager;
218    // Align sound effect volume on music volume
219    private final float FX_VOLUME = -1.0f;
220    private boolean mSilentMode;
221
222    private String mWordSeparators;
223    private String mSentenceSeparators;
224    private VoiceInput mVoiceInput;
225    private VoiceResults mVoiceResults = new VoiceResults();
226    private long mSwipeTriggerTimeMillis;
227    private boolean mConfigurationChanging;
228
229    // For each word, a list of potential replacements, usually from voice.
230    private Map<String, List<CharSequence>> mWordToSuggestions =
231            new HashMap<String, List<CharSequence>>();
232
233    private class VoiceResults {
234        List<String> candidates;
235        Map<String, List<CharSequence>> alternatives;
236    }
237    private boolean mRefreshKeyboardRequired;
238
239    Handler mHandler = new Handler() {
240        @Override
241        public void handleMessage(Message msg) {
242            switch (msg.what) {
243                case MSG_UPDATE_SUGGESTIONS:
244                    updateSuggestions();
245                    break;
246                case MSG_START_TUTORIAL:
247                    if (mTutorial == null) {
248                        if (mInputView.isShown()) {
249                            mTutorial = new Tutorial(LatinIME.this, mInputView);
250                            mTutorial.start();
251                        } else {
252                            // Try again soon if the view is not yet showing
253                            sendMessageDelayed(obtainMessage(MSG_START_TUTORIAL), 100);
254                        }
255                    }
256                    break;
257                case MSG_UPDATE_SHIFT_STATE:
258                    updateShiftKeyState(getCurrentInputEditorInfo());
259                    break;
260                case MSG_VOICE_RESULTS:
261                    handleVoiceResults();
262                    break;
263                case MSG_START_LISTENING_AFTER_SWIPE:
264                    if (mLastKeyTime < mSwipeTriggerTimeMillis) {
265                        startListening(true);
266                    }
267            }
268        }
269    };
270
271    @Override public void onCreate() {
272        super.onCreate();
273        //setStatusIcon(R.drawable.ime_qwerty);
274        mResources = getResources();
275        final Configuration conf = mResources.getConfiguration();
276        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
277        mLanguageSwitcher = new LanguageSwitcher(this);
278        mLanguageSwitcher.loadLocales(prefs);
279        mKeyboardSwitcher = new KeyboardSwitcher(this, this);
280        mKeyboardSwitcher.setLanguageSwitcher(mLanguageSwitcher);
281        mSystemLocale = conf.locale.toString();
282        String inputLanguage = mLanguageSwitcher.getInputLanguage();
283        if (inputLanguage == null) {
284            inputLanguage = conf.locale.toString();
285        }
286        initSuggest(inputLanguage);
287        mOrientation = conf.orientation;
288        initSuggestPuncList();
289
290        // register to receive ringer mode changes for silent mode
291        IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
292        registerReceiver(mReceiver, filter);
293        if (VOICE_INSTALLED) {
294            mVoiceInput = new VoiceInput(this, this);
295            mHints = new Hints(this, new Hints.Display() {
296                public void showHint(int viewResource) {
297                    LayoutInflater inflater = (LayoutInflater) getSystemService(
298                            Context.LAYOUT_INFLATER_SERVICE);
299                    View view = inflater.inflate(viewResource, null);
300                    setCandidatesView(view);
301                    setCandidatesViewShown(true);
302                    mIsShowingHint = true;
303                }
304              });
305        }
306        prefs.registerOnSharedPreferenceChangeListener(this);
307    }
308
309    private void initSuggest(String locale) {
310        mInputLocale = locale;
311
312        Resources orig = getResources();
313        Configuration conf = orig.getConfiguration();
314        Locale saveLocale = conf.locale;
315        conf.locale = new Locale(locale);
316        orig.updateConfiguration(conf, orig.getDisplayMetrics());
317        if (mSuggest != null) {
318            mSuggest.close();
319        }
320        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
321        mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true);
322        mSuggest = new Suggest(this, R.raw.main);
323        updateAutoTextEnabled(saveLocale);
324        if (mUserDictionary != null) mUserDictionary.close();
325        mUserDictionary = new UserDictionary(this, mInputLocale);
326        if (mContactsDictionary == null) {
327            mContactsDictionary = new ContactsDictionary(this);
328        }
329        if (mAutoDictionary != null) {
330            mAutoDictionary.close();
331        }
332        mAutoDictionary = new AutoDictionary(this, this, mInputLocale);
333        mSuggest.setUserDictionary(mUserDictionary);
334        mSuggest.setContactsDictionary(mContactsDictionary);
335        mSuggest.setAutoDictionary(mAutoDictionary);
336        updateCorrectionMode();
337        mWordSeparators = mResources.getString(R.string.word_separators);
338        mSentenceSeparators = mResources.getString(R.string.sentence_separators);
339
340        conf.locale = saveLocale;
341        orig.updateConfiguration(conf, orig.getDisplayMetrics());
342    }
343
344    @Override
345    public void onDestroy() {
346        mUserDictionary.close();
347        mContactsDictionary.close();
348        unregisterReceiver(mReceiver);
349        if (VOICE_INSTALLED) {
350            mVoiceInput.destroy();
351        }
352        super.onDestroy();
353    }
354
355    @Override
356    public void onConfigurationChanged(Configuration conf) {
357        // If the system locale changes and is different from the saved
358        // locale (mSystemLocale), then reload the input locale list from the
359        // latin ime settings (shared prefs) and reset the input locale
360        // to the first one.
361        final String systemLocale = conf.locale.toString();
362        if (!TextUtils.equals(systemLocale, mSystemLocale)) {
363            mSystemLocale = systemLocale;
364            if (mLanguageSwitcher != null) {
365                mLanguageSwitcher.loadLocales(
366                        PreferenceManager.getDefaultSharedPreferences(this));
367                toggleLanguage(true, true);
368            } else {
369                reloadKeyboards();
370            }
371        }
372        // If orientation changed while predicting, commit the change
373        if (conf.orientation != mOrientation) {
374            InputConnection ic = getCurrentInputConnection();
375            commitTyped(ic);
376            if (ic != null) ic.finishComposingText(); // For voice input
377            mOrientation = conf.orientation;
378            reloadKeyboards();
379        }
380        mConfigurationChanging = true;
381        super.onConfigurationChanged(conf);
382        if (mRecognizing) {
383            switchToRecognitionStatusView();
384        }
385        mConfigurationChanging = false;
386    }
387
388    @Override
389    public View onCreateInputView() {
390        mInputView = (LatinKeyboardView) getLayoutInflater().inflate(
391                R.layout.input, null);
392        mKeyboardSwitcher.setInputView(mInputView);
393        mKeyboardSwitcher.makeKeyboards(true);
394        mInputView.setOnKeyboardActionListener(this);
395        mKeyboardSwitcher.setKeyboardMode(
396                KeyboardSwitcher.MODE_TEXT, 0,
397                shouldShowVoiceButton(makeFieldContext(), getCurrentInputEditorInfo()));
398        return mInputView;
399    }
400
401    @Override
402    public View onCreateCandidatesView() {
403        mKeyboardSwitcher.makeKeyboards(true);
404        mCandidateViewContainer = (CandidateViewContainer) getLayoutInflater().inflate(
405                R.layout.candidates, null);
406        mCandidateViewContainer.initViews();
407        mCandidateView = (CandidateView) mCandidateViewContainer.findViewById(R.id.candidates);
408        mCandidateView.setService(this);
409        setCandidatesViewShown(true);
410        return mCandidateViewContainer;
411    }
412
413    @Override
414    public void onStartInputView(EditorInfo attribute, boolean restarting) {
415        // In landscape mode, this method gets called without the input view being created.
416        if (mInputView == null) {
417            return;
418        }
419
420        if (mRefreshKeyboardRequired) {
421            mRefreshKeyboardRequired = false;
422            toggleLanguage(true, true);
423        }
424
425        mKeyboardSwitcher.makeKeyboards(false);
426
427        TextEntryState.newSession(this);
428
429        // Most such things we decide below in the switch statement, but we need to know
430        // now whether this is a password text field, because we need to know now (before
431        // the switch statement) whether we want to enable the voice button.
432        mPasswordText = false;
433        int variation = attribute.inputType & EditorInfo.TYPE_MASK_VARIATION;
434        if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD ||
435                variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) {
436            mPasswordText = true;
437        }
438
439        mEnableVoiceButton = shouldShowVoiceButton(makeFieldContext(), attribute);
440        final boolean enableVoiceButton = mEnableVoiceButton && mEnableVoice;
441
442        mAfterVoiceInput = false;
443        mImmediatelyAfterVoiceInput = false;
444        mShowingVoiceSuggestions = false;
445        mImmediatelyAfterVoiceSuggestions = false;
446        mVoiceInputHighlighted = false;
447        mWordToSuggestions.clear();
448        mInputTypeNoAutoCorrect = false;
449        mPredictionOn = false;
450        mCompletionOn = false;
451        mCompletions = null;
452        mCapsLock = false;
453        mEmailText = false;
454        switch (attribute.inputType & EditorInfo.TYPE_MASK_CLASS) {
455            case EditorInfo.TYPE_CLASS_NUMBER:
456            case EditorInfo.TYPE_CLASS_DATETIME:
457                mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_SYMBOLS,
458                        attribute.imeOptions, enableVoiceButton);
459                break;
460            case EditorInfo.TYPE_CLASS_PHONE:
461                mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_PHONE,
462                        attribute.imeOptions, enableVoiceButton);
463                break;
464            case EditorInfo.TYPE_CLASS_TEXT:
465                mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT,
466                        attribute.imeOptions, enableVoiceButton);
467                //startPrediction();
468                mPredictionOn = true;
469                // Make sure that passwords are not displayed in candidate view
470                if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD ||
471                        variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD ) {
472                    mPredictionOn = false;
473                }
474                if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) {
475                    mEmailText = true;
476                }
477                if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
478                        || variation == EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME) {
479                    mAutoSpace = false;
480                } else {
481                    mAutoSpace = true;
482                }
483                if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) {
484                    mPredictionOn = false;
485                    mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_EMAIL,
486                            attribute.imeOptions, enableVoiceButton);
487                } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_URI) {
488                    mPredictionOn = false;
489                    mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_URL,
490                            attribute.imeOptions, enableVoiceButton);
491                } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
492                    mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_IM,
493                            attribute.imeOptions, enableVoiceButton);
494                } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_FILTER) {
495                    mPredictionOn = false;
496                } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) {
497                    mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_WEB,
498                            attribute.imeOptions, enableVoiceButton);
499                    // If it's a browser edit field and auto correct is not ON explicitly, then
500                    // disable auto correction, but keep suggestions on.
501                    if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) {
502                        mInputTypeNoAutoCorrect = true;
503                    }
504                }
505
506                // If NO_SUGGESTIONS is set, don't do prediction.
507                if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) {
508                    mPredictionOn = false;
509                    mInputTypeNoAutoCorrect = true;
510                }
511                // If it's not multiline and the autoCorrect flag is not set, then don't correct
512                if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0 &&
513                        (attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) == 0) {
514                    mInputTypeNoAutoCorrect = true;
515                }
516                if ((attribute.inputType&EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
517                    mPredictionOn = false;
518                    mCompletionOn = true && isFullscreenMode();
519                }
520                updateShiftKeyState(attribute);
521                break;
522            default:
523                mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT,
524                        attribute.imeOptions, enableVoiceButton);
525                updateShiftKeyState(attribute);
526        }
527        mInputView.closing();
528        mComposing.setLength(0);
529        mPredicting = false;
530        mDeleteCount = 0;
531        mJustAddedAutoSpace = false;
532        loadSettings();
533        updateShiftKeyState(attribute);
534
535        setCandidatesViewShown(false);
536        setSuggestions(null, false, false, false);
537
538        // If the dictionary is not big enough, don't auto correct
539        mHasDictionary = mSuggest.hasMainDictionary();
540
541        updateCorrectionMode();
542
543        mInputView.setProximityCorrectionEnabled(true);
544        mPredictionOn = mPredictionOn && (mCorrectionMode > 0 || mShowSuggestions);
545        checkTutorial(attribute.privateImeOptions);
546        if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
547    }
548
549    @Override
550    public void onFinishInput() {
551        super.onFinishInput();
552
553        if (VOICE_INSTALLED && !mConfigurationChanging) {
554            if (mAfterVoiceInput) {
555                mVoiceInput.logInputEnded();
556            }
557            mVoiceInput.flushLogs();
558            mVoiceInput.cancel();
559        }
560        if (mInputView != null) {
561            mInputView.closing();
562        }
563    }
564
565    @Override
566    public void onUpdateExtractedText(int token, ExtractedText text) {
567        super.onUpdateExtractedText(token, text);
568        InputConnection ic = getCurrentInputConnection();
569        if (!mImmediatelyAfterVoiceInput && mAfterVoiceInput && ic != null) {
570            mVoiceInput.logTextModified();
571
572            if (mHints.showPunctuationHintIfNecessary(ic)) {
573                mVoiceInput.logPunctuationHintDisplayed();
574            }
575        }
576        mImmediatelyAfterVoiceInput = false;
577    }
578
579    @Override
580    public void onUpdateSelection(int oldSelStart, int oldSelEnd,
581            int newSelStart, int newSelEnd,
582            int candidatesStart, int candidatesEnd) {
583        super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
584                candidatesStart, candidatesEnd);
585
586        if (DEBUG) {
587            Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart
588                    + ", ose=" + oldSelEnd
589                    + ", nss=" + newSelStart
590                    + ", nse=" + newSelEnd
591                    + ", cs=" + candidatesStart
592                    + ", ce=" + candidatesEnd);
593        }
594
595        mSuggestionShouldReplaceCurrentWord = false;
596        // If the current selection in the text view changes, we should
597        // clear whatever candidate text we have.
598        if ((((mComposing.length() > 0 && mPredicting) || mVoiceInputHighlighted)
599                && (newSelStart != candidatesEnd
600                    || newSelEnd != candidatesEnd))) {
601            mComposing.setLength(0);
602            mPredicting = false;
603            updateSuggestions();
604            TextEntryState.reset();
605            InputConnection ic = getCurrentInputConnection();
606            if (ic != null) {
607                ic.finishComposingText();
608            }
609            mVoiceInputHighlighted = false;
610        } else if (!mPredicting && !mJustAccepted) {
611            switch (TextEntryState.getState()) {
612                case TextEntryState.STATE_ACCEPTED_DEFAULT:
613                    TextEntryState.reset();
614                    // fall through
615                case TextEntryState.STATE_SPACE_AFTER_PICKED:
616                    mJustAddedAutoSpace = false;  // The user moved the cursor.
617                    break;
618            }
619        }
620        mJustAccepted = false;
621        postUpdateShiftKeyState();
622
623        if (VOICE_INSTALLED) {
624            if (mShowingVoiceSuggestions) {
625                if (mImmediatelyAfterVoiceSuggestions) {
626                    mImmediatelyAfterVoiceSuggestions = false;
627                } else {
628                    updateSuggestions();
629                    mShowingVoiceSuggestions = false;
630                }
631            }
632            if (VoiceInput.ENABLE_WORD_CORRECTIONS) {
633                // If we have alternatives for the current word, then show them.
634                String word = EditingUtil.getWordAtCursor(
635                        getCurrentInputConnection(), getWordSeparators());
636                if (word != null && mWordToSuggestions.containsKey(word.trim())) {
637                    mSuggestionShouldReplaceCurrentWord = true;
638                    final List<CharSequence> suggestions = mWordToSuggestions.get(word.trim());
639
640                    setSuggestions(suggestions, false, true, true);
641                    setCandidatesViewShown(true);
642                }
643            }
644        }
645    }
646
647    @Override
648    public void hideWindow() {
649        if (TRACE) Debug.stopMethodTracing();
650        if (mOptionsDialog != null && mOptionsDialog.isShowing()) {
651            mOptionsDialog.dismiss();
652            mOptionsDialog = null;
653        }
654        if (!mConfigurationChanging) {
655            if (mAfterVoiceInput) mVoiceInput.logInputEnded();
656            if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) {
657                mVoiceInput.logKeyboardWarningDialogDismissed();
658                mVoiceWarningDialog.dismiss();
659                mVoiceWarningDialog = null;
660            }
661            if (VOICE_INSTALLED & mRecognizing) {
662                mVoiceInput.cancel();
663            }
664        }
665        super.hideWindow();
666        TextEntryState.endSession();
667    }
668
669    @Override
670    public void onDisplayCompletions(CompletionInfo[] completions) {
671        if (false) {
672            Log.i("foo", "Received completions:");
673            for (int i=0; i<(completions != null ? completions.length : 0); i++) {
674                Log.i("foo", "  #" + i + ": " + completions[i]);
675            }
676        }
677        if (mCompletionOn) {
678            mCompletions = completions;
679            if (completions == null) {
680                setSuggestions(null, false, false, false);
681                return;
682            }
683
684            List<CharSequence> stringList = new ArrayList<CharSequence>();
685            for (int i=0; i<(completions != null ? completions.length : 0); i++) {
686                CompletionInfo ci = completions[i];
687                if (ci != null) stringList.add(ci.getText());
688            }
689            //CharSequence typedWord = mWord.getTypedWord();
690            setSuggestions(stringList, true, true, true);
691            mBestWord = null;
692            setCandidatesViewShown(isCandidateStripVisible() || mCompletionOn);
693        }
694    }
695
696    @Override
697    public void setCandidatesViewShown(boolean shown) {
698        // TODO: Remove this if we support candidates with hard keyboard
699        if (onEvaluateInputViewShown()) {
700            // Show the candidates view only if input view is showing
701            super.setCandidatesViewShown(shown && mInputView != null && mInputView.isShown());
702        }
703    }
704
705    @Override
706    public void onComputeInsets(InputMethodService.Insets outInsets) {
707        super.onComputeInsets(outInsets);
708        if (!isFullscreenMode()) {
709            outInsets.contentTopInsets = outInsets.visibleTopInsets;
710        }
711    }
712
713    @Override
714    public boolean onKeyDown(int keyCode, KeyEvent event) {
715        switch (keyCode) {
716            case KeyEvent.KEYCODE_BACK:
717                if (event.getRepeatCount() == 0 && mInputView != null) {
718                    if (mInputView.handleBack()) {
719                        return true;
720                    } else if (mTutorial != null) {
721                        mTutorial.close();
722                        mTutorial = null;
723                    }
724                }
725                break;
726            case KeyEvent.KEYCODE_DPAD_DOWN:
727            case KeyEvent.KEYCODE_DPAD_UP:
728            case KeyEvent.KEYCODE_DPAD_LEFT:
729            case KeyEvent.KEYCODE_DPAD_RIGHT:
730                // If tutorial is visible, don't allow dpad to work
731                if (mTutorial != null) {
732                    return true;
733                }
734                break;
735        }
736        return super.onKeyDown(keyCode, event);
737    }
738
739    @Override
740    public boolean onKeyUp(int keyCode, KeyEvent event) {
741        switch (keyCode) {
742            case KeyEvent.KEYCODE_DPAD_DOWN:
743            case KeyEvent.KEYCODE_DPAD_UP:
744            case KeyEvent.KEYCODE_DPAD_LEFT:
745            case KeyEvent.KEYCODE_DPAD_RIGHT:
746                // If tutorial is visible, don't allow dpad to work
747                if (mTutorial != null) {
748                    return true;
749                }
750                // Enable shift key and DPAD to do selections
751                if (mInputView != null && mInputView.isShown() && mInputView.isShifted()) {
752                    event = new KeyEvent(event.getDownTime(), event.getEventTime(),
753                            event.getAction(), event.getKeyCode(), event.getRepeatCount(),
754                            event.getDeviceId(), event.getScanCode(),
755                            KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON);
756                    InputConnection ic = getCurrentInputConnection();
757                    if (ic != null) ic.sendKeyEvent(event);
758                    return true;
759                }
760                break;
761        }
762        return super.onKeyUp(keyCode, event);
763    }
764
765    private void revertVoiceInput() {
766        InputConnection ic = getCurrentInputConnection();
767        if (ic != null) ic.commitText("", 1);
768        updateSuggestions();
769        mVoiceInputHighlighted = false;
770    }
771
772    private void commitVoiceInput() {
773        InputConnection ic = getCurrentInputConnection();
774        if (ic != null) ic.finishComposingText();
775        updateSuggestions();
776        mVoiceInputHighlighted = false;
777    }
778
779    private void reloadKeyboards() {
780        if (mKeyboardSwitcher == null) {
781            mKeyboardSwitcher = new KeyboardSwitcher(this, this);
782        }
783        mKeyboardSwitcher.setLanguageSwitcher(mLanguageSwitcher);
784        if (mInputView != null) {
785            mKeyboardSwitcher.setVoiceMode(mEnableVoice && mEnableVoiceButton, mVoiceOnPrimary);
786        }
787        mKeyboardSwitcher.makeKeyboards(true);
788    }
789
790    private void commitTyped(InputConnection inputConnection) {
791        if (mPredicting) {
792            mPredicting = false;
793            if (mComposing.length() > 0) {
794                if (inputConnection != null) {
795                    inputConnection.commitText(mComposing, 1);
796                }
797                mCommittedLength = mComposing.length();
798                TextEntryState.acceptedTyped(mComposing);
799                checkAddToDictionary(mComposing, AutoDictionary.FREQUENCY_FOR_TYPED);
800            }
801            updateSuggestions();
802        }
803    }
804
805    private void postUpdateShiftKeyState() {
806        mHandler.removeMessages(MSG_UPDATE_SHIFT_STATE);
807        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SHIFT_STATE), 300);
808    }
809
810    public void updateShiftKeyState(EditorInfo attr) {
811        InputConnection ic = getCurrentInputConnection();
812        if (attr != null && mInputView != null && mKeyboardSwitcher.isAlphabetMode()
813                && ic != null) {
814            mInputView.setShifted(mCapsLock || getCursorCapsMode(ic, attr) != 0);
815        }
816    }
817
818    private int getCursorCapsMode(InputConnection ic, EditorInfo attr) {
819        int caps = 0;
820        EditorInfo ei = getCurrentInputEditorInfo();
821        if (mAutoCap && ei != null && ei.inputType != EditorInfo.TYPE_NULL) {
822            caps = ic.getCursorCapsMode(attr.inputType);
823        }
824        return caps;
825    }
826
827    private void swapPunctuationAndSpace() {
828        final InputConnection ic = getCurrentInputConnection();
829        if (ic == null) return;
830        CharSequence lastTwo = ic.getTextBeforeCursor(2, 0);
831        if (lastTwo != null && lastTwo.length() == 2
832                && lastTwo.charAt(0) == KEYCODE_SPACE && isSentenceSeparator(lastTwo.charAt(1))) {
833            ic.beginBatchEdit();
834            ic.deleteSurroundingText(2, 0);
835            ic.commitText(lastTwo.charAt(1) + " ", 1);
836            ic.endBatchEdit();
837            updateShiftKeyState(getCurrentInputEditorInfo());
838            mJustAddedAutoSpace = true;
839        }
840    }
841
842    private void reswapPeriodAndSpace() {
843        final InputConnection ic = getCurrentInputConnection();
844        if (ic == null) return;
845        CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
846        if (lastThree != null && lastThree.length() == 3
847                && lastThree.charAt(0) == KEYCODE_PERIOD
848                && lastThree.charAt(1) == KEYCODE_SPACE
849                && lastThree.charAt(2) == KEYCODE_PERIOD) {
850            ic.beginBatchEdit();
851            ic.deleteSurroundingText(3, 0);
852            ic.commitText(" ..", 1);
853            ic.endBatchEdit();
854            updateShiftKeyState(getCurrentInputEditorInfo());
855        }
856    }
857
858    private void doubleSpace() {
859        //if (!mAutoPunctuate) return;
860        if (mCorrectionMode == Suggest.CORRECTION_NONE) return;
861        final InputConnection ic = getCurrentInputConnection();
862        if (ic == null) return;
863        CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
864        if (lastThree != null && lastThree.length() == 3
865                && Character.isLetterOrDigit(lastThree.charAt(0))
866                && lastThree.charAt(1) == KEYCODE_SPACE && lastThree.charAt(2) == KEYCODE_SPACE) {
867            ic.beginBatchEdit();
868            ic.deleteSurroundingText(2, 0);
869            ic.commitText(". ", 1);
870            ic.endBatchEdit();
871            updateShiftKeyState(getCurrentInputEditorInfo());
872            mJustAddedAutoSpace = true;
873        }
874    }
875
876    private void maybeRemovePreviousPeriod(CharSequence text) {
877        final InputConnection ic = getCurrentInputConnection();
878        if (ic == null) return;
879
880        // When the text's first character is '.', remove the previous period
881        // if there is one.
882        CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
883        if (lastOne != null && lastOne.length() == 1
884                && lastOne.charAt(0) == KEYCODE_PERIOD
885                && text.charAt(0) == KEYCODE_PERIOD) {
886            ic.deleteSurroundingText(1, 0);
887        }
888    }
889
890    private void removeTrailingSpace() {
891        final InputConnection ic = getCurrentInputConnection();
892        if (ic == null) return;
893
894        CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
895        if (lastOne != null && lastOne.length() == 1
896                && lastOne.charAt(0) == KEYCODE_SPACE) {
897            ic.deleteSurroundingText(1, 0);
898        }
899    }
900
901    public boolean addWordToDictionary(String word) {
902        mUserDictionary.addWord(word, 128);
903        return true;
904    }
905
906    private boolean isAlphabet(int code) {
907        if (Character.isLetter(code)) {
908            return true;
909        } else {
910            return false;
911        }
912    }
913
914    // Implementation of KeyboardViewListener
915
916    public void onKey(int primaryCode, int[] keyCodes) {
917        long when = SystemClock.uptimeMillis();
918        if (primaryCode != Keyboard.KEYCODE_DELETE ||
919                when > mLastKeyTime + QUICK_PRESS) {
920            mDeleteCount = 0;
921        }
922        mLastKeyTime = when;
923        switch (primaryCode) {
924            case Keyboard.KEYCODE_DELETE:
925                handleBackspace();
926                mDeleteCount++;
927                break;
928            case Keyboard.KEYCODE_SHIFT:
929                handleShift();
930                break;
931            case Keyboard.KEYCODE_CANCEL:
932                if (mOptionsDialog == null || !mOptionsDialog.isShowing()) {
933                    handleClose();
934                }
935                break;
936            case LatinKeyboardView.KEYCODE_OPTIONS:
937                showOptionsMenu();
938                break;
939            case LatinKeyboardView.KEYCODE_NEXT_LANGUAGE:
940                toggleLanguage(false, true);
941                break;
942            case LatinKeyboardView.KEYCODE_PREV_LANGUAGE:
943                toggleLanguage(false, false);
944                break;
945            case LatinKeyboardView.KEYCODE_SHIFT_LONGPRESS:
946                if (mCapsLock) {
947                    handleShift();
948                } else {
949                    toggleCapsLock();
950                }
951                break;
952            case Keyboard.KEYCODE_MODE_CHANGE:
953                changeKeyboardMode();
954                break;
955            case LatinKeyboardView.KEYCODE_VOICE:
956                if (VOICE_INSTALLED) {
957                    startListening(false /* was a button press, was not a swipe */);
958                }
959                break;
960            case 9 /*Tab*/:
961                sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB);
962                break;
963            default:
964                if (primaryCode != KEYCODE_ENTER) {
965                    mJustAddedAutoSpace = false;
966                }
967                if (isWordSeparator(primaryCode)) {
968                    handleSeparator(primaryCode);
969                } else {
970                    handleCharacter(primaryCode, keyCodes);
971                }
972                // Cancel the just reverted state
973                mJustRevertedSeparator = null;
974        }
975        if (mKeyboardSwitcher.onKey(primaryCode)) {
976            changeKeyboardMode();
977        }
978    }
979
980    public void onText(CharSequence text) {
981        if (VOICE_INSTALLED && mVoiceInputHighlighted) {
982            commitVoiceInput();
983        }
984        InputConnection ic = getCurrentInputConnection();
985        if (ic == null) return;
986        ic.beginBatchEdit();
987        if (mPredicting) {
988            commitTyped(ic);
989        }
990        maybeRemovePreviousPeriod(text);
991        ic.commitText(text, 1);
992        ic.endBatchEdit();
993        updateShiftKeyState(getCurrentInputEditorInfo());
994        mJustRevertedSeparator = null;
995        mJustAddedAutoSpace = false;
996    }
997
998    private void handleBackspace() {
999        if (VOICE_INSTALLED && mVoiceInputHighlighted) {
1000            revertVoiceInput();
1001            return;
1002        }
1003        boolean deleteChar = false;
1004        InputConnection ic = getCurrentInputConnection();
1005        if (ic == null) return;
1006        if (mPredicting) {
1007            final int length = mComposing.length();
1008            if (length > 0) {
1009                mComposing.delete(length - 1, length);
1010                mWord.deleteLast();
1011                ic.setComposingText(mComposing, 1);
1012                if (mComposing.length() == 0) {
1013                    mPredicting = false;
1014                }
1015                postUpdateSuggestions();
1016            } else {
1017                ic.deleteSurroundingText(1, 0);
1018            }
1019        } else {
1020            deleteChar = true;
1021        }
1022        postUpdateShiftKeyState();
1023        TextEntryState.backspace();
1024        if (TextEntryState.getState() == TextEntryState.STATE_UNDO_COMMIT) {
1025            revertLastWord(deleteChar);
1026            return;
1027        } else if (deleteChar) {
1028            sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
1029            if (mDeleteCount > DELETE_ACCELERATE_AT) {
1030                sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
1031            }
1032        }
1033        mJustRevertedSeparator = null;
1034    }
1035
1036    private void handleShift() {
1037        mHandler.removeMessages(MSG_UPDATE_SHIFT_STATE);
1038        if (mKeyboardSwitcher.isAlphabetMode()) {
1039            // Alphabet keyboard
1040            checkToggleCapsLock();
1041            mInputView.setShifted(mCapsLock || !mInputView.isShifted());
1042        } else {
1043            mKeyboardSwitcher.toggleShift();
1044        }
1045    }
1046
1047    private void handleCharacter(int primaryCode, int[] keyCodes) {
1048        if (VOICE_INSTALLED && mVoiceInputHighlighted) {
1049            commitVoiceInput();
1050        }
1051        if (isAlphabet(primaryCode) && isPredictionOn() && !isCursorTouchingWord()) {
1052            if (!mPredicting) {
1053                mPredicting = true;
1054                mComposing.setLength(0);
1055                mWord.reset();
1056            }
1057        }
1058        if (mInputView.isShifted()) {
1059            // TODO: This doesn't work with ß, need to fix it in the next release.
1060            if (keyCodes == null || keyCodes[0] < Character.MIN_CODE_POINT
1061                    || keyCodes[0] > Character.MAX_CODE_POINT) {
1062                return;
1063            }
1064            primaryCode = new String(keyCodes, 0, 1).toUpperCase().charAt(0);
1065        }
1066        if (mPredicting) {
1067            if (mInputView.isShifted() && mComposing.length() == 0) {
1068                mWord.setCapitalized(true);
1069            }
1070            mComposing.append((char) primaryCode);
1071            mWord.add(primaryCode, keyCodes);
1072            InputConnection ic = getCurrentInputConnection();
1073            if (ic != null) {
1074                // If it's the first letter, make note of auto-caps state
1075                if (mWord.size() == 1) {
1076                    mWord.setAutoCapitalized(
1077                            getCursorCapsMode(ic, getCurrentInputEditorInfo()) != 0);
1078                }
1079                ic.setComposingText(mComposing, 1);
1080            }
1081            postUpdateSuggestions();
1082        } else {
1083            sendKeyChar((char)primaryCode);
1084        }
1085        updateShiftKeyState(getCurrentInputEditorInfo());
1086        measureCps();
1087        TextEntryState.typedCharacter((char) primaryCode, isWordSeparator(primaryCode));
1088    }
1089
1090    private void handleSeparator(int primaryCode) {
1091        if (VOICE_INSTALLED && mVoiceInputHighlighted) {
1092            commitVoiceInput();
1093        }
1094        boolean pickedDefault = false;
1095        // Handle separator
1096        InputConnection ic = getCurrentInputConnection();
1097        if (ic != null) {
1098            ic.beginBatchEdit();
1099        }
1100        if (mPredicting) {
1101            // In certain languages where single quote is a separator, it's better
1102            // not to auto correct, but accept the typed word. For instance,
1103            // in Italian dov' should not be expanded to dove' because the elision
1104            // requires the last vowel to be removed.
1105            if (mAutoCorrectOn && primaryCode != '\'' &&
1106                    (mJustRevertedSeparator == null
1107                            || mJustRevertedSeparator.length() == 0
1108                            || mJustRevertedSeparator.charAt(0) != primaryCode)) {
1109                pickDefaultSuggestion();
1110                pickedDefault = true;
1111                // Picked the suggestion by the space key.  We consider this
1112                // as "added an auto space".
1113                if (primaryCode == KEYCODE_SPACE) {
1114                    mJustAddedAutoSpace = true;
1115                }
1116            } else {
1117                commitTyped(ic);
1118            }
1119        }
1120        if (mJustAddedAutoSpace && primaryCode == KEYCODE_ENTER) {
1121            removeTrailingSpace();
1122            mJustAddedAutoSpace = false;
1123        }
1124        sendKeyChar((char)primaryCode);
1125
1126        // Handle the case of ". ." -> " .." with auto-space if necessary
1127        // before changing the TextEntryState.
1128        if (TextEntryState.getState() == TextEntryState.STATE_PUNCTUATION_AFTER_ACCEPTED
1129                && primaryCode == KEYCODE_PERIOD) {
1130            reswapPeriodAndSpace();
1131        }
1132
1133        TextEntryState.typedCharacter((char) primaryCode, true);
1134        if (TextEntryState.getState() == TextEntryState.STATE_PUNCTUATION_AFTER_ACCEPTED
1135                && primaryCode != KEYCODE_ENTER) {
1136            swapPunctuationAndSpace();
1137        } else if (isPredictionOn() && primaryCode == KEYCODE_SPACE) {
1138        //else if (TextEntryState.STATE_SPACE_AFTER_ACCEPTED) {
1139            doubleSpace();
1140        }
1141        if (pickedDefault && mBestWord != null) {
1142            TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord);
1143        }
1144        updateShiftKeyState(getCurrentInputEditorInfo());
1145        if (ic != null) {
1146            ic.endBatchEdit();
1147        }
1148    }
1149
1150    private void handleClose() {
1151        commitTyped(getCurrentInputConnection());
1152        if (VOICE_INSTALLED & mRecognizing) {
1153            mVoiceInput.cancel();
1154        }
1155        requestHideSelf(0);
1156        mInputView.closing();
1157        TextEntryState.endSession();
1158    }
1159
1160    private void checkToggleCapsLock() {
1161        if (mInputView.getKeyboard().isShifted()) {
1162            toggleCapsLock();
1163        }
1164    }
1165
1166    private void toggleCapsLock() {
1167        mCapsLock = !mCapsLock;
1168        if (mKeyboardSwitcher.isAlphabetMode()) {
1169            ((LatinKeyboard) mInputView.getKeyboard()).setShiftLocked(mCapsLock);
1170        }
1171    }
1172
1173    private void postUpdateSuggestions() {
1174        mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS);
1175        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SUGGESTIONS), 100);
1176    }
1177
1178    private boolean isPredictionOn() {
1179        boolean predictionOn = mPredictionOn;
1180        return predictionOn;
1181    }
1182
1183    private boolean isCandidateStripVisible() {
1184        return isPredictionOn() && mShowSuggestions;
1185    }
1186
1187    public void onCancelVoice() {
1188        if (mRecognizing) {
1189            switchToKeyboardView();
1190        }
1191    }
1192
1193    private void switchToKeyboardView() {
1194      mHandler.post(new Runnable() {
1195          public void run() {
1196              mRecognizing = false;
1197              if (mInputView != null) {
1198                setInputView(mInputView);
1199              }
1200              updateInputViewShown();
1201          }});
1202    }
1203
1204    private void switchToRecognitionStatusView() {
1205        final boolean configChanged = mConfigurationChanging;
1206        mHandler.post(new Runnable() {
1207            public void run() {
1208                mRecognizing = true;
1209                View v = mVoiceInput.getView();
1210                ViewParent p = v.getParent();
1211                if (p != null && p instanceof ViewGroup) {
1212                    ((ViewGroup)v.getParent()).removeView(v);
1213                }
1214                setInputView(v);
1215                updateInputViewShown();
1216                if (configChanged) {
1217                    mVoiceInput.onConfigurationChanged();
1218                }
1219        }});
1220    }
1221
1222    private void startListening(boolean swipe) {
1223        if (!mHasUsedVoiceInput ||
1224                (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale)) {
1225            // Calls reallyStartListening if user clicks OK, does nothing if user clicks Cancel.
1226            showVoiceWarningDialog(swipe);
1227        } else {
1228            reallyStartListening(swipe);
1229        }
1230    }
1231
1232    private void reallyStartListening(boolean swipe) {
1233        if (!mHasUsedVoiceInput) {
1234            // The user has started a voice input, so remember that in the
1235            // future (so we don't show the warning dialog after the first run).
1236            SharedPreferences.Editor editor =
1237                    PreferenceManager.getDefaultSharedPreferences(this).edit();
1238            editor.putBoolean(PREF_HAS_USED_VOICE_INPUT, true);
1239            editor.commit();
1240            mHasUsedVoiceInput = true;
1241        }
1242
1243        if (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale) {
1244            // The user has started a voice input from an unsupported locale, so remember that
1245            // in the future (so we don't show the warning dialog the next time they do this).
1246            SharedPreferences.Editor editor =
1247                    PreferenceManager.getDefaultSharedPreferences(this).edit();
1248            editor.putBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, true);
1249            editor.commit();
1250            mHasUsedVoiceInputUnsupportedLocale = true;
1251        }
1252
1253        // Clear N-best suggestions
1254        setSuggestions(null, false, false, true);
1255
1256        FieldContext context = new FieldContext(
1257            getCurrentInputConnection(),
1258            getCurrentInputEditorInfo(),
1259            mLanguageSwitcher.getInputLanguage(),
1260            mLanguageSwitcher.getEnabledLanguages());
1261        mVoiceInput.startListening(context, swipe);
1262        switchToRecognitionStatusView();
1263    }
1264
1265    private void showVoiceWarningDialog(final boolean swipe) {
1266        AlertDialog.Builder builder = new AlertDialog.Builder(this);
1267        builder.setCancelable(true);
1268        builder.setIcon(R.drawable.ic_mic_dialog);
1269        builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
1270            public void onClick(DialogInterface dialog, int whichButton) {
1271                mVoiceInput.logKeyboardWarningDialogOk();
1272                reallyStartListening(swipe);
1273            }
1274        });
1275        builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
1276            public void onClick(DialogInterface dialog, int whichButton) {
1277                mVoiceInput.logKeyboardWarningDialogCancel();
1278            }
1279        });
1280
1281        if (mLocaleSupportedForVoiceInput) {
1282            String message = getString(R.string.voice_warning_may_not_understand) + "\n\n" +
1283                    getString(R.string.voice_warning_how_to_turn_off);
1284            builder.setMessage(message);
1285        } else {
1286            String message = getString(R.string.voice_warning_locale_not_supported) + "\n\n" +
1287                    getString(R.string.voice_warning_may_not_understand) + "\n\n" +
1288                    getString(R.string.voice_warning_how_to_turn_off);
1289            builder.setMessage(message);
1290        }
1291
1292        builder.setTitle(R.string.voice_warning_title);
1293        mVoiceWarningDialog = builder.create();
1294
1295        Window window = mVoiceWarningDialog.getWindow();
1296        WindowManager.LayoutParams lp = window.getAttributes();
1297        lp.token = mInputView.getWindowToken();
1298        lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
1299        window.setAttributes(lp);
1300        window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
1301        mVoiceInput.logKeyboardWarningDialogShown();
1302        mVoiceWarningDialog.show();
1303    }
1304
1305    public void onVoiceResults(List<String> candidates,
1306            Map<String, List<CharSequence>> alternatives) {
1307        if (!mRecognizing) {
1308            return;
1309        }
1310        mVoiceResults.candidates = candidates;
1311        mVoiceResults.alternatives = alternatives;
1312        mHandler.sendMessage(mHandler.obtainMessage(MSG_VOICE_RESULTS));
1313    }
1314
1315    private void handleVoiceResults() {
1316        mAfterVoiceInput = true;
1317        mImmediatelyAfterVoiceInput = true;
1318
1319        InputConnection ic = getCurrentInputConnection();
1320        if (!isFullscreenMode()) {
1321            // Start listening for updates to the text from typing, etc.
1322            if (ic != null) {
1323                ExtractedTextRequest req = new ExtractedTextRequest();
1324                ic.getExtractedText(req, InputConnection.GET_EXTRACTED_TEXT_MONITOR);
1325            }
1326        }
1327
1328        vibrate();
1329        switchToKeyboardView();
1330
1331        final List<CharSequence> nBest = new ArrayList<CharSequence>();
1332        boolean capitalizeFirstWord = preferCapitalization()
1333                || (mKeyboardSwitcher.isAlphabetMode() && mInputView.isShifted());
1334        for (String c : mVoiceResults.candidates) {
1335            if (capitalizeFirstWord) {
1336                c = Character.toUpperCase(c.charAt(0)) + c.substring(1, c.length());
1337            }
1338            nBest.add(c);
1339        }
1340
1341        if (nBest.size() == 0) {
1342            return;
1343        }
1344
1345        String bestResult = nBest.get(0).toString();
1346
1347        mVoiceInput.logVoiceInputDelivered();
1348
1349        mHints.registerVoiceResult(bestResult);
1350
1351        if (ic != null) ic.beginBatchEdit(); // To avoid extra updates on committing older text
1352
1353        commitTyped(ic);
1354        EditingUtil.appendText(ic, bestResult);
1355
1356        if (ic != null) ic.endBatchEdit();
1357
1358        // Show N-Best alternates, if there is more than one choice.
1359        if (nBest.size() > 1) {
1360            mImmediatelyAfterVoiceSuggestions = true;
1361            mShowingVoiceSuggestions = true;
1362            setSuggestions(nBest.subList(1, nBest.size()), false, true, true);
1363            setCandidatesViewShown(true);
1364        }
1365        mVoiceInputHighlighted = true;
1366        mWordToSuggestions.putAll(mVoiceResults.alternatives);
1367
1368    }
1369
1370    private void setSuggestions(
1371            List<CharSequence> suggestions,
1372            boolean completions,
1373
1374            boolean typedWordValid,
1375            boolean haveMinimalSuggestion) {
1376
1377        if (mIsShowingHint) {
1378             setCandidatesView(mCandidateViewContainer);
1379             mIsShowingHint = false;
1380        }
1381
1382        if (mCandidateView != null) {
1383            mCandidateView.setSuggestions(
1384                    suggestions, completions, typedWordValid, haveMinimalSuggestion);
1385        }
1386    }
1387
1388    private void updateSuggestions() {
1389        mSuggestionShouldReplaceCurrentWord = false;
1390
1391        ((LatinKeyboard) mInputView.getKeyboard()).setPreferredLetters(null);
1392
1393        // Check if we have a suggestion engine attached.
1394        if ((mSuggest == null || !isPredictionOn()) && !mVoiceInputHighlighted) {
1395            return;
1396        }
1397
1398        if (!mPredicting) {
1399            setNextSuggestions();
1400            return;
1401        }
1402
1403        List<CharSequence> stringList = mSuggest.getSuggestions(mInputView, mWord, false);
1404        int[] nextLettersFrequencies = mSuggest.getNextLettersFrequencies();
1405
1406        ((LatinKeyboard) mInputView.getKeyboard()).setPreferredLetters(nextLettersFrequencies);
1407
1408        boolean correctionAvailable = mSuggest.hasMinimalCorrection();
1409        //|| mCorrectionMode == mSuggest.CORRECTION_FULL;
1410        CharSequence typedWord = mWord.getTypedWord();
1411        // If we're in basic correct
1412        boolean typedWordValid = mSuggest.isValidWord(typedWord) ||
1413                (preferCapitalization() && mSuggest.isValidWord(typedWord.toString().toLowerCase()));
1414        if (mCorrectionMode == Suggest.CORRECTION_FULL) {
1415            correctionAvailable |= typedWordValid;
1416        }
1417        // Don't auto-correct words with multiple capital letter
1418        correctionAvailable &= !mWord.isMostlyCaps();
1419
1420        setSuggestions(stringList, false, typedWordValid, correctionAvailable);
1421        if (stringList.size() > 0) {
1422            if (correctionAvailable && !typedWordValid && stringList.size() > 1) {
1423                mBestWord = stringList.get(1);
1424            } else {
1425                mBestWord = typedWord;
1426            }
1427        } else {
1428            mBestWord = null;
1429        }
1430        setCandidatesViewShown(isCandidateStripVisible() || mCompletionOn);
1431    }
1432
1433    private void pickDefaultSuggestion() {
1434        // Complete any pending candidate query first
1435        if (mHandler.hasMessages(MSG_UPDATE_SUGGESTIONS)) {
1436            mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS);
1437            updateSuggestions();
1438        }
1439        if (mBestWord != null) {
1440            TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord);
1441            mJustAccepted = true;
1442            pickSuggestion(mBestWord);
1443            // Add the word to the auto dictionary if it's not a known word
1444            checkAddToDictionary(mBestWord, AutoDictionary.FREQUENCY_FOR_TYPED);
1445        }
1446    }
1447
1448    public void pickSuggestionManually(int index, CharSequence suggestion) {
1449        if (mAfterVoiceInput && mShowingVoiceSuggestions) mVoiceInput.logNBestChoose(index);
1450
1451        InputConnection ic = getCurrentInputConnection();
1452        if (ic != null) {
1453            ic.beginBatchEdit();
1454        }
1455        if (mCompletionOn && mCompletions != null && index >= 0
1456                && index < mCompletions.length) {
1457            CompletionInfo ci = mCompletions[index];
1458            if (ic != null) {
1459                ic.commitCompletion(ci);
1460            }
1461            mCommittedLength = suggestion.length();
1462            if (mCandidateView != null) {
1463                mCandidateView.clear();
1464            }
1465            updateShiftKeyState(getCurrentInputEditorInfo());
1466            if (ic != null) {
1467                ic.endBatchEdit();
1468            }
1469            return;
1470        }
1471
1472        // If this is a punctuation, apply it through the normal key press
1473        if (suggestion.length() == 1 && isWordSeparator(suggestion.charAt(0))) {
1474            onKey(suggestion.charAt(0), null);
1475            if (ic != null) {
1476                ic.endBatchEdit();
1477            }
1478            return;
1479        }
1480        mJustAccepted = true;
1481        pickSuggestion(suggestion);
1482        // Add the word to the auto dictionary if it's not a known word
1483        checkAddToDictionary(suggestion, AutoDictionary.FREQUENCY_FOR_PICKED);
1484        TextEntryState.acceptedSuggestion(mComposing.toString(), suggestion);
1485        // Follow it with a space
1486        if (mAutoSpace) {
1487            sendSpace();
1488            mJustAddedAutoSpace = true;
1489        }
1490        // Fool the state watcher so that a subsequent backspace will not do a revert
1491        TextEntryState.typedCharacter((char) KEYCODE_SPACE, true);
1492        if (index == 0 && mCorrectionMode > 0 && !mSuggest.isValidWord(suggestion)) {
1493            mCandidateView.showAddToDictionaryHint(suggestion);
1494        }
1495        if (ic != null) {
1496            ic.endBatchEdit();
1497        }
1498    }
1499
1500    private void pickSuggestion(CharSequence suggestion) {
1501        if (mCapsLock) {
1502            suggestion = suggestion.toString().toUpperCase();
1503        } else if (preferCapitalization()
1504                || (mKeyboardSwitcher.isAlphabetMode() && mInputView.isShifted())) {
1505            suggestion = suggestion.toString().toUpperCase().charAt(0)
1506                    + suggestion.subSequence(1, suggestion.length()).toString();
1507        }
1508        InputConnection ic = getCurrentInputConnection();
1509        if (ic != null) {
1510            if (mSuggestionShouldReplaceCurrentWord) {
1511                EditingUtil.deleteWordAtCursor(ic, getWordSeparators());
1512            }
1513            if (!VoiceInput.DELETE_SYMBOL.equals(suggestion)) {
1514                ic.commitText(suggestion, 1);
1515            }
1516        }
1517        mPredicting = false;
1518        mCommittedLength = suggestion.length();
1519        ((LatinKeyboard) mInputView.getKeyboard()).setPreferredLetters(null);
1520        setNextSuggestions();
1521        updateShiftKeyState(getCurrentInputEditorInfo());
1522    }
1523
1524    private void setNextSuggestions() {
1525        setSuggestions(mSuggestPuncList, false, false, false);
1526    }
1527
1528    private void checkAddToDictionary(CharSequence suggestion, int frequencyDelta) {
1529        if (mAutoDictionary.isValidWord(suggestion)
1530                || !mSuggest.isValidWord(suggestion.toString().toLowerCase())) {
1531            mAutoDictionary.addWord(suggestion.toString(), frequencyDelta);
1532        }
1533    }
1534
1535    private boolean isCursorTouchingWord() {
1536        InputConnection ic = getCurrentInputConnection();
1537        if (ic == null) return false;
1538        CharSequence toLeft = ic.getTextBeforeCursor(1, 0);
1539        CharSequence toRight = ic.getTextAfterCursor(1, 0);
1540        if (!TextUtils.isEmpty(toLeft)
1541                && !isWordSeparator(toLeft.charAt(0))) {
1542            return true;
1543        }
1544        if (!TextUtils.isEmpty(toRight)
1545                && !isWordSeparator(toRight.charAt(0))) {
1546            return true;
1547        }
1548        return false;
1549    }
1550
1551    public void revertLastWord(boolean deleteChar) {
1552        final int length = mComposing.length();
1553        if (!mPredicting && length > 0) {
1554            final InputConnection ic = getCurrentInputConnection();
1555            mPredicting = true;
1556            ic.beginBatchEdit();
1557            mJustRevertedSeparator = ic.getTextBeforeCursor(1, 0);
1558            if (deleteChar) ic.deleteSurroundingText(1, 0);
1559            int toDelete = mCommittedLength;
1560            CharSequence toTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0);
1561            if (toTheLeft != null && toTheLeft.length() > 0
1562                    && isWordSeparator(toTheLeft.charAt(0))) {
1563                toDelete--;
1564            }
1565            ic.deleteSurroundingText(toDelete, 0);
1566            ic.setComposingText(mComposing, 1);
1567            TextEntryState.backspace();
1568            ic.endBatchEdit();
1569            postUpdateSuggestions();
1570        } else {
1571            sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
1572            mJustRevertedSeparator = null;
1573        }
1574    }
1575
1576    protected String getWordSeparators() {
1577        return mWordSeparators;
1578    }
1579
1580    public boolean isWordSeparator(int code) {
1581        String separators = getWordSeparators();
1582        return separators.contains(String.valueOf((char)code));
1583    }
1584
1585    public boolean isSentenceSeparator(int code) {
1586        return mSentenceSeparators.contains(String.valueOf((char)code));
1587    }
1588
1589    private void sendSpace() {
1590        sendKeyChar((char)KEYCODE_SPACE);
1591        updateShiftKeyState(getCurrentInputEditorInfo());
1592        //onKey(KEY_SPACE[0], KEY_SPACE);
1593    }
1594
1595    public boolean preferCapitalization() {
1596        return mWord.isCapitalized();
1597    }
1598
1599    public void swipeRight() {
1600        if (userHasNotTypedRecently() && VOICE_INSTALLED && mEnableVoice &&
1601                fieldCanDoVoice(makeFieldContext())) {
1602            startListening(true /* was a swipe */);
1603        }
1604
1605        if (LatinKeyboardView.DEBUG_AUTO_PLAY) {
1606            ClipboardManager cm = ((ClipboardManager)getSystemService(CLIPBOARD_SERVICE));
1607            CharSequence text = cm.getText();
1608            if (!TextUtils.isEmpty(text)) {
1609                mInputView.startPlaying(text.toString());
1610            }
1611        }
1612    }
1613
1614    private void toggleLanguage(boolean reset, boolean next) {
1615        if (reset) {
1616            mLanguageSwitcher.reset();
1617        } else {
1618            if (next) {
1619                mLanguageSwitcher.next();
1620            } else {
1621                mLanguageSwitcher.prev();
1622            }
1623        }
1624        int currentKeyboardMode = mKeyboardSwitcher.getKeyboardMode();
1625        reloadKeyboards();
1626        mKeyboardSwitcher.makeKeyboards(true);
1627        mKeyboardSwitcher.setKeyboardMode(currentKeyboardMode, 0,
1628                mEnableVoiceButton && mEnableVoice);
1629        initSuggest(mLanguageSwitcher.getInputLanguage());
1630        mLanguageSwitcher.persist();
1631        updateShiftKeyState(getCurrentInputEditorInfo());
1632    }
1633
1634    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
1635            String key) {
1636        if (PREF_SELECTED_LANGUAGES.equals(key)) {
1637            mLanguageSwitcher.loadLocales(sharedPreferences);
1638            mRefreshKeyboardRequired = true;
1639        }
1640    }
1641
1642    public void swipeLeft() {
1643    }
1644
1645    public void swipeDown() {
1646        handleClose();
1647    }
1648
1649    public void swipeUp() {
1650        //launchSettings();
1651    }
1652
1653    public void onPress(int primaryCode) {
1654        vibrate();
1655        playKeyClick(primaryCode);
1656    }
1657
1658    public void onRelease(int primaryCode) {
1659        // Reset any drag flags in the keyboard
1660        ((LatinKeyboard) mInputView.getKeyboard()).keyReleased();
1661        //vibrate();
1662    }
1663
1664    private FieldContext makeFieldContext() {
1665        return new FieldContext(
1666                getCurrentInputConnection(),
1667                getCurrentInputEditorInfo(),
1668                mLanguageSwitcher.getInputLanguage(),
1669                mLanguageSwitcher.getEnabledLanguages());
1670    }
1671
1672    private boolean fieldCanDoVoice(FieldContext fieldContext) {
1673        return !mPasswordText
1674                && mVoiceInput != null
1675                && !mVoiceInput.isBlacklistedField(fieldContext);
1676    }
1677
1678    private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) {
1679        return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext)
1680                && !(attribute != null && attribute.privateImeOptions != null
1681                        && attribute.privateImeOptions.equals(IME_OPTION_NO_MICROPHONE))
1682                && RecognitionManager.isRecognitionAvailable(this);
1683    }
1684
1685    // receive ringer mode changes to detect silent mode
1686    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
1687        @Override
1688        public void onReceive(Context context, Intent intent) {
1689            updateRingerMode();
1690        }
1691    };
1692
1693    // update flags for silent mode
1694    private void updateRingerMode() {
1695        if (mAudioManager == null) {
1696            mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
1697        }
1698        if (mAudioManager != null) {
1699            mSilentMode = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL);
1700        }
1701    }
1702
1703    private boolean userHasNotTypedRecently() {
1704        return (SystemClock.uptimeMillis() - mLastKeyTime)
1705            > MIN_MILLIS_AFTER_TYPING_BEFORE_SWIPE;
1706    }
1707
1708    private void playKeyClick(int primaryCode) {
1709        // if mAudioManager is null, we don't have the ringer state yet
1710        // mAudioManager will be set by updateRingerMode
1711        if (mAudioManager == null) {
1712            if (mInputView != null) {
1713                updateRingerMode();
1714            }
1715        }
1716        if (mSoundOn && !mSilentMode) {
1717            // FIXME: Volume and enable should come from UI settings
1718            // FIXME: These should be triggered after auto-repeat logic
1719            int sound = AudioManager.FX_KEYPRESS_STANDARD;
1720            switch (primaryCode) {
1721                case Keyboard.KEYCODE_DELETE:
1722                    sound = AudioManager.FX_KEYPRESS_DELETE;
1723                    break;
1724                case KEYCODE_ENTER:
1725                    sound = AudioManager.FX_KEYPRESS_RETURN;
1726                    break;
1727                case KEYCODE_SPACE:
1728                    sound = AudioManager.FX_KEYPRESS_SPACEBAR;
1729                    break;
1730            }
1731            mAudioManager.playSoundEffect(sound, FX_VOLUME);
1732        }
1733    }
1734
1735    private void vibrate() {
1736        if (!mVibrateOn) {
1737            return;
1738        }
1739        if (mInputView != null) {
1740            mInputView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP,
1741                    HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
1742        }
1743    }
1744
1745    private void checkTutorial(String privateImeOptions) {
1746        if (privateImeOptions == null) return;
1747        if (privateImeOptions.equals("com.android.setupwizard:ShowTutorial")) {
1748            if (mTutorial == null) startTutorial();
1749        } else if (privateImeOptions.equals("com.android.setupwizard:HideTutorial")) {
1750            if (mTutorial != null) {
1751                if (mTutorial.close()) {
1752                    mTutorial = null;
1753                }
1754            }
1755        }
1756    }
1757
1758    private void startTutorial() {
1759        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_TUTORIAL), 500);
1760    }
1761
1762    void tutorialDone() {
1763        mTutorial = null;
1764    }
1765
1766    void promoteToUserDictionary(String word, int frequency) {
1767        if (mUserDictionary.isValidWord(word)) return;
1768        mUserDictionary.addWord(word, frequency);
1769    }
1770
1771    WordComposer getCurrentWord() {
1772        return mWord;
1773    }
1774
1775    private void updateCorrectionMode() {
1776        mHasDictionary = mSuggest != null ? mSuggest.hasMainDictionary() : false;
1777        mAutoCorrectOn = (mAutoCorrectEnabled || mQuickFixes)
1778                && !mInputTypeNoAutoCorrect && mHasDictionary;
1779        mCorrectionMode = (mAutoCorrectOn && mAutoCorrectEnabled)
1780                ? Suggest.CORRECTION_FULL
1781                : (mAutoCorrectOn ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE);
1782        if (mSuggest != null) {
1783            mSuggest.setCorrectionMode(mCorrectionMode);
1784        }
1785    }
1786
1787    private void updateAutoTextEnabled(Locale systemLocale) {
1788        if (mSuggest == null) return;
1789        boolean different =
1790                !systemLocale.getLanguage().equalsIgnoreCase(mInputLocale.substring(0, 2));
1791        mSuggest.setAutoTextEnabled(!different && mQuickFixes);
1792    }
1793
1794    protected void launchSettings() {
1795        launchSettings(LatinIMESettings.class);
1796    }
1797
1798    protected void launchSettings(Class settingsClass) {
1799        handleClose();
1800        Intent intent = new Intent();
1801        intent.setClass(LatinIME.this, settingsClass);
1802        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1803        startActivity(intent);
1804    }
1805
1806    private void loadSettings() {
1807        // Get the settings preferences
1808        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
1809        mVibrateOn = sp.getBoolean(PREF_VIBRATE_ON, false);
1810        mSoundOn = sp.getBoolean(PREF_SOUND_ON, false);
1811        mAutoCap = sp.getBoolean(PREF_AUTO_CAP, true);
1812        mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true);
1813        mHasUsedVoiceInput = sp.getBoolean(PREF_HAS_USED_VOICE_INPUT, false);
1814        mHasUsedVoiceInputUnsupportedLocale =
1815                sp.getBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, false);
1816
1817        // Get the current list of supported locales and check the current locale against that
1818        // list. We cache this value so as not to check it every time the user starts a voice
1819        // input. Because this method is called by onStartInputView, this should mean that as
1820        // long as the locale doesn't change while the user is keeping the IME open, the
1821        // value should never be stale.
1822        String supportedLocalesString = SettingsUtil.getSettingsString(
1823                getContentResolver(),
1824                SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES,
1825                DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES);
1826        ArrayList<String> voiceInputSupportedLocales =
1827                newArrayList(supportedLocalesString.split("\\s+"));
1828
1829        mLocaleSupportedForVoiceInput = voiceInputSupportedLocales.contains(mInputLocale);
1830
1831        mShowSuggestions = sp.getBoolean(PREF_SHOW_SUGGESTIONS, true);
1832
1833        if (VOICE_INSTALLED) {
1834            final String voiceMode = sp.getString(PREF_VOICE_MODE,
1835                    getString(R.string.voice_mode_main));
1836            boolean enableVoice = !voiceMode.equals(getString(R.string.voice_mode_off))
1837                    && mEnableVoiceButton;
1838            boolean voiceOnPrimary = voiceMode.equals(getString(R.string.voice_mode_main));
1839            if (mKeyboardSwitcher != null &&
1840                    (enableVoice != mEnableVoice || voiceOnPrimary != mVoiceOnPrimary)) {
1841                mKeyboardSwitcher.setVoiceMode(enableVoice, voiceOnPrimary);
1842            }
1843            mEnableVoice = enableVoice;
1844            mVoiceOnPrimary = voiceOnPrimary;
1845        }
1846        mAutoCorrectEnabled = sp.getBoolean(PREF_AUTO_COMPLETE,
1847                mResources.getBoolean(R.bool.enable_autocorrect)) & mShowSuggestions;
1848        updateCorrectionMode();
1849        updateAutoTextEnabled(mResources.getConfiguration().locale);
1850        mLanguageSwitcher.loadLocales(sp);
1851    }
1852
1853    private void initSuggestPuncList() {
1854        mSuggestPuncList = new ArrayList<CharSequence>();
1855        String suggestPuncs = mResources.getString(R.string.suggested_punctuations);
1856        if (suggestPuncs != null) {
1857            for (int i = 0; i < suggestPuncs.length(); i++) {
1858                mSuggestPuncList.add(suggestPuncs.subSequence(i, i + 1));
1859            }
1860        }
1861    }
1862
1863    private void showOptionsMenu() {
1864        AlertDialog.Builder builder = new AlertDialog.Builder(this);
1865        builder.setCancelable(true);
1866        builder.setIcon(R.drawable.ic_dialog_keyboard);
1867        builder.setNegativeButton(android.R.string.cancel, null);
1868        CharSequence itemSettings = getString(R.string.english_ime_settings);
1869        CharSequence itemInputMethod = getString(R.string.inputMethod);
1870        builder.setItems(new CharSequence[] {
1871                itemSettings, itemInputMethod},
1872                new DialogInterface.OnClickListener() {
1873
1874            public void onClick(DialogInterface di, int position) {
1875                di.dismiss();
1876                switch (position) {
1877                    case POS_SETTINGS:
1878                        launchSettings();
1879                        break;
1880                    case POS_METHOD:
1881                        ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE))
1882                            .showInputMethodPicker();
1883                        break;
1884                }
1885            }
1886        });
1887        builder.setTitle(mResources.getString(R.string.english_ime_name));
1888        mOptionsDialog = builder.create();
1889        Window window = mOptionsDialog.getWindow();
1890        WindowManager.LayoutParams lp = window.getAttributes();
1891        lp.token = mInputView.getWindowToken();
1892        lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
1893        window.setAttributes(lp);
1894        window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
1895        mOptionsDialog.show();
1896    }
1897
1898    private void changeKeyboardMode() {
1899        mKeyboardSwitcher.toggleSymbols();
1900        if (mCapsLock && mKeyboardSwitcher.isAlphabetMode()) {
1901            ((LatinKeyboard) mInputView.getKeyboard()).setShiftLocked(mCapsLock);
1902        }
1903
1904        updateShiftKeyState(getCurrentInputEditorInfo());
1905    }
1906
1907    public static <E> ArrayList<E> newArrayList(E... elements) {
1908        int capacity = (elements.length * 110) / 100 + 5;
1909        ArrayList<E> list = new ArrayList<E>(capacity);
1910        Collections.addAll(list, elements);
1911        return list;
1912    }
1913
1914    @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
1915        super.dump(fd, fout, args);
1916
1917        final Printer p = new PrintWriterPrinter(fout);
1918        p.println("LatinIME state :");
1919        p.println("  Keyboard mode = " + mKeyboardSwitcher.getKeyboardMode());
1920        p.println("  mCapsLock=" + mCapsLock);
1921        p.println("  mComposing=" + mComposing.toString());
1922        p.println("  mPredictionOn=" + mPredictionOn);
1923        p.println("  mCorrectionMode=" + mCorrectionMode);
1924        p.println("  mPredicting=" + mPredicting);
1925        p.println("  mAutoCorrectOn=" + mAutoCorrectOn);
1926        p.println("  mAutoSpace=" + mAutoSpace);
1927        p.println("  mCompletionOn=" + mCompletionOn);
1928        p.println("  TextEntryState.state=" + TextEntryState.getState());
1929        p.println("  mSoundOn=" + mSoundOn);
1930        p.println("  mVibrateOn=" + mVibrateOn);
1931    }
1932
1933    // Characters per second measurement
1934
1935    private static final boolean PERF_DEBUG = false;
1936    private long mLastCpsTime;
1937    private static final int CPS_BUFFER_SIZE = 16;
1938    private long[] mCpsIntervals = new long[CPS_BUFFER_SIZE];
1939    private int mCpsIndex;
1940    private boolean mInputTypeNoAutoCorrect;
1941
1942    private void measureCps() {
1943        if (!LatinIME.PERF_DEBUG) return;
1944        long now = System.currentTimeMillis();
1945        if (mLastCpsTime == 0) mLastCpsTime = now - 100; // Initial
1946        mCpsIntervals[mCpsIndex] = now - mLastCpsTime;
1947        mLastCpsTime = now;
1948        mCpsIndex = (mCpsIndex + 1) % CPS_BUFFER_SIZE;
1949        long total = 0;
1950        for (int i = 0; i < CPS_BUFFER_SIZE; i++) total += mCpsIntervals[i];
1951        System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total));
1952    }
1953
1954}
1955