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