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