diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin/LatinIME.java')
-rw-r--r-- | java/src/com/android/inputmethod/latin/LatinIME.java | 2942 |
1 files changed, 1228 insertions, 1714 deletions
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 4f3d3ba9f..9c6465dd2 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -25,18 +25,17 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.res.Configuration; import android.content.res.Resources; -import android.content.res.XmlResourceParser; import android.inputmethodservice.InputMethodService; -import android.inputmethodservice.Keyboard; import android.media.AudioManager; +import android.net.ConnectivityManager; import android.os.Debug; import android.os.Handler; +import android.os.IBinder; import android.os.Message; import android.os.SystemClock; import android.preference.PreferenceActivity; import android.preference.PreferenceManager; -import android.speech.SpeechRecognizer; -import android.text.ClipboardManager; +import android.text.InputType; import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; @@ -44,7 +43,6 @@ import android.util.PrintWriterPrinter; import android.util.Printer; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; -import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; @@ -53,441 +51,422 @@ import android.view.WindowManager; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; -import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputMethodManager; -import android.widget.LinearLayout; -import com.android.inputmethod.latin.LatinIMEUtil.RingCharBuffer; -import com.android.inputmethod.voice.FieldContext; -import com.android.inputmethod.voice.SettingsUtil; -import com.android.inputmethod.voice.VoiceInput; - -import org.xmlpull.v1.XmlPullParserException; +import com.android.inputmethod.accessibility.AccessibilityUtils; +import com.android.inputmethod.compat.CompatUtils; +import com.android.inputmethod.compat.EditorInfoCompatUtils; +import com.android.inputmethod.compat.InputConnectionCompatUtils; +import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; +import com.android.inputmethod.compat.InputMethodServiceCompatWrapper; +import com.android.inputmethod.compat.InputTypeCompatUtils; +import com.android.inputmethod.compat.SuggestionSpanUtils; +import com.android.inputmethod.deprecated.LanguageSwitcherProxy; +import com.android.inputmethod.deprecated.VoiceProxy; +import com.android.inputmethod.deprecated.recorrection.Recorrection; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.keyboard.KeyboardActionListener; +import com.android.inputmethod.keyboard.KeyboardSwitcher; +import com.android.inputmethod.keyboard.KeyboardView; +import com.android.inputmethod.keyboard.LatinKeyboard; +import com.android.inputmethod.keyboard.LatinKeyboardView; import java.io.FileDescriptor; -import java.io.IOException; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; import java.util.Locale; -import java.util.Map; /** * Input method implementation for Qwerty'ish keyboard. */ -public class LatinIME extends InputMethodService - implements LatinKeyboardBaseView.OnKeyboardActionListener, - VoiceInput.UiListener, - SharedPreferences.OnSharedPreferenceChangeListener { - private static final String TAG = "LatinIME"; +public class LatinIME extends InputMethodServiceCompatWrapper implements KeyboardActionListener, + CandidateView.Listener { + private static final String TAG = LatinIME.class.getSimpleName(); private static final boolean PERF_DEBUG = false; - static final boolean DEBUG = false; - static final boolean TRACE = false; - static final boolean VOICE_INSTALLED = true; - static final boolean ENABLE_VOICE_BUTTON = true; - - private static final String PREF_VIBRATE_ON = "vibrate_on"; - private static final String PREF_SOUND_ON = "sound_on"; - private static final String PREF_POPUP_ON = "popup_on"; - private static final String PREF_AUTO_CAP = "auto_cap"; - private static final String PREF_QUICK_FIXES = "quick_fixes"; - private static final String PREF_SHOW_SUGGESTIONS = "show_suggestions"; - private static final String PREF_AUTO_COMPLETE = "auto_complete"; - //private static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion"; - private static final String PREF_VOICE_MODE = "voice_mode"; - - // Whether or not the user has used voice input before (and thus, whether to show the - // first-run warning dialog or not). - private static final String PREF_HAS_USED_VOICE_INPUT = "has_used_voice_input"; - - // Whether or not the user has used voice input from an unsupported locale UI before. - // For example, the user has a Chinese UI but activates voice input. - private static final String PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE = - "has_used_voice_input_unsupported_locale"; - - // A list of locales which are supported by default for voice input, unless we get a - // different list from Gservices. - public static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES = - "en " + - "en_US " + - "en_GB " + - "en_AU " + - "en_CA " + - "en_IE " + - "en_IN " + - "en_NZ " + - "en_SG " + - "en_ZA "; - - // The private IME option used to indicate that no microphone should be shown for a - // given text field. For instance this is specified by the search dialog when the - // dialog is already showing a voice search button. - private static final String IME_OPTION_NO_MICROPHONE = "nm"; - - public static final String PREF_SELECTED_LANGUAGES = "selected_languages"; - public static final String PREF_INPUT_LANGUAGE = "input_language"; - private static final String PREF_RECORRECTION_ENABLED = "recorrection_enabled"; - - private static final int MSG_UPDATE_SUGGESTIONS = 0; - private static final int MSG_START_TUTORIAL = 1; - private static final int MSG_UPDATE_SHIFT_STATE = 2; - private static final int MSG_VOICE_RESULTS = 3; - private static final int MSG_UPDATE_OLD_SUGGESTIONS = 4; + private static final boolean TRACE = false; + private static boolean DEBUG; + + /** + * The private IME option used to indicate that no microphone should be + * shown for a given text field. For instance, this is specified by the + * search dialog when the dialog is already showing a voice search button. + * + * @deprecated Use {@link LatinIME#IME_OPTION_NO_MICROPHONE} with package name prefixed. + */ + @SuppressWarnings("dep-ann") + public static final String IME_OPTION_NO_MICROPHONE_COMPAT = "nm"; + + /** + * The private IME option used to indicate that no microphone should be + * shown for a given text field. For instance, this is specified by the + * search dialog when the dialog is already showing a voice search button. + */ + public static final String IME_OPTION_NO_MICROPHONE = "noMicrophoneKey"; + + /** + * The private IME option used to indicate that no settings key should be + * shown for a given text field. + */ + public static final String IME_OPTION_NO_SETTINGS_KEY = "noSettingsKey"; + + private static final int EXTENDED_TOUCHABLE_REGION_HEIGHT = 100; // How many continuous deletes at which to start deleting at a higher speed. private static final int DELETE_ACCELERATE_AT = 20; // Key events coming any faster than this are long-presses. private static final int QUICK_PRESS = 200; - static final int KEYCODE_ENTER = '\n'; - static final int KEYCODE_SPACE = ' '; - static final int KEYCODE_PERIOD = '.'; + /** + * The name of the scheme used by the Package Manager to warn of a new package installation, + * replacement or removal. + */ + private static final String SCHEME_PACKAGE = "package"; + + private int mSuggestionVisibility; + private static final int SUGGESTION_VISIBILILTY_SHOW_VALUE + = R.string.prefs_suggestion_visibility_show_value; + private static final int SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE + = R.string.prefs_suggestion_visibility_show_only_portrait_value; + private static final int SUGGESTION_VISIBILILTY_HIDE_VALUE + = R.string.prefs_suggestion_visibility_hide_value; + + private static final int[] SUGGESTION_VISIBILITY_VALUE_ARRAY = new int[] { + SUGGESTION_VISIBILILTY_SHOW_VALUE, + SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE, + SUGGESTION_VISIBILILTY_HIDE_VALUE + }; - // Contextual menu positions - private static final int POS_METHOD = 0; - private static final int POS_SETTINGS = 1; + private Settings.Values mSettingsValues; - //private LatinKeyboardView mInputView; - private LinearLayout mCandidateViewContainer; + private View mCandidateViewContainer; + private int mCandidateStripHeight; private CandidateView mCandidateView; private Suggest mSuggest; - private CompletionInfo[] mCompletions; + private CompletionInfo[] mApplicationSpecifiedCompletions; private AlertDialog mOptionsDialog; - private AlertDialog mVoiceWarningDialog; - /* package */ KeyboardSwitcher mKeyboardSwitcher; + private InputMethodManagerCompatWrapper mImm; + private Resources mResources; + private SharedPreferences mPrefs; + private String mInputMethodId; + private KeyboardSwitcher mKeyboardSwitcher; + private SubtypeSwitcher mSubtypeSwitcher; + private VoiceProxy mVoiceProxy; + private Recorrection mRecorrection; private UserDictionary mUserDictionary; private UserBigramDictionary mUserBigramDictionary; - private ContactsDictionary mContactsDictionary; private AutoDictionary mAutoDictionary; - private Hints mHints; - - private Resources mResources; - - private String mInputLocale; - private String mSystemLocale; - private LanguageSwitcher mLanguageSwitcher; + // TODO: Create an inner class to group options and pseudo-options to improve readability. + // These variables are initialized according to the {@link EditorInfo#inputType}. + private boolean mShouldInsertMagicSpace; + private boolean mInputTypeNoAutoCorrect; + private boolean mIsSettingsSuggestionStripOn; + private boolean mApplicationSpecifiedCompletionOn; - private StringBuilder mComposing = new StringBuilder(); + private final StringBuilder mComposing = new StringBuilder(); private WordComposer mWord = new WordComposer(); - private int mCommittedLength; - private boolean mPredicting; - private boolean mRecognizing; - private boolean mAfterVoiceInput; - private boolean mImmediatelyAfterVoiceInput; - private boolean mShowingVoiceSuggestions; - private boolean mVoiceInputHighlighted; - private boolean mEnableVoiceButton; private CharSequence mBestWord; - private boolean mPredictionOn; - private boolean mCompletionOn; + private boolean mHasUncommittedTypedChars; private boolean mHasDictionary; - private boolean mAutoSpace; - private boolean mJustAddedAutoSpace; - private boolean mAutoCorrectEnabled; - private boolean mReCorrectionEnabled; - // Bigram Suggestion is disabled in this version. - private final boolean mBigramSuggestionEnabled = false; - private boolean mAutoCorrectOn; - // TODO move this state variable outside LatinIME - private boolean mCapsLock; - private boolean mPasswordText; - private boolean mVibrateOn; - private boolean mSoundOn; - private boolean mPopupOn; - private boolean mAutoCap; - private boolean mQuickFixes; - private boolean mHasUsedVoiceInput; - private boolean mHasUsedVoiceInputUnsupportedLocale; - private boolean mLocaleSupportedForVoiceInput; - private boolean mShowSuggestions; - private boolean mIsShowingHint; - private int mCorrectionMode; - private boolean mEnableVoice = true; - private boolean mVoiceOnPrimary; - private int mOrientation; - private List<CharSequence> mSuggestPuncList; + // Magic space: a space that should disappear on space/apostrophe insertion, move after the + // punctuation on punctuation insertion, and become a real space on alpha char insertion. + private boolean mJustAddedMagicSpace; // This indicates whether the last char is a magic space. + // This indicates whether the last keypress resulted in processing of double space replacement + // with period-space. + private boolean mJustReplacedDoubleSpace; + + private int mCorrectionMode; + private int mCommittedLength; + private int mOrientation; // Keep track of the last selection range to decide if we need to show word alternatives - private int mLastSelectionStart; - private int mLastSelectionEnd; - - // Input type is such that we should not auto-correct - private boolean mInputTypeNoAutoCorrect; + private int mLastSelectionStart; + private int mLastSelectionEnd; - // Indicates whether the suggestion strip is to be on in landscape - private boolean mJustAccepted; - private CharSequence mJustRevertedSeparator; + // Whether we are expecting an onUpdateSelection event to fire. If it does when we don't + // "expect" it, it means the user actually moved the cursor. + private boolean mExpectingUpdateSelection; private int mDeleteCount; private long mLastKeyTime; - // Modifier keys state - private ModifierKeyState mShiftKeyState = new ModifierKeyState(); - private ModifierKeyState mSymbolKeyState = new ModifierKeyState(); - - private Tutorial mTutorial; - private AudioManager mAudioManager; // Align sound effect volume on music volume - private final float FX_VOLUME = -1.0f; - private boolean mSilentMode; - - /* package */ String mWordSeparators; - private String mSentenceSeparators; - private String mSuggestPuncs; - private VoiceInput mVoiceInput; - private VoiceResults mVoiceResults = new VoiceResults(); + private static final float FX_VOLUME = -1.0f; + private boolean mSilentModeOn; // System-wide current configuration + + // TODO: Move this flag to VoiceProxy private boolean mConfigurationChanging; + // Object for reacting to adding/removing a dictionary pack. + private BroadcastReceiver mDictionaryPackInstallReceiver = + new DictionaryPackInstallBroadcastReceiver(this); + // Keeps track of most recently inserted text (multi-character key) for reverting private CharSequence mEnteredText; - private boolean mRefreshKeyboardRequired; - // For each word, a list of potential replacements, usually from voice. - private Map<String, List<CharSequence>> mWordToSuggestions = - new HashMap<String, List<CharSequence>>(); - private ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>(); + public final UIHandler mHandler = new UIHandler(); - private class VoiceResults { - List<String> candidates; - Map<String, List<CharSequence>> alternatives; - } - - public abstract static class WordAlternatives { - protected CharSequence mChosenWord; + public class UIHandler extends Handler { + private static final int MSG_UPDATE_SUGGESTIONS = 0; + private static final int MSG_UPDATE_OLD_SUGGESTIONS = 1; + private static final int MSG_UPDATE_SHIFT_STATE = 2; + private static final int MSG_VOICE_RESULTS = 3; + private static final int MSG_FADEOUT_LANGUAGE_ON_SPACEBAR = 4; + private static final int MSG_DISMISS_LANGUAGE_ON_SPACEBAR = 5; + private static final int MSG_SPACE_TYPED = 6; + private static final int MSG_SET_BIGRAM_PREDICTIONS = 7; - public WordAlternatives() { - // Nothing + @Override + public void handleMessage(Message msg) { + final KeyboardSwitcher switcher = mKeyboardSwitcher; + final LatinKeyboardView inputView = switcher.getKeyboardView(); + switch (msg.what) { + case MSG_UPDATE_SUGGESTIONS: + updateSuggestions(); + break; + case MSG_UPDATE_OLD_SUGGESTIONS: + mRecorrection.fetchAndDisplayRecorrectionSuggestions(mVoiceProxy, mCandidateView, + mSuggest, mKeyboardSwitcher, mWord, mHasUncommittedTypedChars, + mLastSelectionStart, mLastSelectionEnd, mSettingsValues.mWordSeparators); + break; + case MSG_UPDATE_SHIFT_STATE: + switcher.updateShiftState(); + break; + case MSG_SET_BIGRAM_PREDICTIONS: + updateBigramPredictions(); + break; + case MSG_VOICE_RESULTS: + mVoiceProxy.handleVoiceResults(preferCapitalization() + || (switcher.isAlphabetMode() && switcher.isShiftedOrShiftLocked())); + break; + case MSG_FADEOUT_LANGUAGE_ON_SPACEBAR: + if (inputView != null) { + inputView.setSpacebarTextFadeFactor( + (1.0f + mSettingsValues.mFinalFadeoutFactorOfLanguageOnSpacebar) / 2, + (LatinKeyboard)msg.obj); + } + sendMessageDelayed(obtainMessage(MSG_DISMISS_LANGUAGE_ON_SPACEBAR, msg.obj), + mSettingsValues.mDurationOfFadeoutLanguageOnSpacebar); + break; + case MSG_DISMISS_LANGUAGE_ON_SPACEBAR: + if (inputView != null) { + inputView.setSpacebarTextFadeFactor( + mSettingsValues.mFinalFadeoutFactorOfLanguageOnSpacebar, + (LatinKeyboard)msg.obj); + } + break; + } } - public WordAlternatives(CharSequence chosenWord) { - mChosenWord = chosenWord; + public void postUpdateSuggestions() { + removeMessages(MSG_UPDATE_SUGGESTIONS); + sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTIONS), + mSettingsValues.mDelayUpdateSuggestions); } - @Override - public int hashCode() { - return mChosenWord.hashCode(); + public void cancelUpdateSuggestions() { + removeMessages(MSG_UPDATE_SUGGESTIONS); } - public abstract CharSequence getOriginalWord(); + public boolean hasPendingUpdateSuggestions() { + return hasMessages(MSG_UPDATE_SUGGESTIONS); + } - public CharSequence getChosenWord() { - return mChosenWord; + public void postUpdateOldSuggestions() { + removeMessages(MSG_UPDATE_OLD_SUGGESTIONS); + sendMessageDelayed(obtainMessage(MSG_UPDATE_OLD_SUGGESTIONS), + mSettingsValues.mDelayUpdateOldSuggestions); } - public abstract List<CharSequence> getAlternatives(); - } + public void cancelUpdateOldSuggestions() { + removeMessages(MSG_UPDATE_OLD_SUGGESTIONS); + } - public class TypedWordAlternatives extends WordAlternatives { - private WordComposer word; + public void postUpdateShiftKeyState() { + removeMessages(MSG_UPDATE_SHIFT_STATE); + sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), + mSettingsValues.mDelayUpdateShiftState); + } - public TypedWordAlternatives() { - // Nothing + public void cancelUpdateShiftState() { + removeMessages(MSG_UPDATE_SHIFT_STATE); } - public TypedWordAlternatives(CharSequence chosenWord, WordComposer wordComposer) { - super(chosenWord); - word = wordComposer; + public void postUpdateBigramPredictions() { + removeMessages(MSG_SET_BIGRAM_PREDICTIONS); + sendMessageDelayed(obtainMessage(MSG_SET_BIGRAM_PREDICTIONS), + mSettingsValues.mDelayUpdateSuggestions); } - @Override - public CharSequence getOriginalWord() { - return word.getTypedWord(); + public void cancelUpdateBigramPredictions() { + removeMessages(MSG_SET_BIGRAM_PREDICTIONS); } - @Override - public List<CharSequence> getAlternatives() { - return getTypedSuggestions(word); + public void updateVoiceResults() { + sendMessage(obtainMessage(MSG_VOICE_RESULTS)); } - } - /* package */ Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_UPDATE_SUGGESTIONS: - updateSuggestions(); - break; - case MSG_UPDATE_OLD_SUGGESTIONS: - setOldSuggestions(); - break; - case MSG_START_TUTORIAL: - if (mTutorial == null) { - if (mKeyboardSwitcher.getInputView().isShown()) { - mTutorial = new Tutorial( - LatinIME.this, mKeyboardSwitcher.getInputView()); - mTutorial.start(); - } else { - // Try again soon if the view is not yet showing - sendMessageDelayed(obtainMessage(MSG_START_TUTORIAL), 100); - } - } - break; - case MSG_UPDATE_SHIFT_STATE: - updateShiftKeyState(getCurrentInputEditorInfo()); - break; - case MSG_VOICE_RESULTS: - handleVoiceResults(); - break; + public void startDisplayLanguageOnSpacebar(boolean localeChanged) { + removeMessages(MSG_FADEOUT_LANGUAGE_ON_SPACEBAR); + removeMessages(MSG_DISMISS_LANGUAGE_ON_SPACEBAR); + final LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); + if (inputView != null) { + final LatinKeyboard keyboard = mKeyboardSwitcher.getLatinKeyboard(); + // The language is always displayed when the delay is negative. + final boolean needsToDisplayLanguage = localeChanged + || mSettingsValues.mDelayBeforeFadeoutLanguageOnSpacebar < 0; + // The language is never displayed when the delay is zero. + if (mSettingsValues.mDelayBeforeFadeoutLanguageOnSpacebar != 0) { + inputView.setSpacebarTextFadeFactor(needsToDisplayLanguage ? 1.0f + : mSettingsValues.mFinalFadeoutFactorOfLanguageOnSpacebar, keyboard); + } + // The fadeout animation will start when the delay is positive. + if (localeChanged && mSettingsValues.mDelayBeforeFadeoutLanguageOnSpacebar > 0) { + sendMessageDelayed(obtainMessage(MSG_FADEOUT_LANGUAGE_ON_SPACEBAR, keyboard), + mSettingsValues.mDelayBeforeFadeoutLanguageOnSpacebar); + } } } - }; + + public void startDoubleSpacesTimer() { + removeMessages(MSG_SPACE_TYPED); + sendMessageDelayed(obtainMessage(MSG_SPACE_TYPED), + mSettingsValues.mDoubleSpacesTurnIntoPeriodTimeout); + } + + public void cancelDoubleSpacesTimer() { + removeMessages(MSG_SPACE_TYPED); + } + + public boolean isAcceptingDoubleSpaces() { + return hasMessages(MSG_SPACE_TYPED); + } + } @Override public void onCreate() { - LatinImeLogger.init(this); - KeyboardSwitcher.init(this); - super.onCreate(); - //setStatusIcon(R.drawable.ime_qwerty); - mResources = getResources(); - final Configuration conf = mResources.getConfiguration(); final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - mLanguageSwitcher = new LanguageSwitcher(this); - mLanguageSwitcher.loadLocales(prefs); + mPrefs = prefs; + LatinImeLogger.init(this, prefs); + LanguageSwitcherProxy.init(this, prefs); + SubtypeSwitcher.init(this, prefs); + KeyboardSwitcher.init(this, prefs); + Recorrection.init(this, prefs); + AccessibilityUtils.init(this, prefs); + + super.onCreate(); + + mImm = InputMethodManagerCompatWrapper.getInstance(this); + mInputMethodId = Utils.getInputMethodId(mImm, getPackageName()); + mSubtypeSwitcher = SubtypeSwitcher.getInstance(); mKeyboardSwitcher = KeyboardSwitcher.getInstance(); - mKeyboardSwitcher.setLanguageSwitcher(mLanguageSwitcher); - mSystemLocale = conf.locale.toString(); - mLanguageSwitcher.setSystemLocale(conf.locale); - String inputLanguage = mLanguageSwitcher.getInputLanguage(); - if (inputLanguage == null) { - inputLanguage = conf.locale.toString(); - } - mReCorrectionEnabled = prefs.getBoolean(PREF_RECORRECTION_ENABLED, - getResources().getBoolean(R.bool.default_recorrection_enabled)); + mRecorrection = Recorrection.getInstance(); + DEBUG = LatinImeLogger.sDBG; - LatinIMEUtil.GCUtils.getInstance().reset(); + loadSettings(); + + final Resources res = getResources(); + mResources = res; + + Utils.GCUtils.getInstance().reset(); boolean tryGC = true; - for (int i = 0; i < LatinIMEUtil.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) { + for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) { try { - initSuggest(inputLanguage); + initSuggest(); tryGC = false; } catch (OutOfMemoryError e) { - tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait(inputLanguage, e); + tryGC = Utils.GCUtils.getInstance().tryGCOrWait("InitSuggest", e); } } - mOrientation = conf.orientation; - initSuggestPuncList(); + mOrientation = res.getConfiguration().orientation; - // register to receive ringer mode changes for silent mode - IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); + // Register to receive ringer mode change and network state change. + // Also receive installation and removal of a dictionary pack. + final IntentFilter filter = new IntentFilter(); + filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); registerReceiver(mReceiver, filter); - if (VOICE_INSTALLED) { - mVoiceInput = new VoiceInput(this, this); - mHints = new Hints(this, new Hints.Display() { - public void showHint(int viewResource) { - LayoutInflater inflater = (LayoutInflater) getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - View view = inflater.inflate(viewResource, null); - setCandidatesView(view); - setCandidatesViewShown(true); - mIsShowingHint = true; - } - }); - } - prefs.registerOnSharedPreferenceChangeListener(this); - } + mVoiceProxy = VoiceProxy.init(this, prefs, mHandler); - /** - * Loads a dictionary or multiple separated dictionary - * @return returns array of dictionary resource ids - */ - /* package */ static int[] getDictionary(Resources res) { - String packageName = LatinIME.class.getPackage().getName(); - XmlResourceParser xrp = res.getXml(R.xml.dictionary); - ArrayList<Integer> dictionaries = new ArrayList<Integer>(); - - try { - int current = xrp.getEventType(); - while (current != XmlResourceParser.END_DOCUMENT) { - if (current == XmlResourceParser.START_TAG) { - String tag = xrp.getName(); - if (tag != null) { - if (tag.equals("part")) { - String dictFileName = xrp.getAttributeValue(null, "name"); - dictionaries.add(res.getIdentifier(dictFileName, "raw", packageName)); - } - } - } - xrp.next(); - current = xrp.getEventType(); - } - } catch (XmlPullParserException e) { - Log.e(TAG, "Dictionary XML parsing failure"); - } catch (IOException e) { - Log.e(TAG, "Dictionary XML IOException"); - } + final IntentFilter packageFilter = new IntentFilter(); + packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + packageFilter.addDataScheme(SCHEME_PACKAGE); + registerReceiver(mDictionaryPackInstallReceiver, packageFilter); - int count = dictionaries.size(); - int[] dict = new int[count]; - for (int i = 0; i < count; i++) { - dict[i] = dictionaries.get(i); - } + final IntentFilter newDictFilter = new IntentFilter(); + newDictFilter.addAction( + DictionaryPackInstallBroadcastReceiver.NEW_DICTIONARY_INTENT_ACTION); + registerReceiver(mDictionaryPackInstallReceiver, newDictFilter); + } - return dict; + // Has to be package-visible for unit tests + /* package */ void loadSettings() { + if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this); + if (null == mSubtypeSwitcher) mSubtypeSwitcher = SubtypeSwitcher.getInstance(); + mSettingsValues = new Settings.Values(mPrefs, this, mSubtypeSwitcher.getInputLocaleStr()); + resetContactsDictionary(); } - private void initSuggest(String locale) { - mInputLocale = locale; + private void initSuggest() { + final String localeStr = mSubtypeSwitcher.getInputLocaleStr(); + final Locale keyboardLocale = Utils.constructLocaleFromString(localeStr); - Resources orig = getResources(); - Configuration conf = orig.getConfiguration(); - Locale saveLocale = conf.locale; - conf.locale = new Locale(locale); - orig.updateConfiguration(conf, orig.getDisplayMetrics()); + final Resources res = mResources; + final Locale savedLocale = Utils.setSystemLocale(res, keyboardLocale); if (mSuggest != null) { mSuggest.close(); } - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); - mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true); - int[] dictionaries = getDictionary(orig); - mSuggest = new Suggest(this, dictionaries); - updateAutoTextEnabled(saveLocale); - if (mUserDictionary != null) mUserDictionary.close(); - mUserDictionary = new UserDictionary(this, mInputLocale); - if (mContactsDictionary == null) { - mContactsDictionary = new ContactsDictionary(this, Suggest.DIC_CONTACTS); - } - if (mAutoDictionary != null) { - mAutoDictionary.close(); + int mainDicResId = Utils.getMainDictionaryResourceId(res); + mSuggest = new Suggest(this, mainDicResId, keyboardLocale); + if (mSettingsValues.mAutoCorrectEnabled) { + mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold); } - mAutoDictionary = new AutoDictionary(this, this, mInputLocale, Suggest.DIC_AUTO); - if (mUserBigramDictionary != null) { - mUserBigramDictionary.close(); - } - mUserBigramDictionary = new UserBigramDictionary(this, this, mInputLocale, - Suggest.DIC_USER); - mSuggest.setUserBigramDictionary(mUserBigramDictionary); + updateAutoTextEnabled(); + + mUserDictionary = new UserDictionary(this, localeStr); mSuggest.setUserDictionary(mUserDictionary); - mSuggest.setContactsDictionary(mContactsDictionary); + + resetContactsDictionary(); + + mAutoDictionary = new AutoDictionary(this, this, localeStr, Suggest.DIC_AUTO); mSuggest.setAutoDictionary(mAutoDictionary); + + mUserBigramDictionary = new UserBigramDictionary(this, this, localeStr, Suggest.DIC_USER); + mSuggest.setUserBigramDictionary(mUserBigramDictionary); + updateCorrectionMode(); - mWordSeparators = mResources.getString(R.string.word_separators); - mSentenceSeparators = mResources.getString(R.string.sentence_separators); - conf.locale = saveLocale; - orig.updateConfiguration(conf, orig.getDisplayMetrics()); + Utils.setSystemLocale(res, savedLocale); + } + + private void resetContactsDictionary() { + if (null == mSuggest) return; + ContactsDictionary contactsDictionary = mSettingsValues.mUseContactsDict + ? new ContactsDictionary(this, Suggest.DIC_CONTACTS) : null; + mSuggest.setContactsDictionary(contactsDictionary); + } + + /* package private */ void resetSuggestMainDict() { + final String localeStr = mSubtypeSwitcher.getInputLocaleStr(); + final Locale keyboardLocale = Utils.constructLocaleFromString(localeStr); + int mainDicResId = Utils.getMainDictionaryResourceId(mResources); + mSuggest.resetMainDict(this, mainDicResId, keyboardLocale); } @Override public void onDestroy() { - if (mUserDictionary != null) { - mUserDictionary.close(); - } - if (mContactsDictionary != null) { - mContactsDictionary.close(); + if (mSuggest != null) { + mSuggest.close(); + mSuggest = null; } unregisterReceiver(mReceiver); - if (VOICE_INSTALLED && mVoiceInput != null) { - mVoiceInput.destroy(); - } + unregisterReceiver(mDictionaryPackInstallReceiver); + mVoiceProxy.destroy(); LatinImeLogger.commit(); LatinImeLogger.onDestroy(); super.onDestroy(); @@ -495,236 +474,192 @@ public class LatinIME extends InputMethodService @Override public void onConfigurationChanged(Configuration conf) { - // If the system locale changes and is different from the saved - // locale (mSystemLocale), then reload the input locale list from the - // latin ime settings (shared prefs) and reset the input locale - // to the first one. - final String systemLocale = conf.locale.toString(); - if (!TextUtils.equals(systemLocale, mSystemLocale)) { - mSystemLocale = systemLocale; - if (mLanguageSwitcher != null) { - mLanguageSwitcher.loadLocales( - PreferenceManager.getDefaultSharedPreferences(this)); - mLanguageSwitcher.setSystemLocale(conf.locale); - toggleLanguage(true, true); - } else { - reloadKeyboards(); - } - } + mSubtypeSwitcher.onConfigurationChanged(conf); // If orientation changed while predicting, commit the change if (conf.orientation != mOrientation) { InputConnection ic = getCurrentInputConnection(); commitTyped(ic); if (ic != null) ic.finishComposingText(); // For voice input mOrientation = conf.orientation; - reloadKeyboards(); + if (isShowingOptionDialog()) + mOptionsDialog.dismiss(); } + mConfigurationChanging = true; super.onConfigurationChanged(conf); - if (mRecognizing) { - switchToRecognitionStatusView(); - } + mVoiceProxy.onConfigurationChanged(conf); mConfigurationChanging = false; + + // This will work only when the subtype is not supported. + LanguageSwitcherProxy.onConfigurationChanged(conf); } @Override public View onCreateInputView() { - mKeyboardSwitcher.recreateInputView(); - mKeyboardSwitcher.makeKeyboards(true); - mKeyboardSwitcher.setKeyboardMode( - KeyboardSwitcher.MODE_TEXT, 0, - shouldShowVoiceButton(makeFieldContext(), getCurrentInputEditorInfo())); - return mKeyboardSwitcher.getInputView(); + return mKeyboardSwitcher.onCreateInputView(); } @Override - public View onCreateCandidatesView() { - mKeyboardSwitcher.makeKeyboards(true); - mCandidateViewContainer = (LinearLayout) getLayoutInflater().inflate( - R.layout.candidates, null); - mCandidateView = (CandidateView) mCandidateViewContainer.findViewById(R.id.candidates); - mCandidateView.setService(this); - setCandidatesViewShown(true); - return mCandidateViewContainer; + public void setInputView(View view) { + super.setInputView(view); + mCandidateViewContainer = view.findViewById(R.id.candidates_container); + mCandidateView = (CandidateView) view.findViewById(R.id.candidates); + mCandidateView.setListener(this, view); + mCandidateStripHeight = (int)mResources.getDimension(R.dimen.candidate_strip_height); + } + + @Override + public void setCandidatesView(View view) { + // To ensure that CandidatesView will never be set. + return; } @Override public void onStartInputView(EditorInfo attribute, boolean restarting) { - LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); + final KeyboardSwitcher switcher = mKeyboardSwitcher; + LatinKeyboardView inputView = switcher.getKeyboardView(); + + if (DEBUG) { + Log.d(TAG, "onStartInputView: attribute:" + ((attribute == null) ? "none" + : String.format("inputType=0x%08x imeOptions=0x%08x", + attribute.inputType, attribute.imeOptions))); + } // In landscape mode, this method gets called without the input view being created. if (inputView == null) { return; } - if (mRefreshKeyboardRequired) { - mRefreshKeyboardRequired = false; - toggleLanguage(true, true); - } - - mKeyboardSwitcher.makeKeyboards(false); - - TextEntryState.newSession(this); + mSubtypeSwitcher.updateParametersOnStartInputView(); - // Most such things we decide below in the switch statement, but we need to know - // now whether this is a password text field, because we need to know now (before - // the switch statement) whether we want to enable the voice button. - mPasswordText = false; - int variation = attribute.inputType & EditorInfo.TYPE_MASK_VARIATION; - if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD || - variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) { - mPasswordText = true; - } + TextEntryState.reset(); - mEnableVoiceButton = shouldShowVoiceButton(makeFieldContext(), attribute); - final boolean enableVoiceButton = mEnableVoiceButton && mEnableVoice; + // Most such things we decide below in initializeInputAttributesAndGetMode, but we need to + // know now whether this is a password text field, because we need to know now whether we + // want to enable the voice button. + final VoiceProxy voiceIme = mVoiceProxy; + voiceIme.resetVoiceStates(InputTypeCompatUtils.isPasswordInputType(attribute.inputType) + || InputTypeCompatUtils.isVisiblePasswordInputType(attribute.inputType)); - mAfterVoiceInput = false; - mImmediatelyAfterVoiceInput = false; - mShowingVoiceSuggestions = false; - mVoiceInputHighlighted = false; - mInputTypeNoAutoCorrect = false; - mPredictionOn = false; - mCompletionOn = false; - mCompletions = null; - mCapsLock = false; - mEnteredText = null; - - switch (attribute.inputType & EditorInfo.TYPE_MASK_CLASS) { - case EditorInfo.TYPE_CLASS_NUMBER: - case EditorInfo.TYPE_CLASS_DATETIME: - // fall through - // NOTE: For now, we use the phone keyboard for NUMBER and DATETIME until we get - // a dedicated number entry keypad. - // TODO: Use a dedicated number entry keypad here when we get one. - case EditorInfo.TYPE_CLASS_PHONE: - mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_PHONE, - attribute.imeOptions, enableVoiceButton); - break; - case EditorInfo.TYPE_CLASS_TEXT: - mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT, - attribute.imeOptions, enableVoiceButton); - //startPrediction(); - mPredictionOn = true; - // Make sure that passwords are not displayed in candidate view - if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD || - variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD ) { - mPredictionOn = false; - } - if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS - || variation == EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME) { - mAutoSpace = false; - } else { - mAutoSpace = true; - } - if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) { - mPredictionOn = false; - mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_EMAIL, - attribute.imeOptions, enableVoiceButton); - } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_URI) { - mPredictionOn = false; - mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_URL, - attribute.imeOptions, enableVoiceButton); - } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) { - mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_IM, - attribute.imeOptions, enableVoiceButton); - } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_FILTER) { - mPredictionOn = false; - } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) { - mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_WEB, - attribute.imeOptions, enableVoiceButton); - // If it's a browser edit field and auto correct is not ON explicitly, then - // disable auto correction, but keep suggestions on. - if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) { - mInputTypeNoAutoCorrect = true; - } - } + initializeInputAttributes(attribute); - // If NO_SUGGESTIONS is set, don't do prediction. - if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) { - mPredictionOn = false; - mInputTypeNoAutoCorrect = true; - } - // If it's not multiline and the autoCorrect flag is not set, then don't correct - if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0 && - (attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) == 0) { - mInputTypeNoAutoCorrect = true; - } - if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) { - mPredictionOn = false; - mCompletionOn = isFullscreenMode(); - } - break; - default: - mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT, - attribute.imeOptions, enableVoiceButton); - } inputView.closing(); + mEnteredText = null; mComposing.setLength(0); - mPredicting = false; + mHasUncommittedTypedChars = false; mDeleteCount = 0; - mJustAddedAutoSpace = false; + mJustAddedMagicSpace = false; + mJustReplacedDoubleSpace = false; + loadSettings(); - updateShiftKeyState(attribute); + updateCorrectionMode(); + updateAutoTextEnabled(); + updateSuggestionVisibility(mPrefs, mResources); - setCandidatesViewShownInternal(isCandidateStripVisible() || mCompletionOn, - false /* needsInputViewShown */ ); - updateSuggestions(); + if (mSuggest != null && mSettingsValues.mAutoCorrectEnabled) { + mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold); + } + mVoiceProxy.loadSettings(attribute, mPrefs); + // This will work only when the subtype is not supported. + LanguageSwitcherProxy.loadSettings(); - // If the dictionary is not big enough, don't auto correct - mHasDictionary = mSuggest.hasMainDictionary(); + if (mSubtypeSwitcher.isKeyboardMode()) { + switcher.loadKeyboard(attribute, + mSubtypeSwitcher.isShortcutImeEnabled() && voiceIme.isVoiceButtonEnabled(), + voiceIme.isVoiceButtonOnPrimary()); + switcher.updateShiftState(); + } + + setSuggestionStripShownInternal(isCandidateStripVisible(), /* needsInputViewShown */ false); + // Delay updating suggestions because keyboard input view may not be shown at this point. + mHandler.postUpdateSuggestions(); updateCorrectionMode(); - inputView.setPreviewEnabled(mPopupOn); + inputView.setKeyPreviewPopupEnabled(mSettingsValues.mKeyPreviewPopupOn, + mSettingsValues.mKeyPreviewPopupDismissDelay); inputView.setProximityCorrectionEnabled(true); - mPredictionOn = mPredictionOn && (mCorrectionMode > 0 || mShowSuggestions); // If we just entered a text field, maybe it has some old text that requires correction - checkReCorrectionOnStart(); - checkTutorial(attribute.privateImeOptions); + mRecorrection.checkRecorrectionOnStart(); + inputView.setForeground(true); + + voiceIme.onStartInputView(inputView.getWindowToken()); + if (TRACE) Debug.startMethodTracing("/data/trace/latinime"); } - private void checkReCorrectionOnStart() { - if (mReCorrectionEnabled && isPredictionOn()) { - // First get the cursor position. This is required by setOldSuggestions(), so that - // it can pass the correct range to setComposingRegion(). At this point, we don't - // have valid values for mLastSelectionStart/Stop because onUpdateSelection() has - // not been called yet. - InputConnection ic = getCurrentInputConnection(); - if (ic == null) return; - ExtractedTextRequest etr = new ExtractedTextRequest(); - etr.token = 0; // anything is fine here - ExtractedText et = ic.getExtractedText(etr, 0); - if (et == null) return; - - mLastSelectionStart = et.startOffset + et.selectionStart; - mLastSelectionEnd = et.startOffset + et.selectionEnd; - - // Then look for possible corrections in a delayed fashion - if (!TextUtils.isEmpty(et.text) && isCursorTouchingWord()) { - postUpdateOldSuggestions(); + private void initializeInputAttributes(EditorInfo attribute) { + if (attribute == null) + return; + final int inputType = attribute.inputType; + final int variation = inputType & InputType.TYPE_MASK_VARIATION; + mShouldInsertMagicSpace = false; + mInputTypeNoAutoCorrect = false; + mIsSettingsSuggestionStripOn = false; + mApplicationSpecifiedCompletionOn = false; + mApplicationSpecifiedCompletions = null; + + if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) { + mIsSettingsSuggestionStripOn = true; + // Make sure that passwords are not displayed in candidate view + if (InputTypeCompatUtils.isPasswordInputType(inputType) + || InputTypeCompatUtils.isVisiblePasswordInputType(inputType)) { + mIsSettingsSuggestionStripOn = false; + } + if (InputTypeCompatUtils.isEmailVariation(variation) + || variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) { + mShouldInsertMagicSpace = false; + } else { + mShouldInsertMagicSpace = true; + } + if (InputTypeCompatUtils.isEmailVariation(variation)) { + mIsSettingsSuggestionStripOn = false; + } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) { + mIsSettingsSuggestionStripOn = false; + } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) { + mIsSettingsSuggestionStripOn = false; + } else if (variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) { + // If it's a browser edit field and auto correct is not ON explicitly, then + // disable auto correction, but keep suggestions on. + if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) { + mInputTypeNoAutoCorrect = true; + } + } + + // If NO_SUGGESTIONS is set, don't do prediction. + if ((inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) { + mIsSettingsSuggestionStripOn = false; + mInputTypeNoAutoCorrect = true; + } + // If it's not multiline and the autoCorrect flag is not set, then don't correct + if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0 + && (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) == 0) { + mInputTypeNoAutoCorrect = true; + } + if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) { + mIsSettingsSuggestionStripOn = false; + mApplicationSpecifiedCompletionOn = isFullscreenMode(); } } } @Override + public void onWindowHidden() { + super.onWindowHidden(); + KeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); + if (inputView != null) inputView.closing(); + } + + @Override public void onFinishInput() { super.onFinishInput(); LatinImeLogger.commit(); - onAutoCompletionStateChanged(false); + mKeyboardSwitcher.onAutoCorrectionStateChanged(false); - if (VOICE_INSTALLED && !mConfigurationChanging) { - if (mAfterVoiceInput) { - mVoiceInput.flushAllTextModificationCounters(); - mVoiceInput.logInputEnded(); - } - mVoiceInput.flushLogs(); - mVoiceInput.cancel(); - } - if (mKeyboardSwitcher.getInputView() != null) { - mKeyboardSwitcher.getInputView().closing(); - } + mVoiceProxy.flushVoiceInputLogs(mConfigurationChanging); + + KeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); + if (inputView != null) inputView.closing(); if (mAutoDictionary != null) mAutoDictionary.flushPendingWrites(); if (mUserBigramDictionary != null) mUserBigramDictionary.flushPendingWrites(); } @@ -732,21 +667,17 @@ public class LatinIME extends InputMethodService @Override public void onFinishInputView(boolean finishingInput) { super.onFinishInputView(finishingInput); - // Remove penging messages related to update suggestions - mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS); - mHandler.removeMessages(MSG_UPDATE_OLD_SUGGESTIONS); + KeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); + if (inputView != null) inputView.setForeground(false); + // Remove pending messages related to update suggestions + mHandler.cancelUpdateSuggestions(); + mHandler.cancelUpdateOldSuggestions(); } @Override public void onUpdateExtractedText(int token, ExtractedText text) { super.onUpdateExtractedText(token, text); - InputConnection ic = getCurrentInputConnection(); - if (!mImmediatelyAfterVoiceInput && mAfterVoiceInput && ic != null) { - if (mHints.showPunctuationHintIfNecessary(ic)) { - mVoiceInput.logPunctuationHintDisplayed(); - } - } - mImmediatelyAfterVoiceInput = false; + mVoiceProxy.showPunctuationHintIfNecessary(); } @Override @@ -759,75 +690,70 @@ public class LatinIME extends InputMethodService if (DEBUG) { Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart + ", ose=" + oldSelEnd + + ", lss=" + mLastSelectionStart + + ", lse=" + mLastSelectionEnd + ", nss=" + newSelStart + ", nse=" + newSelEnd + ", cs=" + candidatesStart + ", ce=" + candidatesEnd); } - if (mAfterVoiceInput) { - mVoiceInput.setCursorPos(newSelEnd); - mVoiceInput.setSelectionSpan(newSelEnd - newSelStart); - } + mVoiceProxy.setCursorAndSelection(newSelEnd, newSelStart); // If the current selection in the text view changes, we should // clear whatever candidate text we have. - if ((((mComposing.length() > 0 && mPredicting) || mVoiceInputHighlighted) - && (newSelStart != candidatesEnd - || newSelEnd != candidatesEnd) - && mLastSelectionStart != newSelStart)) { + final boolean selectionChanged = (newSelStart != candidatesEnd + || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart; + final boolean candidatesCleared = candidatesStart == -1 && candidatesEnd == -1; + if (((mComposing.length() > 0 && mHasUncommittedTypedChars) + || mVoiceProxy.isVoiceInputHighlighted()) + && (selectionChanged || candidatesCleared)) { + if (candidatesCleared) { + // If the composing span has been cleared, save the typed word in the history for + // recorrection before we reset the candidate strip. Then, we'll be able to show + // suggestions for recorrection right away. + mRecorrection.saveRecorrectionSuggestion(mWord, mComposing); + } mComposing.setLength(0); - mPredicting = false; - postUpdateSuggestions(); + mHasUncommittedTypedChars = false; + if (isCursorTouchingWord()) { + mHandler.cancelUpdateBigramPredictions(); + mHandler.postUpdateSuggestions(); + } else { + setPunctuationSuggestions(); + } TextEntryState.reset(); InputConnection ic = getCurrentInputConnection(); if (ic != null) { ic.finishComposingText(); } - mVoiceInputHighlighted = false; - } else if (!mPredicting && !mJustAccepted) { - switch (TextEntryState.getState()) { - case ACCEPTED_DEFAULT: + mVoiceProxy.setVoiceInputHighlighted(false); + } else if (!mHasUncommittedTypedChars && !mExpectingUpdateSelection) { + if (TextEntryState.isAcceptedDefault() || TextEntryState.isSpaceAfterPicked()) { + if (TextEntryState.isAcceptedDefault()) TextEntryState.reset(); - // fall through - case SPACE_AFTER_PICKED: - mJustAddedAutoSpace = false; // The user moved the cursor. - break; } } - mJustAccepted = false; - postUpdateShiftKeyState(); + if (!mExpectingUpdateSelection) { + mJustAddedMagicSpace = false; // The user moved the cursor. + mJustReplacedDoubleSpace = false; + } + mExpectingUpdateSelection = false; + mHandler.postUpdateShiftKeyState(); // Make a note of the cursor position mLastSelectionStart = newSelStart; mLastSelectionEnd = newSelEnd; - if (mReCorrectionEnabled) { - // Don't look for corrections if the keyboard is not visible - if (mKeyboardSwitcher != null && mKeyboardSwitcher.getInputView() != null - && mKeyboardSwitcher.getInputView().isShown()) { - // Check if we should go in or out of correction mode. - if (isPredictionOn() - && mJustRevertedSeparator == null - && (candidatesStart == candidatesEnd || newSelStart != oldSelStart - || TextEntryState.isCorrecting()) - && (newSelStart < newSelEnd - 1 || (!mPredicting)) - && !mVoiceInputHighlighted) { - if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) { - postUpdateOldSuggestions(); - } else { - abortCorrection(false); - // Show the punctuation suggestions list if the current one is not - // and if not showing "Touch again to save". - if (mCandidateView != null - && !mSuggestPuncList.equals(mCandidateView.getSuggestions()) - && !mCandidateView.isShowingAddToDictionaryHint()) { - setNextSuggestions(); - } - } - } - } - } + mRecorrection.updateRecorrectionSelection(mKeyboardSwitcher, + mCandidateView, candidatesStart, candidatesEnd, newSelStart, + newSelEnd, oldSelStart, mLastSelectionStart, + mLastSelectionEnd, mHasUncommittedTypedChars); + } + + public void setLastSelection(int start, int end) { + mLastSelectionStart = start; + mLastSelectionEnd = end; } /** @@ -840,7 +766,7 @@ public class LatinIME extends InputMethodService */ @Override public void onExtractedTextClicked() { - if (mReCorrectionEnabled && isPredictionOn()) return; + if (mRecorrection.isRecorrectionEnabled() && isSuggestionsRequested()) return; super.onExtractedTextClicked(); } @@ -856,7 +782,7 @@ public class LatinIME extends InputMethodService */ @Override public void onExtractedCursorMovement(int dx, int dy) { - if (mReCorrectionEnabled && isPredictionOn()) return; + if (mRecorrection.isRecorrectionEnabled() && isSuggestionsRequested()) return; super.onExtractedCursorMovement(dx, dy); } @@ -864,84 +790,103 @@ public class LatinIME extends InputMethodService @Override public void hideWindow() { LatinImeLogger.commit(); - onAutoCompletionStateChanged(false); + mKeyboardSwitcher.onAutoCorrectionStateChanged(false); if (TRACE) Debug.stopMethodTracing(); if (mOptionsDialog != null && mOptionsDialog.isShowing()) { mOptionsDialog.dismiss(); mOptionsDialog = null; } - if (!mConfigurationChanging) { - if (mAfterVoiceInput) mVoiceInput.logInputEnded(); - if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) { - mVoiceInput.logKeyboardWarningDialogDismissed(); - mVoiceWarningDialog.dismiss(); - mVoiceWarningDialog = null; - } - if (VOICE_INSTALLED & mRecognizing) { - mVoiceInput.cancel(); - } - } - mWordToSuggestions.clear(); - mWordHistory.clear(); + mVoiceProxy.hideVoiceWindow(mConfigurationChanging); + mRecorrection.clearWordsInHistory(); super.hideWindow(); - TextEntryState.endSession(); } @Override - public void onDisplayCompletions(CompletionInfo[] completions) { + public void onDisplayCompletions(CompletionInfo[] applicationSpecifiedCompletions) { if (DEBUG) { - Log.i("foo", "Received completions:"); - for (int i=0; i<(completions != null ? completions.length : 0); i++) { - Log.i("foo", " #" + i + ": " + completions[i]); + Log.i(TAG, "Received completions:"); + if (applicationSpecifiedCompletions != null) { + for (int i = 0; i < applicationSpecifiedCompletions.length; i++) { + Log.i(TAG, " #" + i + ": " + applicationSpecifiedCompletions[i]); + } } } - if (mCompletionOn) { - mCompletions = completions; - if (completions == null) { + if (mApplicationSpecifiedCompletionOn) { + mApplicationSpecifiedCompletions = applicationSpecifiedCompletions; + if (applicationSpecifiedCompletions == null) { clearSuggestions(); return; } - List<CharSequence> stringList = new ArrayList<CharSequence>(); - for (int i=0; i<(completions != null ? completions.length : 0); i++) { - CompletionInfo ci = completions[i]; - if (ci != null) stringList.add(ci.getText()); - } + SuggestedWords.Builder builder = new SuggestedWords.Builder() + .setApplicationSpecifiedCompletions(applicationSpecifiedCompletions) + .setTypedWordValid(true) + .setHasMinimalSuggestion(true); // When in fullscreen mode, show completions generated by the application - setSuggestions(stringList, true, true, true); + setSuggestions(builder.build()); mBestWord = null; - setCandidatesViewShown(true); + setSuggestionStripShown(true); } } - private void setCandidatesViewShownInternal(boolean shown, boolean needsInputViewShown) { - // TODO: Remove this if we support candidates with hard keyboard + private void setSuggestionStripShownInternal(boolean shown, boolean needsInputViewShown) { + // TODO: Modify this if we support candidates with hard keyboard if (onEvaluateInputViewShown()) { - super.setCandidatesViewShown(shown && mKeyboardSwitcher.getInputView() != null - && (needsInputViewShown ? mKeyboardSwitcher.getInputView().isShown() : true)); + final boolean shouldShowCandidates = shown + && (needsInputViewShown ? mKeyboardSwitcher.isInputViewShown() : true); + if (isExtractViewShown()) { + // No need to have extra space to show the key preview. + mCandidateViewContainer.setMinimumHeight(0); + mCandidateViewContainer.setVisibility( + shouldShowCandidates ? View.VISIBLE : View.GONE); + } else { + // We must control the visibility of the suggestion strip in order to avoid clipped + // key previews, even when we don't show the suggestion strip. + mCandidateViewContainer.setVisibility( + shouldShowCandidates ? View.VISIBLE : View.INVISIBLE); + } } } - @Override - public void setCandidatesViewShown(boolean shown) { - setCandidatesViewShownInternal(shown, true /* needsInputViewShown */ ); + private void setSuggestionStripShown(boolean shown) { + setSuggestionStripShownInternal(shown, /* needsInputViewShown */true); } @Override public void onComputeInsets(InputMethodService.Insets outInsets) { super.onComputeInsets(outInsets); - if (!isFullscreenMode()) { - outInsets.contentTopInsets = outInsets.visibleTopInsets; + final KeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); + if (inputView == null || mCandidateViewContainer == null) + return; + final int containerHeight = mCandidateViewContainer.getHeight(); + int touchY = containerHeight; + // Need to set touchable region only if input view is being shown + if (mKeyboardSwitcher.isInputViewShown()) { + if (mCandidateViewContainer.getVisibility() == View.VISIBLE) { + touchY -= mCandidateStripHeight; + } + final int touchWidth = inputView.getWidth(); + final int touchHeight = inputView.getHeight() + containerHeight + // Extend touchable region below the keyboard. + + EXTENDED_TOUCHABLE_REGION_HEIGHT; + if (DEBUG) { + Log.d(TAG, "Touchable region: y=" + touchY + " width=" + touchWidth + + " height=" + touchHeight); + } + setTouchableRegionCompat(outInsets, 0, touchY, touchWidth, touchHeight); } + outInsets.contentTopInsets = touchY; + outInsets.visibleTopInsets = touchY; } @Override public boolean onEvaluateFullscreenMode() { - DisplayMetrics dm = getResources().getDisplayMetrics(); + final Resources res = mResources; + DisplayMetrics dm = res.getDisplayMetrics(); float displayHeight = dm.heightPixels; // If the display is more than X inches high, don't go to fullscreen mode - float dimen = getResources().getDimension(R.dimen.max_height_for_fullscreen); + float dimen = res.getDimension(R.dimen.max_height_for_fullscreen); if (displayHeight > dimen) { return false; } else { @@ -952,25 +897,13 @@ public class LatinIME extends InputMethodService @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { - case KeyEvent.KEYCODE_BACK: - if (event.getRepeatCount() == 0 && mKeyboardSwitcher.getInputView() != null) { - if (mKeyboardSwitcher.getInputView().handleBack()) { - return true; - } else if (mTutorial != null) { - mTutorial.close(); - mTutorial = null; - } - } - break; - case KeyEvent.KEYCODE_DPAD_DOWN: - case KeyEvent.KEYCODE_DPAD_UP: - case KeyEvent.KEYCODE_DPAD_LEFT: - case KeyEvent.KEYCODE_DPAD_RIGHT: - // If tutorial is visible, don't allow dpad to work - if (mTutorial != null) { + case KeyEvent.KEYCODE_BACK: + if (event.getRepeatCount() == 0 && mKeyboardSwitcher.getKeyboardView() != null) { + if (mKeyboardSwitcher.getKeyboardView().handleBack()) { return true; } - break; + } + break; } return super.onKeyDown(keyCode, event); } @@ -978,138 +911,86 @@ public class LatinIME extends InputMethodService @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_DOWN: - case KeyEvent.KEYCODE_DPAD_UP: - case KeyEvent.KEYCODE_DPAD_LEFT: - case KeyEvent.KEYCODE_DPAD_RIGHT: - // If tutorial is visible, don't allow dpad to work - if (mTutorial != null) { - return true; - } - LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); - // Enable shift key and DPAD to do selections - if (inputView != null && inputView.isShown() - && inputView.isShifted()) { - event = new KeyEvent(event.getDownTime(), event.getEventTime(), - event.getAction(), event.getKeyCode(), event.getRepeatCount(), - event.getDeviceId(), event.getScanCode(), - KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON); - InputConnection ic = getCurrentInputConnection(); - if (ic != null) ic.sendKeyEvent(event); - return true; - } - break; + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + // Enable shift key and DPAD to do selections + if (mKeyboardSwitcher.isInputViewShown() + && mKeyboardSwitcher.isShiftedOrShiftLocked()) { + KeyEvent newEvent = new KeyEvent(event.getDownTime(), event.getEventTime(), + event.getAction(), event.getKeyCode(), event.getRepeatCount(), + event.getDeviceId(), event.getScanCode(), + KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON); + InputConnection ic = getCurrentInputConnection(); + if (ic != null) + ic.sendKeyEvent(newEvent); + return true; + } + break; } return super.onKeyUp(keyCode, event); } - private void revertVoiceInput() { - InputConnection ic = getCurrentInputConnection(); - if (ic != null) ic.commitText("", 1); - updateSuggestions(); - mVoiceInputHighlighted = false; - } - - private void commitVoiceInput() { - InputConnection ic = getCurrentInputConnection(); - if (ic != null) ic.finishComposingText(); - updateSuggestions(); - mVoiceInputHighlighted = false; - } - - private void reloadKeyboards() { - mKeyboardSwitcher.setLanguageSwitcher(mLanguageSwitcher); - if (mKeyboardSwitcher.getInputView() != null - && mKeyboardSwitcher.getKeyboardMode() != KeyboardSwitcher.MODE_NONE) { - mKeyboardSwitcher.setVoiceMode(mEnableVoice && mEnableVoiceButton, mVoiceOnPrimary); - } - mKeyboardSwitcher.makeKeyboards(true); - } - - private void commitTyped(InputConnection inputConnection) { - if (mPredicting) { - mPredicting = false; + public void commitTyped(InputConnection inputConnection) { + if (mHasUncommittedTypedChars) { + mHasUncommittedTypedChars = false; if (mComposing.length() > 0) { if (inputConnection != null) { inputConnection.commitText(mComposing, 1); } mCommittedLength = mComposing.length(); TextEntryState.acceptedTyped(mComposing); - addToDictionaries(mComposing, AutoDictionary.FREQUENCY_FOR_TYPED); + addToAutoAndUserBigramDictionaries(mComposing, AutoDictionary.FREQUENCY_FOR_TYPED); } updateSuggestions(); } } - private void postUpdateShiftKeyState() { - mHandler.removeMessages(MSG_UPDATE_SHIFT_STATE); - // TODO: Should remove this 300ms delay? - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SHIFT_STATE), 300); - } - - public void updateShiftKeyState(EditorInfo attr) { + public boolean getCurrentAutoCapsState() { InputConnection ic = getCurrentInputConnection(); - if (ic != null && attr != null && mKeyboardSwitcher.isAlphabetMode()) { - mKeyboardSwitcher.setShifted(mShiftKeyState.isMomentary() || mCapsLock - || getCursorCapsMode(ic, attr) != 0); - } - } - - private int getCursorCapsMode(InputConnection ic, EditorInfo attr) { - int caps = 0; EditorInfo ei = getCurrentInputEditorInfo(); - if (mAutoCap && ei != null && ei.inputType != EditorInfo.TYPE_NULL) { - caps = ic.getCursorCapsMode(attr.inputType); + if (mSettingsValues.mAutoCap && ic != null && ei != null + && ei.inputType != InputType.TYPE_NULL) { + return ic.getCursorCapsMode(ei.inputType) != 0; } - return caps; + return false; } - private void swapPunctuationAndSpace() { + private void swapSwapperAndSpace() { final InputConnection ic = getCurrentInputConnection(); if (ic == null) return; CharSequence lastTwo = ic.getTextBeforeCursor(2, 0); + // It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called. if (lastTwo != null && lastTwo.length() == 2 - && lastTwo.charAt(0) == KEYCODE_SPACE && isSentenceSeparator(lastTwo.charAt(1))) { + && lastTwo.charAt(0) == Keyboard.CODE_SPACE) { ic.beginBatchEdit(); ic.deleteSurroundingText(2, 0); ic.commitText(lastTwo.charAt(1) + " ", 1); ic.endBatchEdit(); - updateShiftKeyState(getCurrentInputEditorInfo()); - mJustAddedAutoSpace = true; + mKeyboardSwitcher.updateShiftState(); } } - private void reswapPeriodAndSpace() { - final InputConnection ic = getCurrentInputConnection(); - if (ic == null) return; - CharSequence lastThree = ic.getTextBeforeCursor(3, 0); - if (lastThree != null && lastThree.length() == 3 - && lastThree.charAt(0) == KEYCODE_PERIOD - && lastThree.charAt(1) == KEYCODE_SPACE - && lastThree.charAt(2) == KEYCODE_PERIOD) { - ic.beginBatchEdit(); - ic.deleteSurroundingText(3, 0); - ic.commitText(" ..", 1); - ic.endBatchEdit(); - updateShiftKeyState(getCurrentInputEditorInfo()); - } - } - - private void doubleSpace() { - //if (!mAutoPunctuate) return; + private void maybeDoubleSpace() { if (mCorrectionMode == Suggest.CORRECTION_NONE) return; final InputConnection ic = getCurrentInputConnection(); if (ic == null) return; CharSequence lastThree = ic.getTextBeforeCursor(3, 0); if (lastThree != null && lastThree.length() == 3 && Character.isLetterOrDigit(lastThree.charAt(0)) - && lastThree.charAt(1) == KEYCODE_SPACE && lastThree.charAt(2) == KEYCODE_SPACE) { + && lastThree.charAt(1) == Keyboard.CODE_SPACE + && lastThree.charAt(2) == Keyboard.CODE_SPACE + && mHandler.isAcceptingDoubleSpaces()) { + mHandler.cancelDoubleSpacesTimer(); ic.beginBatchEdit(); ic.deleteSurroundingText(2, 0); ic.commitText(". ", 1); ic.endBatchEdit(); - updateShiftKeyState(getCurrentInputEditorInfo()); - mJustAddedAutoSpace = true; + mKeyboardSwitcher.updateShiftState(); + mJustReplacedDoubleSpace = true; + } else { + mHandler.startDoubleSpacesTimer(); } } @@ -1121,8 +1002,8 @@ public class LatinIME extends InputMethodService // if there is one. CharSequence lastOne = ic.getTextBeforeCursor(1, 0); if (lastOne != null && lastOne.length() == 1 - && lastOne.charAt(0) == KEYCODE_PERIOD - && text.charAt(0) == KEYCODE_PERIOD) { + && lastOne.charAt(0) == Keyboard.CODE_PERIOD + && text.charAt(0) == Keyboard.CODE_PERIOD) { ic.deleteSurroundingText(1, 0); } } @@ -1133,16 +1014,17 @@ public class LatinIME extends InputMethodService CharSequence lastOne = ic.getTextBeforeCursor(1, 0); if (lastOne != null && lastOne.length() == 1 - && lastOne.charAt(0) == KEYCODE_SPACE) { + && lastOne.charAt(0) == Keyboard.CODE_SPACE) { ic.deleteSurroundingText(1, 0); } } + @Override public boolean addWordToDictionary(String word) { mUserDictionary.addWord(word, 128); // Suggestion strip should be updated after the operation of adding word to the // user dictionary - postUpdateSuggestions(); + mHandler.postUpdateSuggestions(); return true; } @@ -1154,25 +1036,22 @@ public class LatinIME extends InputMethodService } } - private void showInputMethodPicker() { - ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)) - .showInputMethodPicker(); - } - - private void onOptionKeyPressed() { - if (!isShowingOptionDialog()) { - if (LatinIMEUtil.hasMultipleEnabledIMEs(this)) { - showOptionsMenu(); - } else { - launchSettings(); - } + private void onSettingsKeyPressed() { + if (isShowingOptionDialog()) + return; + if (InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) { + showSubtypeSelectorAndSettings(); + } else if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm)) { + showOptionsMenu(); + } else { + launchSettings(); } } - private void onOptionKeyLongPressed() { + private void onSettingsKeyLongPressed() { if (!isShowingOptionDialog()) { - if (LatinIMEUtil.hasMultipleEnabledIMEs(this)) { - showInputMethodPicker(); + if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm)) { + mImm.showInputMethodPicker(); } else { launchSettings(); } @@ -1183,150 +1062,155 @@ public class LatinIME extends InputMethodService return mOptionsDialog != null && mOptionsDialog.isShowing(); } - // Implementation of KeyboardViewListener - - public void onKey(int primaryCode, int[] keyCodes, int x, int y) { + // Implementation of {@link KeyboardActionListener}. + @Override + public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) { long when = SystemClock.uptimeMillis(); - if (primaryCode != Keyboard.KEYCODE_DELETE || - when > mLastKeyTime + QUICK_PRESS) { + if (primaryCode != Keyboard.CODE_DELETE || when > mLastKeyTime + QUICK_PRESS) { mDeleteCount = 0; } mLastKeyTime = when; - final boolean distinctMultiTouch = mKeyboardSwitcher.hasDistinctMultitouch(); + KeyboardSwitcher switcher = mKeyboardSwitcher; + final boolean distinctMultiTouch = switcher.hasDistinctMultitouch(); + final boolean lastStateOfJustReplacedDoubleSpace = mJustReplacedDoubleSpace; + mJustReplacedDoubleSpace = false; switch (primaryCode) { - case Keyboard.KEYCODE_DELETE: - handleBackspace(); - mDeleteCount++; - LatinImeLogger.logOnDelete(); - break; - case Keyboard.KEYCODE_SHIFT: - // Shift key is handled in onPress() when device has distinct multi-touch panel. - if (!distinctMultiTouch) - handleShift(); - break; - case Keyboard.KEYCODE_MODE_CHANGE: - // Symbol key is handled in onPress() when device has distinct multi-touch panel. - if (!distinctMultiTouch) - changeKeyboardMode(); - break; - case Keyboard.KEYCODE_CANCEL: - if (!isShowingOptionDialog()) { - handleClose(); - } - break; - case LatinKeyboardView.KEYCODE_OPTIONS: - onOptionKeyPressed(); - break; - case LatinKeyboardView.KEYCODE_OPTIONS_LONGPRESS: - onOptionKeyLongPressed(); - break; - case LatinKeyboardView.KEYCODE_NEXT_LANGUAGE: - toggleLanguage(false, true); - break; - case LatinKeyboardView.KEYCODE_PREV_LANGUAGE: - toggleLanguage(false, false); - break; - case LatinKeyboardView.KEYCODE_VOICE: - if (VOICE_INSTALLED) { - startListening(false /* was a button press, was not a swipe */); - } - break; - case 9 /*Tab*/: - sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB); - break; - default: - if (primaryCode != KEYCODE_ENTER) { - mJustAddedAutoSpace = false; - } - RingCharBuffer.getInstance().push((char)primaryCode, x, y); - LatinImeLogger.logOnInputChar(); - if (isWordSeparator(primaryCode)) { - handleSeparator(primaryCode); - } else { - handleCharacter(primaryCode, keyCodes); - } - // Cancel the just reverted state - mJustRevertedSeparator = null; + case Keyboard.CODE_DELETE: + handleBackspace(lastStateOfJustReplacedDoubleSpace); + mDeleteCount++; + mExpectingUpdateSelection = true; + LatinImeLogger.logOnDelete(); + break; + case Keyboard.CODE_SHIFT: + // Shift key is handled in onPress() when device has distinct multi-touch panel. + if (!distinctMultiTouch) + switcher.toggleShift(); + break; + case Keyboard.CODE_SWITCH_ALPHA_SYMBOL: + // Symbol key is handled in onPress() when device has distinct multi-touch panel. + if (!distinctMultiTouch) + switcher.changeKeyboardMode(); + break; + case Keyboard.CODE_CANCEL: + if (!isShowingOptionDialog()) { + handleClose(); + } + break; + case Keyboard.CODE_SETTINGS: + onSettingsKeyPressed(); + break; + case Keyboard.CODE_SETTINGS_LONGPRESS: + onSettingsKeyLongPressed(); + break; + case LatinKeyboard.CODE_NEXT_LANGUAGE: + toggleLanguage(true); + break; + case LatinKeyboard.CODE_PREV_LANGUAGE: + toggleLanguage(false); + break; + case Keyboard.CODE_CAPSLOCK: + switcher.toggleCapsLock(); + break; + case Keyboard.CODE_SHORTCUT: + mSubtypeSwitcher.switchToShortcutIME(); + break; + case Keyboard.CODE_TAB: + handleTab(); + // There are two cases for tab. Either we send a "next" event, that may change the + // focus but will never move the cursor. Or, we send a real tab keycode, which some + // applications may accept or ignore, and we don't know whether this will move the + // cursor or not. So actually, we don't really know. + // So to go with the safer option, we'd rather behave as if the user moved the + // cursor when they didn't than the opposite. We also expect that most applications + // will actually use tab only for focus movement. + // To sum it up: do not update mExpectingUpdateSelection here. + break; + default: + if (mSettingsValues.isWordSeparator(primaryCode)) { + handleSeparator(primaryCode, x, y); + } else { + handleCharacter(primaryCode, keyCodes, x, y); + } + mExpectingUpdateSelection = true; + break; } - mKeyboardSwitcher.onKey(primaryCode); + switcher.onKey(primaryCode); // Reset after any single keystroke mEnteredText = null; } - public void onText(CharSequence text) { - if (VOICE_INSTALLED && mVoiceInputHighlighted) { - commitVoiceInput(); - } + @Override + public void onTextInput(CharSequence text) { + mVoiceProxy.commitVoiceInput(); InputConnection ic = getCurrentInputConnection(); if (ic == null) return; - abortCorrection(false); + mRecorrection.abortRecorrection(false); ic.beginBatchEdit(); - if (mPredicting) { - commitTyped(ic); - } + commitTyped(ic); maybeRemovePreviousPeriod(text); ic.commitText(text, 1); ic.endBatchEdit(); - updateShiftKeyState(getCurrentInputEditorInfo()); - mKeyboardSwitcher.onKey(0); // dummy key code. - mJustRevertedSeparator = null; - mJustAddedAutoSpace = false; + mKeyboardSwitcher.updateShiftState(); + mKeyboardSwitcher.onKey(Keyboard.CODE_DUMMY); + mJustAddedMagicSpace = false; mEnteredText = text; } - public void onCancel() { + @Override + public void onCancelInput() { // User released a finger outside any key mKeyboardSwitcher.onCancelInput(); } - private void handleBackspace() { - if (VOICE_INSTALLED && mVoiceInputHighlighted) { - mVoiceInput.incrementTextModificationDeleteCount( - mVoiceResults.candidates.get(0).toString().length()); - revertVoiceInput(); - return; - } - boolean deleteChar = false; - InputConnection ic = getCurrentInputConnection(); - if (ic == null) return; + private void handleBackspace(boolean justReplacedDoubleSpace) { + if (mVoiceProxy.logAndRevertVoiceInput()) return; + final InputConnection ic = getCurrentInputConnection(); + if (ic == null) return; ic.beginBatchEdit(); - if (mAfterVoiceInput) { - // Don't log delete if the user is pressing delete at - // the beginning of the text box (hence not deleting anything) - if (mVoiceInput.getCursorPos() > 0) { - // If anything was selected before the delete was pressed, increment the - // delete count by the length of the selection - int deleteLen = mVoiceInput.getSelectionSpan() > 0 ? - mVoiceInput.getSelectionSpan() : 1; - mVoiceInput.incrementTextModificationDeleteCount(deleteLen); - } - } + mVoiceProxy.handleBackspace(); - if (mPredicting) { + boolean deleteChar = false; + if (mHasUncommittedTypedChars) { final int length = mComposing.length(); if (length > 0) { mComposing.delete(length - 1, length); mWord.deleteLast(); ic.setComposingText(mComposing, 1); if (mComposing.length() == 0) { - mPredicting = false; + mHasUncommittedTypedChars = false; + } + if (1 == length) { + // 1 == length means we are about to erase the last character of the word, + // so we can show bigrams. + mHandler.postUpdateBigramPredictions(); + } else { + // length > 1, so we still have letters to deduce a suggestion from. + mHandler.postUpdateSuggestions(); } - postUpdateSuggestions(); } else { ic.deleteSurroundingText(1, 0); } } else { deleteChar = true; } - postUpdateShiftKeyState(); + mHandler.postUpdateShiftKeyState(); + TextEntryState.backspace(); - if (TextEntryState.getState() == TextEntryState.State.UNDO_COMMIT) { + if (TextEntryState.isUndoCommit()) { revertLastWord(deleteChar); ic.endBatchEdit(); return; - } else if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) { + } + if (justReplacedDoubleSpace) { + if (revertDoubleSpace()) { + ic.endBatchEdit(); + return; + } + } + + if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) { ic.deleteSurroundingText(mEnteredText.length(), 0); } else if (deleteChar) { if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) { @@ -1345,178 +1229,176 @@ public class LatinIME extends InputMethodService } } } - mJustRevertedSeparator = null; ic.endBatchEdit(); } - private void resetShift() { - handleShiftInternal(true); - } + private void handleTab() { + final int imeOptions = getCurrentInputEditorInfo().imeOptions; + if (!EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions) + && !EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions)) { + sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB); + return; + } - private void handleShift() { - handleShiftInternal(false); - } + final InputConnection ic = getCurrentInputConnection(); + if (ic == null) + return; - private void handleShiftInternal(boolean forceNormal) { - mHandler.removeMessages(MSG_UPDATE_SHIFT_STATE); - KeyboardSwitcher switcher = mKeyboardSwitcher; - LatinKeyboardView inputView = switcher.getInputView(); - if (switcher.isAlphabetMode()) { - if (mCapsLock || forceNormal) { - mCapsLock = false; - switcher.setShifted(false); - } else if (inputView != null) { - if (inputView.isShifted()) { - mCapsLock = true; - switcher.setShiftLocked(true); - } else { - switcher.setShifted(true); - } - } - } else { - switcher.toggleShift(); + // True if keyboard is in either chording shift or manual temporary upper case mode. + final boolean isManualTemporaryUpperCase = mKeyboardSwitcher.isManualTemporaryUpperCase(); + if (EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions) + && !isManualTemporaryUpperCase) { + EditorInfoCompatUtils.performEditorActionNext(ic); + } else if (EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions) + && isManualTemporaryUpperCase) { + EditorInfoCompatUtils.performEditorActionPrevious(ic); } } - private void abortCorrection(boolean force) { - if (force || TextEntryState.isCorrecting()) { - getCurrentInputConnection().finishComposingText(); - clearSuggestions(); - } - } + private void handleCharacter(int primaryCode, int[] keyCodes, int x, int y) { + mVoiceProxy.handleCharacter(); - private void handleCharacter(int primaryCode, int[] keyCodes) { - if (VOICE_INSTALLED && mVoiceInputHighlighted) { - commitVoiceInput(); + if (mJustAddedMagicSpace && mSettingsValues.isMagicSpaceStripper(primaryCode)) { + removeTrailingSpace(); } - if (mAfterVoiceInput) { - // Assume input length is 1. This assumption fails for smiley face insertions. - mVoiceInput.incrementTextModificationInsertCount(1); - } - if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isCorrecting()) { - abortCorrection(false); + if (mLastSelectionStart == mLastSelectionEnd) { + mRecorrection.abortRecorrection(false); } - if (isAlphabet(primaryCode) && isPredictionOn() && !isCursorTouchingWord()) { - if (!mPredicting) { - mPredicting = true; + int code = primaryCode; + if (isAlphabet(code) && isSuggestionsRequested() && !isCursorTouchingWord()) { + if (!mHasUncommittedTypedChars) { + mHasUncommittedTypedChars = true; mComposing.setLength(0); - saveWordInHistory(mBestWord); + mRecorrection.saveRecorrectionSuggestion(mWord, mBestWord); mWord.reset(); + clearSuggestions(); } } - if (mKeyboardSwitcher.getInputView().isShifted()) { + final KeyboardSwitcher switcher = mKeyboardSwitcher; + if (switcher.isShiftedOrShiftLocked()) { if (keyCodes == null || keyCodes[0] < Character.MIN_CODE_POINT || keyCodes[0] > Character.MAX_CODE_POINT) { return; } - primaryCode = keyCodes[0]; - if (mKeyboardSwitcher.isAlphabetMode() && Character.isLowerCase(primaryCode)) { + code = keyCodes[0]; + if (switcher.isAlphabetMode() && Character.isLowerCase(code)) { // In some locales, such as Turkish, Character.toUpperCase() may return a wrong // character because it doesn't take care of locale. - final String upperCaseString = new String(new int[] {primaryCode}, 0, 1) - .toUpperCase(mLanguageSwitcher.getInputLocale()); + final String upperCaseString = new String(new int[] {code}, 0, 1) + .toUpperCase(mSubtypeSwitcher.getInputLocale()); if (upperCaseString.codePointCount(0, upperCaseString.length()) == 1) { - primaryCode = upperCaseString.codePointAt(0); + code = upperCaseString.codePointAt(0); } else { // Some keys, such as [eszett], have upper case as multi-characters. - onText(upperCaseString); + onTextInput(upperCaseString); return; } } } - if (mPredicting) { - if (mKeyboardSwitcher.getInputView().isShifted() - && mKeyboardSwitcher.isAlphabetMode() - && mComposing.length() == 0) { + if (mHasUncommittedTypedChars) { + if (mComposing.length() == 0 && switcher.isAlphabetMode() + && switcher.isShiftedOrShiftLocked()) { mWord.setFirstCharCapitalized(true); } - mComposing.append((char) primaryCode); - mWord.add(primaryCode, keyCodes); + mComposing.append((char) code); + mWord.add(code, keyCodes, x, y); InputConnection ic = getCurrentInputConnection(); if (ic != null) { // If it's the first letter, make note of auto-caps state if (mWord.size() == 1) { - mWord.setAutoCapitalized( - getCursorCapsMode(ic, getCurrentInputEditorInfo()) != 0); + mWord.setAutoCapitalized(getCurrentAutoCapsState()); } ic.setComposingText(mComposing, 1); } - postUpdateSuggestions(); + mHandler.postUpdateSuggestions(); } else { - sendKeyChar((char)primaryCode); + sendKeyChar((char)code); + } + if (mJustAddedMagicSpace && mSettingsValues.isMagicSpaceSwapper(primaryCode)) { + swapSwapperAndSpace(); + } else { + mJustAddedMagicSpace = false; } - updateShiftKeyState(getCurrentInputEditorInfo()); + + switcher.updateShiftState(); if (LatinIME.PERF_DEBUG) measureCps(); - TextEntryState.typedCharacter((char) primaryCode, isWordSeparator(primaryCode)); + TextEntryState.typedCharacter((char) code, mSettingsValues.isWordSeparator(code), x, y); } - private void handleSeparator(int primaryCode) { - if (VOICE_INSTALLED && mVoiceInputHighlighted) { - commitVoiceInput(); - } - - if (mAfterVoiceInput){ - // Assume input length is 1. This assumption fails for smiley face insertions. - mVoiceInput.incrementTextModificationInsertPunctuationCount(1); - } + private void handleSeparator(int primaryCode, int x, int y) { + mVoiceProxy.handleSeparator(); // Should dismiss the "Touch again to save" message when handling separator if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) { - postUpdateSuggestions(); + mHandler.cancelUpdateBigramPredictions(); + mHandler.postUpdateSuggestions(); } boolean pickedDefault = false; // Handle separator - InputConnection ic = getCurrentInputConnection(); + final InputConnection ic = getCurrentInputConnection(); if (ic != null) { ic.beginBatchEdit(); - abortCorrection(false); + mRecorrection.abortRecorrection(false); } - if (mPredicting) { + if (mHasUncommittedTypedChars) { // In certain languages where single quote is a separator, it's better // not to auto correct, but accept the typed word. For instance, // in Italian dov' should not be expanded to dove' because the elision // requires the last vowel to be removed. - if (mAutoCorrectOn && primaryCode != '\'' && - (mJustRevertedSeparator == null - || mJustRevertedSeparator.length() == 0 - || mJustRevertedSeparator.charAt(0) != primaryCode)) { - pickedDefault = pickDefaultSuggestion(); - // Picked the suggestion by the space key. We consider this - // as "added an auto space". - if (primaryCode == KEYCODE_SPACE) { - mJustAddedAutoSpace = true; - } + final boolean shouldAutoCorrect = + (mSettingsValues.mAutoCorrectEnabled || mSettingsValues.mQuickFixes) + && !mInputTypeNoAutoCorrect && mHasDictionary; + if (shouldAutoCorrect && primaryCode != Keyboard.CODE_SINGLE_QUOTE) { + pickedDefault = pickDefaultSuggestion(primaryCode); } else { commitTyped(ic); } } - if (mJustAddedAutoSpace && primaryCode == KEYCODE_ENTER) { - removeTrailingSpace(); - mJustAddedAutoSpace = false; - } - sendKeyChar((char)primaryCode); - // Handle the case of ". ." -> " .." with auto-space if necessary - // before changing the TextEntryState. - if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED - && primaryCode == KEYCODE_PERIOD) { - reswapPeriodAndSpace(); + if (mJustAddedMagicSpace) { + if (mSettingsValues.isMagicSpaceSwapper(primaryCode)) { + sendKeyChar((char)primaryCode); + swapSwapperAndSpace(); + } else { + if (mSettingsValues.isMagicSpaceStripper(primaryCode)) removeTrailingSpace(); + sendKeyChar((char)primaryCode); + mJustAddedMagicSpace = false; + } + } else { + sendKeyChar((char)primaryCode); } - TextEntryState.typedCharacter((char) primaryCode, true); - if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED - && primaryCode != KEYCODE_ENTER) { - swapPunctuationAndSpace(); - } else if (isPredictionOn() && primaryCode == KEYCODE_SPACE) { - doubleSpace(); + if (isSuggestionsRequested() && primaryCode == Keyboard.CODE_SPACE) { + maybeDoubleSpace(); } + + TextEntryState.typedCharacter((char) primaryCode, true, x, y); + if (pickedDefault) { - TextEntryState.backToAcceptedDefault(mWord.getTypedWord()); + CharSequence typedWord = mWord.getTypedWord(); + TextEntryState.backToAcceptedDefault(typedWord); + if (!TextUtils.isEmpty(typedWord) && !typedWord.equals(mBestWord)) { + InputConnectionCompatUtils.commitCorrection( + ic, mLastSelectionEnd - typedWord.length(), typedWord, mBestWord); + if (mCandidateView != null) + mCandidateView.onAutoCorrectionInverted(mBestWord); + } + } + if (Keyboard.CODE_SPACE == primaryCode) { + if (!isCursorTouchingWord()) { + mHandler.cancelUpdateSuggestions(); + mHandler.cancelUpdateOldSuggestions(); + mHandler.postUpdateBigramPredictions(); + } + } else { + // Set punctuation right away. onUpdateSelection will fire but tests whether it is + // already displayed or not, so it's okay. + setPunctuationSuggestions(); } - updateShiftKeyState(getCurrentInputEditorInfo()); + mKeyboardSwitcher.updateShiftState(); if (ic != null) { ic.endBatchEdit(); } @@ -1524,367 +1406,172 @@ public class LatinIME extends InputMethodService private void handleClose() { commitTyped(getCurrentInputConnection()); - if (VOICE_INSTALLED & mRecognizing) { - mVoiceInput.cancel(); - } + mVoiceProxy.handleClose(); requestHideSelf(0); - if (mKeyboardSwitcher != null) { - LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); - if (inputView != null) { - inputView.closing(); - } - } - TextEntryState.endSession(); - } - - private void saveWordInHistory(CharSequence result) { - if (mWord.size() <= 1) { - mWord.reset(); - return; - } - // Skip if result is null. It happens in some edge case. - if (TextUtils.isEmpty(result)) { - return; - } - - // Make a copy of the CharSequence, since it is/could be a mutable CharSequence - final String resultCopy = result.toString(); - TypedWordAlternatives entry = new TypedWordAlternatives(resultCopy, - new WordComposer(mWord)); - mWordHistory.add(entry); + LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); + if (inputView != null) + inputView.closing(); } - private void postUpdateSuggestions() { - mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS); - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SUGGESTIONS), 100); + public boolean isSuggestionsRequested() { + return mIsSettingsSuggestionStripOn + && (mCorrectionMode > 0 || isShowingSuggestionsStrip()); } - private void postUpdateOldSuggestions() { - mHandler.removeMessages(MSG_UPDATE_OLD_SUGGESTIONS); - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_OLD_SUGGESTIONS), 300); + public boolean isShowingPunctuationList() { + return mSettingsValues.mSuggestPuncList == mCandidateView.getSuggestions(); } - private boolean isPredictionOn() { - return mPredictionOn; + public boolean isShowingSuggestionsStrip() { + return (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_VALUE) + || (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE + && mOrientation == Configuration.ORIENTATION_PORTRAIT); } - private boolean isCandidateStripVisible() { - return isPredictionOn() && mShowSuggestions; - } - - public void onCancelVoice() { - if (mRecognizing) { - switchToKeyboardView(); - } - } - - private void switchToKeyboardView() { - mHandler.post(new Runnable() { - public void run() { - mRecognizing = false; - if (mKeyboardSwitcher.getInputView() != null) { - setInputView(mKeyboardSwitcher.getInputView()); - } - setCandidatesViewShown(true); - updateInputViewShown(); - postUpdateSuggestions(); - }}); - } - - private void switchToRecognitionStatusView() { - final boolean configChanged = mConfigurationChanging; - mHandler.post(new Runnable() { - public void run() { - setCandidatesViewShown(false); - mRecognizing = true; - View v = mVoiceInput.getView(); - ViewParent p = v.getParent(); - if (p != null && p instanceof ViewGroup) { - ((ViewGroup)v.getParent()).removeView(v); - } - setInputView(v); - updateInputViewShown(); - if (configChanged) { - mVoiceInput.onConfigurationChanged(); - } - }}); - } - - private void startListening(boolean swipe) { - if (!mHasUsedVoiceInput || - (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale)) { - // Calls reallyStartListening if user clicks OK, does nothing if user clicks Cancel. - showVoiceWarningDialog(swipe); - } else { - reallyStartListening(swipe); - } - } - - private void reallyStartListening(boolean swipe) { - if (!mHasUsedVoiceInput) { - // The user has started a voice input, so remember that in the - // future (so we don't show the warning dialog after the first run). - SharedPreferences.Editor editor = - PreferenceManager.getDefaultSharedPreferences(this).edit(); - editor.putBoolean(PREF_HAS_USED_VOICE_INPUT, true); - SharedPreferencesCompat.apply(editor); - mHasUsedVoiceInput = true; - } - - if (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale) { - // The user has started a voice input from an unsupported locale, so remember that - // in the future (so we don't show the warning dialog the next time they do this). - SharedPreferences.Editor editor = - PreferenceManager.getDefaultSharedPreferences(this).edit(); - editor.putBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, true); - SharedPreferencesCompat.apply(editor); - mHasUsedVoiceInputUnsupportedLocale = true; - } - - // Clear N-best suggestions - clearSuggestions(); - - FieldContext context = new FieldContext( - getCurrentInputConnection(), - getCurrentInputEditorInfo(), - mLanguageSwitcher.getInputLanguage(), - mLanguageSwitcher.getEnabledLanguages()); - mVoiceInput.startListening(context, swipe); - switchToRecognitionStatusView(); - } - - private void showVoiceWarningDialog(final boolean swipe) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setCancelable(true); - builder.setIcon(R.drawable.ic_mic_dialog); - builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - mVoiceInput.logKeyboardWarningDialogOk(); - reallyStartListening(swipe); - } - }); - builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - mVoiceInput.logKeyboardWarningDialogCancel(); - } - }); - - if (mLocaleSupportedForVoiceInput) { - String message = getString(R.string.voice_warning_may_not_understand) + "\n\n" + - getString(R.string.voice_warning_how_to_turn_off); - builder.setMessage(message); - } else { - String message = getString(R.string.voice_warning_locale_not_supported) + "\n\n" + - getString(R.string.voice_warning_may_not_understand) + "\n\n" + - getString(R.string.voice_warning_how_to_turn_off); - builder.setMessage(message); - } - - builder.setTitle(R.string.voice_warning_title); - mVoiceWarningDialog = builder.create(); - - Window window = mVoiceWarningDialog.getWindow(); - WindowManager.LayoutParams lp = window.getAttributes(); - lp.token = mKeyboardSwitcher.getInputView().getWindowToken(); - lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; - window.setAttributes(lp); - window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); - mVoiceInput.logKeyboardWarningDialogShown(); - mVoiceWarningDialog.show(); - } - - public void onVoiceResults(List<String> candidates, - Map<String, List<CharSequence>> alternatives) { - if (!mRecognizing) { - return; - } - mVoiceResults.candidates = candidates; - mVoiceResults.alternatives = alternatives; - mHandler.sendMessage(mHandler.obtainMessage(MSG_VOICE_RESULTS)); + public boolean isCandidateStripVisible() { + if (mCandidateView == null) + return false; + if (mCandidateView.isShowingAddToDictionaryHint() || TextEntryState.isRecorrecting()) + return true; + if (!isShowingSuggestionsStrip()) + return false; + if (mApplicationSpecifiedCompletionOn) + return true; + return isSuggestionsRequested(); } - private void handleVoiceResults() { - mAfterVoiceInput = true; - mImmediatelyAfterVoiceInput = true; - - InputConnection ic = getCurrentInputConnection(); - if (!isFullscreenMode()) { - // Start listening for updates to the text from typing, etc. - if (ic != null) { - ExtractedTextRequest req = new ExtractedTextRequest(); - ic.getExtractedText(req, InputConnection.GET_EXTRACTED_TEXT_MONITOR); - } - } - - vibrate(); - switchToKeyboardView(); - - final List<CharSequence> nBest = new ArrayList<CharSequence>(); - boolean capitalizeFirstWord = preferCapitalization() - || (mKeyboardSwitcher.isAlphabetMode() - && mKeyboardSwitcher.getInputView().isShifted()); - for (String c : mVoiceResults.candidates) { - if (capitalizeFirstWord) { - c = Character.toUpperCase(c.charAt(0)) + c.substring(1, c.length()); + public void switchToKeyboardView() { + if (DEBUG) { + Log.d(TAG, "Switch to keyboard view."); + } + View v = mKeyboardSwitcher.getKeyboardView(); + if (v != null) { + // Confirms that the keyboard view doesn't have parent view. + ViewParent p = v.getParent(); + if (p != null && p instanceof ViewGroup) { + ((ViewGroup) p).removeView(v); } - nBest.add(c); + setInputView(v); } - - if (nBest.size() == 0) { - return; - } - - String bestResult = nBest.get(0).toString(); - - mVoiceInput.logVoiceInputDelivered(bestResult.length()); - - mHints.registerVoiceResult(bestResult); - - if (ic != null) ic.beginBatchEdit(); // To avoid extra updates on committing older text - - commitTyped(ic); - EditingUtil.appendText(ic, bestResult); - - if (ic != null) ic.endBatchEdit(); - - mVoiceInputHighlighted = true; - mWordToSuggestions.putAll(mVoiceResults.alternatives); + setSuggestionStripShown(isCandidateStripVisible()); + updateInputViewShown(); + mHandler.postUpdateSuggestions(); } - private void clearSuggestions() { - setSuggestions(null, false, false, false); + public void clearSuggestions() { + setSuggestions(SuggestedWords.EMPTY); } - private void setSuggestions( - List<CharSequence> suggestions, - boolean completions, - boolean typedWordValid, - boolean haveMinimalSuggestion) { - - if (mIsShowingHint) { - setCandidatesView(mCandidateViewContainer); - mIsShowingHint = false; - } - + public void setSuggestions(SuggestedWords words) { if (mCandidateView != null) { - mCandidateView.setSuggestions( - suggestions, completions, typedWordValid, haveMinimalSuggestion); + mCandidateView.setSuggestions(words); + mKeyboardSwitcher.onAutoCorrectionStateChanged( + words.hasWordAboveAutoCorrectionScoreThreshold()); } } - private void updateSuggestions() { - LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); - ((LatinKeyboard) inputView.getKeyboard()).setPreferredLetters(null); - + public void updateSuggestions() { // Check if we have a suggestion engine attached. - if ((mSuggest == null || !isPredictionOn()) && !mVoiceInputHighlighted) { + if ((mSuggest == null || !isSuggestionsRequested()) + && !mVoiceProxy.isVoiceInputHighlighted()) { return; } - if (!mPredicting) { - setNextSuggestions(); + if (!mHasUncommittedTypedChars) { + setPunctuationSuggestions(); return; } showSuggestions(mWord); } - private List<CharSequence> getTypedSuggestions(WordComposer word) { - List<CharSequence> stringList = mSuggest.getSuggestions( - mKeyboardSwitcher.getInputView(), word, false, null); - return stringList; - } - - private void showCorrections(WordAlternatives alternatives) { - List<CharSequence> stringList = alternatives.getAlternatives(); - ((LatinKeyboard) mKeyboardSwitcher.getInputView().getKeyboard()).setPreferredLetters(null); - showSuggestions(stringList, alternatives.getOriginalWord(), false, false); - } - private void showSuggestions(WordComposer word) { - // long startTime = System.currentTimeMillis(); // TIME MEASUREMENT! - // TODO Maybe need better way of retrieving previous word - CharSequence prevWord = EditingUtil.getPreviousWord(getCurrentInputConnection(), - mWordSeparators); - List<CharSequence> stringList = mSuggest.getSuggestions( - mKeyboardSwitcher.getInputView(), word, false, prevWord); - // long stopTime = System.currentTimeMillis(); // TIME MEASUREMENT! - // Log.d("LatinIME","Suggest Total Time - " + (stopTime - startTime)); - - int[] nextLettersFrequencies = mSuggest.getNextLettersFrequencies(); - - ((LatinKeyboard) mKeyboardSwitcher.getInputView().getKeyboard()).setPreferredLetters( - nextLettersFrequencies); - - boolean correctionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasMinimalCorrection(); - //|| mCorrectionMode == mSuggest.CORRECTION_FULL; - CharSequence typedWord = word.getTypedWord(); - // If we're in basic correct - boolean typedWordValid = mSuggest.isValidWord(typedWord) || - (preferCapitalization() - && mSuggest.isValidWord(typedWord.toString().toLowerCase())); + // TODO: May need a better way of retrieving previous word + CharSequence prevWord = EditingUtils.getPreviousWord(getCurrentInputConnection(), + mSettingsValues.mWordSeparators); + SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder( + mKeyboardSwitcher.getKeyboardView(), word, prevWord); + + boolean correctionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasAutoCorrection(); + final CharSequence typedWord = word.getTypedWord(); + // Here, we want to promote a whitelisted word if exists. + final boolean typedWordValid = AutoCorrection.isValidWordForAutoCorrection( + mSuggest.getUnigramDictionaries(), typedWord, preferCapitalization()); if (mCorrectionMode == Suggest.CORRECTION_FULL || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) { correctionAvailable |= typedWordValid; } // Don't auto-correct words with multiple capital letter correctionAvailable &= !word.isMostlyCaps(); - correctionAvailable &= !TextEntryState.isCorrecting(); - - showSuggestions(stringList, typedWord, typedWordValid, correctionAvailable); + correctionAvailable &= !TextEntryState.isRecorrecting(); + + // Basically, we update the suggestion strip only when suggestion count > 1. However, + // there is an exception: We update the suggestion strip whenever typed word's length + // is 1 or typed word is found in dictionary, regardless of suggestion count. Actually, + // in most cases, suggestion count is 1 when typed word's length is 1, but we do always + // need to clear the previous state when the user starts typing a word (i.e. typed word's + // length == 1). + if (typedWord != null) { + if (builder.size() > 1 || typedWord.length() == 1 || typedWordValid + || mCandidateView.isShowingAddToDictionaryHint()) { + builder.setTypedWordValid(typedWordValid).setHasMinimalSuggestion( + correctionAvailable); + } else { + final SuggestedWords previousSuggestions = mCandidateView.getSuggestions(); + if (previousSuggestions == mSettingsValues.mSuggestPuncList) + return; + builder.addTypedWordAndPreviousSuggestions(typedWord, previousSuggestions); + } + } + showSuggestions(builder.build(), typedWord); } - private void showSuggestions(List<CharSequence> stringList, CharSequence typedWord, - boolean typedWordValid, boolean correctionAvailable) { - setSuggestions(stringList, false, typedWordValid, correctionAvailable); - if (stringList.size() > 0) { - if (correctionAvailable && !typedWordValid && stringList.size() > 1) { - mBestWord = stringList.get(1); + public void showSuggestions(SuggestedWords suggestedWords, CharSequence typedWord) { + setSuggestions(suggestedWords); + if (suggestedWords.size() > 0) { + if (Utils.shouldBlockedBySafetyNetForAutoCorrection(suggestedWords, mSuggest)) { + mBestWord = typedWord; + } else if (suggestedWords.hasAutoCorrectionWord()) { + mBestWord = suggestedWords.getWord(1); } else { mBestWord = typedWord; } } else { mBestWord = null; } - setCandidatesViewShown(isCandidateStripVisible() || mCompletionOn); + setSuggestionStripShown(isCandidateStripVisible()); } - private boolean pickDefaultSuggestion() { + private boolean pickDefaultSuggestion(int separatorCode) { // Complete any pending candidate query first - if (mHandler.hasMessages(MSG_UPDATE_SUGGESTIONS)) { - mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS); + if (mHandler.hasPendingUpdateSuggestions()) { + mHandler.cancelUpdateSuggestions(); updateSuggestions(); } if (mBestWord != null && mBestWord.length() > 0) { - TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord); - mJustAccepted = true; - pickSuggestion(mBestWord, false); + TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord, separatorCode); + mExpectingUpdateSelection = true; + commitBestWord(mBestWord); // Add the word to the auto dictionary if it's not a known word - addToDictionaries(mBestWord, AutoDictionary.FREQUENCY_FOR_TYPED); + addToAutoAndUserBigramDictionaries(mBestWord, AutoDictionary.FREQUENCY_FOR_TYPED); return true; - } return false; } + @Override public void pickSuggestionManually(int index, CharSequence suggestion) { - List<CharSequence> suggestions = mCandidateView.getSuggestions(); - - if (mAfterVoiceInput && mShowingVoiceSuggestions) { - mVoiceInput.flushAllTextModificationCounters(); - // send this intent AFTER logging any prior aggregated edits. - mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.toString(), index, - mWordSeparators, - getCurrentInputConnection()); - } + SuggestedWords suggestions = mCandidateView.getSuggestions(); + mVoiceProxy.flushAndLogAllTextModificationCounters(index, suggestion, + mSettingsValues.mWordSeparators); - final boolean correcting = TextEntryState.isCorrecting(); + final boolean recorrecting = TextEntryState.isRecorrecting(); InputConnection ic = getCurrentInputConnection(); if (ic != null) { ic.beginBatchEdit(); } - if (mCompletionOn && mCompletions != null && index >= 0 - && index < mCompletions.length) { - CompletionInfo ci = mCompletions[index]; + if (mApplicationSpecifiedCompletionOn && mApplicationSpecifiedCompletions != null + && index >= 0 && index < mApplicationSpecifiedCompletions.length) { + CompletionInfo ci = mApplicationSpecifiedCompletions[index]; if (ic != null) { ic.commitCompletion(ci); } @@ -1892,7 +1579,7 @@ public class LatinIME extends InputMethodService if (mCandidateView != null) { mCandidateView.clear(); } - updateShiftKeyState(getCurrentInputEditorInfo()); + mKeyboardSwitcher.updateShiftState(); if (ic != null) { ic.endBatchEdit(); } @@ -1900,52 +1587,81 @@ public class LatinIME extends InputMethodService } // If this is a punctuation, apply it through the normal key press - if (suggestion.length() == 1 && (isWordSeparator(suggestion.charAt(0)) - || isSuggestedPunctuation(suggestion.charAt(0)))) { + if (suggestion.length() == 1 && (mSettingsValues.isWordSeparator(suggestion.charAt(0)) + || mSettingsValues.isSuggestedPunctuation(suggestion.charAt(0)))) { // Word separators are suggested before the user inputs something. // So, LatinImeLogger logs "" as a user's input. LatinImeLogger.logOnManualSuggestion( - "", suggestion.toString(), index, suggestions); + "", suggestion.toString(), index, suggestions.mWords); + // Find out whether the previous character is a space. If it is, as a special case + // for punctuation entered through the suggestion strip, it should be considered + // a magic space even if it was a normal space. This is meant to help in case the user + // pressed space on purpose of displaying the suggestion strip punctuation. final char primaryCode = suggestion.charAt(0); - onKey(primaryCode, new int[]{primaryCode}, LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE, - LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE); + final CharSequence beforeText = ic != null ? ic.getTextBeforeCursor(1, 0) : ""; + final int toLeft = (ic == null || TextUtils.isEmpty(beforeText)) + ? 0 : beforeText.charAt(0); + final boolean oldMagicSpace = mJustAddedMagicSpace; + if (Keyboard.CODE_SPACE == toLeft) mJustAddedMagicSpace = true; + onCodeInput(primaryCode, new int[] { primaryCode }, + KeyboardActionListener.NOT_A_TOUCH_COORDINATE, + KeyboardActionListener.NOT_A_TOUCH_COORDINATE); + mJustAddedMagicSpace = oldMagicSpace; if (ic != null) { ic.endBatchEdit(); } return; } - mJustAccepted = true; - pickSuggestion(suggestion, correcting); + if (!mHasUncommittedTypedChars) { + // If we are not composing a word, then it was a suggestion inferred from + // context - no user input. We should reset the word composer. + mWord.reset(); + } + mExpectingUpdateSelection = true; + commitBestWord(suggestion); // Add the word to the auto dictionary if it's not a known word if (index == 0) { - addToDictionaries(suggestion, AutoDictionary.FREQUENCY_FOR_PICKED); + addToAutoAndUserBigramDictionaries(suggestion, AutoDictionary.FREQUENCY_FOR_PICKED); } else { - addToBigramDictionary(suggestion, 1); + addToOnlyBigramDictionary(suggestion, 1); } LatinImeLogger.logOnManualSuggestion(mComposing.toString(), suggestion.toString(), - index, suggestions); + index, suggestions.mWords); TextEntryState.acceptedSuggestion(mComposing.toString(), suggestion); // Follow it with a space - if (mAutoSpace && !correcting) { - sendSpace(); - mJustAddedAutoSpace = true; - } - - final boolean showingAddToDictionaryHint = index == 0 && mCorrectionMode > 0 - && !mSuggest.isValidWord(suggestion) - && !mSuggest.isValidWord(suggestion.toString().toLowerCase()); - - if (!correcting) { + if (mShouldInsertMagicSpace && !recorrecting) { + sendMagicSpace(); + } + + // We should show the hint if the user pressed the first entry AND either: + // - There is no dictionary (we know that because we tried to load it => null != mSuggest + // AND mHasDictionary is false) + // - There is a dictionary and the word is not in it + // Please note that if mSuggest is null, it means that everything is off: suggestion + // and correction, so we shouldn't try to show the hint + // We used to look at mCorrectionMode here, but showing the hint should have nothing + // to do with the autocorrection setting. + final boolean showingAddToDictionaryHint = index == 0 && mSuggest != null + // If there is no dictionary the hint should be shown. + && (!mHasDictionary + // If "suggestion" is not in the dictionary, the hint should be shown. + || !AutoCorrection.isValidWord( + mSuggest.getUnigramDictionaries(), suggestion, true)); + + if (!recorrecting) { // Fool the state watcher so that a subsequent backspace will not do a revert, unless // we just did a correction, in which case we need to stay in // TextEntryState.State.PICKED_SUGGESTION state. - TextEntryState.typedCharacter((char) KEYCODE_SPACE, true); - setNextSuggestions(); - } else if (!showingAddToDictionaryHint) { + TextEntryState.typedCharacter((char) Keyboard.CODE_SPACE, true, + WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE); + } + if (!showingAddToDictionaryHint) { // If we're not showing the "Touch again to save", then show corrections again. // In case the cursor position doesn't change, make sure we show the suggestions again. - clearSuggestions(); - postUpdateOldSuggestions(); + updateBigramPredictions(); + // Updating the predictions right away may be slow and feel unresponsive on slower + // terminals. On the other hand if we just postUpdateBigramPredictions() it will + // take a noticeable delay to update them which may feel uneasy. } if (showingAddToDictionaryHint) { mCandidateView.showAddToDictionaryHint(suggestion); @@ -1955,193 +1671,71 @@ public class LatinIME extends InputMethodService } } - private void rememberReplacedWord(CharSequence suggestion) { - if (mShowingVoiceSuggestions) { - // Retain the replaced word in the alternatives array. - EditingUtil.Range range = new EditingUtil.Range(); - String wordToBeReplaced = EditingUtil.getWordAtCursor(getCurrentInputConnection(), - mWordSeparators, range); - if (!mWordToSuggestions.containsKey(wordToBeReplaced)) { - wordToBeReplaced = wordToBeReplaced.toLowerCase(); - } - if (mWordToSuggestions.containsKey(wordToBeReplaced)) { - List<CharSequence> suggestions = mWordToSuggestions.get(wordToBeReplaced); - if (suggestions.contains(suggestion)) { - suggestions.remove(suggestion); - } - suggestions.add(wordToBeReplaced); - mWordToSuggestions.remove(wordToBeReplaced); - mWordToSuggestions.put(suggestion.toString(), suggestions); - } - } - } - /** * Commits the chosen word to the text field and saves it for later * retrieval. - * @param suggestion the suggestion picked by the user to be committed to - * the text field - * @param correcting whether this is due to a correction of an existing - * word. */ - private void pickSuggestion(CharSequence suggestion, boolean correcting) { - final LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); - final Locale inputLocale = mLanguageSwitcher.getInputLocale(); - if (mCapsLock) { - suggestion = suggestion.toString().toUpperCase(inputLocale); - } else if (preferCapitalization() - || (mKeyboardSwitcher.isAlphabetMode() - && inputView.isShifted())) { - suggestion = suggestion.toString().toUpperCase(inputLocale).charAt(0) - + suggestion.subSequence(1, suggestion.length()).toString(); - } + private void commitBestWord(CharSequence bestWord) { + KeyboardSwitcher switcher = mKeyboardSwitcher; + if (!switcher.isKeyboardAvailable()) + return; InputConnection ic = getCurrentInputConnection(); if (ic != null) { - rememberReplacedWord(suggestion); - ic.commitText(suggestion, 1); - } - saveWordInHistory(suggestion); - mPredicting = false; - mCommittedLength = suggestion.length(); - ((LatinKeyboard) inputView.getKeyboard()).setPreferredLetters(null); - // If we just corrected a word, then don't show punctuations - if (!correcting) { - setNextSuggestions(); - } - updateShiftKeyState(getCurrentInputEditorInfo()); - } - - /** - * Tries to apply any voice alternatives for the word if this was a spoken word and - * there are voice alternatives. - * @param touching The word that the cursor is touching, with position information - * @return true if an alternative was found, false otherwise. - */ - private boolean applyVoiceAlternatives(EditingUtil.SelectedWord touching) { - // Search for result in spoken word alternatives - String selectedWord = touching.word.toString().trim(); - if (!mWordToSuggestions.containsKey(selectedWord)) { - selectedWord = selectedWord.toLowerCase(); - } - if (mWordToSuggestions.containsKey(selectedWord)) { - mShowingVoiceSuggestions = true; - List<CharSequence> suggestions = mWordToSuggestions.get(selectedWord); - // If the first letter of touching is capitalized, make all the suggestions - // start with a capital letter. - if (Character.isUpperCase(touching.word.charAt(0))) { - final Locale inputLocale = mLanguageSwitcher.getInputLocale(); - for (int i = 0; i < suggestions.size(); i++) { - String origSugg = (String) suggestions.get(i); - String capsSugg = origSugg.toUpperCase(inputLocale).charAt(0) - + origSugg.subSequence(1, origSugg.length()).toString(); - suggestions.set(i, capsSugg); - } - } - setSuggestions(suggestions, false, true, true); - setCandidatesViewShown(true); - return true; + mVoiceProxy.rememberReplacedWord(bestWord, mSettingsValues.mWordSeparators); + SuggestedWords suggestedWords = mCandidateView.getSuggestions(); + ic.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan( + this, bestWord, suggestedWords), 1); } - return false; + mRecorrection.saveRecorrectionSuggestion(mWord, bestWord); + mHasUncommittedTypedChars = false; + mCommittedLength = bestWord.length(); } - /** - * Tries to apply any typed alternatives for the word if we have any cached alternatives, - * otherwise tries to find new corrections and completions for the word. - * @param touching The word that the cursor is touching, with position information - * @return true if an alternative was found, false otherwise. - */ - private boolean applyTypedAlternatives(EditingUtil.SelectedWord touching) { - // If we didn't find a match, search for result in typed word history - WordComposer foundWord = null; - WordAlternatives alternatives = null; - for (WordAlternatives entry : mWordHistory) { - if (TextUtils.equals(entry.getChosenWord(), touching.word)) { - if (entry instanceof TypedWordAlternatives) { - foundWord = ((TypedWordAlternatives) entry).word; - } - alternatives = entry; - break; - } - } - // If we didn't find a match, at least suggest completions - if (foundWord == null - && (mSuggest.isValidWord(touching.word) - || mSuggest.isValidWord(touching.word.toString().toLowerCase()))) { - foundWord = new WordComposer(); - for (int i = 0; i < touching.word.length(); i++) { - foundWord.add(touching.word.charAt(i), new int[] { - touching.word.charAt(i) - }); - } - foundWord.setFirstCharCapitalized(Character.isUpperCase(touching.word.charAt(0))); - } - // Found a match, show suggestions - if (foundWord != null || alternatives != null) { - if (alternatives == null) { - alternatives = new TypedWordAlternatives(touching.word, foundWord); - } - showCorrections(alternatives); - if (foundWord != null) { - mWord = new WordComposer(foundWord); - } else { - mWord.reset(); - } - return true; - } - return false; - } + private static final WordComposer sEmptyWordComposer = new WordComposer(); + public void updateBigramPredictions() { + if (mSuggest == null || !isSuggestionsRequested()) + return; - private void setOldSuggestions() { - mShowingVoiceSuggestions = false; - if (mCandidateView != null && mCandidateView.isShowingAddToDictionaryHint()) { + if (!mSettingsValues.mBigramPredictionEnabled) { + setPunctuationSuggestions(); return; } - InputConnection ic = getCurrentInputConnection(); - if (ic == null) return; - if (!mPredicting) { - // Extract the selected or touching text - EditingUtil.SelectedWord touching = EditingUtil.getWordAtCursorOrSelection(ic, - mLastSelectionStart, mLastSelectionEnd, mWordSeparators); - if (touching != null && touching.word.length() > 1) { - ic.beginBatchEdit(); + final CharSequence prevWord = EditingUtils.getThisWord(getCurrentInputConnection(), + mSettingsValues.mWordSeparators); + SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder( + mKeyboardSwitcher.getKeyboardView(), sEmptyWordComposer, prevWord); - if (!applyVoiceAlternatives(touching) && !applyTypedAlternatives(touching)) { - abortCorrection(true); - } else { - TextEntryState.selectedForCorrection(); - EditingUtil.underlineWord(ic, touching); - } - - ic.endBatchEdit(); - } else { - abortCorrection(true); - setNextSuggestions(); // Show the punctuation suggestions list - } + if (builder.size() > 0) { + // Explicitly supply an empty typed word (the no-second-arg version of + // showSuggestions will retrieve the word near the cursor, we don't want that here) + showSuggestions(builder.build(), ""); } else { - abortCorrection(true); + if (!isShowingPunctuationList()) setPunctuationSuggestions(); } } - private void setNextSuggestions() { - setSuggestions(mSuggestPuncList, false, false, false); + public void setPunctuationSuggestions() { + setSuggestions(mSettingsValues.mSuggestPuncList); + setSuggestionStripShown(isCandidateStripVisible()); } - private void addToDictionaries(CharSequence suggestion, int frequencyDelta) { + private void addToAutoAndUserBigramDictionaries(CharSequence suggestion, int frequencyDelta) { checkAddToDictionary(suggestion, frequencyDelta, false); } - private void addToBigramDictionary(CharSequence suggestion, int frequencyDelta) { + private void addToOnlyBigramDictionary(CharSequence suggestion, int frequencyDelta) { checkAddToDictionary(suggestion, frequencyDelta, true); } /** * Adds to the UserBigramDictionary and/or AutoDictionary - * @param addToBigramDictionary true if it should be added to bigram dictionary if possible + * @param selectedANotTypedWord true if it should be added to bigram dictionary if possible */ private void checkAddToDictionary(CharSequence suggestion, int frequencyDelta, - boolean addToBigramDictionary) { + boolean selectedANotTypedWord) { if (suggestion == null || suggestion.length() < 1) return; + // Only auto-add to dictionary if auto-correct is ON. Otherwise we'll be // adding words in situations where the user or application really didn't // want corrections enabled or learned. @@ -2149,36 +1743,42 @@ public class LatinIME extends InputMethodService || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM)) { return; } - if (suggestion != null) { - if (!addToBigramDictionary && mAutoDictionary.isValidWord(suggestion) - || (!mSuggest.isValidWord(suggestion.toString()) - && !mSuggest.isValidWord(suggestion.toString().toLowerCase()))) { - mAutoDictionary.addWord(suggestion.toString(), frequencyDelta); - } - if (mUserBigramDictionary != null) { - CharSequence prevWord = EditingUtil.getPreviousWord(getCurrentInputConnection(), - mSentenceSeparators); - if (!TextUtils.isEmpty(prevWord)) { - mUserBigramDictionary.addBigrams(prevWord.toString(), suggestion.toString()); - } + final boolean selectedATypedWordAndItsInAutoDic = + !selectedANotTypedWord && mAutoDictionary.isValidWord(suggestion); + final boolean isValidWord = AutoCorrection.isValidWord( + mSuggest.getUnigramDictionaries(), suggestion, true); + final boolean needsToAddToAutoDictionary = selectedATypedWordAndItsInAutoDic + || !isValidWord; + if (needsToAddToAutoDictionary) { + mAutoDictionary.addWord(suggestion.toString(), frequencyDelta); + } + + if (mUserBigramDictionary != null) { + // We don't want to register as bigrams words separated by a separator. + // For example "I will, and you too" : we don't want the pair ("will" "and") to be + // a bigram. + CharSequence prevWord = EditingUtils.getPreviousWord(getCurrentInputConnection(), + mSettingsValues.mWordSeparators); + if (!TextUtils.isEmpty(prevWord)) { + mUserBigramDictionary.addBigrams(prevWord.toString(), suggestion.toString()); } } } - private boolean isCursorTouchingWord() { + public boolean isCursorTouchingWord() { InputConnection ic = getCurrentInputConnection(); if (ic == null) return false; CharSequence toLeft = ic.getTextBeforeCursor(1, 0); CharSequence toRight = ic.getTextAfterCursor(1, 0); if (!TextUtils.isEmpty(toLeft) - && !isWordSeparator(toLeft.charAt(0)) - && !isSuggestedPunctuation(toLeft.charAt(0))) { + && !mSettingsValues.isWordSeparator(toLeft.charAt(0)) + && !mSettingsValues.isSuggestedPunctuation(toLeft.charAt(0))) { return true; } if (!TextUtils.isEmpty(toRight) - && !isWordSeparator(toRight.charAt(0)) - && !isSuggestedPunctuation(toRight.charAt(0))) { + && !mSettingsValues.isWordSeparator(toRight.charAt(0)) + && !mSettingsValues.isSuggestedPunctuation(toRight.charAt(0))) { return true; } return false; @@ -2191,165 +1791,140 @@ public class LatinIME extends InputMethodService public void revertLastWord(boolean deleteChar) { final int length = mComposing.length(); - if (!mPredicting && length > 0) { + if (!mHasUncommittedTypedChars && length > 0) { final InputConnection ic = getCurrentInputConnection(); - mPredicting = true; - mJustRevertedSeparator = ic.getTextBeforeCursor(1, 0); + final CharSequence punctuation = ic.getTextBeforeCursor(1, 0); if (deleteChar) ic.deleteSurroundingText(1, 0); int toDelete = mCommittedLength; - CharSequence toTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0); - if (toTheLeft != null && toTheLeft.length() > 0 - && isWordSeparator(toTheLeft.charAt(0))) { + final CharSequence toTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0); + if (!TextUtils.isEmpty(toTheLeft) + && mSettingsValues.isWordSeparator(toTheLeft.charAt(0))) { toDelete--; } ic.deleteSurroundingText(toDelete, 0); - ic.setComposingText(mComposing, 1); - TextEntryState.backspace(); - postUpdateSuggestions(); + // Re-insert punctuation only when the deleted character was word separator and the + // composing text wasn't equal to the auto-corrected text. + if (deleteChar + && !TextUtils.isEmpty(punctuation) + && mSettingsValues.isWordSeparator(punctuation.charAt(0)) + && !TextUtils.equals(mComposing, toTheLeft)) { + ic.commitText(mComposing, 1); + TextEntryState.acceptedTyped(mComposing); + ic.commitText(punctuation, 1); + TextEntryState.typedCharacter(punctuation.charAt(0), true, + WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE); + // Clear composing text + mComposing.setLength(0); + } else { + mHasUncommittedTypedChars = true; + ic.setComposingText(mComposing, 1); + TextEntryState.backspace(); + } + mHandler.cancelUpdateBigramPredictions(); + mHandler.postUpdateSuggestions(); } else { sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); - mJustRevertedSeparator = null; } } - protected String getWordSeparators() { - return mWordSeparators; + public boolean revertDoubleSpace() { + mHandler.cancelDoubleSpacesTimer(); + final InputConnection ic = getCurrentInputConnection(); + // Here we test whether we indeed have a period and a space before us. This should not + // be needed, but it's there just in case something went wrong. + final CharSequence textBeforeCursor = ic.getTextBeforeCursor(2, 0); + if (!". ".equals(textBeforeCursor)) + return false; + ic.beginBatchEdit(); + ic.deleteSurroundingText(2, 0); + ic.commitText(" ", 1); + ic.endBatchEdit(); + return true; } public boolean isWordSeparator(int code) { - String separators = getWordSeparators(); - return separators.contains(String.valueOf((char)code)); - } - - private boolean isSentenceSeparator(int code) { - return mSentenceSeparators.contains(String.valueOf((char)code)); + return mSettingsValues.isWordSeparator(code); } - private void sendSpace() { - sendKeyChar((char)KEYCODE_SPACE); - updateShiftKeyState(getCurrentInputEditorInfo()); - //onKey(KEY_SPACE[0], KEY_SPACE); + private void sendMagicSpace() { + sendKeyChar((char)Keyboard.CODE_SPACE); + mJustAddedMagicSpace = true; + mKeyboardSwitcher.updateShiftState(); } public boolean preferCapitalization() { return mWord.isFirstCharCapitalized(); } - private void toggleLanguage(boolean reset, boolean next) { - if (reset) { - mLanguageSwitcher.reset(); - } else { - if (next) { - mLanguageSwitcher.next(); - } else { - mLanguageSwitcher.prev(); - } - } - int currentKeyboardMode = mKeyboardSwitcher.getKeyboardMode(); - reloadKeyboards(); - mKeyboardSwitcher.makeKeyboards(true); - mKeyboardSwitcher.setKeyboardMode(currentKeyboardMode, 0, - mEnableVoiceButton && mEnableVoice); - initSuggest(mLanguageSwitcher.getInputLanguage()); - mLanguageSwitcher.persist(); - updateShiftKeyState(getCurrentInputEditorInfo()); - } - - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, - String key) { - if (PREF_SELECTED_LANGUAGES.equals(key)) { - mLanguageSwitcher.loadLocales(sharedPreferences); - mRefreshKeyboardRequired = true; - } else if (PREF_RECORRECTION_ENABLED.equals(key)) { - mReCorrectionEnabled = sharedPreferences.getBoolean(PREF_RECORRECTION_ENABLED, - getResources().getBoolean(R.bool.default_recorrection_enabled)); - } - } - - public void swipeRight() { - if (LatinKeyboardView.DEBUG_AUTO_PLAY) { - ClipboardManager cm = ((ClipboardManager)getSystemService(CLIPBOARD_SERVICE)); - CharSequence text = cm.getText(); - if (!TextUtils.isEmpty(text)) { - mKeyboardSwitcher.getInputView().startPlaying(text.toString()); - } - } - } - - public void swipeLeft() { + // Notify that language or mode have been changed and toggleLanguage will update KeyboardID + // according to new language or mode. + public void onRefreshKeyboard() { + // Reload keyboard because the current language has been changed. + mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), + mSubtypeSwitcher.isShortcutImeEnabled() && mVoiceProxy.isVoiceButtonEnabled(), + mVoiceProxy.isVoiceButtonOnPrimary()); + initSuggest(); + loadSettings(); + mKeyboardSwitcher.updateShiftState(); } - public void swipeDown() { - handleClose(); - } + // "reset" and "next" are used only for USE_SPACEBAR_LANGUAGE_SWITCHER. + private void toggleLanguage(boolean next) { + if (mSubtypeSwitcher.useSpacebarLanguageSwitcher()) { + mSubtypeSwitcher.toggleLanguage(next); + } + // The following is necessary because on API levels < 10, we don't get notified when + // subtype changes. + if (!CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) + onRefreshKeyboard(); + } - public void swipeUp() { - //launchSettings(); + @Override + public void onSwipeDown() { + if (mSettingsValues.mSwipeDownDismissKeyboardEnabled) + handleClose(); } - public void onPress(int primaryCode) { + @Override + public void onPress(int primaryCode, boolean withSliding) { if (mKeyboardSwitcher.isVibrateAndSoundFeedbackRequired()) { vibrate(); playKeyClick(primaryCode); } - final boolean distinctMultiTouch = mKeyboardSwitcher.hasDistinctMultitouch(); - if (distinctMultiTouch && primaryCode == Keyboard.KEYCODE_SHIFT) { - mShiftKeyState.onPress(); - handleShift(); - } else if (distinctMultiTouch && primaryCode == Keyboard.KEYCODE_MODE_CHANGE) { - changeKeyboardMode(); - mSymbolKeyState.onPress(); - mKeyboardSwitcher.setAutoModeSwitchStateMomentary(); + KeyboardSwitcher switcher = mKeyboardSwitcher; + final boolean distinctMultiTouch = switcher.hasDistinctMultitouch(); + if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) { + switcher.onPressShift(withSliding); + } else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { + switcher.onPressSymbol(); } else { - mShiftKeyState.onOtherKeyPressed(); - mSymbolKeyState.onOtherKeyPressed(); + switcher.onOtherKeyPressed(); } } - public void onRelease(int primaryCode) { + @Override + public void onRelease(int primaryCode, boolean withSliding) { + KeyboardSwitcher switcher = mKeyboardSwitcher; // Reset any drag flags in the keyboard - ((LatinKeyboard) mKeyboardSwitcher.getInputView().getKeyboard()).keyReleased(); - //vibrate(); - final boolean distinctMultiTouch = mKeyboardSwitcher.hasDistinctMultitouch(); - if (distinctMultiTouch && primaryCode == Keyboard.KEYCODE_SHIFT) { - if (mShiftKeyState.isMomentary()) - resetShift(); - mShiftKeyState.onRelease(); - } else if (distinctMultiTouch && primaryCode == Keyboard.KEYCODE_MODE_CHANGE) { - // Snap back to the previous keyboard mode if the user chords the mode change key and - // other key, then released the mode change key. - if (mKeyboardSwitcher.isInChordingAutoModeSwitchState()) - changeKeyboardMode(); - mSymbolKeyState.onRelease(); + final boolean distinctMultiTouch = switcher.hasDistinctMultitouch(); + if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) { + switcher.onReleaseShift(withSliding); + } else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { + switcher.onReleaseSymbol(); } } - private FieldContext makeFieldContext() { - return new FieldContext( - getCurrentInputConnection(), - getCurrentInputEditorInfo(), - mLanguageSwitcher.getInputLanguage(), - mLanguageSwitcher.getEnabledLanguages()); - } - - private boolean fieldCanDoVoice(FieldContext fieldContext) { - return !mPasswordText - && mVoiceInput != null - && !mVoiceInput.isBlacklistedField(fieldContext); - } - - private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) { - return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext) - && !(attribute != null - && IME_OPTION_NO_MICROPHONE.equals(attribute.privateImeOptions)) - && SpeechRecognizer.isRecognitionAvailable(this); - } - // receive ringer mode changes to detect silent mode + // receive ringer mode change and network state change. private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - updateRingerMode(); + final String action = intent.getAction(); + if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { + updateRingerMode(); + } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { + mSubtypeSwitcher.onNetworkStateChanged(intent); + } } }; @@ -2359,7 +1934,7 @@ public class LatinIME extends InputMethodService mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); } if (mAudioManager != null) { - mSilentMode = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL); + mSilentModeOn = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL); } } @@ -2367,22 +1942,22 @@ public class LatinIME extends InputMethodService // if mAudioManager is null, we don't have the ringer state yet // mAudioManager will be set by updateRingerMode if (mAudioManager == null) { - if (mKeyboardSwitcher.getInputView() != null) { + if (mKeyboardSwitcher.getKeyboardView() != null) { updateRingerMode(); } } - if (mSoundOn && !mSilentMode) { + if (isSoundOn()) { // FIXME: Volume and enable should come from UI settings // FIXME: These should be triggered after auto-repeat logic int sound = AudioManager.FX_KEYPRESS_STANDARD; switch (primaryCode) { - case Keyboard.KEYCODE_DELETE: + case Keyboard.CODE_DELETE: sound = AudioManager.FX_KEYPRESS_DELETE; break; - case KEYCODE_ENTER: + case Keyboard.CODE_ENTER: sound = AudioManager.FX_KEYPRESS_RETURN; break; - case KEYCODE_SPACE: + case Keyboard.CODE_SPACE: sound = AudioManager.FX_KEYPRESS_SPACEBAR; break; } @@ -2390,81 +1965,69 @@ public class LatinIME extends InputMethodService } } - private void vibrate() { - if (!mVibrateOn) { + public void vibrate() { + if (!mSettingsValues.mVibrateOn) { return; } - if (mKeyboardSwitcher.getInputView() != null) { - mKeyboardSwitcher.getInputView().performHapticFeedback( + LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); + if (inputView != null) { + inputView.performHapticFeedback( HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } } - private void checkTutorial(String privateImeOptions) { - if (privateImeOptions == null) return; - if (privateImeOptions.equals("com.android.setupwizard:ShowTutorial")) { - if (mTutorial == null) startTutorial(); - } else if (privateImeOptions.equals("com.android.setupwizard:HideTutorial")) { - if (mTutorial != null) { - if (mTutorial.close()) { - mTutorial = null; - } - } - } - } - - private void startTutorial() { - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_TUTORIAL), 500); - } - - /* package */ void tutorialDone() { - mTutorial = null; - } - - /* package */ void promoteToUserDictionary(String word, int frequency) { - if (mUserDictionary.isValidWord(word)) return; - mUserDictionary.addWord(word, frequency); - } - - /* package */ WordComposer getCurrentWord() { + public WordComposer getCurrentWord() { return mWord; } - /* package */ boolean getPopupOn() { - return mPopupOn; + boolean isSoundOn() { + return mSettingsValues.mSoundOn && !mSilentModeOn; } private void updateCorrectionMode() { + // TODO: cleanup messy flags mHasDictionary = mSuggest != null ? mSuggest.hasMainDictionary() : false; - mAutoCorrectOn = (mAutoCorrectEnabled || mQuickFixes) - && !mInputTypeNoAutoCorrect && mHasDictionary; - mCorrectionMode = (mAutoCorrectOn && mAutoCorrectEnabled) + final boolean shouldAutoCorrect = (mSettingsValues.mAutoCorrectEnabled + || mSettingsValues.mQuickFixes) && !mInputTypeNoAutoCorrect && mHasDictionary; + mCorrectionMode = (shouldAutoCorrect && mSettingsValues.mAutoCorrectEnabled) ? Suggest.CORRECTION_FULL - : (mAutoCorrectOn ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE); - mCorrectionMode = (mBigramSuggestionEnabled && mAutoCorrectOn && mAutoCorrectEnabled) + : (shouldAutoCorrect ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE); + mCorrectionMode = (mSettingsValues.mBigramSuggestionEnabled && shouldAutoCorrect + && mSettingsValues.mAutoCorrectEnabled) ? Suggest.CORRECTION_FULL_BIGRAM : mCorrectionMode; if (mSuggest != null) { mSuggest.setCorrectionMode(mCorrectionMode); } } - private void updateAutoTextEnabled(Locale systemLocale) { + private void updateAutoTextEnabled() { if (mSuggest == null) return; - boolean different = - !systemLocale.getLanguage().equalsIgnoreCase(mInputLocale.substring(0, 2)); - mSuggest.setAutoTextEnabled(!different && mQuickFixes); + mSuggest.setQuickFixesEnabled(mSettingsValues.mQuickFixes + && SubtypeSwitcher.getInstance().isSystemLanguageSameAsInputLanguage()); + } + + private void updateSuggestionVisibility(final SharedPreferences prefs, final Resources res) { + final String suggestionVisiblityStr = prefs.getString( + Settings.PREF_SHOW_SUGGESTIONS_SETTING, + res.getString(R.string.prefs_suggestion_visibility_default_value)); + for (int visibility : SUGGESTION_VISIBILITY_VALUE_ARRAY) { + if (suggestionVisiblityStr.equals(res.getString(visibility))) { + mSuggestionVisibility = visibility; + break; + } + } } protected void launchSettings() { - launchSettings(LatinIMESettings.class); + launchSettings(Settings.class); } public void launchDebugSettings() { - launchSettings(LatinIMEDebugSettings.class); + launchSettings(DebugSettings.class); } - protected void launchSettings (Class<? extends PreferenceActivity> settingsClass) { + protected void launchSettings(Class<? extends PreferenceActivity> settingsClass) { handleClose(); Intent intent = new Intent(); intent.setClass(LatinIME.this, settingsClass); @@ -2472,122 +2035,78 @@ public class LatinIME extends InputMethodService startActivity(intent); } - private void loadSettings() { - // Get the settings preferences - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); - mVibrateOn = sp.getBoolean(PREF_VIBRATE_ON, false); - mSoundOn = sp.getBoolean(PREF_SOUND_ON, false); - mPopupOn = sp.getBoolean(PREF_POPUP_ON, - mResources.getBoolean(R.bool.default_popup_preview)); - mAutoCap = sp.getBoolean(PREF_AUTO_CAP, true); - mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true); - mHasUsedVoiceInput = sp.getBoolean(PREF_HAS_USED_VOICE_INPUT, false); - mHasUsedVoiceInputUnsupportedLocale = - sp.getBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, false); - - // Get the current list of supported locales and check the current locale against that - // list. We cache this value so as not to check it every time the user starts a voice - // input. Because this method is called by onStartInputView, this should mean that as - // long as the locale doesn't change while the user is keeping the IME open, the - // value should never be stale. - String supportedLocalesString = SettingsUtil.getSettingsString( - getContentResolver(), - SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES, - DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES); - ArrayList<String> voiceInputSupportedLocales = - newArrayList(supportedLocalesString.split("\\s+")); - - mLocaleSupportedForVoiceInput = voiceInputSupportedLocales.contains(mInputLocale); - - mShowSuggestions = sp.getBoolean(PREF_SHOW_SUGGESTIONS, true); - - if (VOICE_INSTALLED) { - final String voiceMode = sp.getString(PREF_VOICE_MODE, - getString(R.string.voice_mode_main)); - boolean enableVoice = !voiceMode.equals(getString(R.string.voice_mode_off)) - && mEnableVoiceButton; - boolean voiceOnPrimary = voiceMode.equals(getString(R.string.voice_mode_main)); - if (mKeyboardSwitcher != null && - (enableVoice != mEnableVoice || voiceOnPrimary != mVoiceOnPrimary)) { - mKeyboardSwitcher.setVoiceMode(enableVoice, voiceOnPrimary); + private void showSubtypeSelectorAndSettings() { + final CharSequence title = getString(R.string.english_ime_input_options); + final CharSequence[] items = new CharSequence[] { + // TODO: Should use new string "Select active input modes". + getString(R.string.language_selection_title), + getString(R.string.english_ime_settings), + }; + final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface di, int position) { + di.dismiss(); + switch (position) { + case 0: + Intent intent = CompatUtils.getInputLanguageSelectionIntent( + mInputMethodId, Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + break; + case 1: + launchSettings(); + break; + } } - mEnableVoice = enableVoice; - mVoiceOnPrimary = voiceOnPrimary; - } - mAutoCorrectEnabled = sp.getBoolean(PREF_AUTO_COMPLETE, - mResources.getBoolean(R.bool.enable_autocorrect)) & mShowSuggestions; - //mBigramSuggestionEnabled = sp.getBoolean( - // PREF_BIGRAM_SUGGESTIONS, true) & mShowSuggestions; - updateCorrectionMode(); - updateAutoTextEnabled(mResources.getConfiguration().locale); - mLanguageSwitcher.loadLocales(sp); + }; + showOptionsMenuInternal(title, items, listener); } - private void initSuggestPuncList() { - mSuggestPuncList = new ArrayList<CharSequence>(); - mSuggestPuncs = mResources.getString(R.string.suggested_punctuations); - if (mSuggestPuncs != null) { - for (int i = 0; i < mSuggestPuncs.length(); i++) { - mSuggestPuncList.add(mSuggestPuncs.subSequence(i, i + 1)); + private void showOptionsMenu() { + final CharSequence title = getString(R.string.english_ime_input_options); + final CharSequence[] items = new CharSequence[] { + getString(R.string.selectInputMethod), + getString(R.string.english_ime_settings), + }; + final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface di, int position) { + di.dismiss(); + switch (position) { + case 0: + mImm.showInputMethodPicker(); + break; + case 1: + launchSettings(); + break; + } } - } + }; + showOptionsMenuInternal(title, items, listener); } - private boolean isSuggestedPunctuation(int code) { - return mSuggestPuncs.contains(String.valueOf((char)code)); - } - - private void showOptionsMenu() { + private void showOptionsMenuInternal(CharSequence title, CharSequence[] items, + DialogInterface.OnClickListener listener) { + final IBinder windowToken = mKeyboardSwitcher.getKeyboardView().getWindowToken(); + if (windowToken == null) return; AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setCancelable(true); builder.setIcon(R.drawable.ic_dialog_keyboard); builder.setNegativeButton(android.R.string.cancel, null); - CharSequence itemSettings = getString(R.string.english_ime_settings); - CharSequence itemInputMethod = getString(R.string.selectInputMethod); - builder.setItems(new CharSequence[] { - itemInputMethod, itemSettings}, - new DialogInterface.OnClickListener() { - - public void onClick(DialogInterface di, int position) { - di.dismiss(); - switch (position) { - case POS_SETTINGS: - launchSettings(); - break; - case POS_METHOD: - ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)) - .showInputMethodPicker(); - break; - } - } - }); - builder.setTitle(mResources.getString(R.string.english_ime_input_options)); + builder.setItems(items, listener); + builder.setTitle(title); mOptionsDialog = builder.create(); + mOptionsDialog.setCanceledOnTouchOutside(true); Window window = mOptionsDialog.getWindow(); WindowManager.LayoutParams lp = window.getAttributes(); - lp.token = mKeyboardSwitcher.getInputView().getWindowToken(); + lp.token = windowToken; lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; window.setAttributes(lp); window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); mOptionsDialog.show(); } - public void changeKeyboardMode() { - mKeyboardSwitcher.toggleSymbols(); - if (mCapsLock && mKeyboardSwitcher.isAlphabetMode()) { - mKeyboardSwitcher.setShiftLocked(mCapsLock); - } - - updateShiftKeyState(getCurrentInputEditorInfo()); - } - - public static <E> ArrayList<E> newArrayList(E... elements) { - int capacity = (elements.length * 110) / 100 + 5; - ArrayList<E> list = new ArrayList<E>(capacity); - Collections.addAll(list, elements); - return list; - } - @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { super.dump(fd, fout, args); @@ -2595,18 +2114,17 @@ public class LatinIME extends InputMethodService final Printer p = new PrintWriterPrinter(fout); p.println("LatinIME state :"); p.println(" Keyboard mode = " + mKeyboardSwitcher.getKeyboardMode()); - p.println(" mCapsLock=" + mCapsLock); p.println(" mComposing=" + mComposing.toString()); - p.println(" mPredictionOn=" + mPredictionOn); + p.println(" mIsSuggestionsRequested=" + mIsSettingsSuggestionStripOn); p.println(" mCorrectionMode=" + mCorrectionMode); - p.println(" mPredicting=" + mPredicting); - p.println(" mAutoCorrectOn=" + mAutoCorrectOn); - p.println(" mAutoSpace=" + mAutoSpace); - p.println(" mCompletionOn=" + mCompletionOn); + p.println(" mHasUncommittedTypedChars=" + mHasUncommittedTypedChars); + p.println(" mAutoCorrectEnabled=" + mSettingsValues.mAutoCorrectEnabled); + p.println(" mShouldInsertMagicSpace=" + mShouldInsertMagicSpace); + p.println(" mApplicationSpecifiedCompletionOn=" + mApplicationSpecifiedCompletionOn); p.println(" TextEntryState.state=" + TextEntryState.getState()); - p.println(" mSoundOn=" + mSoundOn); - p.println(" mVibrateOn=" + mVibrateOn); - p.println(" mPopupOn=" + mPopupOn); + p.println(" mSoundOn=" + mSettingsValues.mSoundOn); + p.println(" mVibrateOn=" + mSettingsValues.mVibrateOn); + p.println(" mKeyPreviewPopupOn=" + mSettingsValues.mKeyPreviewPopupOn); } // Characters per second measurement @@ -2626,8 +2144,4 @@ public class LatinIME extends InputMethodService for (int i = 0; i < CPS_BUFFER_SIZE; i++) total += mCpsIntervals[i]; System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total)); } - - public void onAutoCompletionStateChanged(boolean isAutoCompletion) { - mKeyboardSwitcher.onAutoCompletionStateChanged(isAutoCompletion); - } } |