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