diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
28 files changed, 1584 insertions, 5767 deletions
diff --git a/java/src/com/android/inputmethod/latin/AutoDictionary.java b/java/src/com/android/inputmethod/latin/AutoDictionary.java index 4fbb5b012..a3bf25642 100644 --- a/java/src/com/android/inputmethod/latin/AutoDictionary.java +++ b/java/src/com/android/inputmethod/latin/AutoDictionary.java @@ -16,10 +16,6 @@ package com.android.inputmethod.latin; -import java.util.HashMap; -import java.util.Set; -import java.util.Map.Entry; - import android.content.ContentValues; import android.content.Context; import android.database.Cursor; @@ -30,6 +26,10 @@ import android.os.AsyncTask; import android.provider.BaseColumns; import android.util.Log; +import java.util.HashMap; +import java.util.Map.Entry; +import java.util.Set; + /** * Stores new words temporarily until they are promoted to the user dictionary * for longevity. Words in the auto dictionary are used to determine if it's ok diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index d0e143dd0..c7f629f15 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -16,16 +16,16 @@ package com.android.inputmethod.latin; -import java.io.InputStream; +import android.content.Context; +import android.util.Log; + import java.io.IOException; +import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.Channels; import java.util.Arrays; -import android.content.Context; -import android.util.Log; - /** * Implements a static, compacted, binary dictionary of standard words. */ @@ -45,16 +45,15 @@ public class BinaryDictionary extends Dictionary { private static final int MAX_BIGRAMS = 60; private static final int TYPED_LETTER_MULTIPLIER = 2; - private static final boolean ENABLE_MISSED_CHARACTERS = true; private int mDicTypeId; private int mNativeDict; private int mDictLength; - private int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_ALTERNATIVES]; - private char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS]; - private char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS]; - private int[] mFrequencies = new int[MAX_WORDS]; - private int[] mFrequencies_bigrams = new int[MAX_BIGRAMS]; + private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_ALTERNATIVES]; + private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS]; + private final char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS]; + private final int[] mFrequencies = new int[MAX_WORDS]; + private final int[] mFrequencies_bigrams = new int[MAX_BIGRAMS]; // Keep a reference to the native dict direct buffer in Java to avoid // unexpected deallocation of the direct buffer. private ByteBuffer mNativeDictDirectBuffer; @@ -95,18 +94,19 @@ public class BinaryDictionary extends Dictionary { } mDictLength = byteBuffer.capacity(); mNativeDict = openNative(mNativeDictDirectBuffer, - TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER); + TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER, + MAX_WORD_LENGTH, MAX_WORDS, MAX_ALTERNATIVES); } mDicTypeId = dicTypeId; } private native int openNative(ByteBuffer bb, int typedLetterMultiplier, - int fullWordMultiplier); + int fullWordMultiplier, int maxWordLength, int maxWords, int maxAlternatives); private native void closeNative(int dict); private native boolean isValidWordNative(int nativeData, char[] word, int wordLength); private native int getSuggestionsNative(int dict, int[] inputCodes, int codesSize, - char[] outputChars, int[] frequencies, int maxWordLength, int maxWords, - int maxAlternatives, int skipPos, int[] nextLettersFrequencies, int nextLettersSize); + char[] outputChars, int[] frequencies, + int[] nextLettersFrequencies, int nextLettersSize); private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength, int[] inputCodes, int inputCodesLength, char[] outputChars, int[] frequencies, int maxWordLength, int maxBigrams, int maxAlternatives); @@ -132,7 +132,8 @@ public class BinaryDictionary extends Dictionary { Log.e(TAG, "Read " + got + " bytes, expected " + total); } else { mNativeDict = openNative(mNativeDictDirectBuffer, - TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER); + TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER, + MAX_WORD_LENGTH, MAX_WORDS, MAX_ALTERNATIVES); mDictLength = total; } } catch (IOException e) { @@ -189,7 +190,7 @@ public class BinaryDictionary extends Dictionary { final int codesSize = codes.size(); // Won't deal with really long words. if (codesSize > MAX_WORD_LENGTH - 1) return; - + Arrays.fill(mInputCodes, -1); for (int i = 0; i < codesSize; i++) { int[] alternatives = codes.getCodesAt(i); @@ -199,27 +200,10 @@ public class BinaryDictionary extends Dictionary { Arrays.fill(mOutputChars, (char) 0); Arrays.fill(mFrequencies, 0); - int count = getSuggestionsNative(mNativeDict, mInputCodes, codesSize, - mOutputChars, mFrequencies, - MAX_WORD_LENGTH, MAX_WORDS, MAX_ALTERNATIVES, -1, - nextLettersFrequencies, + int count = getSuggestionsNative(mNativeDict, mInputCodes, codesSize, mOutputChars, + mFrequencies, nextLettersFrequencies, nextLettersFrequencies != null ? nextLettersFrequencies.length : 0); - // If there aren't sufficient suggestions, search for words by allowing wild cards at - // the different character positions. This feature is not ready for prime-time as we need - // to figure out the best ranking for such words compared to proximity corrections and - // completions. - if (ENABLE_MISSED_CHARACTERS && count < 5) { - for (int skip = 0; skip < codesSize; skip++) { - int tempCount = getSuggestionsNative(mNativeDict, mInputCodes, codesSize, - mOutputChars, mFrequencies, - MAX_WORD_LENGTH, MAX_WORDS, MAX_ALTERNATIVES, skip, - null, 0); - count = Math.max(count, tempCount); - if (tempCount > 0) break; - } - } - for (int j = 0; j < count; j++) { if (mFrequencies[j] < 1) break; int start = j * MAX_WORD_LENGTH; diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java index 68f288925..68f288925 100755..100644 --- a/java/src/com/android/inputmethod/latin/CandidateView.java +++ b/java/src/com/android/inputmethod/latin/CandidateView.java diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java index e954c0818..2a7767ddd 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java @@ -16,11 +16,11 @@ package com.android.inputmethod.latin; -import java.util.LinkedList; - import android.content.Context; import android.os.AsyncTask; +import java.util.LinkedList; + /** * Base class for an in-memory dictionary that can grow dynamically and can * be searched for suggestions and valid words. diff --git a/java/src/com/android/inputmethod/latin/Hints.java b/java/src/com/android/inputmethod/latin/Hints.java deleted file mode 100644 index c467365e7..000000000 --- a/java/src/com/android/inputmethod/latin/Hints.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2009 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.inputmethod.latin; - -import com.android.inputmethod.voice.SettingsUtil; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.view.inputmethod.InputConnection; - -import java.util.Calendar; -import java.util.HashMap; -import java.util.Map; - -/** - * Logic to determine when to display hints on usage to the user. - */ -public class Hints { - public interface Display { - public void showHint(int viewResource); - } - - private static final String PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN = - "voice_hint_num_unique_days_shown"; - private static final String PREF_VOICE_HINT_LAST_TIME_SHOWN = - "voice_hint_last_time_shown"; - private static final String PREF_VOICE_INPUT_LAST_TIME_USED = - "voice_input_last_time_used"; - private static final String PREF_VOICE_PUNCTUATION_HINT_VIEW_COUNT = - "voice_punctuation_hint_view_count"; - private static final int DEFAULT_SWIPE_HINT_MAX_DAYS_TO_SHOW = 7; - private static final int DEFAULT_PUNCTUATION_HINT_MAX_DISPLAYS = 7; - - private Context mContext; - private Display mDisplay; - private boolean mVoiceResultContainedPunctuation; - private int mSwipeHintMaxDaysToShow; - private int mPunctuationHintMaxDisplays; - - // Only show punctuation hint if voice result did not contain punctuation. - static final Map<CharSequence, String> SPEAKABLE_PUNCTUATION - = new HashMap<CharSequence, String>(); - static { - SPEAKABLE_PUNCTUATION.put(",", "comma"); - SPEAKABLE_PUNCTUATION.put(".", "period"); - SPEAKABLE_PUNCTUATION.put("?", "question mark"); - } - - public Hints(Context context, Display display) { - mContext = context; - mDisplay = display; - - ContentResolver cr = mContext.getContentResolver(); - mSwipeHintMaxDaysToShow = SettingsUtil.getSettingsInt( - cr, - SettingsUtil.LATIN_IME_VOICE_INPUT_SWIPE_HINT_MAX_DAYS, - DEFAULT_SWIPE_HINT_MAX_DAYS_TO_SHOW); - mPunctuationHintMaxDisplays = SettingsUtil.getSettingsInt( - cr, - SettingsUtil.LATIN_IME_VOICE_INPUT_PUNCTUATION_HINT_MAX_DISPLAYS, - DEFAULT_PUNCTUATION_HINT_MAX_DISPLAYS); - } - - public boolean showSwipeHintIfNecessary(boolean fieldRecommended) { - if (fieldRecommended && shouldShowSwipeHint()) { - showHint(R.layout.voice_swipe_hint); - return true; - } - - return false; - } - - public boolean showPunctuationHintIfNecessary(InputConnection ic) { - if (!mVoiceResultContainedPunctuation - && ic != null - && getAndIncrementPref(PREF_VOICE_PUNCTUATION_HINT_VIEW_COUNT) - < mPunctuationHintMaxDisplays) { - CharSequence charBeforeCursor = ic.getTextBeforeCursor(1, 0); - if (SPEAKABLE_PUNCTUATION.containsKey(charBeforeCursor)) { - showHint(R.layout.voice_punctuation_hint); - return true; - } - } - - return false; - } - - public void registerVoiceResult(String text) { - // Update the current time as the last time voice input was used. - SharedPreferences.Editor editor = - PreferenceManager.getDefaultSharedPreferences(mContext).edit(); - editor.putLong(PREF_VOICE_INPUT_LAST_TIME_USED, System.currentTimeMillis()); - SharedPreferencesCompat.apply(editor); - - mVoiceResultContainedPunctuation = false; - for (CharSequence s : SPEAKABLE_PUNCTUATION.keySet()) { - if (text.indexOf(s.toString()) >= 0) { - mVoiceResultContainedPunctuation = true; - break; - } - } - } - - private boolean shouldShowSwipeHint() { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); - - int numUniqueDaysShown = sp.getInt(PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN, 0); - - // If we've already shown the hint for enough days, we'll return false. - if (numUniqueDaysShown < mSwipeHintMaxDaysToShow) { - - long lastTimeVoiceWasUsed = sp.getLong(PREF_VOICE_INPUT_LAST_TIME_USED, 0); - - // If the user has used voice today, we'll return false. (We don't show the hint on - // any day that the user has already used voice.) - if (!isFromToday(lastTimeVoiceWasUsed)) { - return true; - } - } - - return false; - } - - /** - * Determines whether the provided time is from some time today (i.e., this day, month, - * and year). - */ - private boolean isFromToday(long timeInMillis) { - if (timeInMillis == 0) return false; - - Calendar today = Calendar.getInstance(); - today.setTimeInMillis(System.currentTimeMillis()); - - Calendar timestamp = Calendar.getInstance(); - timestamp.setTimeInMillis(timeInMillis); - - return (today.get(Calendar.YEAR) == timestamp.get(Calendar.YEAR) && - today.get(Calendar.DAY_OF_MONTH) == timestamp.get(Calendar.DAY_OF_MONTH) && - today.get(Calendar.MONTH) == timestamp.get(Calendar.MONTH)); - } - - private void showHint(int hintViewResource) { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); - - int numUniqueDaysShown = sp.getInt(PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN, 0); - long lastTimeHintWasShown = sp.getLong(PREF_VOICE_HINT_LAST_TIME_SHOWN, 0); - - // If this is the first time the hint is being shown today, increase the saved values - // to represent that. We don't need to increase the last time the hint was shown unless - // it is a different day from the current value. - if (!isFromToday(lastTimeHintWasShown)) { - SharedPreferences.Editor editor = sp.edit(); - editor.putInt(PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN, numUniqueDaysShown + 1); - editor.putLong(PREF_VOICE_HINT_LAST_TIME_SHOWN, System.currentTimeMillis()); - SharedPreferencesCompat.apply(editor); - } - - if (mDisplay != null) { - mDisplay.showHint(hintViewResource); - } - } - - private int getAndIncrementPref(String pref) { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); - int value = sp.getInt(pref, 0); - SharedPreferences.Editor editor = sp.edit(); - editor.putInt(pref, value + 1); - SharedPreferencesCompat.apply(editor); - return value; - } -} diff --git a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java index e811a2cdd..ad3f1db9b 100644 --- a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java +++ b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java @@ -16,11 +16,6 @@ package com.android.inputmethod.latin; -import java.text.Collator; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Locale; - import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.content.res.Configuration; @@ -32,12 +27,18 @@ import android.preference.PreferenceGroup; import android.preference.PreferenceManager; import android.text.TextUtils; +import java.text.Collator; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Locale; + public class InputLanguageSelection extends PreferenceActivity { + private SharedPreferences mPrefs; private String mSelectedLanguages; private ArrayList<Loc> mAvailableLanguages = new ArrayList<Loc>(); private static final String[] BLACKLIST_LANGUAGES = { - "ko", "ja", "zh", "el" + "ko", "ja", "zh", "el", "zz" }; private static class Loc implements Comparable<Object> { @@ -56,6 +57,7 @@ public class InputLanguageSelection extends PreferenceActivity { return this.label; } + @Override public int compareTo(Object o) { return sCollator.compare(this.label, ((Loc) o).label); } @@ -66,15 +68,15 @@ public class InputLanguageSelection extends PreferenceActivity { super.onCreate(icicle); addPreferencesFromResource(R.xml.language_prefs); // Get the settings preferences - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); - mSelectedLanguages = sp.getString(LatinIME.PREF_SELECTED_LANGUAGES, ""); + mPrefs = PreferenceManager.getDefaultSharedPreferences(this); + mSelectedLanguages = mPrefs.getString(LatinIME.PREF_SELECTED_LANGUAGES, ""); String[] languageList = mSelectedLanguages.split(","); mAvailableLanguages = getUniqueLocales(); PreferenceGroup parent = getPreferenceScreen(); for (int i = 0; i < mAvailableLanguages.size(); i++) { CheckBoxPreference pref = new CheckBoxPreference(this); Locale locale = mAvailableLanguages.get(i).locale; - pref.setTitle(LanguageSwitcher.toTitleCase(locale.getDisplayName(locale))); + pref.setTitle(SubtypeSwitcher.getFullDisplayName(locale, true)); boolean checked = isLocaleIn(locale, languageList); pref.setChecked(checked); if (hasDictionary(locale)) { @@ -140,8 +142,7 @@ public class InputLanguageSelection extends PreferenceActivity { } } if (checkedLanguages.length() < 1) checkedLanguages = null; // Save null - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); - Editor editor = sp.edit(); + Editor editor = mPrefs.edit(); editor.putString(LatinIME.PREF_SELECTED_LANGUAGES, checkedLanguages); SharedPreferencesCompat.apply(editor); } @@ -167,7 +168,7 @@ public class InputLanguageSelection extends PreferenceActivity { if (finalSize == 0) { preprocess[finalSize++] = - new Loc(LanguageSwitcher.toTitleCase(l.getDisplayName(l)), l); + new Loc(SubtypeSwitcher.getFullDisplayName(l, true), l); } else { // check previous entry: // same lang and a country -> upgrade to full name and @@ -175,15 +176,15 @@ public class InputLanguageSelection extends PreferenceActivity { // diff lang -> insert ours with lang-only name if (preprocess[finalSize-1].locale.getLanguage().equals( language)) { - preprocess[finalSize-1].label = LanguageSwitcher.toTitleCase( - preprocess[finalSize-1].locale.getDisplayName()); + preprocess[finalSize-1].label = SubtypeSwitcher.getFullDisplayName( + preprocess[finalSize-1].locale, false); preprocess[finalSize++] = - new Loc(LanguageSwitcher.toTitleCase(l.getDisplayName()), l); + new Loc(SubtypeSwitcher.getFullDisplayName(l, false), l); } else { String displayName; if (s.equals("zz_ZZ")) { } else { - displayName = LanguageSwitcher.toTitleCase(l.getDisplayName(l)); + displayName = SubtypeSwitcher.getFullDisplayName(l, true); preprocess[finalSize++] = new Loc(displayName, l); } } diff --git a/java/src/com/android/inputmethod/latin/KeyDetector.java b/java/src/com/android/inputmethod/latin/KeyDetector.java deleted file mode 100644 index 76fe1200e..000000000 --- a/java/src/com/android/inputmethod/latin/KeyDetector.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2010 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.inputmethod.latin; - -import android.inputmethodservice.Keyboard; -import android.inputmethodservice.Keyboard.Key; - -import java.util.Arrays; -import java.util.List; - -abstract class KeyDetector { - protected Keyboard mKeyboard; - - private Key[] mKeys; - - protected int mCorrectionX; - - protected int mCorrectionY; - - protected boolean mProximityCorrectOn; - - protected int mProximityThresholdSquare; - - public Key[] setKeyboard(Keyboard keyboard, float correctionX, float correctionY) { - if (keyboard == null) - throw new NullPointerException(); - mCorrectionX = (int)correctionX; - mCorrectionY = (int)correctionY; - mKeyboard = keyboard; - List<Key> keys = mKeyboard.getKeys(); - Key[] array = keys.toArray(new Key[keys.size()]); - mKeys = array; - return array; - } - - protected int getTouchX(int x) { - return x + mCorrectionX; - } - - protected int getTouchY(int y) { - return y + mCorrectionY; - } - - protected Key[] getKeys() { - if (mKeys == null) - throw new IllegalStateException("keyboard isn't set"); - // mKeyboard is guaranteed not to be null at setKeybaord() method if mKeys is not null - return mKeys; - } - - public void setProximityCorrectionEnabled(boolean enabled) { - mProximityCorrectOn = enabled; - } - - public boolean isProximityCorrectionEnabled() { - return mProximityCorrectOn; - } - - public void setProximityThreshold(int threshold) { - mProximityThresholdSquare = threshold * threshold; - } - - /** - * Allocates array that can hold all key indices returned by {@link #getKeyIndexAndNearbyCodes} - * method. The maximum size of the array should be computed by {@link #getMaxNearbyKeys}. - * - * @return Allocates and returns an array that can hold all key indices returned by - * {@link #getKeyIndexAndNearbyCodes} method. All elements in the returned array are - * initialized by {@link com.android.inputmethod.latin.LatinKeyboardView.NOT_A_KEY} - * value. - */ - public int[] newCodeArray() { - int[] codes = new int[getMaxNearbyKeys()]; - Arrays.fill(codes, LatinKeyboardBaseView.NOT_A_KEY); - return codes; - } - - /** - * Computes maximum size of the array that can contain all nearby key indices returned by - * {@link #getKeyIndexAndNearbyCodes}. - * - * @return Returns maximum size of the array that can contain all nearby key indices returned - * by {@link #getKeyIndexAndNearbyCodes}. - */ - abstract protected int getMaxNearbyKeys(); - - /** - * Finds all possible nearby key indices around a touch event point and returns the nearest key - * index. The algorithm to determine the nearby keys depends on the threshold set by - * {@link #setProximityThreshold(int)} and the mode set by - * {@link #setProximityCorrectionEnabled(boolean)}. - * - * @param x The x-coordinate of a touch point - * @param y The y-coordinate of a touch point - * @param allKeys All nearby key indices are returned in this array - * @return The nearest key index - */ - abstract public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys); -} diff --git a/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java b/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java deleted file mode 100644 index a7b695eb3..000000000 --- a/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java +++ /dev/null @@ -1,538 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.inputmethod.latin; - -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.preference.PreferenceManager; -import android.view.InflateException; - -import java.lang.ref.SoftReference; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Locale; - -public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceChangeListener { - - public static final int MODE_NONE = 0; - public static final int MODE_TEXT = 1; - public static final int MODE_SYMBOLS = 2; - public static final int MODE_PHONE = 3; - public static final int MODE_URL = 4; - public static final int MODE_EMAIL = 5; - public static final int MODE_IM = 6; - public static final int MODE_WEB = 7; - - // Main keyboard layouts without the settings key - public static final int KEYBOARDMODE_NORMAL = R.id.mode_normal; - public static final int KEYBOARDMODE_URL = R.id.mode_url; - public static final int KEYBOARDMODE_EMAIL = R.id.mode_email; - public static final int KEYBOARDMODE_IM = R.id.mode_im; - public static final int KEYBOARDMODE_WEB = R.id.mode_webentry; - // Main keyboard layouts with the settings key - public static final int KEYBOARDMODE_NORMAL_WITH_SETTINGS_KEY = - R.id.mode_normal_with_settings_key; - public static final int KEYBOARDMODE_URL_WITH_SETTINGS_KEY = - R.id.mode_url_with_settings_key; - public static final int KEYBOARDMODE_EMAIL_WITH_SETTINGS_KEY = - R.id.mode_email_with_settings_key; - public static final int KEYBOARDMODE_IM_WITH_SETTINGS_KEY = - R.id.mode_im_with_settings_key; - public static final int KEYBOARDMODE_WEB_WITH_SETTINGS_KEY = - R.id.mode_webentry_with_settings_key; - - // Symbols keyboard layout without the settings key - public static final int KEYBOARDMODE_SYMBOLS = R.id.mode_symbols; - // Symbols keyboard layout with the settings key - public static final int KEYBOARDMODE_SYMBOLS_WITH_SETTINGS_KEY = - R.id.mode_symbols_with_settings_key; - - public static final String DEFAULT_LAYOUT_ID = "4"; - public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20100902"; - private static final int[] THEMES = new int [] { - R.layout.input_basic, R.layout.input_basic_highcontrast, R.layout.input_stone_normal, - R.layout.input_stone_bold, R.layout.input_gingerbread}; - - // Ids for each characters' color in the keyboard - private static final int CHAR_THEME_COLOR_WHITE = 0; - private static final int CHAR_THEME_COLOR_BLACK = 1; - - // Tables which contains resource ids for each character theme color - private static final int[] KBD_PHONE = new int[] {R.xml.kbd_phone, R.xml.kbd_phone_black}; - private static final int[] KBD_PHONE_SYMBOLS = new int[] { - R.xml.kbd_phone_symbols, R.xml.kbd_phone_symbols_black}; - private static final int[] KBD_SYMBOLS = new int[] { - R.xml.kbd_symbols, R.xml.kbd_symbols_black}; - private static final int[] KBD_SYMBOLS_SHIFT = new int[] { - R.xml.kbd_symbols_shift, R.xml.kbd_symbols_shift_black}; - private static final int[] KBD_QWERTY = new int[] {R.xml.kbd_qwerty, R.xml.kbd_qwerty_black}; - - private static final int SYMBOLS_MODE_STATE_NONE = 0; - private static final int SYMBOLS_MODE_STATE_BEGIN = 1; - private static final int SYMBOLS_MODE_STATE_SYMBOL = 2; - - private LatinKeyboardView mInputView; - private static final int[] ALPHABET_MODES = { - KEYBOARDMODE_NORMAL, - KEYBOARDMODE_URL, - KEYBOARDMODE_EMAIL, - KEYBOARDMODE_IM, - KEYBOARDMODE_WEB, - KEYBOARDMODE_NORMAL_WITH_SETTINGS_KEY, - KEYBOARDMODE_URL_WITH_SETTINGS_KEY, - KEYBOARDMODE_EMAIL_WITH_SETTINGS_KEY, - KEYBOARDMODE_IM_WITH_SETTINGS_KEY, - KEYBOARDMODE_WEB_WITH_SETTINGS_KEY }; - - private final LatinIME mInputMethodService; - - private KeyboardId mSymbolsId; - private KeyboardId mSymbolsShiftedId; - - private KeyboardId mCurrentId; - private final HashMap<KeyboardId, SoftReference<LatinKeyboard>> mKeyboards; - - private int mMode = MODE_NONE; /** One of the MODE_XXX values */ - private int mImeOptions; - private boolean mIsSymbols; - /** mIsAutoCompletionActive indicates that auto completed word will be input instead of - * what user actually typed. */ - private boolean mIsAutoCompletionActive; - private boolean mHasVoice; - private boolean mVoiceOnPrimary; - private boolean mPreferSymbols; - private int mSymbolsModeState = SYMBOLS_MODE_STATE_NONE; - - // Indicates whether or not we have the settings key - private boolean mHasSettingsKey; - private static final int SETTINGS_KEY_MODE_AUTO = R.string.settings_key_mode_auto; - private static final int SETTINGS_KEY_MODE_ALWAYS_SHOW = R.string.settings_key_mode_always_show; - // NOTE: No need to have SETTINGS_KEY_MODE_ALWAYS_HIDE here because it's not being referred to - // in the source code now. - // Default is SETTINGS_KEY_MODE_AUTO. - private static final int DEFAULT_SETTINGS_KEY_MODE = SETTINGS_KEY_MODE_AUTO; - - private int mLastDisplayWidth; - private LanguageSwitcher mLanguageSwitcher; - private Locale mInputLocale; - - private int mLayoutId; - - public KeyboardSwitcher(LatinIME ims) { - mInputMethodService = ims; - - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ims); - mLayoutId = Integer.valueOf(prefs.getString(PREF_KEYBOARD_LAYOUT, DEFAULT_LAYOUT_ID)); - updateSettingsKeyState(prefs); - prefs.registerOnSharedPreferenceChangeListener(this); - - mKeyboards = new HashMap<KeyboardId, SoftReference<LatinKeyboard>>(); - mSymbolsId = makeSymbolsId(false); - mSymbolsShiftedId = makeSymbolsShiftedId(false); - } - - /** - * Sets the input locale, when there are multiple locales for input. - * If no locale switching is required, then the locale should be set to null. - * @param locale the current input locale, or null for default locale with no locale - * button. - */ - public void setLanguageSwitcher(LanguageSwitcher languageSwitcher) { - mLanguageSwitcher = languageSwitcher; - mInputLocale = mLanguageSwitcher.getInputLocale(); - } - - private KeyboardId makeSymbolsId(boolean hasVoice) { - return new KeyboardId(KBD_SYMBOLS[getCharColorId()], mHasSettingsKey ? - KEYBOARDMODE_SYMBOLS_WITH_SETTINGS_KEY : KEYBOARDMODE_SYMBOLS, - false, hasVoice); - } - - private KeyboardId makeSymbolsShiftedId(boolean hasVoice) { - return new KeyboardId(KBD_SYMBOLS_SHIFT[getCharColorId()], mHasSettingsKey ? - KEYBOARDMODE_SYMBOLS_WITH_SETTINGS_KEY : KEYBOARDMODE_SYMBOLS, - false, hasVoice); - } - - public void makeKeyboards(boolean forceCreate) { - mSymbolsId = makeSymbolsId(mHasVoice && !mVoiceOnPrimary); - mSymbolsShiftedId = makeSymbolsShiftedId(mHasVoice && !mVoiceOnPrimary); - - if (forceCreate) mKeyboards.clear(); - // Configuration change is coming after the keyboard gets recreated. So don't rely on that. - // If keyboards have already been made, check if we have a screen width change and - // create the keyboard layouts again at the correct orientation - int displayWidth = mInputMethodService.getMaxWidth(); - if (displayWidth == mLastDisplayWidth) return; - mLastDisplayWidth = displayWidth; - if (!forceCreate) mKeyboards.clear(); - } - - /** - * Represents the parameters necessary to construct a new LatinKeyboard, - * which also serve as a unique identifier for each keyboard type. - */ - private static class KeyboardId { - // TODO: should have locale and portrait/landscape orientation? - public final int mXml; - public final int mKeyboardMode; /** A KEYBOARDMODE_XXX value */ - public final boolean mEnableShiftLock; - public final boolean mHasVoice; - - private final int mHashCode; - - public KeyboardId(int xml, int mode, boolean enableShiftLock, boolean hasVoice) { - this.mXml = xml; - this.mKeyboardMode = mode; - this.mEnableShiftLock = enableShiftLock; - this.mHasVoice = hasVoice; - - this.mHashCode = Arrays.hashCode(new Object[] { - xml, mode, enableShiftLock, hasVoice - }); - } - - public KeyboardId(int xml, boolean hasVoice) { - this(xml, 0, false, hasVoice); - } - - @Override - public boolean equals(Object other) { - return other instanceof KeyboardId && equals((KeyboardId) other); - } - - private boolean equals(KeyboardId other) { - return other.mXml == this.mXml - && other.mKeyboardMode == this.mKeyboardMode - && other.mEnableShiftLock == this.mEnableShiftLock - && other.mHasVoice == this.mHasVoice; - } - - @Override - public int hashCode() { - return mHashCode; - } - } - - public void setVoiceMode(boolean enableVoice, boolean voiceOnPrimary) { - if (enableVoice != mHasVoice || voiceOnPrimary != mVoiceOnPrimary) { - mKeyboards.clear(); - } - mHasVoice = enableVoice; - mVoiceOnPrimary = voiceOnPrimary; - setKeyboardMode(mMode, mImeOptions, mHasVoice, mIsSymbols); - } - - private boolean hasVoiceButton(boolean isSymbols) { - return mHasVoice && (isSymbols != mVoiceOnPrimary); - } - - public void setKeyboardMode(int mode, int imeOptions, boolean enableVoice) { - mSymbolsModeState = SYMBOLS_MODE_STATE_NONE; - mPreferSymbols = mode == MODE_SYMBOLS; - if (mode == MODE_SYMBOLS) { - mode = MODE_TEXT; - } - try { - setKeyboardMode(mode, imeOptions, enableVoice, mPreferSymbols); - } catch (RuntimeException e) { - LatinImeLogger.logOnException(mode + "," + imeOptions + "," + mPreferSymbols, e); - } - } - - private void setKeyboardMode(int mode, int imeOptions, boolean enableVoice, boolean isSymbols) { - if (mInputView == null) return; - mMode = mode; - mImeOptions = imeOptions; - if (enableVoice != mHasVoice) { - // TODO clean up this unnecessary recursive call. - setVoiceMode(enableVoice, mVoiceOnPrimary); - } - mIsSymbols = isSymbols; - - mInputView.setPreviewEnabled(mInputMethodService.getPopupOn()); - KeyboardId id = getKeyboardId(mode, imeOptions, isSymbols); - LatinKeyboard keyboard = null; - keyboard = getKeyboard(id); - - if (mode == MODE_PHONE) { - mInputView.setPhoneKeyboard(keyboard); - } - - mCurrentId = id; - mInputView.setKeyboard(keyboard); - keyboard.setShifted(false); - keyboard.setShiftLocked(keyboard.isShiftLocked()); - keyboard.setImeOptions(mInputMethodService.getResources(), mMode, imeOptions); - keyboard.setColorOfSymbolIcons(mIsAutoCompletionActive, isBlackSym()); - // Update the settings key state because number of enabled IMEs could have been changed - updateSettingsKeyState(PreferenceManager.getDefaultSharedPreferences(mInputMethodService)); - } - - private LatinKeyboard getKeyboard(KeyboardId id) { - SoftReference<LatinKeyboard> ref = mKeyboards.get(id); - LatinKeyboard keyboard = (ref == null) ? null : ref.get(); - if (keyboard == null) { - Resources orig = mInputMethodService.getResources(); - Configuration conf = orig.getConfiguration(); - Locale saveLocale = conf.locale; - conf.locale = mInputLocale; - orig.updateConfiguration(conf, null); - keyboard = new LatinKeyboard(mInputMethodService, id.mXml, id.mKeyboardMode); - keyboard.setVoiceMode(hasVoiceButton(id.mXml == R.xml.kbd_symbols - || id.mXml == R.xml.kbd_symbols_black), mHasVoice); - keyboard.setLanguageSwitcher(mLanguageSwitcher, mIsAutoCompletionActive, isBlackSym()); - - if (id.mEnableShiftLock) { - keyboard.enableShiftLock(); - } - mKeyboards.put(id, new SoftReference<LatinKeyboard>(keyboard)); - - conf.locale = saveLocale; - orig.updateConfiguration(conf, null); - } - return keyboard; - } - - private KeyboardId getKeyboardId(int mode, int imeOptions, boolean isSymbols) { - boolean hasVoice = hasVoiceButton(isSymbols); - int charColorId = getCharColorId(); - // TODO: generalize for any KeyboardId - int keyboardRowsResId = KBD_QWERTY[charColorId]; - if (isSymbols) { - if (mode == MODE_PHONE) { - return new KeyboardId(KBD_PHONE_SYMBOLS[charColorId], hasVoice); - } else { - return new KeyboardId(KBD_SYMBOLS[charColorId], mHasSettingsKey ? - KEYBOARDMODE_SYMBOLS_WITH_SETTINGS_KEY : KEYBOARDMODE_SYMBOLS, - false, hasVoice); - } - } - switch (mode) { - case MODE_NONE: - LatinImeLogger.logOnWarning( - "getKeyboardId:" + mode + "," + imeOptions + "," + isSymbols); - /* fall through */ - case MODE_TEXT: - return new KeyboardId(keyboardRowsResId, mHasSettingsKey ? - KEYBOARDMODE_NORMAL_WITH_SETTINGS_KEY : KEYBOARDMODE_NORMAL, - true, hasVoice); - case MODE_SYMBOLS: - return new KeyboardId(KBD_SYMBOLS[charColorId], mHasSettingsKey ? - KEYBOARDMODE_SYMBOLS_WITH_SETTINGS_KEY : KEYBOARDMODE_SYMBOLS, - false, hasVoice); - case MODE_PHONE: - return new KeyboardId(KBD_PHONE[charColorId], hasVoice); - case MODE_URL: - return new KeyboardId(keyboardRowsResId, mHasSettingsKey ? - KEYBOARDMODE_URL_WITH_SETTINGS_KEY : KEYBOARDMODE_URL, true, hasVoice); - case MODE_EMAIL: - return new KeyboardId(keyboardRowsResId, mHasSettingsKey ? - KEYBOARDMODE_EMAIL_WITH_SETTINGS_KEY : KEYBOARDMODE_EMAIL, true, hasVoice); - case MODE_IM: - return new KeyboardId(keyboardRowsResId, mHasSettingsKey ? - KEYBOARDMODE_IM_WITH_SETTINGS_KEY : KEYBOARDMODE_IM, true, hasVoice); - case MODE_WEB: - return new KeyboardId(keyboardRowsResId, mHasSettingsKey ? - KEYBOARDMODE_WEB_WITH_SETTINGS_KEY : KEYBOARDMODE_WEB, true, hasVoice); - } - return null; - } - - public int getKeyboardMode() { - return mMode; - } - - public boolean isAlphabetMode() { - if (mCurrentId == null) { - return false; - } - int currentMode = mCurrentId.mKeyboardMode; - for (Integer mode : ALPHABET_MODES) { - if (currentMode == mode) { - return true; - } - } - return false; - } - - public void setShifted(boolean shifted) { - if (mInputView != null) { - mInputView.setShifted(shifted); - } - } - - public void setShiftLocked(boolean shiftLocked) { - if (mInputView != null) { - mInputView.setShiftLocked(shiftLocked); - } - } - - public void toggleShift() { - if (isAlphabetMode()) - return; - if (mCurrentId.equals(mSymbolsId) || !mCurrentId.equals(mSymbolsShiftedId)) { - LatinKeyboard symbolsShiftedKeyboard = getKeyboard(mSymbolsShiftedId); - mCurrentId = mSymbolsShiftedId; - mInputView.setKeyboard(symbolsShiftedKeyboard); - // Symbol shifted keyboard has an ALT key that has a caps lock style indicator. To - // enable the indicator, we need to call enableShiftLock() and setShiftLocked(true). - // Thus we can keep the ALT key's Key.on value true while LatinKey.onRelease() is - // called. - symbolsShiftedKeyboard.enableShiftLock(); - symbolsShiftedKeyboard.setShiftLocked(true); - symbolsShiftedKeyboard.setImeOptions(mInputMethodService.getResources(), - mMode, mImeOptions); - } else { - LatinKeyboard symbolsKeyboard = getKeyboard(mSymbolsId); - mCurrentId = mSymbolsId; - mInputView.setKeyboard(symbolsKeyboard); - // Symbol keyboard has an ALT key that has a caps lock style indicator. To disable the - // indicator, we need to call enableShiftLock() and setShiftLocked(false). - symbolsKeyboard.enableShiftLock(); - symbolsKeyboard.setShifted(false); - symbolsKeyboard.setImeOptions(mInputMethodService.getResources(), mMode, mImeOptions); - } - } - - public void toggleSymbols() { - setKeyboardMode(mMode, mImeOptions, mHasVoice, !mIsSymbols); - if (mIsSymbols && !mPreferSymbols) { - mSymbolsModeState = SYMBOLS_MODE_STATE_BEGIN; - } else { - mSymbolsModeState = SYMBOLS_MODE_STATE_NONE; - } - } - - public boolean hasDistinctMultitouch() { - return mInputView != null && mInputView.hasDistinctMultitouch(); - } - - /** - * Updates state machine to figure out when to automatically switch back to alpha mode. - * Returns true if the keyboard needs to switch back - */ - public boolean onKey(int key) { - // Switch back to alpha mode if user types one or more non-space/enter characters - // followed by a space/enter - switch (mSymbolsModeState) { - case SYMBOLS_MODE_STATE_BEGIN: - if (key != LatinIME.KEYCODE_SPACE && key != LatinIME.KEYCODE_ENTER && key > 0) { - mSymbolsModeState = SYMBOLS_MODE_STATE_SYMBOL; - } - break; - case SYMBOLS_MODE_STATE_SYMBOL: - if (key == LatinIME.KEYCODE_ENTER || key == LatinIME.KEYCODE_SPACE) return true; - break; - } - return false; - } - - public LatinKeyboardView getInputView() { - return mInputView; - } - - public void recreateInputView() { - changeLatinKeyboardView(mLayoutId, true); - } - - private void changeLatinKeyboardView(int newLayout, boolean forceReset) { - if (mLayoutId != newLayout || mInputView == null || forceReset) { - if (mInputView != null) { - mInputView.closing(); - } - if (THEMES.length <= newLayout) { - newLayout = Integer.valueOf(DEFAULT_LAYOUT_ID); - } - - LatinIMEUtil.GCUtils.getInstance().reset(); - boolean tryGC = true; - for (int i = 0; i < LatinIMEUtil.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) { - try { - mInputView = (LatinKeyboardView) mInputMethodService.getLayoutInflater( - ).inflate(THEMES[newLayout], null); - tryGC = false; - } catch (OutOfMemoryError e) { - tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait( - mLayoutId + "," + newLayout, e); - } catch (InflateException e) { - tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait( - mLayoutId + "," + newLayout, e); - } - } - mInputView.setOnKeyboardActionListener(mInputMethodService); - mLayoutId = newLayout; - } - mInputMethodService.mHandler.post(new Runnable() { - public void run() { - if (mInputView != null) { - mInputMethodService.setInputView(mInputView); - } - mInputMethodService.updateInputViewShown(); - }}); - } - - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (PREF_KEYBOARD_LAYOUT.equals(key)) { - changeLatinKeyboardView( - Integer.valueOf(sharedPreferences.getString(key, DEFAULT_LAYOUT_ID)), false); - } else if (LatinIMESettings.PREF_SETTINGS_KEY.equals(key)) { - updateSettingsKeyState(sharedPreferences); - recreateInputView(); - } - } - - public boolean isBlackSym () { - if (mInputView != null && mInputView.getSymbolColorScheme() == 1) { - return true; - } - return false; - } - - private int getCharColorId () { - if (isBlackSym()) { - return CHAR_THEME_COLOR_BLACK; - } else { - return CHAR_THEME_COLOR_WHITE; - } - } - - public void onAutoCompletionStateChanged(boolean isAutoCompletion) { - if (isAutoCompletion != mIsAutoCompletionActive) { - LatinKeyboardView keyboardView = getInputView(); - mIsAutoCompletionActive = isAutoCompletion; - keyboardView.invalidateKey(((LatinKeyboard) keyboardView.getKeyboard()) - .onAutoCompletionStateChanged(isAutoCompletion)); - } - } - - private void updateSettingsKeyState(SharedPreferences prefs) { - Resources resources = mInputMethodService.getResources(); - final String settingsKeyMode = prefs.getString(LatinIMESettings.PREF_SETTINGS_KEY, - resources.getString(DEFAULT_SETTINGS_KEY_MODE)); - // We show the settings key when 1) SETTINGS_KEY_MODE_ALWAYS_SHOW or - // 2) SETTINGS_KEY_MODE_AUTO and there are two or more enabled IMEs on the system - if (settingsKeyMode.equals(resources.getString(SETTINGS_KEY_MODE_ALWAYS_SHOW)) - || (settingsKeyMode.equals(resources.getString(SETTINGS_KEY_MODE_AUTO)) - && LatinIMEUtil.hasMultipleEnabledIMEs(mInputMethodService))) { - mHasSettingsKey = true; - } else { - mHasSettingsKey = false; - } - } -} diff --git a/java/src/com/android/inputmethod/latin/LanguageSwitcher.java b/java/src/com/android/inputmethod/latin/LanguageSwitcher.java index 7b5c30491..fc725bf1b 100644 --- a/java/src/com/android/inputmethod/latin/LanguageSwitcher.java +++ b/java/src/com/android/inputmethod/latin/LanguageSwitcher.java @@ -16,21 +16,21 @@ package com.android.inputmethod.latin; -import java.util.Locale; - import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; -import android.preference.PreferenceManager; import android.text.TextUtils; +import java.util.ArrayList; +import java.util.Locale; + /** * Keeps track of list of selected input languages and the current * input language that the user has selected. */ public class LanguageSwitcher { - private Locale[] mLocales; - private LatinIME mIme; + private final ArrayList<Locale> mLocales = new ArrayList<Locale>(); + private final LatinIME mIme; private String[] mSelectedLanguageArray; private String mSelectedLanguages; private int mCurrentIndex = 0; @@ -40,15 +40,10 @@ public class LanguageSwitcher { public LanguageSwitcher(LatinIME ime) { mIme = ime; - mLocales = new Locale[0]; - } - - public Locale[] getLocales() { - return mLocales; } public int getLocaleCount() { - return mLocales.length; + return mLocales.size(); } /** @@ -61,10 +56,10 @@ public class LanguageSwitcher { String currentLanguage = sp.getString(LatinIME.PREF_INPUT_LANGUAGE, null); if (selectedLanguages == null || selectedLanguages.length() < 1) { loadDefaults(); - if (mLocales.length == 0) { + if (mLocales.size() == 0) { return false; } - mLocales = new Locale[0]; + mLocales.clear(); return true; } if (selectedLanguages.equals(mSelectedLanguages)) { @@ -77,7 +72,7 @@ public class LanguageSwitcher { if (currentLanguage != null) { // Find the index mCurrentIndex = 0; - for (int i = 0; i < mLocales.length; i++) { + for (int i = 0; i < mLocales.size(); i++) { if (mSelectedLanguageArray[i].equals(currentLanguage)) { mCurrentIndex = i; break; @@ -96,11 +91,11 @@ public class LanguageSwitcher { } private void constructLocales() { - mLocales = new Locale[mSelectedLanguageArray.length]; - for (int i = 0; i < mLocales.length; i++) { - final String lang = mSelectedLanguageArray[i]; - mLocales[i] = new Locale(lang.substring(0, 2), + mLocales.clear(); + for (final String lang : mSelectedLanguageArray) { + final Locale locale = new Locale(lang.substring(0, 2), lang.length() > 4 ? lang.substring(3, 5) : ""); + mLocales.add(locale); } } @@ -129,7 +124,17 @@ public class LanguageSwitcher { public Locale getInputLocale() { if (getLocaleCount() == 0) return mDefaultInputLocale; - return mLocales[mCurrentIndex]; + return mLocales.get(mCurrentIndex); + } + + private int nextLocaleIndex() { + final int size = mLocales.size(); + return (mCurrentIndex + 1) % size; + } + + private int prevLocaleIndex() { + final int size = mLocales.size(); + return (mCurrentIndex - 1 + size) % size; } /** @@ -139,8 +144,7 @@ public class LanguageSwitcher { */ public Locale getNextInputLocale() { if (getLocaleCount() == 0) return mDefaultInputLocale; - - return mLocales[(mCurrentIndex + 1) % mLocales.length]; + return mLocales.get(nextLocaleIndex()); } /** @@ -166,8 +170,7 @@ public class LanguageSwitcher { */ public Locale getPrevInputLocale() { if (getLocaleCount() == 0) return mDefaultInputLocale; - - return mLocales[(mCurrentIndex - 1 + mLocales.length) % mLocales.length]; + return mLocales.get(prevLocaleIndex()); } public void reset() { @@ -175,27 +178,16 @@ public class LanguageSwitcher { } public void next() { - mCurrentIndex++; - if (mCurrentIndex >= mLocales.length) mCurrentIndex = 0; // Wrap around + mCurrentIndex = nextLocaleIndex(); } public void prev() { - mCurrentIndex--; - if (mCurrentIndex < 0) mCurrentIndex = mLocales.length - 1; // Wrap around + mCurrentIndex = prevLocaleIndex(); } - public void persist() { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mIme); - Editor editor = sp.edit(); + public void persist(SharedPreferences prefs) { + Editor editor = prefs.edit(); editor.putString(LatinIME.PREF_INPUT_LANGUAGE, getInputLanguage()); SharedPreferencesCompat.apply(editor); } - - static String toTitleCase(String s) { - if (s.length() == 0) { - return s; - } - - return Character.toUpperCase(s.charAt(0)) + s.substring(1); - } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index b6fee1170..3089153ad 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -16,10 +16,14 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.keyboard.KeyboardActionListener; +import com.android.inputmethod.keyboard.KeyboardId; +import com.android.inputmethod.keyboard.KeyboardSwitcher; +import com.android.inputmethod.keyboard.KeyboardView; +import com.android.inputmethod.keyboard.LatinKeyboardView; 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 com.android.inputmethod.voice.VoiceIMEConnector; import org.xmlpull.v1.XmlPullParserException; @@ -34,16 +38,14 @@ 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.os.Debug; import android.os.Handler; import android.os.Message; import android.os.SystemClock; +import android.os.Vibrator; import android.preference.PreferenceActivity; import android.preference.PreferenceManager; -import android.speech.SpeechRecognizer; -import android.text.ClipboardManager; import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; @@ -51,7 +53,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; @@ -63,128 +64,93 @@ import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InputMethodSubtype; import android.widget.LinearLayout; 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.Arrays; 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 { + implements KeyboardActionListener, + SharedPreferences.OnSharedPreferenceChangeListener, + Tutorial.TutorialListener { private static final String TAG = "LatinIME"; 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 boolean DEBUG = false; + private static final boolean TRACE = false; - 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"; + private static final String PREF_SHOW_SUGGESTIONS_SETTING = "show_suggestions_setting"; + private static final String PREF_AUTO_COMPLETION_THRESHOLD = "auto_completion_threshold"; + private static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion"; 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 int DELAY_UPDATE_SUGGESTIONS = 180; + private static final int DELAY_UPDATE_OLD_SUGGESTIONS = 300; + private static final int DELAY_UPDATE_SHIFT_STATE = 300; + private static final int DELAY_START_TUTORIAL = 500; // 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 = '.'; - // Contextual menu positions private static final int POS_METHOD = 0; private static final int POS_SETTINGS = 1; - //private LatinKeyboardView mInputView; + 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 + }; + private LinearLayout mCandidateViewContainer; private CandidateView mCandidateView; private Suggest mSuggest; private CompletionInfo[] mCompletions; private AlertDialog mOptionsDialog; - private AlertDialog mVoiceWarningDialog; - /* package */ KeyboardSwitcher mKeyboardSwitcher; + private InputMethodManager mImm; + private KeyboardSwitcher mKeyboardSwitcher; + private SubtypeSwitcher mSubtypeSwitcher; + private VoiceIMEConnector mVoiceConnector; private UserDictionary mUserDictionary; private UserBigramDictionary mUserBigramDictionary; private ContactsDictionary mContactsDictionary; private AutoDictionary mAutoDictionary; - private Hints mHints; - private Resources mResources; + private SharedPreferences mPrefs; - private String mInputLocale; - private String mSystemLocale; - private LanguageSwitcher mLanguageSwitcher; - - 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 mPredicting; private boolean mPredictionOn; private boolean mCompletionOn; private boolean mHasDictionary; @@ -192,72 +158,49 @@ public class LatinIME extends InputMethodService private boolean mJustAddedAutoSpace; private boolean mAutoCorrectEnabled; private boolean mReCorrectionEnabled; - // Bigram Suggestion is disabled in this version. - private final boolean mBigramSuggestionEnabled = false; + private boolean mBigramSuggestionEnabled; 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; + + 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; + private int mLastSelectionStart; + private int mLastSelectionEnd; + private List<CharSequence> mSuggestPuncList; // Input type is such that we should not auto-correct private boolean mInputTypeNoAutoCorrect; // Indicates whether the suggestion strip is to be on in landscape private boolean mJustAccepted; - private CharSequence mJustRevertedSeparator; + private boolean mJustReverted; 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 static 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(); + // TODO: Move this flag to VoiceIMEConnector private boolean mConfigurationChanging; // 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>(); - - private class VoiceResults { - List<String> candidates; - Map<String, List<CharSequence>> alternatives; - } + private final ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>(); public abstract static class WordAlternatives { protected CharSequence mChosenWord; @@ -307,56 +250,101 @@ public class LatinIME extends InputMethodService } } - /* package */ Handler mHandler = new Handler() { + public final UIHandler mHandler = new UIHandler(); + + 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_START_TUTORIAL = 4; + @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); - } + case MSG_UPDATE_SUGGESTIONS: + updateSuggestions(); + break; + case MSG_UPDATE_OLD_SUGGESTIONS: + setOldSuggestions(); + break; + case MSG_UPDATE_SHIFT_STATE: + mKeyboardSwitcher.updateShiftState(); + break; + case MSG_VOICE_RESULTS: + mVoiceConnector.handleVoiceResults(mKeyboardSwitcher, preferCapitalization() + || (mKeyboardSwitcher.isAlphabetMode() + && mKeyboardSwitcher.isShiftedOrShiftLocked())); + break; + case MSG_START_TUTORIAL: + if (mTutorial == null) { + if (mKeyboardSwitcher.isInputViewShown()) { + mTutorial = new Tutorial(LatinIME.this, mKeyboardSwitcher); + 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; + } + break; } } - }; + + public void postUpdateSuggestions() { + removeMessages(MSG_UPDATE_SUGGESTIONS); + sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTIONS), DELAY_UPDATE_SUGGESTIONS); + } + + public void cancelUpdateSuggestions() { + removeMessages(MSG_UPDATE_SUGGESTIONS); + } + + public boolean hasPendingUpdateSuggestions() { + return hasMessages(MSG_UPDATE_SUGGESTIONS); + } + + public void postUpdateOldSuggestions() { + removeMessages(MSG_UPDATE_OLD_SUGGESTIONS); + sendMessageDelayed(obtainMessage(MSG_UPDATE_OLD_SUGGESTIONS), + DELAY_UPDATE_OLD_SUGGESTIONS); + } + + public void cancelUpdateOldSuggestions() { + removeMessages(MSG_UPDATE_OLD_SUGGESTIONS); + } + + public void postUpdateShiftKeyState() { + removeMessages(MSG_UPDATE_SHIFT_STATE); + sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), DELAY_UPDATE_SHIFT_STATE); + } + + public void cancelUpdateShiftState() { + removeMessages(MSG_UPDATE_SHIFT_STATE); + } + + public void updateVoiceResults() { + sendMessage(obtainMessage(MSG_VOICE_RESULTS)); + } + + public void startTutorial() { + sendMessageDelayed(obtainMessage(MSG_START_TUTORIAL), DELAY_START_TUTORIAL); + } + } @Override public void onCreate() { - LatinImeLogger.init(this); + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + mPrefs = prefs; + LatinImeLogger.init(this, prefs); + SubtypeSwitcher.init(this, prefs); + KeyboardSwitcher.init(this, prefs); super.onCreate(); //setStatusIcon(R.drawable.ime_qwerty); mResources = getResources(); + mImm = ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)); final Configuration conf = mResources.getConfiguration(); - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - mLanguageSwitcher = new LanguageSwitcher(this); - mLanguageSwitcher.loadLocales(prefs); - mKeyboardSwitcher = new KeyboardSwitcher(this); - mKeyboardSwitcher.setLanguageSwitcher(mLanguageSwitcher); - mSystemLocale = conf.locale.toString(); - mLanguageSwitcher.setSystemLocale(conf.locale); - String inputLanguage = mLanguageSwitcher.getInputLanguage(); - if (inputLanguage == null) { - inputLanguage = conf.locale.toString(); - } + mSubtypeSwitcher = SubtypeSwitcher.getInstance(); + mKeyboardSwitcher = KeyboardSwitcher.getInstance(); mReCorrectionEnabled = prefs.getBoolean(PREF_RECORRECTION_ENABLED, getResources().getBoolean(R.bool.default_recorrection_enabled)); @@ -364,10 +352,10 @@ public class LatinIME extends InputMethodService boolean tryGC = true; for (int i = 0; i < LatinIMEUtil.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) { try { - initSuggest(inputLanguage); + initSuggest(); tryGC = false; } catch (OutOfMemoryError e) { - tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait(inputLanguage, e); + tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait("InitSuggest", e); } } @@ -377,19 +365,7 @@ public class LatinIME extends InputMethodService // register to receive ringer mode changes for silent mode IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_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; - } - }); - } + mVoiceConnector = VoiceIMEConnector.init(this, prefs, mHandler); prefs.registerOnSharedPreferenceChangeListener(this); } @@ -397,7 +373,7 @@ public class LatinIME extends InputMethodService * Loads a dictionary or multiple separated dictionary * @return returns array of dictionary resource ids */ - /* package */ static int[] getDictionary(Resources res) { + public static int[] getDictionary(Resources res) { String packageName = LatinIME.class.getPackage().getName(); XmlResourceParser xrp = res.getXml(R.xml.dictionary); ArrayList<Integer> dictionaries = new ArrayList<Integer>(); @@ -432,37 +408,34 @@ public class LatinIME extends InputMethodService return dict; } - private void initSuggest(String locale) { - mInputLocale = locale; + private void initSuggest() { + updateAutoTextEnabled(); + String locale = mSubtypeSwitcher.getInputLocaleStr(); Resources orig = getResources(); - Configuration conf = orig.getConfiguration(); - Locale saveLocale = conf.locale; - conf.locale = new Locale(locale); - orig.updateConfiguration(conf, orig.getDisplayMetrics()); + Locale savedLocale = mSubtypeSwitcher.changeSystemLocale(new Locale(locale)); if (mSuggest != null) { mSuggest.close(); } - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); - mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true); + final SharedPreferences prefs = mPrefs; + mQuickFixes = prefs.getBoolean(PREF_QUICK_FIXES, true); int[] dictionaries = getDictionary(orig); mSuggest = new Suggest(this, dictionaries); - updateAutoTextEnabled(saveLocale); + loadAndSetAutoCompletionThreshold(prefs); if (mUserDictionary != null) mUserDictionary.close(); - mUserDictionary = new UserDictionary(this, mInputLocale); + mUserDictionary = new UserDictionary(this, locale); if (mContactsDictionary == null) { mContactsDictionary = new ContactsDictionary(this, Suggest.DIC_CONTACTS); } if (mAutoDictionary != null) { mAutoDictionary.close(); } - mAutoDictionary = new AutoDictionary(this, this, mInputLocale, Suggest.DIC_AUTO); + mAutoDictionary = new AutoDictionary(this, this, locale, Suggest.DIC_AUTO); if (mUserBigramDictionary != null) { mUserBigramDictionary.close(); } - mUserBigramDictionary = new UserBigramDictionary(this, this, mInputLocale, - Suggest.DIC_USER); + mUserBigramDictionary = new UserBigramDictionary(this, this, locale, Suggest.DIC_USER); mSuggest.setUserBigramDictionary(mUserBigramDictionary); mSuggest.setUserDictionary(mUserDictionary); mSuggest.setContactsDictionary(mContactsDictionary); @@ -471,8 +444,7 @@ public class LatinIME extends InputMethodService mWordSeparators = mResources.getString(R.string.word_separators); mSentenceSeparators = mResources.getString(R.string.sentence_separators); - conf.locale = saveLocale; - orig.updateConfiguration(conf, orig.getDisplayMetrics()); + mSubtypeSwitcher.changeSystemLocale(savedLocale); } @Override @@ -484,9 +456,7 @@ public class LatinIME extends InputMethodService mContactsDictionary.close(); } unregisterReceiver(mReceiver); - if (VOICE_INSTALLED && mVoiceInput != null) { - mVoiceInput.destroy(); - } + mVoiceConnector.destroy(); LatinImeLogger.commit(); LatinImeLogger.onDestroy(); super.onDestroy(); @@ -494,51 +464,38 @@ 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 (mSubtypeSwitcher.isKeyboardMode()) + onKeyboardLanguageChanged(); + updateAutoTextEnabled(); + // 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(); + final int mode = mKeyboardSwitcher.getKeyboardMode(); + final EditorInfo attribute = getCurrentInputEditorInfo(); + final int imeOptions = (attribute != null) ? attribute.imeOptions : 0; + mKeyboardSwitcher.loadKeyboard(mode, imeOptions, + mVoiceConnector.isVoiceButtonEnabled(), + mVoiceConnector.isVoiceButtonOnPrimary()); } + mConfigurationChanging = true; super.onConfigurationChanged(conf); - if (mRecognizing) { - switchToRecognitionStatusView(); - } + mVoiceConnector.onConfigurationChanged(mConfigurationChanging); mConfigurationChanging = false; } @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); @@ -547,95 +504,89 @@ public class LatinIME extends InputMethodService return mCandidateViewContainer; } + private static boolean isPasswordVariation(int variation) { + return variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD + || variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD + || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD; + } + + private static boolean isEmailVariation(int variation) { + return variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS + || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; + } + @Override public void onStartInputView(EditorInfo attribute, boolean restarting) { - LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); + final KeyboardSwitcher switcher = mKeyboardSwitcher; + LatinKeyboardView inputView = switcher.getInputView(); + // In landscape mode, this method gets called without the input view being created. if (inputView == null) { return; } + mSubtypeSwitcher.updateParametersOnStartInputView(); + if (mRefreshKeyboardRequired) { mRefreshKeyboardRequired = false; - toggleLanguage(true, true); + onKeyboardLanguageChanged(); } - mKeyboardSwitcher.makeKeyboards(false); - TextEntryState.newSession(this); // 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; - } - - mEnableVoiceButton = shouldShowVoiceButton(makeFieldContext(), attribute); - final boolean enableVoiceButton = mEnableVoiceButton && mEnableVoice; - - mAfterVoiceInput = false; - mImmediatelyAfterVoiceInput = false; - mShowingVoiceSuggestions = false; - mVoiceInputHighlighted = false; + mVoiceConnector.resetVoiceStates(isPasswordVariation(variation)); mInputTypeNoAutoCorrect = false; mPredictionOn = false; mCompletionOn = false; mCompletions = null; - mCapsLock = false; mEnteredText = null; + final int mode; 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. + mode = KeyboardId.MODE_NUMBER; + break; case EditorInfo.TYPE_CLASS_PHONE: - mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_PHONE, - attribute.imeOptions, enableVoiceButton); + mode = KeyboardId.MODE_PHONE; 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 ) { + if (isPasswordVariation(variation)) { mPredictionOn = false; } - if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS + if (isEmailVariation(variation) || variation == EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME) { mAutoSpace = false; } else { mAutoSpace = true; } - if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) { + if (isEmailVariation(variation)) { mPredictionOn = false; - mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_EMAIL, - attribute.imeOptions, enableVoiceButton); + mode = KeyboardId.MODE_EMAIL; } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_URI) { mPredictionOn = false; - mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_URL, - attribute.imeOptions, enableVoiceButton); + mode = KeyboardId.MODE_URL; } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) { - mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_IM, - attribute.imeOptions, enableVoiceButton); + mode = KeyboardId.MODE_IM; } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_FILTER) { mPredictionOn = false; + mode = KeyboardId.MODE_TEXT; } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) { - mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_WEB, - attribute.imeOptions, enableVoiceButton); + mode = KeyboardId.MODE_WEB; // 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; } + } else { + mode = KeyboardId.MODE_TEXT; } // If NO_SUGGESTIONS is set, don't do prediction. @@ -654,18 +605,24 @@ public class LatinIME extends InputMethodService } break; default: - mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT, - attribute.imeOptions, enableVoiceButton); + mode = KeyboardId.MODE_TEXT; + break; } inputView.closing(); mComposing.setLength(0); mPredicting = false; mDeleteCount = 0; mJustAddedAutoSpace = false; - loadSettings(); - updateShiftKeyState(attribute); - setCandidatesViewShownInternal(isCandidateStripVisible() || mCompletionOn, + loadSettings(attribute); + if (mSubtypeSwitcher.isKeyboardMode()) { + switcher.loadKeyboard(mode, attribute.imeOptions, + mVoiceConnector.isVoiceButtonEnabled(), + mVoiceConnector.isVoiceButtonOnPrimary()); + switcher.updateShiftState(); + } + + setCandidatesViewShownInternal(isCandidateStripVisible(), false /* needsInputViewShown */ ); updateSuggestions(); @@ -676,21 +633,30 @@ public class LatinIME extends InputMethodService inputView.setPreviewEnabled(mPopupOn); inputView.setProximityCorrectionEnabled(true); - mPredictionOn = mPredictionOn && (mCorrectionMode > 0 || mShowSuggestions); + mPredictionOn = mPredictionOn && (mCorrectionMode > 0 || isSuggestionShown()); // If we just entered a text field, maybe it has some old text that requires correction checkReCorrectionOnStart(); checkTutorial(attribute.privateImeOptions); + inputView.setForeground(true); + + mVoiceConnector.onStartInputView(mKeyboardSwitcher.getInputView().getWindowToken()); + if (TRACE) Debug.startMethodTracing("/data/trace/latinime"); } private void checkReCorrectionOnStart() { - if (mReCorrectionEnabled && isPredictionOn()) { + if (!mReCorrectionEnabled) return; + + final InputConnection ic = getCurrentInputConnection(); + if (ic == null) return; + // There could be a pending composing span. Clean it up first. + ic.finishComposingText(); + + if (isSuggestionShown() && 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); @@ -701,7 +667,7 @@ public class LatinIME extends InputMethodService // Then look for possible corrections in a delayed fashion if (!TextUtils.isEmpty(et.text) && isCursorTouchingWord()) { - postUpdateOldSuggestions(); + mHandler.postUpdateOldSuggestions(); } } } @@ -713,17 +679,10 @@ public class LatinIME extends InputMethodService LatinImeLogger.commit(); onAutoCompletionStateChanged(false); - if (VOICE_INSTALLED && !mConfigurationChanging) { - if (mAfterVoiceInput) { - mVoiceInput.flushAllTextModificationCounters(); - mVoiceInput.logInputEnded(); - } - mVoiceInput.flushLogs(); - mVoiceInput.cancel(); - } - if (mKeyboardSwitcher.getInputView() != null) { - mKeyboardSwitcher.getInputView().closing(); - } + mVoiceConnector.flushVoiceInputLogs(mConfigurationChanging); + + KeyboardView inputView = mKeyboardSwitcher.getInputView(); + if (inputView != null) inputView.closing(); if (mAutoDictionary != null) mAutoDictionary.flushPendingWrites(); if (mUserBigramDictionary != null) mUserBigramDictionary.flushPendingWrites(); } @@ -731,21 +690,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.getInputView(); + 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; + mVoiceConnector.showPunctuationHintIfNecessary(); } @Override @@ -764,26 +719,22 @@ public class LatinIME extends InputMethodService + ", ce=" + candidatesEnd); } - if (mAfterVoiceInput) { - mVoiceInput.setCursorPos(newSelEnd); - mVoiceInput.setSelectionSpan(newSelEnd - newSelStart); - } + mVoiceConnector.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)) { + if ((((mComposing.length() > 0 && mPredicting) + || mVoiceConnector.isVoiceInputHighlighted()) && (newSelStart != candidatesEnd + || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart)) { mComposing.setLength(0); mPredicting = false; - postUpdateSuggestions(); + mHandler.postUpdateSuggestions(); TextEntryState.reset(); InputConnection ic = getCurrentInputConnection(); if (ic != null) { ic.finishComposingText(); } - mVoiceInputHighlighted = false; + mVoiceConnector.setVoiceInputHighlighted(false); } else if (!mPredicting && !mJustAccepted) { switch (TextEntryState.getState()) { case ACCEPTED_DEFAULT: @@ -795,33 +746,29 @@ public class LatinIME extends InputMethodService } } mJustAccepted = false; - postUpdateShiftKeyState(); + mHandler.postUpdateShiftKeyState(); // Make a note of the cursor position mLastSelectionStart = newSelStart; mLastSelectionEnd = newSelEnd; - if (mReCorrectionEnabled) { + if (mReCorrectionEnabled && isSuggestionShown()) { // Don't look for corrections if the keyboard is not visible - if (mKeyboardSwitcher != null && mKeyboardSwitcher.getInputView() != null - && mKeyboardSwitcher.getInputView().isShown()) { + if (mKeyboardSwitcher.isInputViewShown()) { // Check if we should go in or out of correction mode. - if (isPredictionOn() - && mJustRevertedSeparator == null + if (isPredictionOn() && !mJustReverted && (candidatesStart == candidatesEnd || newSelStart != oldSelStart || TextEntryState.isCorrecting()) - && (newSelStart < newSelEnd - 1 || (!mPredicting)) - && !mVoiceInputHighlighted) { + && (newSelStart < newSelEnd - 1 || (!mPredicting))) { if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) { - postUpdateOldSuggestions(); + mHandler.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(); + if (mCandidateView != null && !isShowingPunctuationList() + && !mCandidateView.isShowingAddToDictionaryHint()) { + setPunctuationSuggestions(); } } } @@ -870,18 +817,7 @@ public class LatinIME extends InputMethodService 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(); + mVoiceConnector.hideVoiceWindow(mConfigurationChanging); mWordHistory.clear(); super.hideWindow(); TextEntryState.endSession(); @@ -917,8 +853,8 @@ public class LatinIME extends InputMethodService private void setCandidatesViewShownInternal(boolean shown, boolean needsInputViewShown) { // TODO: Remove this if we support candidates with hard keyboard if (onEvaluateInputViewShown()) { - super.setCandidatesViewShown(shown && mKeyboardSwitcher.getInputView() != null - && (needsInputViewShown ? mKeyboardSwitcher.getInputView().isShown() : true)); + super.setCandidatesViewShown(shown + && (needsInputViewShown ? mKeyboardSwitcher.isInputViewShown() : true)); } } @@ -985,10 +921,9 @@ public class LatinIME extends InputMethodService if (mTutorial != null) { return true; } - LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); // Enable shift key and DPAD to do selections - if (inputView != null && inputView.isShown() - && inputView.isShifted()) { + if (mKeyboardSwitcher.isInputViewShown() + && mKeyboardSwitcher.isShiftedOrShiftLocked()) { event = new KeyEvent(event.getDownTime(), event.getEventTime(), event.getAction(), event.getKeyCode(), event.getRepeatCount(), event.getDeviceId(), event.getScanCode(), @@ -1002,30 +937,7 @@ public class LatinIME extends InputMethodService 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) { + public void commitTyped(InputConnection inputConnection) { if (mPredicting) { mPredicting = false; if (mComposing.length() > 0) { @@ -1040,27 +952,13 @@ public class LatinIME extends InputMethodService } } - 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 (mAutoCap && ic != null && ei != null && ei.inputType != EditorInfo.TYPE_NULL) { + return ic.getCursorCapsMode(ei.inputType) != 0; } - return caps; + return false; } private void swapPunctuationAndSpace() { @@ -1068,12 +966,13 @@ public class LatinIME extends InputMethodService if (ic == null) return; CharSequence lastTwo = ic.getTextBeforeCursor(2, 0); if (lastTwo != null && lastTwo.length() == 2 - && lastTwo.charAt(0) == KEYCODE_SPACE && isSentenceSeparator(lastTwo.charAt(1))) { + && lastTwo.charAt(0) == Keyboard.CODE_SPACE + && isSentenceSeparator(lastTwo.charAt(1))) { ic.beginBatchEdit(); ic.deleteSurroundingText(2, 0); ic.commitText(lastTwo.charAt(1) + " ", 1); ic.endBatchEdit(); - updateShiftKeyState(getCurrentInputEditorInfo()); + mKeyboardSwitcher.updateShiftState(); mJustAddedAutoSpace = true; } } @@ -1083,14 +982,14 @@ public class LatinIME extends InputMethodService 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) { + && lastThree.charAt(0) == Keyboard.CODE_PERIOD + && lastThree.charAt(1) == Keyboard.CODE_SPACE + && lastThree.charAt(2) == Keyboard.CODE_PERIOD) { ic.beginBatchEdit(); ic.deleteSurroundingText(3, 0); ic.commitText(" ..", 1); ic.endBatchEdit(); - updateShiftKeyState(getCurrentInputEditorInfo()); + mKeyboardSwitcher.updateShiftState(); } } @@ -1102,12 +1001,13 @@ public class LatinIME extends InputMethodService 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) { ic.beginBatchEdit(); ic.deleteSurroundingText(2, 0); ic.commitText(". ", 1); ic.endBatchEdit(); - updateShiftKeyState(getCurrentInputEditorInfo()); + mKeyboardSwitcher.updateShiftState(); mJustAddedAutoSpace = true; } } @@ -1120,8 +1020,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); } } @@ -1132,7 +1032,7 @@ 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); } } @@ -1141,7 +1041,7 @@ public class LatinIME extends InputMethodService mUserDictionary.addWord(word, 128); // Suggestion strip should be updated after the operation of adding word to the // user dictionary - postUpdateSuggestions(); + mHandler.postUpdateSuggestions(); return true; } @@ -1153,14 +1053,9 @@ public class LatinIME extends InputMethodService } } - private void showInputMethodPicker() { - ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)) - .showInputMethodPicker(); - } - - private void onOptionKeyPressed() { + private void onSettingsKeyPressed() { if (!isShowingOptionDialog()) { - if (LatinIMEUtil.hasMultipleEnabledIMEs(this)) { + if (LatinIMEUtil.hasMultipleEnabledIMEsOrSubtypes(mImm)) { showOptionsMenu(); } else { launchSettings(); @@ -1168,10 +1063,10 @@ public class LatinIME extends InputMethodService } } - private void onOptionKeyLongPressed() { + private void onSettingsKeyLongPressed() { if (!isShowingOptionDialog()) { - if (LatinIMEUtil.hasMultipleEnabledIMEs(this)) { - showInputMethodPicker(); + if (LatinIMEUtil.hasMultipleEnabledIMEsOrSubtypes(mImm)) { + mImm.showInputMethodPicker(); } else { launchSettings(); } @@ -1184,80 +1079,80 @@ public class LatinIME extends InputMethodService // Implementation of KeyboardViewListener + @Override public void onKey(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(); 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; - } - if (mKeyboardSwitcher.onKey(primaryCode)) { - changeKeyboardMode(); + case Keyboard.CODE_DELETE: + handleBackspace(); + mDeleteCount++; + 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 Keyboard.CODE_NEXT_LANGUAGE: + toggleLanguage(false, true); + break; + case Keyboard.CODE_PREV_LANGUAGE: + toggleLanguage(false, false); + break; + case Keyboard.CODE_CAPSLOCK: + switcher.toggleCapsLock(); + break; + case Keyboard.CODE_VOICE: /* was a button press, was not a swipe */ + mVoiceConnector.startListening(false, + mKeyboardSwitcher.getInputView().getWindowToken(), mConfigurationChanging); + break; + case Keyboard.CODE_TAB: + handleTab(); + break; + default: + if (primaryCode != Keyboard.CODE_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 + mJustReverted = false; } + switcher.onKey(primaryCode); // Reset after any single keystroke mEnteredText = null; } + @Override public void onText(CharSequence text) { - if (VOICE_INSTALLED && mVoiceInputHighlighted) { - commitVoiceInput(); - } + mVoiceConnector.commitVoiceInput(); InputConnection ic = getCurrentInputConnection(); if (ic == null) return; abortCorrection(false); @@ -1268,40 +1163,26 @@ public class LatinIME extends InputMethodService maybeRemovePreviousPeriod(text); ic.commitText(text, 1); ic.endBatchEdit(); - updateShiftKeyState(getCurrentInputEditorInfo()); - mJustRevertedSeparator = null; + mKeyboardSwitcher.updateShiftState(); + mJustReverted = false; mJustAddedAutoSpace = false; mEnteredText = text; } + @Override public void onCancel() { // User released a finger outside any key } private void handleBackspace() { - if (VOICE_INSTALLED && mVoiceInputHighlighted) { - mVoiceInput.incrementTextModificationDeleteCount( - mVoiceResults.candidates.get(0).toString().length()); - revertVoiceInput(); - return; - } + if (mVoiceConnector.logAndRevertVoiceInput()) return; boolean deleteChar = false; 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); - } - } + mVoiceConnector.handleBackspace(); if (mPredicting) { final int length = mComposing.length(); @@ -1312,14 +1193,14 @@ public class LatinIME extends InputMethodService if (mComposing.length() == 0) { mPredicting = false; } - postUpdateSuggestions(); + mHandler.postUpdateSuggestions(); } else { ic.deleteSurroundingText(1, 0); } } else { deleteChar = true; } - postUpdateShiftKeyState(); + mHandler.postUpdateShiftKeyState(); TextEntryState.backspace(); if (TextEntryState.getState() == TextEntryState.State.UNDO_COMMIT) { revertLastWord(deleteChar); @@ -1344,55 +1225,46 @@ public class LatinIME extends InputMethodService } } } - mJustRevertedSeparator = null; + mJustReverted = false; ic.endBatchEdit(); } - private void resetShift() { - handleShiftInternal(true); - } + private void handleTab() { + final int imeOptions = getCurrentInputEditorInfo().imeOptions; + final int navigationFlags = + EditorInfo.IME_FLAG_NAVIGATE_NEXT | EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; + if ((imeOptions & navigationFlags) == 0) { + 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 ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0 + && !isManualTemporaryUpperCase) { + ic.performEditorAction(EditorInfo.IME_ACTION_NEXT); + } else if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS) != 0 + && isManualTemporaryUpperCase) { + ic.performEditorAction(EditorInfo.IME_ACTION_PREVIOUS); } } private void abortCorrection(boolean force) { if (force || TextEntryState.isCorrecting()) { + TextEntryState.onAbortCorrection(); + setCandidatesViewShown(isCandidateStripVisible()); getCurrentInputConnection().finishComposingText(); clearSuggestions(); } } private void handleCharacter(int primaryCode, int[] keyCodes) { - if (VOICE_INSTALLED && mVoiceInputHighlighted) { - commitVoiceInput(); - } + mVoiceConnector.handleCharacter(); - if (mAfterVoiceInput) { - // Assume input length is 1. This assumption fails for smiley face insertions. - mVoiceInput.incrementTextModificationInsertCount(1); - } if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isCorrecting()) { abortCorrection(false); } @@ -1405,13 +1277,14 @@ public class LatinIME extends InputMethodService mWord.reset(); } } - if (mKeyboardSwitcher.getInputView().isShifted()) { + 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)) { + if (switcher.isAlphabetMode() && Character.isLowerCase(primaryCode)) { int upperCaseCode = Character.toUpperCase(primaryCode); if (upperCaseCode != primaryCode) { primaryCode = upperCaseCode; @@ -1424,9 +1297,8 @@ public class LatinIME extends InputMethodService } } if (mPredicting) { - if (mKeyboardSwitcher.getInputView().isShifted() - && mKeyboardSwitcher.isAlphabetMode() - && mComposing.length() == 0) { + if (mComposing.length() == 0 && switcher.isAlphabetMode() + && switcher.isShiftedOrShiftLocked()) { mWord.setFirstCharCapitalized(true); } mComposing.append((char) primaryCode); @@ -1435,33 +1307,25 @@ public class LatinIME extends InputMethodService 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); } - updateShiftKeyState(getCurrentInputEditorInfo()); + switcher.updateShiftState(); if (LatinIME.PERF_DEBUG) measureCps(); TextEntryState.typedCharacter((char) primaryCode, isWordSeparator(primaryCode)); } 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); - } + mVoiceConnector.handleSeparator(); // Should dismiss the "Touch again to save" message when handling separator if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) { - postUpdateSuggestions(); + mHandler.postUpdateSuggestions(); } boolean pickedDefault = false; @@ -1476,21 +1340,18 @@ public class LatinIME extends InputMethodService // 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)) { + if (mAutoCorrectOn && primaryCode != '\'' && !mJustReverted) { pickedDefault = pickDefaultSuggestion(); // Picked the suggestion by the space key. We consider this // as "added an auto space". - if (primaryCode == KEYCODE_SPACE) { + if (primaryCode == Keyboard.CODE_SPACE) { mJustAddedAutoSpace = true; } } else { commitTyped(ic); } } - if (mJustAddedAutoSpace && primaryCode == KEYCODE_ENTER) { + if (mJustAddedAutoSpace && primaryCode == Keyboard.CODE_ENTER) { removeTrailingSpace(); mJustAddedAutoSpace = false; } @@ -1499,21 +1360,21 @@ public class LatinIME extends InputMethodService // Handle the case of ". ." -> " .." with auto-space if necessary // before changing the TextEntryState. if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED - && primaryCode == KEYCODE_PERIOD) { + && primaryCode == Keyboard.CODE_PERIOD) { reswapPeriodAndSpace(); } TextEntryState.typedCharacter((char) primaryCode, true); if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED - && primaryCode != KEYCODE_ENTER) { + && primaryCode != Keyboard.CODE_ENTER) { swapPunctuationAndSpace(); - } else if (isPredictionOn() && primaryCode == KEYCODE_SPACE) { + } else if (isPredictionOn() && primaryCode == Keyboard.CODE_SPACE) { doubleSpace(); } if (pickedDefault) { TextEntryState.backToAcceptedDefault(mWord.getTypedWord()); } - updateShiftKeyState(getCurrentInputEditorInfo()); + mKeyboardSwitcher.updateShiftState(); if (ic != null) { ic.endBatchEdit(); } @@ -1521,16 +1382,11 @@ public class LatinIME extends InputMethodService private void handleClose() { commitTyped(getCurrentInputConnection()); - if (VOICE_INSTALLED & mRecognizing) { - mVoiceInput.cancel(); - } + mVoiceConnector.handleClose(); requestHideSelf(0); - if (mKeyboardSwitcher != null) { - LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); - if (inputView != null) { - inputView.closing(); - } - } + LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); + if (inputView != null) + inputView.closing(); TextEntryState.endSession(); } @@ -1551,216 +1407,62 @@ public class LatinIME extends InputMethodService mWordHistory.add(entry); } - private void postUpdateSuggestions() { - mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS); - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SUGGESTIONS), 100); - } - - private void postUpdateOldSuggestions() { - mHandler.removeMessages(MSG_UPDATE_OLD_SUGGESTIONS); - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_OLD_SUGGESTIONS), 300); - } - private boolean isPredictionOn() { return mPredictionOn; } - private boolean isCandidateStripVisible() { - return isPredictionOn() && mShowSuggestions; + private boolean isShowingPunctuationList() { + return mSuggestPuncList.equals(mCandidateView.getSuggestions()); } - public void onCancelVoice() { - if (mRecognizing) { - switchToKeyboardView(); - } + private boolean isSuggestionShown() { + return (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_VALUE) + || (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE + && mOrientation == Configuration.ORIENTATION_PORTRAIT); } - private void switchToKeyboardView() { - mHandler.post(new Runnable() { - public void run() { - mRecognizing = false; - if (mKeyboardSwitcher.getInputView() != null) { - setInputView(mKeyboardSwitcher.getInputView()); - } - setCandidatesViewShown(true); - updateInputViewShown(); - postUpdateSuggestions(); - }}); + private boolean isCandidateStripVisible() { + boolean forceVisible = mCandidateView.isShowingAddToDictionaryHint() + || TextEntryState.isCorrecting(); + return forceVisible || (isSuggestionShown() + && (isPredictionOn() || mCompletionOn || isShowingPunctuationList())); } - private void switchToRecognitionStatusView() { - final boolean configChanged = mConfigurationChanging; + public void switchToKeyboardView() { mHandler.post(new Runnable() { + @Override 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); + if (DEBUG) { + Log.d(TAG, "Switch to keyboard view."); } - setInputView(v); - updateInputViewShown(); - if (configChanged) { - mVoiceInput.onConfigurationChanged(); + View v = mKeyboardSwitcher.getInputView(); + 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); + } + setInputView(v); } - }}); - } - - 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(); + setCandidatesViewShown(isCandidateStripVisible()); + updateInputViewShown(); + mHandler.postUpdateSuggestions(); } }); - - 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)); } - 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()); - } - nBest.add(c); - } - - 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); - } - - private void clearSuggestions() { + public void clearSuggestions() { setSuggestions(null, false, false, false); } - private void setSuggestions( + public void setSuggestions( List<CharSequence> suggestions, boolean completions, boolean typedWordValid, boolean haveMinimalSuggestion) { - if (mIsShowingHint) { + if (mVoiceConnector.getAndResetIsShowingHint()) { setCandidatesView(mCandidateViewContainer); - mIsShowingHint = false; } if (mCandidateView != null) { @@ -1769,17 +1471,17 @@ public class LatinIME extends InputMethodService } } - private void updateSuggestions() { - LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); - ((LatinKeyboard) inputView.getKeyboard()).setPreferredLetters(null); + public void updateSuggestions() { + mKeyboardSwitcher.setPreferredLetters(null); // Check if we have a suggestion engine attached. - if ((mSuggest == null || !isPredictionOn()) && !mVoiceInputHighlighted) { + if ((mSuggest == null || !isPredictionOn()) + && !mVoiceConnector.isVoiceInputHighlighted()) { return; } if (!mPredicting) { - setNextSuggestions(); + setPunctuationSuggestions(); return; } showSuggestions(mWord); @@ -1792,8 +1494,8 @@ public class LatinIME extends InputMethodService } private void showCorrections(WordAlternatives alternatives) { + mKeyboardSwitcher.setPreferredLetters(null); List<CharSequence> stringList = alternatives.getAlternatives(); - ((LatinKeyboard) mKeyboardSwitcher.getInputView().getKeyboard()).setPreferredLetters(null); showSuggestions(stringList, alternatives.getOriginalWord(), false, false); } @@ -1808,12 +1510,10 @@ public class LatinIME extends InputMethodService // Log.d("LatinIME","Suggest Total Time - " + (stopTime - startTime)); int[] nextLettersFrequencies = mSuggest.getNextLettersFrequencies(); + mKeyboardSwitcher.setPreferredLetters(nextLettersFrequencies); - ((LatinKeyboard) mKeyboardSwitcher.getInputView().getKeyboard()).setPreferredLetters( - nextLettersFrequencies); - - boolean correctionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasMinimalCorrection(); - //|| mCorrectionMode == mSuggest.CORRECTION_FULL; + boolean correctionAvailable = !mInputTypeNoAutoCorrect && !mJustReverted + && mSuggest.hasMinimalCorrection(); CharSequence typedWord = word.getTypedWord(); // If we're in basic correct boolean typedWordValid = mSuggest.isValidWord(typedWord) || @@ -1842,13 +1542,13 @@ public class LatinIME extends InputMethodService } else { mBestWord = null; } - setCandidatesViewShown(isCandidateStripVisible() || mCompletionOn); + setCandidatesViewShown(isCandidateStripVisible()); } private boolean pickDefaultSuggestion() { // 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) { @@ -1864,14 +1564,8 @@ public class LatinIME extends InputMethodService } public void pickSuggestionManually(int index, CharSequence suggestion) { - if (mAfterVoiceInput && mShowingVoiceSuggestions) mVoiceInput.logNBestChoose(index); List<CharSequence> suggestions = mCandidateView.getSuggestions(); - - if (mAfterVoiceInput && !mShowingVoiceSuggestions) { - mVoiceInput.flushAllTextModificationCounters(); - // send this intent AFTER logging any prior aggregated edits. - mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.length()); - } + mVoiceConnector.flushAndLogAllTextModificationCounters(index, suggestion, mWordSeparators); final boolean correcting = TextEntryState.isCorrecting(); InputConnection ic = getCurrentInputConnection(); @@ -1888,7 +1582,7 @@ public class LatinIME extends InputMethodService if (mCandidateView != null) { mCandidateView.clear(); } - updateShiftKeyState(getCurrentInputEditorInfo()); + mKeyboardSwitcher.updateShiftState(); if (ic != null) { ic.endBatchEdit(); } @@ -1903,8 +1597,8 @@ public class LatinIME extends InputMethodService LatinImeLogger.logOnManualSuggestion( "", suggestion.toString(), index, suggestions); final char primaryCode = suggestion.charAt(0); - onKey(primaryCode, new int[]{primaryCode}, LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE, - LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE); + onKey(primaryCode, new int[]{primaryCode}, KeyboardView.NOT_A_TOUCH_COORDINATE, + KeyboardView.NOT_A_TOUCH_COORDINATE); if (ic != null) { ic.endBatchEdit(); } @@ -1935,13 +1629,13 @@ public class LatinIME extends InputMethodService // 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(); + TextEntryState.typedCharacter((char) Keyboard.CODE_SPACE, true); + setPunctuationSuggestions(); } else 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(); + mHandler.postUpdateOldSuggestions(); } if (showingAddToDictionaryHint) { mCandidateView.showAddToDictionaryHint(suggestion); @@ -1951,27 +1645,6 @@ 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. @@ -1981,61 +1654,23 @@ public class LatinIME extends InputMethodService * word. */ private void pickSuggestion(CharSequence suggestion, boolean correcting) { - LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); - if (mCapsLock) { - suggestion = suggestion.toString().toUpperCase(); - } else if (preferCapitalization() - || (mKeyboardSwitcher.isAlphabetMode() - && inputView.isShifted())) { - suggestion = suggestion.toString().toUpperCase().charAt(0) - + suggestion.subSequence(1, suggestion.length()).toString(); - } + KeyboardSwitcher switcher = mKeyboardSwitcher; + if (!switcher.isKeyboardAvailable()) + return; InputConnection ic = getCurrentInputConnection(); if (ic != null) { - rememberReplacedWord(suggestion); + mVoiceConnector.rememberReplacedWord(suggestion, mWordSeparators); ic.commitText(suggestion, 1); } saveWordInHistory(suggestion); mPredicting = false; mCommittedLength = suggestion.length(); - ((LatinKeyboard) inputView.getKeyboard()).setPreferredLetters(null); + switcher.setPreferredLetters(null); // If we just corrected a word, then don't show punctuations if (!correcting) { - setNextSuggestions(); + setPunctuationSuggestions(); } - 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))) { - for (int i = 0; i < suggestions.size(); i++) { - String origSugg = (String) suggestions.get(i); - String capsSugg = origSugg.toUpperCase().charAt(0) - + origSugg.subSequence(1, origSugg.length()).toString(); - suggestions.set(i, capsSugg); - } - } - setSuggestions(suggestions, false, true, true); - setCandidatesViewShown(true); - return true; - } - return false; + switcher.updateShiftState(); } /** @@ -2086,7 +1721,7 @@ public class LatinIME extends InputMethodService } private void setOldSuggestions() { - mShowingVoiceSuggestions = false; + mVoiceConnector.setShowingVoiceSuggestions(false); if (mCandidateView != null && mCandidateView.isShowingAddToDictionaryHint()) { return; } @@ -2100,7 +1735,8 @@ public class LatinIME extends InputMethodService if (touching != null && touching.word.length() > 1) { ic.beginBatchEdit(); - if (!applyVoiceAlternatives(touching) && !applyTypedAlternatives(touching)) { + if (!mVoiceConnector.applyVoiceAlternatives(touching) + && !applyTypedAlternatives(touching)) { abortCorrection(true); } else { TextEntryState.selectedForCorrection(); @@ -2110,14 +1746,15 @@ public class LatinIME extends InputMethodService ic.endBatchEdit(); } else { abortCorrection(true); - setNextSuggestions(); // Show the punctuation suggestions list + setPunctuationSuggestions(); // Show the punctuation suggestions list } } else { abortCorrection(true); } } - private void setNextSuggestions() { + private void setPunctuationSuggestions() { + setCandidatesViewShown(isCandidateStripVisible()); setSuggestions(mSuggestPuncList, false, false, false); } @@ -2188,7 +1825,7 @@ public class LatinIME extends InputMethodService if (!mPredicting && length > 0) { final InputConnection ic = getCurrentInputConnection(); mPredicting = true; - mJustRevertedSeparator = ic.getTextBeforeCursor(1, 0); + mJustReverted = true; if (deleteChar) ic.deleteSurroundingText(1, 0); int toDelete = mCommittedLength; CharSequence toTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0); @@ -2199,10 +1836,10 @@ public class LatinIME extends InputMethodService ic.deleteSurroundingText(toDelete, 0); ic.setComposingText(mComposing, 1); TextEntryState.backspace(); - postUpdateSuggestions(); + mHandler.postUpdateSuggestions(); } else { sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); - mJustRevertedSeparator = null; + mJustReverted = false; } } @@ -2220,8 +1857,8 @@ public class LatinIME extends InputMethodService } private void sendSpace() { - sendKeyChar((char)KEYCODE_SPACE); - updateShiftKeyState(getCurrentInputEditorInfo()); + sendKeyChar((char)Keyboard.CODE_SPACE); + mKeyboardSwitcher.updateShiftState(); //onKey(KEY_SPACE[0], KEY_SPACE); } @@ -2229,30 +1866,33 @@ public class LatinIME extends InputMethodService return mWord.isFirstCharCapitalized(); } + // Notify that Language has been changed and toggleLanguage will update KeyboaredID according + // to new Language. + public void onKeyboardLanguageChanged() { + toggleLanguage(true, true); + } + + // "reset" and "next" are used only for USE_SPACEBAR_LANGUAGE_SWITCHER. private void toggleLanguage(boolean reset, boolean next) { - if (reset) { - mLanguageSwitcher.reset(); - } else { - if (next) { - mLanguageSwitcher.next(); - } else { - mLanguageSwitcher.prev(); - } + if (SubtypeSwitcher.USE_SPACEBAR_LANGUAGE_SWITCHER) { + mSubtypeSwitcher.toggleLanguage(reset, next); } - int currentKeyboardMode = mKeyboardSwitcher.getKeyboardMode(); - reloadKeyboards(); - mKeyboardSwitcher.makeKeyboards(true); - mKeyboardSwitcher.setKeyboardMode(currentKeyboardMode, 0, - mEnableVoiceButton && mEnableVoice); - initSuggest(mLanguageSwitcher.getInputLanguage()); - mLanguageSwitcher.persist(); - updateShiftKeyState(getCurrentInputEditorInfo()); + // Reload keyboard because the current language has been changed. + KeyboardSwitcher switcher = mKeyboardSwitcher; + final int mode = switcher.getKeyboardMode(); + final EditorInfo attribute = getCurrentInputEditorInfo(); + final int imeOptions = (attribute != null) ? attribute.imeOptions : 0; + switcher.loadKeyboard(mode, imeOptions, mVoiceConnector.isVoiceButtonEnabled(), + mVoiceConnector.isVoiceButtonOnPrimary()); + initSuggest(); + switcher.updateShiftState(); } + @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + mSubtypeSwitcher.onSharedPreferenceChanged(sharedPreferences, 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, @@ -2260,79 +1900,58 @@ public class LatinIME extends InputMethodService } } + @Override public void swipeRight() { if (LatinKeyboardView.DEBUG_AUTO_PLAY) { - ClipboardManager cm = ((ClipboardManager)getSystemService(CLIPBOARD_SERVICE)); - CharSequence text = cm.getText(); + CharSequence text = ((android.text.ClipboardManager)getSystemService( + CLIPBOARD_SERVICE)).getText(); if (!TextUtils.isEmpty(text)) { mKeyboardSwitcher.getInputView().startPlaying(text.toString()); } } } + @Override public void swipeLeft() { } + @Override public void swipeDown() { handleClose(); } + @Override public void swipeUp() { - //launchSettings(); } + @Override public void onPress(int primaryCode) { 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) { - mSymbolKeyState.onPress(); - changeKeyboardMode(); + KeyboardSwitcher switcher = mKeyboardSwitcher; + final boolean distinctMultiTouch = switcher.hasDistinctMultitouch(); + if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) { + switcher.onPressShift(); + } else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { + switcher.onPressSymbol(); } else { - mShiftKeyState.onOtherKeyPressed(); - mSymbolKeyState.onOtherKeyPressed(); + switcher.onOtherKeyPressed(); } } + @Override public void onRelease(int primaryCode) { + 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) { - if (mSymbolKeyState.isMomentary()) - changeKeyboardMode(); - mSymbolKeyState.onRelease(); + switcher.keyReleased(); + final boolean distinctMultiTouch = switcher.hasDistinctMultitouch(); + if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) { + switcher.onReleaseShift(); + } 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 private BroadcastReceiver mReceiver = new BroadcastReceiver() { @@ -2365,13 +1984,13 @@ public class LatinIME extends InputMethodService // 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; } @@ -2379,12 +1998,13 @@ public class LatinIME extends InputMethodService } } - private void vibrate() { + public void vibrate() { if (!mVibrateOn) { return; } - if (mKeyboardSwitcher.getInputView() != null) { - mKeyboardSwitcher.getInputView().performHapticFeedback( + LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); + if (inputView != null) { + inputView.performHapticFeedback( HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } @@ -2393,7 +2013,7 @@ public class LatinIME extends InputMethodService private void checkTutorial(String privateImeOptions) { if (privateImeOptions == null) return; if (privateImeOptions.equals("com.android.setupwizard:ShowTutorial")) { - if (mTutorial == null) startTutorial(); + if (mTutorial == null) mHandler.startTutorial(); } else if (privateImeOptions.equals("com.android.setupwizard:HideTutorial")) { if (mTutorial != null) { if (mTutorial.close()) { @@ -2403,24 +2023,23 @@ public class LatinIME extends InputMethodService } } - private void startTutorial() { - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_TUTORIAL), 500); - } - - /* package */ void tutorialDone() { + // Tutorial.TutorialListener + @Override + public void onTutorialDone() { + sendDownUpKeyEvents(-1); // Inform the setupwizard that tutorial is in last bubble mTutorial = null; } - /* package */ void promoteToUserDictionary(String word, int frequency) { + public 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() { + public boolean getPopupOn() { return mPopupOn; } @@ -2438,11 +2057,22 @@ public class LatinIME extends InputMethodService } } - 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.setAutoTextEnabled(mQuickFixes + && SubtypeSwitcher.getInstance().isSystemLanguageSameAsInputLanguage()); + } + + private void updateSuggestionVisibility(SharedPreferences prefs) { + final String suggestionVisiblityStr = prefs.getString( + PREF_SHOW_SUGGESTIONS_SETTING, mResources.getString( + R.string.prefs_suggestion_visibility_default_value)); + for (int visibility : SUGGESTION_VISIBILITY_VALUE_ARRAY) { + if (suggestionVisiblityStr.equals(mResources.getString(visibility))) { + mSuggestionVisibility = visibility; + break; + } + } } protected void launchSettings() { @@ -2453,7 +2083,7 @@ public class LatinIME extends InputMethodService launchSettings(LatinIMEDebugSettings.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); @@ -2461,55 +2091,78 @@ public class LatinIME extends InputMethodService startActivity(intent); } - private void loadSettings() { + private void loadSettings(EditorInfo attribute) { // 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, + final SharedPreferences prefs = mPrefs; + Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); + mVibrateOn = vibrator != null && vibrator.hasVibrator() + && prefs.getBoolean(LatinIMESettings.PREF_VIBRATE_ON, false); + mSoundOn = prefs.getBoolean(PREF_SOUND_ON, false); + mPopupOn = prefs.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); + mAutoCap = prefs.getBoolean(PREF_AUTO_CAP, true); + mQuickFixes = prefs.getBoolean(PREF_QUICK_FIXES, true); + + mAutoCorrectEnabled = isAutoCorrectEnabled(prefs); + mBigramSuggestionEnabled = mAutoCorrectEnabled && isBigramSuggestionEnabled(prefs); + loadAndSetAutoCompletionThreshold(prefs); + + mVoiceConnector.loadSettings(attribute, prefs); + + updateCorrectionMode(); + updateAutoTextEnabled(); + updateSuggestionVisibility(prefs); + SubtypeSwitcher.getInstance().loadSettings(); + } + + /** + * load Auto completion threshold from SharedPreferences, + * and modify mSuggest's threshold. + */ + private void loadAndSetAutoCompletionThreshold(SharedPreferences sp) { + // When mSuggest is not initialized, cannnot modify mSuggest's threshold. + if (mSuggest == null) return; + // When auto completion setting is turned off, the threshold is ignored. + if (!isAutoCorrectEnabled(sp)) return; + + final String currentAutoCompletionSetting = sp.getString(PREF_AUTO_COMPLETION_THRESHOLD, + mResources.getString(R.string.auto_completion_threshold_mode_value_modest)); + final String[] autoCompletionThresholdValues = mResources.getStringArray( + R.array.auto_complete_threshold_values); + // When autoCompletionThreshold is greater than 1.0, + // auto completion is virtually turned off. + double autoCompletionThreshold = Double.MAX_VALUE; + try { + final int arrayIndex = Integer.valueOf(currentAutoCompletionSetting); + if (arrayIndex >= 0 && arrayIndex < autoCompletionThresholdValues.length) { + autoCompletionThreshold = Double.parseDouble( + autoCompletionThresholdValues[arrayIndex]); } - mEnableVoice = enableVoice; - mVoiceOnPrimary = voiceOnPrimary; + } catch (NumberFormatException e) { + // Whenever the threshold settings are correct, + // never come here. + autoCompletionThreshold = Double.MAX_VALUE; + Log.w(TAG, "Cannot load auto completion threshold setting." + + " currentAutoCompletionSetting: " + currentAutoCompletionSetting + + ", autoCompletionThresholdValues: " + + Arrays.toString(autoCompletionThresholdValues)); } - 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); + // TODO: This should be refactored : + // setAutoCompleteThreshold should be called outside of this method. + mSuggest.setAutoCompleteThreshold(autoCompletionThreshold); + } + + private boolean isAutoCorrectEnabled(SharedPreferences sp) { + final String currentAutoCompletionSetting = sp.getString(PREF_AUTO_COMPLETION_THRESHOLD, + mResources.getString(R.string.auto_completion_threshold_mode_value_modest)); + final String autoCompletionOff = mResources.getString( + R.string.auto_completion_threshold_mode_value_off); + return !currentAutoCompletionSetting.equals(autoCompletionOff); + } + + private boolean isBigramSuggestionEnabled(SharedPreferences sp) { + // TODO: Define default value instead of 'true'. + return sp.getBoolean(PREF_BIGRAM_SUGGESTIONS, true); } private void initSuggestPuncList() { @@ -2537,6 +2190,7 @@ public class LatinIME extends InputMethodService itemInputMethod, itemSettings}, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface di, int position) { di.dismiss(); switch (position) { @@ -2544,8 +2198,7 @@ public class LatinIME extends InputMethodService launchSettings(); break; case POS_METHOD: - ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)) - .showInputMethodPicker(); + mImm.showInputMethodPicker(); break; } } @@ -2561,22 +2214,6 @@ public class LatinIME extends InputMethodService mOptionsDialog.show(); } - private 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); @@ -2584,7 +2221,6 @@ 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(" mCorrectionMode=" + mCorrectionMode); @@ -2619,4 +2255,9 @@ public class LatinIME extends InputMethodService public void onAutoCompletionStateChanged(boolean isAutoCompletion) { mKeyboardSwitcher.onAutoCompletionStateChanged(isAutoCompletion); } + + @Override + public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) { + SubtypeSwitcher.getInstance().updateSubtype(subtype); + } } diff --git a/java/src/com/android/inputmethod/latin/LatinIMEDebugSettings.java b/java/src/com/android/inputmethod/latin/LatinIMEDebugSettings.java index cba1a0af9..68738eca1 100644 --- a/java/src/com/android/inputmethod/latin/LatinIMEDebugSettings.java +++ b/java/src/com/android/inputmethod/latin/LatinIMEDebugSettings.java @@ -43,6 +43,7 @@ public class LatinIMEDebugSettings extends PreferenceActivity updateDebugMode(); } + @Override public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { if (key.equals(DEBUG_MODE_KEY)) { if (mDebugMode != null) { diff --git a/java/src/com/android/inputmethod/latin/LatinIMESettings.java b/java/src/com/android/inputmethod/latin/LatinIMESettings.java index ffff33da2..3aa89dd59 100644 --- a/java/src/com/android/inputmethod/latin/LatinIMESettings.java +++ b/java/src/com/android/inputmethod/latin/LatinIMESettings.java @@ -16,8 +16,8 @@ package com.android.inputmethod.latin; -import java.util.ArrayList; -import java.util.Locale; +import com.android.inputmethod.voice.VoiceIMEConnector; +import com.android.inputmethod.voice.VoiceInputLogger; import android.app.AlertDialog; import android.app.Dialog; @@ -25,16 +25,19 @@ import android.app.backup.BackupManager; import android.content.DialogInterface; import android.content.SharedPreferences; import android.os.Bundle; +import android.os.Vibrator; import android.preference.CheckBoxPreference; import android.preference.ListPreference; import android.preference.PreferenceActivity; import android.preference.PreferenceGroup; import android.speech.SpeechRecognizer; import android.text.AutoText; +import android.text.TextUtils; +import android.text.method.LinkMovementMethod; import android.util.Log; +import android.widget.TextView; -import com.android.inputmethod.voice.SettingsUtil; -import com.android.inputmethod.voice.VoiceInputLogger; +import java.util.Locale; public class LatinIMESettings extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener, @@ -43,7 +46,10 @@ public class LatinIMESettings extends PreferenceActivity private static final String QUICK_FIXES_KEY = "quick_fixes"; private static final String PREDICTION_SETTINGS_KEY = "prediction_settings"; private static final String VOICE_SETTINGS_KEY = "voice_mode"; - /* package */ static final String PREF_SETTINGS_KEY = "settings_key"; + private static final String PREF_AUTO_COMPLETION_THRESHOLD = "auto_completion_threshold"; + private static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion"; + public static final String PREF_SETTINGS_KEY = "settings_key"; + /* package */ static final String PREF_VIBRATE_ON = "vibrate_on"; private static final String TAG = "LatinIMESettings"; @@ -53,13 +59,23 @@ public class LatinIMESettings extends PreferenceActivity private CheckBoxPreference mQuickFixes; private ListPreference mVoicePreference; private ListPreference mSettingsKeyPreference; + private ListPreference mAutoCompletionThreshold; + private CheckBoxPreference mBigramSuggestion; private boolean mVoiceOn; + private AlertDialog mDialog; + private VoiceInputLogger mLogger; private boolean mOkClicked = false; private String mVoiceModeOff; + private void ensureConsistencyOfAutoCompletionSettings() { + final String autoCompletionOff = getResources().getString( + R.string.auto_completion_threshold_mode_value_off); + final String currentSetting = mAutoCompletionThreshold.getValue(); + mBigramSuggestion.setEnabled(!currentSetting.equals(autoCompletionOff)); + } @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -73,6 +89,28 @@ public class LatinIMESettings extends PreferenceActivity mVoiceModeOff = getString(R.string.voice_mode_off); mVoiceOn = !(prefs.getString(VOICE_SETTINGS_KEY, mVoiceModeOff).equals(mVoiceModeOff)); mLogger = VoiceInputLogger.getLogger(this); + + mAutoCompletionThreshold = (ListPreference) findPreference(PREF_AUTO_COMPLETION_THRESHOLD); + mBigramSuggestion = (CheckBoxPreference) findPreference(PREF_BIGRAM_SUGGESTIONS); + ensureConsistencyOfAutoCompletionSettings(); + + final boolean showSettingsKeyOption = getResources().getBoolean( + R.bool.config_enable_show_settings_key_option); + if (!showSettingsKeyOption) { + getPreferenceScreen().removePreference(mSettingsKeyPreference); + } + + final boolean showVoiceKeyOption = getResources().getBoolean( + R.bool.config_enable_show_voice_key_option); + if (!showVoiceKeyOption) { + getPreferenceScreen().removePreference(mVoicePreference); + } + + Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE); + if (vibrator == null || !vibrator.hasVibrator()) { + getPreferenceScreen().removePreference( + getPreferenceScreen().findPreference(PREF_VIBRATE_ON)); + } } @Override @@ -83,7 +121,7 @@ public class LatinIMESettings extends PreferenceActivity ((PreferenceGroup) findPreference(PREDICTION_SETTINGS_KEY)) .removePreference(mQuickFixes); } - if (!LatinIME.VOICE_INSTALLED + if (!VoiceIMEConnector.VOICE_INSTALLED || !SpeechRecognizer.isRecognitionAvailable(this)) { getPreferenceScreen().removePreference(mVoicePreference); } else { @@ -99,6 +137,7 @@ public class LatinIMESettings extends PreferenceActivity super.onDestroy(); } + @Override public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { (new BackupManager(this)).dataChanged(); // If turning on voice input, show dialog @@ -108,6 +147,7 @@ public class LatinIMESettings extends PreferenceActivity showVoiceConfirmation(); } } + ensureConsistencyOfAutoCompletionSettings(); mVoiceOn = !(prefs.getString(VOICE_SETTINGS_KEY, mVoiceModeOff).equals(mVoiceModeOff)); updateVoiceModeSummary(); updateSettingsKeySummary(); @@ -122,6 +162,13 @@ public class LatinIMESettings extends PreferenceActivity private void showVoiceConfirmation() { mOkClicked = false; showDialog(VOICE_INPUT_CONFIRM_DIALOG); + // Make URL in the dialog message clickable + if (mDialog != null) { + TextView textView = (TextView) mDialog.findViewById(android.R.id.message); + if (textView != null) { + textView.setMovementMethod(LinkMovementMethod.getInstance()); + } + } } private void updateVoiceModeSummary() { @@ -135,6 +182,7 @@ public class LatinIMESettings extends PreferenceActivity switch (id) { case VOICE_INPUT_CONFIRM_DIALOG: DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { if (whichButton == DialogInterface.BUTTON_NEGATIVE) { mVoicePreference.setValue(mVoiceModeOff); @@ -154,27 +202,23 @@ public class LatinIMESettings extends PreferenceActivity // Get the current list of supported locales and check the current locale against // that list, to decide whether to put a warning that voice input will not work in // the current language as part of the pop-up confirmation dialog. - String supportedLocalesString = SettingsUtil.getSettingsString( - getContentResolver(), - SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES, - LatinIME.DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES); - ArrayList<String> voiceInputSupportedLocales = - LatinIME.newArrayList(supportedLocalesString.split("\\s+")); - boolean localeSupported = voiceInputSupportedLocales.contains( + boolean localeSupported = SubtypeSwitcher.getInstance().isVoiceSupported( Locale.getDefault().toString()); + final CharSequence message; if (localeSupported) { - String message = getString(R.string.voice_warning_may_not_understand) + "\n\n" + - getString(R.string.voice_hint_dialog_message); - builder.setMessage(message); + message = TextUtils.concat( + getText(R.string.voice_warning_may_not_understand), "\n\n", + getText(R.string.voice_hint_dialog_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_hint_dialog_message); - builder.setMessage(message); + message = TextUtils.concat( + getText(R.string.voice_warning_locale_not_supported), "\n\n", + getText(R.string.voice_warning_may_not_understand), "\n\n", + getText(R.string.voice_hint_dialog_message)); } - + builder.setMessage(message); AlertDialog dialog = builder.create(); + mDialog = dialog; dialog.setOnDismissListener(this); mLogger.settingsWarningDialogShown(); return dialog; @@ -184,6 +228,7 @@ public class LatinIMESettings extends PreferenceActivity } } + @Override public void onDismiss(DialogInterface dialog) { mLogger.settingsWarningDialogDismissed(); if (!mOkClicked) { diff --git a/java/src/com/android/inputmethod/latin/LatinIMEUtil.java b/java/src/com/android/inputmethod/latin/LatinIMEUtil.java index 85ecaee50..f508b9ab8 100644 --- a/java/src/com/android/inputmethod/latin/LatinIMEUtil.java +++ b/java/src/com/android/inputmethod/latin/LatinIMEUtil.java @@ -16,12 +16,24 @@ package com.android.inputmethod.latin; -import android.view.inputmethod.InputMethodManager; - -import android.content.Context; +import android.inputmethodservice.InputMethodService; import android.os.AsyncTask; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Process; import android.text.format.DateUtils; import android.util.Log; +import android.view.inputmethod.InputMethodManager; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Date; public class LatinIMEUtil { @@ -76,9 +88,11 @@ public class LatinIMEUtil { } } - public static boolean hasMultipleEnabledIMEs(Context context) { - return ((InputMethodManager) context.getSystemService( - Context.INPUT_METHOD_SERVICE)).getEnabledInputMethodList().size() > 1; + public static boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm) { + return imm.getEnabledInputMethodList().size() > 1 + // imm.getEnabledInputMethodSubtypeList(null) will return the current IME's enabled input + // method subtype (The current IME should be LatinIME.) + || imm.getEnabledInputMethodSubtypeList(null).size() > 1; } /* package */ static class RingCharBuffer { @@ -86,8 +100,9 @@ public class LatinIMEUtil { private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC'; private static final int INVALID_COORDINATE = -2; /* package */ static final int BUFSIZE = 20; - private Context mContext; + private InputMethodService mContext; private boolean mEnabled = false; + private boolean mUsabilityStudy = false; private int mEnd = 0; /* package */ int mLength = 0; private char[] mCharBuf = new char[BUFSIZE]; @@ -99,9 +114,12 @@ public class LatinIMEUtil { public static RingCharBuffer getInstance() { return sRingCharBuffer; } - public static RingCharBuffer init(Context context, boolean enabled) { + public static RingCharBuffer init(InputMethodService context, boolean enabled, + boolean usabilityStudy) { sRingCharBuffer.mContext = context; - sRingCharBuffer.mEnabled = enabled; + sRingCharBuffer.mEnabled = enabled || usabilityStudy; + sRingCharBuffer.mUsabilityStudy = usabilityStudy; + UsabilityStudyLogUtils.getInstance().init(context); return sRingCharBuffer; } private int normalize(int in) { @@ -110,6 +128,9 @@ public class LatinIMEUtil { } public void push(char c, int x, int y) { if (!mEnabled) return; + if (mUsabilityStudy) { + UsabilityStudyLogUtils.getInstance().writeChar(c, x, y); + } mCharBuf[mEnd] = c; mXBuf[mEnd] = x; mYBuf[mEnd] = y; @@ -153,7 +174,7 @@ public class LatinIMEUtil { } } public String getLastString() { - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); for (int i = 0; i < mLength; ++i) { char c = mCharBuf[normalize(mEnd - 1 - i)]; if (!((LatinIME)mContext).isWordSeparator(c)) { @@ -168,4 +189,205 @@ public class LatinIMEUtil { mLength = 0; } } + + public static int editDistance(CharSequence s, CharSequence t) { + if (s == null || t == null) { + throw new IllegalArgumentException("editDistance: Arguments should not be null."); + } + final int sl = s.length(); + final int tl = t.length(); + int[][] dp = new int [sl + 1][tl + 1]; + for (int i = 0; i <= sl; i++) { + dp[i][0] = i; + } + for (int j = 0; j <= tl; j++) { + dp[0][j] = j; + } + for (int i = 0; i < sl; ++i) { + for (int j = 0; j < tl; ++j) { + if (s.charAt(i) == t.charAt(j)) { + dp[i + 1][j + 1] = dp[i][j]; + } else { + dp[i + 1][j + 1] = 1 + Math.min(dp[i][j], + Math.min(dp[i + 1][j], dp[i][j + 1])); + } + } + } + return dp[sl][tl]; + } + + // In dictionary.cpp, getSuggestion() method, + // suggestion scores are computed using the below formula. + // original score (called 'frequency') + // := pow(mTypedLetterMultiplier (this is defined 2), + // (the number of matched characters between typed word and suggested word)) + // * (individual word's score which defined in the unigram dictionary, + // and this score is defined in range [0, 255].) + // * (when before.length() == after.length(), + // mFullWordMultiplier (this is defined 2)) + // So, maximum original score is pow(2, before.length()) * 255 * 2 + // So, we can normalize original score by dividing this value. + private static final int MAX_INITIAL_SCORE = 255; + private static final int TYPED_LETTER_MULTIPLIER = 2; + private static final int FULL_WORD_MULTIPLYER = 2; + public static double calcNormalizedScore(CharSequence before, CharSequence after, int score) { + final int beforeLength = before.length(); + final int afterLength = after.length(); + final int distance = editDistance(before, after); + final double maximumScore = MAX_INITIAL_SCORE + * Math.pow(TYPED_LETTER_MULTIPLIER, beforeLength) + * FULL_WORD_MULTIPLYER; + // add a weight based on edit distance. + // distance <= max(afterLength, beforeLength) == afterLength, + // so, 0 <= distance / afterLength <= 1 + final double weight = 1.0 - (double) distance / afterLength; + return (score / maximumScore) * weight; + } + + public static class UsabilityStudyLogUtils { + private static final String TAG = "UsabilityStudyLogUtils"; + private static final String FILENAME = "log.txt"; + private static final UsabilityStudyLogUtils sInstance = + new UsabilityStudyLogUtils(); + private final Handler mLoggingHandler; + private File mFile; + private File mDirectory; + private InputMethodService mIms; + private PrintWriter mWriter; + private final Date mDate; + private final SimpleDateFormat mDateFormat; + + private UsabilityStudyLogUtils() { + mDate = new Date(); + mDateFormat = new SimpleDateFormat("dd MMM HH:mm:ss.SSS"); + + HandlerThread handlerThread = new HandlerThread("UsabilityStudyLogUtils logging task", + Process.THREAD_PRIORITY_BACKGROUND); + handlerThread.start(); + mLoggingHandler = new Handler(handlerThread.getLooper()); + } + + public static UsabilityStudyLogUtils getInstance() { + return sInstance; + } + + public void init(InputMethodService ims) { + mIms = ims; + mDirectory = ims.getFilesDir(); + } + + private void createLogFileIfNotExist() { + if ((mFile == null || !mFile.exists()) + && (mDirectory != null && mDirectory.exists())) { + try { + mWriter = getPrintWriter(mDirectory, FILENAME, false); + } catch (IOException e) { + Log.e(TAG, "Can't create log file."); + } + } + } + + public void writeBackSpace() { + UsabilityStudyLogUtils.getInstance().write("<backspace>\t0\t0"); + } + + public void writeChar(char c, int x, int y) { + String inputChar = String.valueOf(c); + switch (c) { + case '\n': + inputChar = "<enter>"; + break; + case '\t': + inputChar = "<tab>"; + break; + case ' ': + inputChar = "<space>"; + break; + } + UsabilityStudyLogUtils.getInstance().write(inputChar + "\t" + x + "\t" + y); + LatinImeLogger.onPrintAllUsabilityStudtyLogs(); + } + + public void write(final String log) { + mLoggingHandler.post(new Runnable() { + @Override + public void run() { + createLogFileIfNotExist(); + final long currentTime = System.currentTimeMillis(); + mDate.setTime(currentTime); + + final String printString = String.format("%s\t%d\t%s\n", + mDateFormat.format(mDate), currentTime, log); + if (LatinImeLogger.sDBG) { + Log.d(TAG, "Write: " + log); + } + mWriter.print(printString); + } + }); + } + + public void printAll() { + mLoggingHandler.post(new Runnable() { + @Override + public void run() { + mWriter.flush(); + StringBuilder sb = new StringBuilder(); + BufferedReader br = getBufferedReader(); + String line; + try { + while ((line = br.readLine()) != null) { + sb.append('\n'); + sb.append(line); + } + } catch (IOException e) { + Log.e(TAG, "Can't read log file."); + } finally { + if (LatinImeLogger.sDBG) { + Log.d(TAG, "output all logs\n" + sb.toString()); + } + mIms.getCurrentInputConnection().commitText(sb.toString(), 0); + try { + br.close(); + } catch (IOException e) { + } + } + } + }); + } + + public void clearAll() { + mLoggingHandler.post(new Runnable() { + @Override + public void run() { + if (mFile != null && mFile.exists()) { + if (LatinImeLogger.sDBG) { + Log.d(TAG, "Delete log file."); + } + mFile.delete(); + mWriter.close(); + } + } + }); + } + + private BufferedReader getBufferedReader() { + createLogFileIfNotExist(); + try { + return new BufferedReader(new FileReader(mFile)); + } catch (FileNotFoundException e) { + return null; + } + } + + private PrintWriter getPrintWriter( + File dir, String filename, boolean renew) throws IOException { + mFile = new File(dir, filename); + if (mFile.exists()) { + if (renew) { + mFile.delete(); + } + } + return new PrintWriter(new FileOutputStream(mFile), true /* autoFlush */); + } + } } diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java index a8ab9cc98..de194d21b 100644 --- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java +++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java @@ -16,19 +16,23 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.Dictionary.DataType; import android.content.Context; import android.content.SharedPreferences; -import android.inputmethodservice.Keyboard; + import java.util.List; public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChangeListener { + public static boolean sDBG = false; + + @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { } - public static void init(Context context) { + public static void init(Context context, SharedPreferences prefs) { } public static void commit() { @@ -68,4 +72,6 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang public static void onSetKeyboard(Keyboard kb) { } + public static void onPrintAllUsabilityStudtyLogs() { + } } diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboard.java b/java/src/com/android/inputmethod/latin/LatinKeyboard.java deleted file mode 100644 index 45a4a9508..000000000 --- a/java/src/com/android/inputmethod/latin/LatinKeyboard.java +++ /dev/null @@ -1,1022 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.inputmethod.latin; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.ColorFilter; -import android.graphics.Paint; -import android.graphics.Paint.Align; -import android.graphics.PixelFormat; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.inputmethodservice.Keyboard; -import android.text.TextPaint; -import android.util.Log; -import android.view.ViewConfiguration; -import android.view.inputmethod.EditorInfo; - -import java.util.List; -import java.util.Locale; - -public class LatinKeyboard extends Keyboard { - - private static final boolean DEBUG_PREFERRED_LETTER = false; - private static final String TAG = "LatinKeyboard"; - private static final int OPACITY_FULLY_OPAQUE = 255; - private static final int SPACE_LED_LENGTH_PERCENT = 80; - - private Drawable mShiftLockIcon; - private Drawable mShiftLockPreviewIcon; - private Drawable mOldShiftIcon; - private Drawable mSpaceIcon; - private Drawable mSpaceAutoCompletionIndicator; - private Drawable mSpacePreviewIcon; - private Drawable mMicIcon; - private Drawable mMicPreviewIcon; - private Drawable m123MicIcon; - private Drawable m123MicPreviewIcon; - private final Drawable mButtonArrowLeftIcon; - private final Drawable mButtonArrowRightIcon; - private Key mShiftKey; - private Key mEnterKey; - private Key mF1Key; - private final Drawable mHintIcon; - private Key mSpaceKey; - private Key m123Key; - private final int NUMBER_HINT_COUNT = 10; - private Key[] mNumberHintKeys; - private Drawable[] mNumberHintIcons = new Drawable[NUMBER_HINT_COUNT]; - private int mSpaceKeyIndex = -1; - private int mSpaceDragStartX; - private int mSpaceDragLastDiff; - private Locale mLocale; - private LanguageSwitcher mLanguageSwitcher; - private final Resources mRes; - private final Context mContext; - private int mMode; - // Whether this keyboard has voice icon on it - private boolean mHasVoiceButton; - // Whether voice icon is enabled at all - private boolean mVoiceEnabled; - private final boolean mIsAlphaKeyboard; - private CharSequence m123Label; - private boolean mCurrentlyInSpace; - private SlidingLocaleDrawable mSlidingLocaleIcon; - private int[] mPrefLetterFrequencies; - private int mPrefLetter; - private int mPrefLetterX; - private int mPrefLetterY; - private int mPrefDistance; - - // TODO: generalize for any keyboardId - private boolean mIsBlackSym; - - // TODO: remove this attribute when either Keyboard.mDefaultVerticalGap or Key.parent becomes - // non-private. - private final int mVerticalGap; - - private static final int SHIFT_OFF = 0; - private static final int SHIFT_ON = 1; - private static final int SHIFT_LOCKED = 2; - - private int mShiftState = SHIFT_OFF; - - private static final float SPACEBAR_DRAG_THRESHOLD = 0.8f; - private static final float OVERLAP_PERCENTAGE_LOW_PROB = 0.70f; - private static final float OVERLAP_PERCENTAGE_HIGH_PROB = 0.85f; - // Minimum width of space key preview (proportional to keyboard width) - private static final float SPACEBAR_POPUP_MIN_RATIO = 0.4f; - // Height in space key the language name will be drawn. (proportional to space key height) - private static final float SPACEBAR_LANGUAGE_BASELINE = 0.6f; - // If the full language name needs to be smaller than this value to be drawn on space key, - // its short language name will be used instead. - private static final float MINIMUM_SCALE_OF_LANGUAGE_NAME = 0.8f; - - private static int sSpacebarVerticalCorrection; - - public LatinKeyboard(Context context, int xmlLayoutResId) { - this(context, xmlLayoutResId, 0); - } - - public LatinKeyboard(Context context, int xmlLayoutResId, int mode) { - super(context, xmlLayoutResId, mode); - final Resources res = context.getResources(); - mContext = context; - mMode = mode; - mRes = res; - mShiftLockIcon = res.getDrawable(R.drawable.sym_keyboard_shift_locked); - mShiftLockPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_shift_locked); - setDefaultBounds(mShiftLockPreviewIcon); - mSpaceIcon = res.getDrawable(R.drawable.sym_keyboard_space); - mSpaceAutoCompletionIndicator = res.getDrawable(R.drawable.sym_keyboard_space_led); - mSpacePreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_space); - mMicIcon = res.getDrawable(R.drawable.sym_keyboard_mic); - mMicPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_mic); - setDefaultBounds(mMicPreviewIcon); - mButtonArrowLeftIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_left); - mButtonArrowRightIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_right); - m123MicIcon = res.getDrawable(R.drawable.sym_keyboard_123_mic); - m123MicPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_123_mic); - mHintIcon = res.getDrawable(R.drawable.hint_popup); - setDefaultBounds(m123MicPreviewIcon); - sSpacebarVerticalCorrection = res.getDimensionPixelOffset( - R.dimen.spacebar_vertical_correction); - mIsAlphaKeyboard = xmlLayoutResId == R.xml.kbd_qwerty - || xmlLayoutResId == R.xml.kbd_qwerty_black; - mSpaceKeyIndex = indexOf(LatinIME.KEYCODE_SPACE); - initializeNumberHintResources(context); - // TODO remove this initialization after cleanup - mVerticalGap = super.getVerticalGap(); - } - - private void initializeNumberHintResources(Context context) { - final Resources res = context.getResources(); - mNumberHintIcons[0] = res.getDrawable(R.drawable.keyboard_hint_0); - mNumberHintIcons[1] = res.getDrawable(R.drawable.keyboard_hint_1); - mNumberHintIcons[2] = res.getDrawable(R.drawable.keyboard_hint_2); - mNumberHintIcons[3] = res.getDrawable(R.drawable.keyboard_hint_3); - mNumberHintIcons[4] = res.getDrawable(R.drawable.keyboard_hint_4); - mNumberHintIcons[5] = res.getDrawable(R.drawable.keyboard_hint_5); - mNumberHintIcons[6] = res.getDrawable(R.drawable.keyboard_hint_6); - mNumberHintIcons[7] = res.getDrawable(R.drawable.keyboard_hint_7); - mNumberHintIcons[8] = res.getDrawable(R.drawable.keyboard_hint_8); - mNumberHintIcons[9] = res.getDrawable(R.drawable.keyboard_hint_9); - } - - @Override - protected Key createKeyFromXml(Resources res, Row parent, int x, int y, - XmlResourceParser parser) { - Key key = new LatinKey(res, parent, x, y, parser); - switch (key.codes[0]) { - case LatinIME.KEYCODE_ENTER: - mEnterKey = key; - break; - case LatinKeyboardView.KEYCODE_F1: - mF1Key = key; - break; - case LatinIME.KEYCODE_SPACE: - mSpaceKey = key; - break; - case KEYCODE_MODE_CHANGE: - m123Key = key; - m123Label = key.label; - break; - } - - // For number hints on the upper-right corner of key - if (mNumberHintKeys == null) { - // NOTE: This protected method is being called from the base class constructor before - // mNumberHintKeys gets initialized. - mNumberHintKeys = new Key[NUMBER_HINT_COUNT]; - } - int hintNumber = -1; - if (LatinKeyboardBaseView.isNumberAtLeftmostPopupChar(key)) { - hintNumber = key.popupCharacters.charAt(0) - '0'; - } else if (LatinKeyboardBaseView.isNumberAtRightmostPopupChar(key)) { - hintNumber = key.popupCharacters.charAt(key.popupCharacters.length() - 1) - '0'; - } - if (hintNumber >= 0 && hintNumber <= 9) { - mNumberHintKeys[hintNumber] = key; - } - - return key; - } - - void setImeOptions(Resources res, int mode, int options) { - mMode = mode; - // TODO should clean up this method - if (mEnterKey != null) { - // Reset some of the rarely used attributes. - mEnterKey.popupCharacters = null; - mEnterKey.popupResId = 0; - mEnterKey.text = null; - switch (options&(EditorInfo.IME_MASK_ACTION|EditorInfo.IME_FLAG_NO_ENTER_ACTION)) { - case EditorInfo.IME_ACTION_GO: - mEnterKey.iconPreview = null; - mEnterKey.icon = null; - mEnterKey.label = res.getText(R.string.label_go_key); - break; - case EditorInfo.IME_ACTION_NEXT: - mEnterKey.iconPreview = null; - mEnterKey.icon = null; - mEnterKey.label = res.getText(R.string.label_next_key); - break; - case EditorInfo.IME_ACTION_DONE: - mEnterKey.iconPreview = null; - mEnterKey.icon = null; - mEnterKey.label = res.getText(R.string.label_done_key); - break; - case EditorInfo.IME_ACTION_SEARCH: - mEnterKey.iconPreview = res.getDrawable( - R.drawable.sym_keyboard_feedback_search); - mEnterKey.icon = res.getDrawable(mIsBlackSym ? - R.drawable.sym_bkeyboard_search : R.drawable.sym_keyboard_search); - mEnterKey.label = null; - break; - case EditorInfo.IME_ACTION_SEND: - mEnterKey.iconPreview = null; - mEnterKey.icon = null; - mEnterKey.label = res.getText(R.string.label_send_key); - break; - default: - if (mode == KeyboardSwitcher.MODE_IM) { - mEnterKey.icon = mHintIcon; - mEnterKey.iconPreview = null; - mEnterKey.label = ":-)"; - mEnterKey.text = ":-) "; - mEnterKey.popupResId = R.xml.popup_smileys; - } else { - mEnterKey.iconPreview = res.getDrawable( - R.drawable.sym_keyboard_feedback_return); - mEnterKey.icon = res.getDrawable(mIsBlackSym ? - R.drawable.sym_bkeyboard_return : R.drawable.sym_keyboard_return); - mEnterKey.label = null; - } - break; - } - // Set the initial size of the preview icon - if (mEnterKey.iconPreview != null) { - setDefaultBounds(mEnterKey.iconPreview); - } - } - } - - void enableShiftLock() { - int index = getShiftKeyIndex(); - if (index >= 0) { - mShiftKey = getKeys().get(index); - if (mShiftKey instanceof LatinKey) { - ((LatinKey)mShiftKey).enableShiftLock(); - } - mOldShiftIcon = mShiftKey.icon; - } - } - - void setShiftLocked(boolean shiftLocked) { - if (mShiftKey != null) { - if (shiftLocked) { - mShiftKey.on = true; - mShiftKey.icon = mShiftLockIcon; - mShiftState = SHIFT_LOCKED; - } else { - mShiftKey.on = false; - mShiftKey.icon = mShiftLockIcon; - mShiftState = SHIFT_ON; - } - } - } - - boolean isShiftLocked() { - return mShiftState == SHIFT_LOCKED; - } - - @Override - public boolean setShifted(boolean shiftState) { - boolean shiftChanged = false; - if (mShiftKey != null) { - if (shiftState == false) { - shiftChanged = mShiftState != SHIFT_OFF; - mShiftState = SHIFT_OFF; - mShiftKey.on = false; - mShiftKey.icon = mOldShiftIcon; - } else { - if (mShiftState == SHIFT_OFF) { - shiftChanged = mShiftState == SHIFT_OFF; - mShiftState = SHIFT_ON; - mShiftKey.icon = mShiftLockIcon; - } - } - } else { - return super.setShifted(shiftState); - } - return shiftChanged; - } - - @Override - public boolean isShifted() { - if (mShiftKey != null) { - return mShiftState != SHIFT_OFF; - } else { - return super.isShifted(); - } - } - - /* package */ boolean isAlphaKeyboard() { - return mIsAlphaKeyboard; - } - - public void setColorOfSymbolIcons(boolean isAutoCompletion, boolean isBlack) { - mIsBlackSym = isBlack; - if (isBlack) { - mShiftLockIcon = mRes.getDrawable(R.drawable.sym_bkeyboard_shift_locked); - mSpaceIcon = mRes.getDrawable(R.drawable.sym_bkeyboard_space); - mMicIcon = mRes.getDrawable(R.drawable.sym_bkeyboard_mic); - m123MicIcon = mRes.getDrawable(R.drawable.sym_bkeyboard_123_mic); - } else { - mShiftLockIcon = mRes.getDrawable(R.drawable.sym_keyboard_shift_locked); - mSpaceIcon = mRes.getDrawable(R.drawable.sym_keyboard_space); - mMicIcon = mRes.getDrawable(R.drawable.sym_keyboard_mic); - m123MicIcon = mRes.getDrawable(R.drawable.sym_keyboard_123_mic); - } - updateDynamicKeys(); - if (mSpaceKey != null) { - updateSpaceBarForLocale(isAutoCompletion, isBlack); - } - updateNumberHintKeys(); - } - - private void setDefaultBounds(Drawable drawable) { - drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); - } - - public void setVoiceMode(boolean hasVoiceButton, boolean hasVoice) { - mHasVoiceButton = hasVoiceButton; - mVoiceEnabled = hasVoice; - updateDynamicKeys(); - } - - private void updateDynamicKeys() { - update123Key(); - updateF1Key(); - } - - private void update123Key() { - // Update KEYCODE_MODE_CHANGE key only on alphabet mode, not on symbol mode. - if (m123Key != null && mIsAlphaKeyboard) { - if (mVoiceEnabled && !mHasVoiceButton) { - m123Key.icon = m123MicIcon; - m123Key.iconPreview = m123MicPreviewIcon; - m123Key.label = null; - } else { - m123Key.icon = null; - m123Key.iconPreview = null; - m123Key.label = m123Label; - } - } - } - - private void updateF1Key() { - // Update KEYCODE_F1 key. Please note that some keyboard layouts have no F1 key. - if (mF1Key == null) - return; - - if (mIsAlphaKeyboard) { - if (mMode == KeyboardSwitcher.MODE_URL) { - setNonMicF1Key(mF1Key, "/", R.xml.popup_slash); - } else if (mMode == KeyboardSwitcher.MODE_EMAIL) { - setNonMicF1Key(mF1Key, "@", R.xml.popup_at); - } else { - if (mVoiceEnabled && mHasVoiceButton) { - setMicF1Key(mF1Key); - } else { - setNonMicF1Key(mF1Key, ",", R.xml.popup_comma); - } - } - } else { // Symbols keyboard - if (mVoiceEnabled && mHasVoiceButton) { - setMicF1Key(mF1Key); - } else { - setNonMicF1Key(mF1Key, ",", R.xml.popup_comma); - } - } - } - - private void setMicF1Key(Key key) { - // HACK: draw mMicIcon and mHintIcon at the same time - final Drawable micWithSettingsHintDrawable = new BitmapDrawable(mRes, - drawSynthesizedSettingsHintImage(key.width, key.height, mMicIcon, mHintIcon)); - - key.label = null; - key.codes = new int[] { LatinKeyboardView.KEYCODE_VOICE }; - key.popupResId = R.xml.popup_mic; - key.icon = micWithSettingsHintDrawable; - key.iconPreview = mMicPreviewIcon; - } - - private void setNonMicF1Key(Key key, String label, int popupResId) { - key.label = label; - key.codes = new int[] { label.charAt(0) }; - key.popupResId = popupResId; - key.icon = mHintIcon; - key.iconPreview = null; - } - - public boolean isF1Key(Key key) { - return key == mF1Key; - } - - public static boolean hasPuncOrSmileysPopup(Key key) { - return key.popupResId == R.xml.popup_punctuation || key.popupResId == R.xml.popup_smileys; - } - - /** - * @return a key which should be invalidated. - */ - public Key onAutoCompletionStateChanged(boolean isAutoCompletion) { - updateSpaceBarForLocale(isAutoCompletion, mIsBlackSym); - return mSpaceKey; - } - - private void updateNumberHintKeys() { - for (int i = 0; i < mNumberHintKeys.length; ++i) { - if (mNumberHintKeys[i] != null) { - mNumberHintKeys[i].icon = mNumberHintIcons[i]; - } - } - } - - public boolean isLanguageSwitchEnabled() { - return mLocale != null; - } - - private void updateSpaceBarForLocale(boolean isAutoCompletion, boolean isBlack) { - // If application locales are explicitly selected. - if (mLocale != null) { - mSpaceKey.icon = new BitmapDrawable(mRes, - drawSpaceBar(OPACITY_FULLY_OPAQUE, isAutoCompletion, isBlack)); - } else { - // sym_keyboard_space_led can be shared with Black and White symbol themes. - if (isAutoCompletion) { - mSpaceKey.icon = new BitmapDrawable(mRes, - drawSpaceBar(OPACITY_FULLY_OPAQUE, isAutoCompletion, isBlack)); - } else { - mSpaceKey.icon = isBlack ? mRes.getDrawable(R.drawable.sym_bkeyboard_space) - : mRes.getDrawable(R.drawable.sym_keyboard_space); - } - } - } - - // Compute width of text with specified text size using paint. - private static int getTextWidth(Paint paint, String text, float textSize, Rect bounds) { - paint.setTextSize(textSize); - paint.getTextBounds(text, 0, text.length(), bounds); - return bounds.width(); - } - - // Overlay two images: mainIcon and hintIcon. - private Bitmap drawSynthesizedSettingsHintImage( - int width, int height, Drawable mainIcon, Drawable hintIcon) { - if (mainIcon == null || hintIcon == null) - return null; - Rect hintIconPadding = new Rect(0, 0, 0, 0); - hintIcon.getPadding(hintIconPadding); - final Bitmap buffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(buffer); - canvas.drawColor(mRes.getColor(R.color.latinkeyboard_transparent), PorterDuff.Mode.CLEAR); - - // Draw main icon at the center of the key visual - // Assuming the hintIcon shares the same padding with the key's background drawable - final int drawableX = (width + hintIconPadding.left - hintIconPadding.right - - mainIcon.getIntrinsicWidth()) / 2; - final int drawableY = (height + hintIconPadding.top - hintIconPadding.bottom - - mainIcon.getIntrinsicHeight()) / 2; - setDefaultBounds(mainIcon); - canvas.translate(drawableX, drawableY); - mainIcon.draw(canvas); - canvas.translate(-drawableX, -drawableY); - - // Draw hint icon fully in the key - hintIcon.setBounds(0, 0, width, height); - hintIcon.draw(canvas); - return buffer; - } - - // Layout local language name and left and right arrow on space bar. - private static String layoutSpaceBar(Paint paint, Locale locale, Drawable lArrow, - Drawable rArrow, int width, int height, float origTextSize, - boolean allowVariableTextSize) { - final float arrowWidth = lArrow.getIntrinsicWidth(); - final float arrowHeight = lArrow.getIntrinsicHeight(); - final float maxTextWidth = width - (arrowWidth + arrowWidth); - final Rect bounds = new Rect(); - - // Estimate appropriate language name text size to fit in maxTextWidth. - String language = LanguageSwitcher.toTitleCase(locale.getDisplayLanguage(locale)); - int textWidth = getTextWidth(paint, language, origTextSize, bounds); - // Assuming text width and text size are proportional to each other. - float textSize = origTextSize * Math.min(maxTextWidth / textWidth, 1.0f); - - final boolean useShortName; - if (allowVariableTextSize) { - textWidth = getTextWidth(paint, language, textSize, bounds); - // If text size goes too small or text does not fit, use short name - useShortName = textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME - || textWidth > maxTextWidth; - } else { - useShortName = textWidth > maxTextWidth; - textSize = origTextSize; - } - if (useShortName) { - language = LanguageSwitcher.toTitleCase(locale.getLanguage()); - textWidth = getTextWidth(paint, language, origTextSize, bounds); - textSize = origTextSize * Math.min(maxTextWidth / textWidth, 1.0f); - } - paint.setTextSize(textSize); - - // Place left and right arrow just before and after language text. - final float baseline = height * SPACEBAR_LANGUAGE_BASELINE; - final int top = (int)(baseline - arrowHeight); - final float remains = (width - textWidth) / 2; - lArrow.setBounds((int)(remains - arrowWidth), top, (int)remains, (int)baseline); - rArrow.setBounds((int)(remains + textWidth), top, (int)(remains + textWidth + arrowWidth), - (int)baseline); - - return language; - } - - private Bitmap drawSpaceBar(int opacity, boolean isAutoCompletion, boolean isBlack) { - final int width = mSpaceKey.width; - final int height = mSpaceIcon.getIntrinsicHeight(); - final Bitmap buffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(buffer); - canvas.drawColor(mRes.getColor(R.color.latinkeyboard_transparent), PorterDuff.Mode.CLEAR); - - // If application locales are explicitly selected. - if (mLocale != null) { - final Paint paint = new Paint(); - paint.setAlpha(opacity); - paint.setAntiAlias(true); - paint.setTextAlign(Align.CENTER); - - final boolean allowVariableTextSize = true; - final String language = layoutSpaceBar(paint, mLanguageSwitcher.getInputLocale(), - mButtonArrowLeftIcon, mButtonArrowRightIcon, width, height, - getTextSizeFromTheme(android.R.style.TextAppearance_Small, 14), - allowVariableTextSize); - - // Draw language text with shadow - final int shadowColor = mRes.getColor(isBlack - ? R.color.latinkeyboard_bar_language_shadow_black - : R.color.latinkeyboard_bar_language_shadow_white); - final float baseline = height * SPACEBAR_LANGUAGE_BASELINE; - final float descent = paint.descent(); - paint.setColor(shadowColor); - canvas.drawText(language, width / 2, baseline - descent - 1, paint); - paint.setColor(mRes.getColor(R.color.latinkeyboard_bar_language_text)); - canvas.drawText(language, width / 2, baseline - descent, paint); - - // Put arrows that are already layed out on either side of the text - if (mLanguageSwitcher.getLocaleCount() > 1) { - mButtonArrowLeftIcon.draw(canvas); - mButtonArrowRightIcon.draw(canvas); - } - } - - // Draw the spacebar icon at the bottom - if (isAutoCompletion) { - final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100; - final int iconHeight = mSpaceAutoCompletionIndicator.getIntrinsicHeight(); - int x = (width - iconWidth) / 2; - int y = height - iconHeight; - mSpaceAutoCompletionIndicator.setBounds(x, y, x + iconWidth, y + iconHeight); - mSpaceAutoCompletionIndicator.draw(canvas); - } else { - final int iconWidth = mSpaceIcon.getIntrinsicWidth(); - final int iconHeight = mSpaceIcon.getIntrinsicHeight(); - int x = (width - iconWidth) / 2; - int y = height - iconHeight; - mSpaceIcon.setBounds(x, y, x + iconWidth, y + iconHeight); - mSpaceIcon.draw(canvas); - } - return buffer; - } - - private void updateLocaleDrag(int diff) { - if (mSlidingLocaleIcon == null) { - final int width = Math.max(mSpaceKey.width, - (int)(getMinWidth() * SPACEBAR_POPUP_MIN_RATIO)); - final int height = mSpacePreviewIcon.getIntrinsicHeight(); - mSlidingLocaleIcon = new SlidingLocaleDrawable(mSpacePreviewIcon, width, height); - mSlidingLocaleIcon.setBounds(0, 0, width, height); - mSpaceKey.iconPreview = mSlidingLocaleIcon; - } - mSlidingLocaleIcon.setDiff(diff); - if (Math.abs(diff) == Integer.MAX_VALUE) { - mSpaceKey.iconPreview = mSpacePreviewIcon; - } else { - mSpaceKey.iconPreview = mSlidingLocaleIcon; - } - mSpaceKey.iconPreview.invalidateSelf(); - } - - public int getLanguageChangeDirection() { - if (mSpaceKey == null || mLanguageSwitcher.getLocaleCount() < 2 - || Math.abs(mSpaceDragLastDiff) < mSpaceKey.width * SPACEBAR_DRAG_THRESHOLD ) { - return 0; // No change - } - return mSpaceDragLastDiff > 0 ? 1 : -1; - } - - public void setLanguageSwitcher(LanguageSwitcher switcher, boolean isAutoCompletion, - boolean isBlackSym) { - mLanguageSwitcher = switcher; - Locale locale = mLanguageSwitcher.getLocaleCount() > 0 - ? mLanguageSwitcher.getInputLocale() - : null; - // If the language count is 1 and is the same as the system language, don't show it. - if (locale != null - && mLanguageSwitcher.getLocaleCount() == 1 - && mLanguageSwitcher.getSystemLocale().getLanguage() - .equalsIgnoreCase(locale.getLanguage())) { - locale = null; - } - mLocale = locale; - setColorOfSymbolIcons(isAutoCompletion, isBlackSym); - } - - boolean isCurrentlyInSpace() { - return mCurrentlyInSpace; - } - - void setPreferredLetters(int[] frequencies) { - mPrefLetterFrequencies = frequencies; - mPrefLetter = 0; - } - - void keyReleased() { - mCurrentlyInSpace = false; - mSpaceDragLastDiff = 0; - mPrefLetter = 0; - mPrefLetterX = 0; - mPrefLetterY = 0; - mPrefDistance = Integer.MAX_VALUE; - if (mSpaceKey != null) { - updateLocaleDrag(Integer.MAX_VALUE); - } - } - - /** - * Does the magic of locking the touch gesture into the spacebar when - * switching input languages. - */ - boolean isInside(LatinKey key, int x, int y) { - final int code = key.codes[0]; - if (code == KEYCODE_SHIFT || - code == KEYCODE_DELETE) { - y -= key.height / 10; - if (code == KEYCODE_SHIFT) x += key.width / 6; - if (code == KEYCODE_DELETE) x -= key.width / 6; - } else if (code == LatinIME.KEYCODE_SPACE) { - y += LatinKeyboard.sSpacebarVerticalCorrection; - if (mLanguageSwitcher.getLocaleCount() > 1) { - if (mCurrentlyInSpace) { - int diff = x - mSpaceDragStartX; - if (Math.abs(diff - mSpaceDragLastDiff) > 0) { - updateLocaleDrag(diff); - } - mSpaceDragLastDiff = diff; - return true; - } else { - boolean insideSpace = key.isInsideSuper(x, y); - if (insideSpace) { - mCurrentlyInSpace = true; - mSpaceDragStartX = x; - updateLocaleDrag(0); - } - return insideSpace; - } - } - } else if (mPrefLetterFrequencies != null) { - // New coordinate? Reset - if (mPrefLetterX != x || mPrefLetterY != y) { - mPrefLetter = 0; - mPrefDistance = Integer.MAX_VALUE; - } - // Handle preferred next letter - final int[] pref = mPrefLetterFrequencies; - if (mPrefLetter > 0) { - if (DEBUG_PREFERRED_LETTER) { - if (mPrefLetter == code && !key.isInsideSuper(x, y)) { - Log.d(TAG, "CORRECTED !!!!!!"); - } - } - return mPrefLetter == code; - } else { - final boolean inside = key.isInsideSuper(x, y); - int[] nearby = getNearestKeys(x, y); - List<Key> nearbyKeys = getKeys(); - if (inside) { - // If it's a preferred letter - if (inPrefList(code, pref)) { - // Check if its frequency is much lower than a nearby key - mPrefLetter = code; - mPrefLetterX = x; - mPrefLetterY = y; - for (int i = 0; i < nearby.length; i++) { - Key k = nearbyKeys.get(nearby[i]); - if (k != key && inPrefList(k.codes[0], pref)) { - final int dist = distanceFrom(k, x, y); - if (dist < (int) (k.width * OVERLAP_PERCENTAGE_LOW_PROB) && - (pref[k.codes[0]] > pref[mPrefLetter] * 3)) { - mPrefLetter = k.codes[0]; - mPrefDistance = dist; - if (DEBUG_PREFERRED_LETTER) { - Log.d(TAG, "CORRECTED ALTHOUGH PREFERRED !!!!!!"); - } - break; - } - } - } - - return mPrefLetter == code; - } - } - - // Get the surrounding keys and intersect with the preferred list - // For all in the intersection - // if distance from touch point is within a reasonable distance - // make this the pref letter - // If no pref letter - // return inside; - // else return thiskey == prefletter; - - for (int i = 0; i < nearby.length; i++) { - Key k = nearbyKeys.get(nearby[i]); - if (inPrefList(k.codes[0], pref)) { - final int dist = distanceFrom(k, x, y); - if (dist < (int) (k.width * OVERLAP_PERCENTAGE_HIGH_PROB) - && dist < mPrefDistance) { - mPrefLetter = k.codes[0]; - mPrefLetterX = x; - mPrefLetterY = y; - mPrefDistance = dist; - } - } - } - // Didn't find any - if (mPrefLetter == 0) { - return inside; - } else { - return mPrefLetter == code; - } - } - } - - // Lock into the spacebar - if (mCurrentlyInSpace) return false; - - return key.isInsideSuper(x, y); - } - - private boolean inPrefList(int code, int[] pref) { - if (code < pref.length && code >= 0) return pref[code] > 0; - return false; - } - - private int distanceFrom(Key k, int x, int y) { - if (y > k.y && y < k.y + k.height) { - return Math.abs(k.x + k.width / 2 - x); - } else { - return Integer.MAX_VALUE; - } - } - - @Override - public int[] getNearestKeys(int x, int y) { - if (mCurrentlyInSpace) { - return new int[] { mSpaceKeyIndex }; - } else { - // Avoid dead pixels at edges of the keyboard - return super.getNearestKeys(Math.max(0, Math.min(x, getMinWidth() - 1)), - Math.max(0, Math.min(y, getHeight() - 1))); - } - } - - private int indexOf(int code) { - List<Key> keys = getKeys(); - int count = keys.size(); - for (int i = 0; i < count; i++) { - if (keys.get(i).codes[0] == code) return i; - } - return -1; - } - - private int getTextSizeFromTheme(int style, int defValue) { - TypedArray array = mContext.getTheme().obtainStyledAttributes( - style, new int[] { android.R.attr.textSize }); - int textSize = array.getDimensionPixelSize(array.getResourceId(0, 0), defValue); - return textSize; - } - - // TODO LatinKey could be static class - class LatinKey extends Keyboard.Key { - - // functional normal state (with properties) - private final int[] KEY_STATE_FUNCTIONAL_NORMAL = { - android.R.attr.state_single - }; - - // functional pressed state (with properties) - private final int[] KEY_STATE_FUNCTIONAL_PRESSED = { - android.R.attr.state_single, - android.R.attr.state_pressed - }; - - private boolean mShiftLockEnabled; - - public LatinKey(Resources res, Keyboard.Row parent, int x, int y, - XmlResourceParser parser) { - super(res, parent, x, y, parser); - if (popupCharacters != null && popupCharacters.length() == 0) { - // If there is a keyboard with no keys specified in popupCharacters - popupResId = 0; - } - } - - private void enableShiftLock() { - mShiftLockEnabled = true; - } - - // sticky is used for shift key. If a key is not sticky and is modifier, - // the key will be treated as functional. - private boolean isFunctionalKey() { - return !sticky && modifier; - } - - @Override - public void onReleased(boolean inside) { - if (!mShiftLockEnabled) { - super.onReleased(inside); - } else { - pressed = !pressed; - } - } - - /** - * Overriding this method so that we can reduce the target area for certain keys. - */ - @Override - public boolean isInside(int x, int y) { - // TODO This should be done by parent.isInside(this, x, y) - // if Key.parent were protected. - boolean result = LatinKeyboard.this.isInside(this, x, y); - return result; - } - - boolean isInsideSuper(int x, int y) { - return super.isInside(x, y); - } - - @Override - public int[] getCurrentDrawableState() { - if (isFunctionalKey()) { - if (pressed) { - return KEY_STATE_FUNCTIONAL_PRESSED; - } else { - return KEY_STATE_FUNCTIONAL_NORMAL; - } - } - return super.getCurrentDrawableState(); - } - - @Override - public int squaredDistanceFrom(int x, int y) { - // We should count vertical gap between rows to calculate the center of this Key. - final int verticalGap = LatinKeyboard.this.mVerticalGap; - final int xDist = this.x + width / 2 - x; - final int yDist = this.y + (height + verticalGap) / 2 - y; - return xDist * xDist + yDist * yDist; - } - } - - /** - * Animation to be displayed on the spacebar preview popup when switching - * languages by swiping the spacebar. It draws the current, previous and - * next languages and moves them by the delta of touch movement on the spacebar. - */ - class SlidingLocaleDrawable extends Drawable { - - private final int mWidth; - private final int mHeight; - private final Drawable mBackground; - private final TextPaint mTextPaint; - private final int mMiddleX; - private final Drawable mLeftDrawable; - private final Drawable mRightDrawable; - private final int mThreshold; - private int mDiff; - private boolean mHitThreshold; - private String mCurrentLanguage; - private String mNextLanguage; - private String mPrevLanguage; - - public SlidingLocaleDrawable(Drawable background, int width, int height) { - mBackground = background; - setDefaultBounds(mBackground); - mWidth = width; - mHeight = height; - mTextPaint = new TextPaint(); - mTextPaint.setTextSize(getTextSizeFromTheme(android.R.style.TextAppearance_Medium, 18)); - mTextPaint.setColor(R.color.latinkeyboard_transparent); - mTextPaint.setTextAlign(Align.CENTER); - mTextPaint.setAlpha(OPACITY_FULLY_OPAQUE); - mTextPaint.setAntiAlias(true); - mMiddleX = (mWidth - mBackground.getIntrinsicWidth()) / 2; - mLeftDrawable = - mRes.getDrawable(R.drawable.sym_keyboard_feedback_language_arrows_left); - mRightDrawable = - mRes.getDrawable(R.drawable.sym_keyboard_feedback_language_arrows_right); - mThreshold = ViewConfiguration.get(mContext).getScaledTouchSlop(); - } - - private void setDiff(int diff) { - if (diff == Integer.MAX_VALUE) { - mHitThreshold = false; - mCurrentLanguage = null; - return; - } - mDiff = diff; - if (mDiff > mWidth) mDiff = mWidth; - if (mDiff < -mWidth) mDiff = -mWidth; - if (Math.abs(mDiff) > mThreshold) mHitThreshold = true; - invalidateSelf(); - } - - private String getLanguageName(Locale locale) { - return LanguageSwitcher.toTitleCase(locale.getDisplayLanguage(locale)); - } - - @Override - public void draw(Canvas canvas) { - canvas.save(); - if (mHitThreshold) { - Paint paint = mTextPaint; - final int width = mWidth; - final int height = mHeight; - final int diff = mDiff; - final Drawable lArrow = mLeftDrawable; - final Drawable rArrow = mRightDrawable; - canvas.clipRect(0, 0, width, height); - if (mCurrentLanguage == null) { - final LanguageSwitcher languageSwitcher = mLanguageSwitcher; - mCurrentLanguage = getLanguageName(languageSwitcher.getInputLocale()); - mNextLanguage = getLanguageName(languageSwitcher.getNextInputLocale()); - mPrevLanguage = getLanguageName(languageSwitcher.getPrevInputLocale()); - } - // Draw language text with shadow - final float baseline = mHeight * SPACEBAR_LANGUAGE_BASELINE - paint.descent(); - paint.setColor(mRes.getColor(R.color.latinkeyboard_feedback_language_text)); - canvas.drawText(mCurrentLanguage, width / 2 + diff, baseline, paint); - canvas.drawText(mNextLanguage, diff - width / 2, baseline, paint); - canvas.drawText(mPrevLanguage, diff + width + width / 2, baseline, paint); - - setDefaultBounds(lArrow); - rArrow.setBounds(width - rArrow.getIntrinsicWidth(), 0, width, - rArrow.getIntrinsicHeight()); - lArrow.draw(canvas); - rArrow.draw(canvas); - } - if (mBackground != null) { - canvas.translate(mMiddleX, 0); - mBackground.draw(canvas); - } - canvas.restore(); - } - - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; - } - - @Override - public void setAlpha(int alpha) { - // Ignore - } - - @Override - public void setColorFilter(ColorFilter cf) { - // Ignore - } - - @Override - public int getIntrinsicWidth() { - return mWidth; - } - - @Override - public int getIntrinsicHeight() { - return mHeight; - } - } -} diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java b/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java deleted file mode 100644 index b1ef08573..000000000 --- a/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java +++ /dev/null @@ -1,1487 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.latin; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Paint.Align; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.Region.Op; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.inputmethodservice.Keyboard; -import android.inputmethodservice.Keyboard.Key; -import android.os.Handler; -import android.os.Message; -import android.os.SystemClock; -import android.util.AttributeSet; -import android.util.Log; -import android.util.TypedValue; -import android.view.GestureDetector; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup.LayoutParams; -import android.widget.PopupWindow; -import android.widget.TextView; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.WeakHashMap; - -/** - * A view that renders a virtual {@link LatinKeyboard}. It handles rendering of keys and - * detecting key presses and touch movements. - * - * TODO: References to LatinKeyboard in this class should be replaced with ones to its base class. - * - * @attr ref R.styleable#LatinKeyboardBaseView_keyBackground - * @attr ref R.styleable#LatinKeyboardBaseView_keyPreviewLayout - * @attr ref R.styleable#LatinKeyboardBaseView_keyPreviewOffset - * @attr ref R.styleable#LatinKeyboardBaseView_labelTextSize - * @attr ref R.styleable#LatinKeyboardBaseView_keyTextSize - * @attr ref R.styleable#LatinKeyboardBaseView_keyTextColor - * @attr ref R.styleable#LatinKeyboardBaseView_verticalCorrection - * @attr ref R.styleable#LatinKeyboardBaseView_popupLayout - */ -public class LatinKeyboardBaseView extends View implements PointerTracker.UIProxy { - private static final String TAG = "LatinKeyboardBaseView"; - private static final boolean DEBUG = false; - - public static final int NOT_A_TOUCH_COORDINATE = -1; - - public interface OnKeyboardActionListener { - - /** - * Called when the user presses a key. This is sent before the - * {@link #onKey} is called. For keys that repeat, this is only - * called once. - * - * @param primaryCode - * the unicode of the key being pressed. If the touch is - * not on a valid key, the value will be zero. - */ - void onPress(int primaryCode); - - /** - * Called when the user releases a key. This is sent after the - * {@link #onKey} is called. For keys that repeat, this is only - * called once. - * - * @param primaryCode - * the code of the key that was released - */ - void onRelease(int primaryCode); - - /** - * Send a key press to the listener. - * - * @param primaryCode - * this is the key that was pressed - * @param keyCodes - * the codes for all the possible alternative keys with - * the primary code being the first. If the primary key - * code is a single character such as an alphabet or - * number or symbol, the alternatives will include other - * characters that may be on the same key or adjacent - * keys. These codes are useful to correct for - * accidental presses of a key adjacent to the intended - * key. - * @param x - * x-coordinate pixel of touched event. If onKey is not called by onTouchEvent, - * the value should be NOT_A_TOUCH_COORDINATE. - * @param y - * y-coordinate pixel of touched event. If onKey is not called by onTouchEvent, - * the value should be NOT_A_TOUCH_COORDINATE. - */ - void onKey(int primaryCode, int[] keyCodes, int x, int y); - - /** - * Sends a sequence of characters to the listener. - * - * @param text - * the sequence of characters to be displayed. - */ - void onText(CharSequence text); - - /** - * Called when user released a finger outside any key. - */ - void onCancel(); - - /** - * Called when the user quickly moves the finger from right to - * left. - */ - void swipeLeft(); - - /** - * Called when the user quickly moves the finger from left to - * right. - */ - void swipeRight(); - - /** - * Called when the user quickly moves the finger from up to down. - */ - void swipeDown(); - - /** - * Called when the user quickly moves the finger from down to up. - */ - void swipeUp(); - } - - // Timing constants - private final int mKeyRepeatInterval; - - // Miscellaneous constants - /* package */ static final int NOT_A_KEY = -1; - private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable }; - private static final int NUMBER_HINT_VERTICAL_ADJUSTMENT_PIXEL = -1; - - // XML attribute - private int mKeyTextSize; - private int mKeyTextColor; - private Typeface mKeyTextStyle = Typeface.DEFAULT; - private int mLabelTextSize; - private int mSymbolColorScheme = 0; - private int mShadowColor; - private float mShadowRadius; - private Drawable mKeyBackground; - private float mBackgroundDimAmount; - private float mKeyHysteresisDistance; - private float mVerticalCorrection; - private int mPreviewOffset; - private int mPreviewHeight; - private int mPopupLayout; - - // Main keyboard - private Keyboard mKeyboard; - private Key[] mKeys; - // TODO this attribute should be gotten from Keyboard. - private int mKeyboardVerticalGap; - - // Key preview popup - private TextView mPreviewText; - private PopupWindow mPreviewPopup; - private int mPreviewTextSizeLarge; - private int[] mOffsetInWindow; - private int mOldPreviewKeyIndex = NOT_A_KEY; - private boolean mShowPreview = true; - private boolean mShowTouchPoints = true; - private int mPopupPreviewOffsetX; - private int mPopupPreviewOffsetY; - private int mWindowY; - private int mPopupPreviewDisplayedY; - private final int mDelayBeforePreview; - private final int mDelayAfterPreview; - - // Popup mini keyboard - private PopupWindow mMiniKeyboardPopup; - private LatinKeyboardBaseView mMiniKeyboard; - private View mMiniKeyboardParent; - private final WeakHashMap<Key, View> mMiniKeyboardCache = new WeakHashMap<Key, View>(); - private int mMiniKeyboardOriginX; - private int mMiniKeyboardOriginY; - private long mMiniKeyboardPopupTime; - private int[] mWindowOffset; - private final float mMiniKeyboardSlideAllowance; - private int mMiniKeyboardTrackerId; - - /** Listener for {@link OnKeyboardActionListener}. */ - private OnKeyboardActionListener mKeyboardActionListener; - - private final ArrayList<PointerTracker> mPointerTrackers = new ArrayList<PointerTracker>(); - - // TODO: Let the PointerTracker class manage this pointer queue - private final PointerQueue mPointerQueue = new PointerQueue(); - - private final boolean mHasDistinctMultitouch; - private int mOldPointerCount = 1; - - protected KeyDetector mKeyDetector = new ProximityKeyDetector(); - - // Swipe gesture detector - private GestureDetector mGestureDetector; - private final SwipeTracker mSwipeTracker = new SwipeTracker(); - private final int mSwipeThreshold; - private final boolean mDisambiguateSwipe; - - // Drawing - /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/ - private boolean mDrawPending; - /** The dirty region in the keyboard bitmap */ - private final Rect mDirtyRect = new Rect(); - /** The keyboard bitmap for faster updates */ - private Bitmap mBuffer; - /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */ - private boolean mKeyboardChanged; - private Key mInvalidatedKey; - /** The canvas for the above mutable keyboard bitmap */ - private Canvas mCanvas; - private final Paint mPaint; - private final Rect mPadding; - private final Rect mClipRegion = new Rect(0, 0, 0, 0); - // This map caches key label text height in pixel as value and key label text size as map key. - private final HashMap<Integer, Integer> mTextHeightCache = new HashMap<Integer, Integer>(); - // Distance from horizontal center of the key, proportional to key label text height. - private final float KEY_LABEL_VERTICAL_ADJUSTMENT_FACTOR = 0.55f; - private final String KEY_LABEL_HEIGHT_REFERENCE_CHAR = "H"; - - private final UIHandler mHandler = new UIHandler(); - - class UIHandler extends Handler { - private static final int MSG_POPUP_PREVIEW = 1; - private static final int MSG_DISMISS_PREVIEW = 2; - private static final int MSG_REPEAT_KEY = 3; - private static final int MSG_LONGPRESS_KEY = 4; - - private boolean mInKeyRepeat; - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_POPUP_PREVIEW: - showKey(msg.arg1, (PointerTracker)msg.obj); - break; - case MSG_DISMISS_PREVIEW: - mPreviewPopup.dismiss(); - break; - case MSG_REPEAT_KEY: { - final PointerTracker tracker = (PointerTracker)msg.obj; - tracker.repeatKey(msg.arg1); - startKeyRepeatTimer(mKeyRepeatInterval, msg.arg1, tracker); - break; - } - case MSG_LONGPRESS_KEY: { - final PointerTracker tracker = (PointerTracker)msg.obj; - openPopupIfRequired(msg.arg1, tracker); - break; - } - } - } - - public void popupPreview(long delay, int keyIndex, PointerTracker tracker) { - removeMessages(MSG_POPUP_PREVIEW); - if (mPreviewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) { - // Show right away, if it's already visible and finger is moving around - showKey(keyIndex, tracker); - } else { - sendMessageDelayed(obtainMessage(MSG_POPUP_PREVIEW, keyIndex, 0, tracker), - delay); - } - } - - public void cancelPopupPreview() { - removeMessages(MSG_POPUP_PREVIEW); - } - - public void dismissPreview(long delay) { - if (mPreviewPopup.isShowing()) { - sendMessageDelayed(obtainMessage(MSG_DISMISS_PREVIEW), delay); - } - } - - public void cancelDismissPreview() { - removeMessages(MSG_DISMISS_PREVIEW); - } - - public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) { - mInKeyRepeat = true; - sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, keyIndex, 0, tracker), delay); - } - - public void cancelKeyRepeatTimer() { - mInKeyRepeat = false; - removeMessages(MSG_REPEAT_KEY); - } - - public boolean isInKeyRepeat() { - return mInKeyRepeat; - } - - public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) { - removeMessages(MSG_LONGPRESS_KEY); - sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, keyIndex, 0, tracker), delay); - } - - public void cancelLongPressTimer() { - removeMessages(MSG_LONGPRESS_KEY); - } - - public void cancelKeyTimers() { - cancelKeyRepeatTimer(); - cancelLongPressTimer(); - } - - public void cancelAllMessages() { - cancelKeyTimers(); - cancelPopupPreview(); - cancelDismissPreview(); - } - }; - - static class PointerQueue { - private LinkedList<PointerTracker> mQueue = new LinkedList<PointerTracker>(); - - public void add(PointerTracker tracker) { - mQueue.add(tracker); - } - - public int lastIndexOf(PointerTracker tracker) { - LinkedList<PointerTracker> queue = mQueue; - for (int index = queue.size() - 1; index >= 0; index--) { - PointerTracker t = queue.get(index); - if (t == tracker) - return index; - } - return -1; - } - - public void releaseAllPointersOlderThan(PointerTracker tracker, long eventTime) { - LinkedList<PointerTracker> queue = mQueue; - int oldestPos = 0; - for (PointerTracker t = queue.get(oldestPos); t != tracker; t = queue.get(oldestPos)) { - if (t.isModifier()) { - oldestPos++; - } else { - t.onUpEvent(t.getLastX(), t.getLastY(), eventTime); - t.setAlreadyProcessed(); - queue.remove(oldestPos); - } - } - } - - public void releaseAllPointersExcept(PointerTracker tracker, long eventTime) { - for (PointerTracker t : mQueue) { - if (t == tracker) - continue; - t.onUpEvent(t.getLastX(), t.getLastY(), eventTime); - t.setAlreadyProcessed(); - } - mQueue.clear(); - if (tracker != null) - mQueue.add(tracker); - } - - public void remove(PointerTracker tracker) { - mQueue.remove(tracker); - } - } - - public LatinKeyboardBaseView(Context context, AttributeSet attrs) { - this(context, attrs, R.attr.keyboardViewStyle); - } - - public LatinKeyboardBaseView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - TypedArray a = context.obtainStyledAttributes( - attrs, R.styleable.LatinKeyboardBaseView, defStyle, R.style.LatinKeyboardBaseView); - LayoutInflater inflate = - (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - int previewLayout = 0; - int keyTextSize = 0; - - int n = a.getIndexCount(); - - for (int i = 0; i < n; i++) { - int attr = a.getIndex(i); - - switch (attr) { - case R.styleable.LatinKeyboardBaseView_keyBackground: - mKeyBackground = a.getDrawable(attr); - break; - case R.styleable.LatinKeyboardBaseView_keyHysteresisDistance: - mKeyHysteresisDistance = a.getDimensionPixelOffset(attr, 0); - break; - case R.styleable.LatinKeyboardBaseView_verticalCorrection: - mVerticalCorrection = a.getDimensionPixelOffset(attr, 0); - break; - case R.styleable.LatinKeyboardBaseView_keyPreviewLayout: - previewLayout = a.getResourceId(attr, 0); - break; - case R.styleable.LatinKeyboardBaseView_keyPreviewOffset: - mPreviewOffset = a.getDimensionPixelOffset(attr, 0); - break; - case R.styleable.LatinKeyboardBaseView_keyPreviewHeight: - mPreviewHeight = a.getDimensionPixelSize(attr, 80); - break; - case R.styleable.LatinKeyboardBaseView_keyTextSize: - mKeyTextSize = a.getDimensionPixelSize(attr, 18); - break; - case R.styleable.LatinKeyboardBaseView_keyTextColor: - mKeyTextColor = a.getColor(attr, 0xFF000000); - break; - case R.styleable.LatinKeyboardBaseView_labelTextSize: - mLabelTextSize = a.getDimensionPixelSize(attr, 14); - break; - case R.styleable.LatinKeyboardBaseView_popupLayout: - mPopupLayout = a.getResourceId(attr, 0); - break; - case R.styleable.LatinKeyboardBaseView_shadowColor: - mShadowColor = a.getColor(attr, 0); - break; - case R.styleable.LatinKeyboardBaseView_shadowRadius: - mShadowRadius = a.getFloat(attr, 0f); - break; - // TODO: Use Theme (android.R.styleable.Theme_backgroundDimAmount) - case R.styleable.LatinKeyboardBaseView_backgroundDimAmount: - mBackgroundDimAmount = a.getFloat(attr, 0.5f); - break; - //case android.R.styleable. - case R.styleable.LatinKeyboardBaseView_keyTextStyle: - int textStyle = a.getInt(attr, 0); - switch (textStyle) { - case 0: - mKeyTextStyle = Typeface.DEFAULT; - break; - case 1: - mKeyTextStyle = Typeface.DEFAULT_BOLD; - break; - default: - mKeyTextStyle = Typeface.defaultFromStyle(textStyle); - break; - } - break; - case R.styleable.LatinKeyboardBaseView_symbolColorScheme: - mSymbolColorScheme = a.getInt(attr, 0); - break; - } - } - - final Resources res = getResources(); - - mPreviewPopup = new PopupWindow(context); - if (previewLayout != 0) { - mPreviewText = (TextView) inflate.inflate(previewLayout, null); - mPreviewTextSizeLarge = (int) res.getDimension(R.dimen.key_preview_text_size_large); - mPreviewPopup.setContentView(mPreviewText); - mPreviewPopup.setBackgroundDrawable(null); - } else { - mShowPreview = false; - } - mPreviewPopup.setTouchable(false); - mPreviewPopup.setAnimationStyle(R.style.KeyPreviewAnimation); - mDelayBeforePreview = res.getInteger(R.integer.config_delay_before_preview); - mDelayAfterPreview = res.getInteger(R.integer.config_delay_after_preview); - - mMiniKeyboardParent = this; - mMiniKeyboardPopup = new PopupWindow(context); - mMiniKeyboardPopup.setBackgroundDrawable(null); - mMiniKeyboardPopup.setAnimationStyle(R.style.MiniKeyboardAnimation); - - mPaint = new Paint(); - mPaint.setAntiAlias(true); - mPaint.setTextSize(keyTextSize); - mPaint.setTextAlign(Align.CENTER); - mPaint.setAlpha(255); - - mPadding = new Rect(0, 0, 0, 0); - mKeyBackground.getPadding(mPadding); - - mSwipeThreshold = (int) (500 * res.getDisplayMetrics().density); - // TODO: Refer frameworks/base/core/res/res/values/config.xml - mDisambiguateSwipe = res.getBoolean(R.bool.config_swipeDisambiguation); - mMiniKeyboardSlideAllowance = res.getDimension(R.dimen.mini_keyboard_slide_allowance); - - GestureDetector.SimpleOnGestureListener listener = - new GestureDetector.SimpleOnGestureListener() { - @Override - public boolean onFling(MotionEvent me1, MotionEvent me2, float velocityX, - float velocityY) { - final float absX = Math.abs(velocityX); - final float absY = Math.abs(velocityY); - float deltaX = me2.getX() - me1.getX(); - float deltaY = me2.getY() - me1.getY(); - int travelX = getWidth() / 2; // Half the keyboard width - int travelY = getHeight() / 2; // Half the keyboard height - mSwipeTracker.computeCurrentVelocity(1000); - final float endingVelocityX = mSwipeTracker.getXVelocity(); - final float endingVelocityY = mSwipeTracker.getYVelocity(); - if (velocityX > mSwipeThreshold && absY < absX && deltaX > travelX) { - if (mDisambiguateSwipe && endingVelocityX >= velocityX / 4) { - swipeRight(); - return true; - } - } else if (velocityX < -mSwipeThreshold && absY < absX && deltaX < -travelX) { - if (mDisambiguateSwipe && endingVelocityX <= velocityX / 4) { - swipeLeft(); - return true; - } - } else if (velocityY < -mSwipeThreshold && absX < absY && deltaY < -travelY) { - if (mDisambiguateSwipe && endingVelocityY <= velocityY / 4) { - swipeUp(); - return true; - } - } else if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) { - if (mDisambiguateSwipe && endingVelocityY >= velocityY / 4) { - swipeDown(); - return true; - } - } - return false; - } - }; - - final boolean ignoreMultitouch = true; - mGestureDetector = new GestureDetector(getContext(), listener, null, ignoreMultitouch); - mGestureDetector.setIsLongpressEnabled(false); - - mHasDistinctMultitouch = context.getPackageManager() - .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); - mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval); - } - - public void setOnKeyboardActionListener(OnKeyboardActionListener listener) { - mKeyboardActionListener = listener; - for (PointerTracker tracker : mPointerTrackers) { - tracker.setOnKeyboardActionListener(listener); - } - } - - /** - * Returns the {@link OnKeyboardActionListener} object. - * @return the listener attached to this keyboard - */ - protected OnKeyboardActionListener getOnKeyboardActionListener() { - return mKeyboardActionListener; - } - - /** - * Attaches a keyboard to this view. The keyboard can be switched at any time and the - * view will re-layout itself to accommodate the keyboard. - * @see Keyboard - * @see #getKeyboard() - * @param keyboard the keyboard to display in this view - */ - public void setKeyboard(Keyboard keyboard) { - if (mKeyboard != null) { - dismissKeyPreview(); - } - // Remove any pending messages, except dismissing preview - mHandler.cancelKeyTimers(); - mHandler.cancelPopupPreview(); - mKeyboard = keyboard; - LatinImeLogger.onSetKeyboard(keyboard); - mKeys = mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), - -getPaddingTop() + mVerticalCorrection); - mKeyboardVerticalGap = (int)getResources().getDimension(R.dimen.key_bottom_gap); - for (PointerTracker tracker : mPointerTrackers) { - tracker.setKeyboard(mKeys, mKeyHysteresisDistance); - } - requestLayout(); - // Hint to reallocate the buffer if the size changed - mKeyboardChanged = true; - invalidateAllKeys(); - computeProximityThreshold(keyboard); - mMiniKeyboardCache.clear(); - } - - /** - * Returns the current keyboard being displayed by this view. - * @return the currently attached keyboard - * @see #setKeyboard(Keyboard) - */ - public Keyboard getKeyboard() { - return mKeyboard; - } - - /** - * Return whether the device has distinct multi-touch panel. - * @return true if the device has distinct multi-touch panel. - */ - public boolean hasDistinctMultitouch() { - return mHasDistinctMultitouch; - } - - /** - * Sets the state of the shift key of the keyboard, if any. - * @param shifted whether or not to enable the state of the shift key - * @return true if the shift key state changed, false if there was no change - */ - public boolean setShifted(boolean shifted) { - if (mKeyboard != null) { - if (mKeyboard.setShifted(shifted)) { - // The whole keyboard probably needs to be redrawn - invalidateAllKeys(); - return true; - } - } - return false; - } - - /** - * Returns the state of the shift key of the keyboard, if any. - * @return true if the shift is in a pressed state, false otherwise. If there is - * no shift key on the keyboard or there is no keyboard attached, it returns false. - */ - public boolean isShifted() { - if (mKeyboard != null) { - return mKeyboard.isShifted(); - } - return false; - } - - /** - * Enables or disables the key feedback popup. This is a popup that shows a magnified - * version of the depressed key. By default the preview is enabled. - * @param previewEnabled whether or not to enable the key feedback popup - * @see #isPreviewEnabled() - */ - public void setPreviewEnabled(boolean previewEnabled) { - mShowPreview = previewEnabled; - } - - /** - * Returns the enabled state of the key feedback popup. - * @return whether or not the key feedback popup is enabled - * @see #setPreviewEnabled(boolean) - */ - public boolean isPreviewEnabled() { - return mShowPreview; - } - - public int getSymbolColorScheme() { - return mSymbolColorScheme; - } - - public void setPopupParent(View v) { - mMiniKeyboardParent = v; - } - - public void setPopupOffset(int x, int y) { - mPopupPreviewOffsetX = x; - mPopupPreviewOffsetY = y; - mPreviewPopup.dismiss(); - } - - /** - * When enabled, calls to {@link OnKeyboardActionListener#onKey} will include key - * codes for adjacent keys. When disabled, only the primary key code will be - * reported. - * @param enabled whether or not the proximity correction is enabled - */ - public void setProximityCorrectionEnabled(boolean enabled) { - mKeyDetector.setProximityCorrectionEnabled(enabled); - } - - /** - * Returns true if proximity correction is enabled. - */ - public boolean isProximityCorrectionEnabled() { - return mKeyDetector.isProximityCorrectionEnabled(); - } - - protected CharSequence adjustCase(CharSequence label) { - if (mKeyboard.isShifted() && label != null && label.length() < 3 - && Character.isLowerCase(label.charAt(0))) { - label = label.toString().toUpperCase(); - } - return label; - } - - @Override - public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - // Round up a little - if (mKeyboard == null) { - setMeasuredDimension( - getPaddingLeft() + getPaddingRight(), getPaddingTop() + getPaddingBottom()); - } else { - int width = mKeyboard.getMinWidth() + getPaddingLeft() + getPaddingRight(); - if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) { - width = MeasureSpec.getSize(widthMeasureSpec); - } - setMeasuredDimension( - width, mKeyboard.getHeight() + getPaddingTop() + getPaddingBottom()); - } - } - - /** - * Compute the average distance between adjacent keys (horizontally and vertically) - * and square it to get the proximity threshold. We use a square here and in computing - * the touch distance from a key's center to avoid taking a square root. - * @param keyboard - */ - private void computeProximityThreshold(Keyboard keyboard) { - if (keyboard == null) return; - final Key[] keys = mKeys; - if (keys == null) return; - int length = keys.length; - int dimensionSum = 0; - for (int i = 0; i < length; i++) { - Key key = keys[i]; - dimensionSum += Math.min(key.width, key.height + mKeyboardVerticalGap) + key.gap; - } - if (dimensionSum < 0 || length == 0) return; - mKeyDetector.setProximityThreshold((int) (dimensionSum * 1.4f / length)); - } - - @Override - public void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - // Release the buffer, if any and it will be reallocated on the next draw - mBuffer = null; - } - - @Override - public void onDraw(Canvas canvas) { - super.onDraw(canvas); - if (mDrawPending || mBuffer == null || mKeyboardChanged) { - onBufferDraw(); - } - canvas.drawBitmap(mBuffer, 0, 0, null); - } - - private void onBufferDraw() { - if (mBuffer == null || mKeyboardChanged) { - if (mBuffer == null || mKeyboardChanged && - (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) { - // Make sure our bitmap is at least 1x1 - final int width = Math.max(1, getWidth()); - final int height = Math.max(1, getHeight()); - mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - mCanvas = new Canvas(mBuffer); - } - invalidateAllKeys(); - mKeyboardChanged = false; - } - final Canvas canvas = mCanvas; - canvas.clipRect(mDirtyRect, Op.REPLACE); - - if (mKeyboard == null) return; - - final Paint paint = mPaint; - final Drawable keyBackground = mKeyBackground; - final Rect clipRegion = mClipRegion; - final Rect padding = mPadding; - final int kbdPaddingLeft = getPaddingLeft(); - final int kbdPaddingTop = getPaddingTop(); - final Key[] keys = mKeys; - final Key invalidKey = mInvalidatedKey; - - paint.setColor(mKeyTextColor); - boolean drawSingleKey = false; - if (invalidKey != null && canvas.getClipBounds(clipRegion)) { - // TODO we should use Rect.inset and Rect.contains here. - // Is clipRegion completely contained within the invalidated key? - if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left && - invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top && - invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right && - invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) { - drawSingleKey = true; - } - } - canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR); - final int keyCount = keys.length; - for (int i = 0; i < keyCount; i++) { - final Key key = keys[i]; - if (drawSingleKey && invalidKey != key) { - continue; - } - int[] drawableState = key.getCurrentDrawableState(); - keyBackground.setState(drawableState); - - // Switch the character to uppercase if shift is pressed - String label = key.label == null? null : adjustCase(key.label).toString(); - - final Rect bounds = keyBackground.getBounds(); - if (key.width != bounds.right || key.height != bounds.bottom) { - keyBackground.setBounds(0, 0, key.width, key.height); - } - canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop); - keyBackground.draw(canvas); - - boolean shouldDrawIcon = true; - if (label != null) { - // For characters, use large font. For labels like "Done", use small font. - final int labelSize; - if (label.length() > 1 && key.codes.length < 2) { - labelSize = mLabelTextSize; - paint.setTypeface(Typeface.DEFAULT_BOLD); - } else { - labelSize = mKeyTextSize; - paint.setTypeface(mKeyTextStyle); - } - paint.setTextSize(labelSize); - - Integer labelHeightValue = mTextHeightCache.get(labelSize); - final int labelHeight; - if (labelHeightValue != null) { - labelHeight = labelHeightValue; - } else { - Rect textBounds = new Rect(); - paint.getTextBounds(KEY_LABEL_HEIGHT_REFERENCE_CHAR, 0, 1, textBounds); - labelHeight = textBounds.height(); - mTextHeightCache.put(labelSize, labelHeight); - } - - // Draw a drop shadow for the text - paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor); - final int centerX = (key.width + padding.left - padding.right) / 2; - final int centerY = (key.height + padding.top - padding.bottom) / 2; - final float baseline = centerY - + labelHeight * KEY_LABEL_VERTICAL_ADJUSTMENT_FACTOR; - canvas.drawText(label, centerX, baseline, paint); - // Turn off drop shadow - paint.setShadowLayer(0, 0, 0, 0); - - // Usually don't draw icon if label is not null, but we draw icon for the number - // hint and popup hint. - shouldDrawIcon = shouldDrawLabelAndIcon(key); - } - if (key.icon != null && shouldDrawIcon) { - // Special handing for the upper-right number hint icons - final int drawableWidth; - final int drawableHeight; - final int drawableX; - final int drawableY; - if (shouldDrawIconFully(key)) { - drawableWidth = key.width; - drawableHeight = key.height; - drawableX = 0; - drawableY = NUMBER_HINT_VERTICAL_ADJUSTMENT_PIXEL; - } else { - drawableWidth = key.icon.getIntrinsicWidth(); - drawableHeight = key.icon.getIntrinsicHeight(); - drawableX = (key.width + padding.left - padding.right - drawableWidth) / 2; - drawableY = (key.height + padding.top - padding.bottom - drawableHeight) / 2; - } - canvas.translate(drawableX, drawableY); - key.icon.setBounds(0, 0, drawableWidth, drawableHeight); - key.icon.draw(canvas); - canvas.translate(-drawableX, -drawableY); - } - canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop); - } - mInvalidatedKey = null; - // Overlay a dark rectangle to dim the keyboard - if (mMiniKeyboard != null) { - paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24); - canvas.drawRect(0, 0, getWidth(), getHeight(), paint); - } - - if (DEBUG) { - if (mShowTouchPoints) { - for (PointerTracker tracker : mPointerTrackers) { - int startX = tracker.getStartX(); - int startY = tracker.getStartY(); - int lastX = tracker.getLastX(); - int lastY = tracker.getLastY(); - paint.setAlpha(128); - paint.setColor(0xFFFF0000); - canvas.drawCircle(startX, startY, 3, paint); - canvas.drawLine(startX, startY, lastX, lastY, paint); - paint.setColor(0xFF0000FF); - canvas.drawCircle(lastX, lastY, 3, paint); - paint.setColor(0xFF00FF00); - canvas.drawCircle((startX + lastX) / 2, (startY + lastY) / 2, 2, paint); - } - } - } - - mDrawPending = false; - mDirtyRect.setEmpty(); - } - - // TODO: clean up this method. - private void dismissKeyPreview() { - for (PointerTracker tracker : mPointerTrackers) - tracker.updateKey(NOT_A_KEY); - showPreview(NOT_A_KEY, null); - } - - public void showPreview(int keyIndex, PointerTracker tracker) { - int oldKeyIndex = mOldPreviewKeyIndex; - mOldPreviewKeyIndex = keyIndex; - final boolean isLanguageSwitchEnabled = (mKeyboard instanceof LatinKeyboard) - && ((LatinKeyboard)mKeyboard).isLanguageSwitchEnabled(); - // We should re-draw popup preview when 1) we need to hide the preview, 2) we will show - // the space key preview and 3) pointer moves off the space key to other letter key, we - // should hide the preview of the previous key. - final boolean hidePreviewOrShowSpaceKeyPreview = (tracker == null) - || tracker.isSpaceKey(keyIndex) || tracker.isSpaceKey(oldKeyIndex); - // If key changed and preview is on or the key is space (language switch is enabled) - if (oldKeyIndex != keyIndex - && (mShowPreview - || (hidePreviewOrShowSpaceKeyPreview && isLanguageSwitchEnabled))) { - if (keyIndex == NOT_A_KEY) { - mHandler.cancelPopupPreview(); - mHandler.dismissPreview(mDelayAfterPreview); - } else if (tracker != null) { - mHandler.popupPreview(mDelayBeforePreview, keyIndex, tracker); - } - } - } - - private void showKey(final int keyIndex, PointerTracker tracker) { - Key key = tracker.getKey(keyIndex); - if (key == null) - return; - // Should not draw hint icon in key preview - if (key.icon != null && !shouldDrawLabelAndIcon(key)) { - mPreviewText.setCompoundDrawables(null, null, null, - key.iconPreview != null ? key.iconPreview : key.icon); - mPreviewText.setText(null); - } else { - mPreviewText.setCompoundDrawables(null, null, null, null); - mPreviewText.setText(adjustCase(tracker.getPreviewText(key))); - if (key.label.length() > 1 && key.codes.length < 2) { - mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyTextSize); - mPreviewText.setTypeface(Typeface.DEFAULT_BOLD); - } else { - mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge); - mPreviewText.setTypeface(mKeyTextStyle); - } - } - mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); - int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width - + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight()); - final int popupHeight = mPreviewHeight; - LayoutParams lp = mPreviewText.getLayoutParams(); - if (lp != null) { - lp.width = popupWidth; - lp.height = popupHeight; - } - - int popupPreviewX = key.x - (popupWidth - key.width) / 2; - int popupPreviewY = key.y - popupHeight + mPreviewOffset; - - mHandler.cancelDismissPreview(); - if (mOffsetInWindow == null) { - mOffsetInWindow = new int[2]; - getLocationInWindow(mOffsetInWindow); - mOffsetInWindow[0] += mPopupPreviewOffsetX; // Offset may be zero - mOffsetInWindow[1] += mPopupPreviewOffsetY; // Offset may be zero - int[] windowLocation = new int[2]; - getLocationOnScreen(windowLocation); - mWindowY = windowLocation[1]; - } - // Set the preview background state - mPreviewText.getBackground().setState( - key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET); - popupPreviewX += mOffsetInWindow[0]; - popupPreviewY += mOffsetInWindow[1]; - - // If the popup cannot be shown above the key, put it on the side - if (popupPreviewY + mWindowY < 0) { - // If the key you're pressing is on the left side of the keyboard, show the popup on - // the right, offset by enough to see at least one key to the left/right. - if (key.x + key.width <= getWidth() / 2) { - popupPreviewX += (int) (key.width * 2.5); - } else { - popupPreviewX -= (int) (key.width * 2.5); - } - popupPreviewY += popupHeight; - } - - if (mPreviewPopup.isShowing()) { - mPreviewPopup.update(popupPreviewX, popupPreviewY, popupWidth, popupHeight); - } else { - mPreviewPopup.setWidth(popupWidth); - mPreviewPopup.setHeight(popupHeight); - mPreviewPopup.showAtLocation(mMiniKeyboardParent, Gravity.NO_GRAVITY, - popupPreviewX, popupPreviewY); - } - // Record popup preview position to display mini-keyboard later at the same positon - mPopupPreviewDisplayedY = popupPreviewY; - mPreviewText.setVisibility(VISIBLE); - } - - /** - * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient - * because the keyboard renders the keys to an off-screen buffer and an invalidate() only - * draws the cached buffer. - * @see #invalidateKey(Key) - */ - public void invalidateAllKeys() { - mDirtyRect.union(0, 0, getWidth(), getHeight()); - mDrawPending = true; - invalidate(); - } - - /** - * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only - * one key is changing it's content. Any changes that affect the position or size of the key - * may not be honored. - * @param key key in the attached {@link Keyboard}. - * @see #invalidateAllKeys - */ - public void invalidateKey(Key key) { - if (key == null) - return; - mInvalidatedKey = key; - // TODO we should clean up this and record key's region to use in onBufferDraw. - mDirtyRect.union(key.x + getPaddingLeft(), key.y + getPaddingTop(), - key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop()); - onBufferDraw(); - invalidate(key.x + getPaddingLeft(), key.y + getPaddingTop(), - key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop()); - } - - private boolean openPopupIfRequired(int keyIndex, PointerTracker tracker) { - // Check if we have a popup layout specified first. - if (mPopupLayout == 0) { - return false; - } - - Key popupKey = tracker.getKey(keyIndex); - if (popupKey == null) - return false; - boolean result = onLongPress(popupKey); - if (result) { - dismissKeyPreview(); - mMiniKeyboardTrackerId = tracker.mPointerId; - // Mark this tracker "already processed" and remove it from the pointer queue - tracker.setAlreadyProcessed(); - mPointerQueue.remove(tracker); - } - return result; - } - - private View inflateMiniKeyboardContainer(Key popupKey) { - int popupKeyboardId = popupKey.popupResId; - LayoutInflater inflater = (LayoutInflater)getContext().getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - View container = inflater.inflate(mPopupLayout, null); - if (container == null) - throw new NullPointerException(); - - LatinKeyboardBaseView miniKeyboard = - (LatinKeyboardBaseView)container.findViewById(R.id.LatinKeyboardBaseView); - miniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() { - public void onKey(int primaryCode, int[] keyCodes, int x, int y) { - mKeyboardActionListener.onKey(primaryCode, keyCodes, x, y); - dismissPopupKeyboard(); - } - - public void onText(CharSequence text) { - mKeyboardActionListener.onText(text); - dismissPopupKeyboard(); - } - - public void onCancel() { - dismissPopupKeyboard(); - } - - public void swipeLeft() { - } - public void swipeRight() { - } - public void swipeUp() { - } - public void swipeDown() { - } - public void onPress(int primaryCode) { - mKeyboardActionListener.onPress(primaryCode); - } - public void onRelease(int primaryCode) { - mKeyboardActionListener.onRelease(primaryCode); - } - }); - // Override default ProximityKeyDetector. - miniKeyboard.mKeyDetector = new MiniKeyboardKeyDetector(mMiniKeyboardSlideAllowance); - // Remove gesture detector on mini-keyboard - miniKeyboard.mGestureDetector = null; - - Keyboard keyboard; - if (popupKey.popupCharacters != null) { - keyboard = new Keyboard(getContext(), popupKeyboardId, popupKey.popupCharacters, - -1, getPaddingLeft() + getPaddingRight()); - } else { - keyboard = new Keyboard(getContext(), popupKeyboardId); - } - miniKeyboard.setKeyboard(keyboard); - miniKeyboard.setPopupParent(this); - - container.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), - MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST)); - - return container; - } - - private static boolean isOneRowKeys(List<Key> keys) { - if (keys.size() == 0) return false; - final int edgeFlags = keys.get(0).edgeFlags; - // HACK: The first key of mini keyboard which was inflated from xml and has multiple rows, - // does not have both top and bottom edge flags on at the same time. On the other hand, - // the first key of mini keyboard that was created with popupCharacters must have both top - // and bottom edge flags on. - // When you want to use one row mini-keyboard from xml file, make sure that the row has - // both top and bottom edge flags set. - return (edgeFlags & Keyboard.EDGE_TOP) != 0 && (edgeFlags & Keyboard.EDGE_BOTTOM) != 0; - } - - /** - * Called when a key is long pressed. By default this will open any popup keyboard associated - * with this key through the attributes popupLayout and popupCharacters. - * @param popupKey the key that was long pressed - * @return true if the long press is handled, false otherwise. Subclasses should call the - * method on the base class if the subclass doesn't wish to handle the call. - */ - protected boolean onLongPress(Key popupKey) { - // TODO if popupKey.popupCharacters has only one letter, send it as key without opening - // mini keyboard. - - if (popupKey.popupResId == 0) - return false; - - View container = mMiniKeyboardCache.get(popupKey); - if (container == null) { - container = inflateMiniKeyboardContainer(popupKey); - mMiniKeyboardCache.put(popupKey, container); - } - mMiniKeyboard = (LatinKeyboardBaseView)container.findViewById(R.id.LatinKeyboardBaseView); - if (mWindowOffset == null) { - mWindowOffset = new int[2]; - getLocationInWindow(mWindowOffset); - } - - // Get width of a key in the mini popup keyboard = "miniKeyWidth". - // On the other hand, "popupKey.width" is width of the pressed key on the main keyboard. - // We adjust the position of mini popup keyboard with the edge key in it: - // a) When we have the leftmost key in popup keyboard directly above the pressed key - // Right edges of both keys should be aligned for consistent default selection - // b) When we have the rightmost key in popup keyboard directly above the pressed key - // Left edges of both keys should be aligned for consistent default selection - final List<Key> miniKeys = mMiniKeyboard.getKeyboard().getKeys(); - final int miniKeyWidth = miniKeys.size() > 0 ? miniKeys.get(0).width : 0; - - // HACK: Have the leftmost number in the popup characters right above the key - boolean isNumberAtLeftmost = - hasMultiplePopupChars(popupKey) && isNumberAtLeftmostPopupChar(popupKey); - int popupX = popupKey.x + mWindowOffset[0]; - popupX += getPaddingLeft(); - if (isNumberAtLeftmost) { - popupX += popupKey.width - miniKeyWidth; // adjustment for a) described above - popupX -= container.getPaddingLeft(); - } else { - popupX += miniKeyWidth; // adjustment for b) described above - popupX -= container.getMeasuredWidth(); - popupX += container.getPaddingRight(); - } - int popupY = popupKey.y + mWindowOffset[1]; - popupY += getPaddingTop(); - popupY -= container.getMeasuredHeight(); - popupY += container.getPaddingBottom(); - final int x = popupX; - final int y = mShowPreview && isOneRowKeys(miniKeys) ? mPopupPreviewDisplayedY : popupY; - - int adjustedX = x; - if (x < 0) { - adjustedX = 0; - } else if (x > (getMeasuredWidth() - container.getMeasuredWidth())) { - adjustedX = getMeasuredWidth() - container.getMeasuredWidth(); - } - mMiniKeyboardOriginX = adjustedX + container.getPaddingLeft() - mWindowOffset[0]; - mMiniKeyboardOriginY = y + container.getPaddingTop() - mWindowOffset[1]; - mMiniKeyboard.setPopupOffset(adjustedX, y); - mMiniKeyboard.setShifted(isShifted()); - // Mini keyboard needs no pop-up key preview displayed. - mMiniKeyboard.setPreviewEnabled(false); - mMiniKeyboardPopup.setContentView(container); - mMiniKeyboardPopup.setWidth(container.getMeasuredWidth()); - mMiniKeyboardPopup.setHeight(container.getMeasuredHeight()); - mMiniKeyboardPopup.showAtLocation(this, Gravity.NO_GRAVITY, x, y); - - // Inject down event on the key to mini keyboard. - long eventTime = SystemClock.uptimeMillis(); - mMiniKeyboardPopupTime = eventTime; - MotionEvent downEvent = generateMiniKeyboardMotionEvent(MotionEvent.ACTION_DOWN, popupKey.x - + popupKey.width / 2, popupKey.y + popupKey.height / 2, eventTime); - mMiniKeyboard.onTouchEvent(downEvent); - downEvent.recycle(); - - invalidateAllKeys(); - return true; - } - - private static boolean hasMultiplePopupChars(Key key) { - if (key.popupCharacters != null && key.popupCharacters.length() > 1) { - return true; - } - return false; - } - - private boolean shouldDrawIconFully(Key key) { - return isNumberAtEdgeOfPopupChars(key) || isLatinF1Key(key) - || LatinKeyboard.hasPuncOrSmileysPopup(key); - } - - private boolean shouldDrawLabelAndIcon(Key key) { - return isNumberAtEdgeOfPopupChars(key) || isNonMicLatinF1Key(key) - || LatinKeyboard.hasPuncOrSmileysPopup(key); - } - - private boolean isLatinF1Key(Key key) { - return (mKeyboard instanceof LatinKeyboard) && ((LatinKeyboard)mKeyboard).isF1Key(key); - } - - private boolean isNonMicLatinF1Key(Key key) { - return isLatinF1Key(key) && key.label != null; - } - - private static boolean isNumberAtEdgeOfPopupChars(Key key) { - return isNumberAtLeftmostPopupChar(key) || isNumberAtRightmostPopupChar(key); - } - - /* package */ static boolean isNumberAtLeftmostPopupChar(Key key) { - if (key.popupCharacters != null && key.popupCharacters.length() > 0 - && isAsciiDigit(key.popupCharacters.charAt(0))) { - return true; - } - return false; - } - - /* package */ static boolean isNumberAtRightmostPopupChar(Key key) { - if (key.popupCharacters != null && key.popupCharacters.length() > 0 - && isAsciiDigit(key.popupCharacters.charAt(key.popupCharacters.length() - 1))) { - return true; - } - return false; - } - - private static boolean isAsciiDigit(char c) { - return (c < 0x80) && Character.isDigit(c); - } - - private MotionEvent generateMiniKeyboardMotionEvent(int action, int x, int y, long eventTime) { - return MotionEvent.obtain(mMiniKeyboardPopupTime, eventTime, action, - x - mMiniKeyboardOriginX, y - mMiniKeyboardOriginY, 0); - } - - private PointerTracker getPointerTracker(final int id) { - final ArrayList<PointerTracker> pointers = mPointerTrackers; - final Key[] keys = mKeys; - final OnKeyboardActionListener listener = mKeyboardActionListener; - - // Create pointer trackers until we can get 'id+1'-th tracker, if needed. - for (int i = pointers.size(); i <= id; i++) { - final PointerTracker tracker = - new PointerTracker(i, mHandler, mKeyDetector, this, getResources()); - if (keys != null) - tracker.setKeyboard(keys, mKeyHysteresisDistance); - if (listener != null) - tracker.setOnKeyboardActionListener(listener); - pointers.add(tracker); - } - - return pointers.get(id); - } - - @Override - public boolean onTouchEvent(MotionEvent me) { - final int pointerCount = me.getPointerCount(); - final int action = me.getActionMasked(); - - // TODO: cleanup this code into a multi-touch to single-touch event converter class? - // If the device does not have distinct multi-touch support panel, ignore all multi-touch - // events except a transition from/to single-touch. - if (!mHasDistinctMultitouch && pointerCount > 1 && mOldPointerCount > 1) { - return true; - } - - // Track the last few movements to look for spurious swipes. - mSwipeTracker.addMovement(me); - - // Gesture detector must be enabled only when mini-keyboard is not on the screen. - if (mMiniKeyboard == null - && mGestureDetector != null && mGestureDetector.onTouchEvent(me)) { - dismissKeyPreview(); - mHandler.cancelKeyTimers(); - return true; - } - - final long eventTime = me.getEventTime(); - final int index = me.getActionIndex(); - final int id = me.getPointerId(index); - final int x = (int)me.getX(index); - final int y = (int)me.getY(index); - - // Needs to be called after the gesture detector gets a turn, as it may have - // displayed the mini keyboard - if (mMiniKeyboard != null) { - final int miniKeyboardPointerIndex = me.findPointerIndex(mMiniKeyboardTrackerId); - if (miniKeyboardPointerIndex >= 0 && miniKeyboardPointerIndex < pointerCount) { - final int miniKeyboardX = (int)me.getX(miniKeyboardPointerIndex); - final int miniKeyboardY = (int)me.getY(miniKeyboardPointerIndex); - MotionEvent translated = generateMiniKeyboardMotionEvent(action, - miniKeyboardX, miniKeyboardY, eventTime); - mMiniKeyboard.onTouchEvent(translated); - translated.recycle(); - } - return true; - } - - if (mHandler.isInKeyRepeat()) { - // It will keep being in the key repeating mode while the key is being pressed. - if (action == MotionEvent.ACTION_MOVE) { - return true; - } - final PointerTracker tracker = getPointerTracker(id); - // Key repeating timer will be canceled if 2 or more keys are in action, and current - // event (UP or DOWN) is non-modifier key. - if (pointerCount > 1 && !tracker.isModifier()) { - mHandler.cancelKeyRepeatTimer(); - } - // Up event will pass through. - } - - // TODO: cleanup this code into a multi-touch to single-touch event converter class? - // Translate mutli-touch event to single-touch events on the device that has no distinct - // multi-touch panel. - if (!mHasDistinctMultitouch) { - // Use only main (id=0) pointer tracker. - PointerTracker tracker = getPointerTracker(0); - int oldPointerCount = mOldPointerCount; - if (pointerCount == 1 && oldPointerCount == 2) { - // Multi-touch to single touch transition. - // Send a down event for the latest pointer. - tracker.onDownEvent(x, y, eventTime); - } else if (pointerCount == 2 && oldPointerCount == 1) { - // Single-touch to multi-touch transition. - // Send an up event for the last pointer. - tracker.onUpEvent(tracker.getLastX(), tracker.getLastY(), eventTime); - } else if (pointerCount == 1 && oldPointerCount == 1) { - tracker.onTouchEvent(action, x, y, eventTime); - } else { - Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount - + " (old " + oldPointerCount + ")"); - } - mOldPointerCount = pointerCount; - return true; - } - - if (action == MotionEvent.ACTION_MOVE) { - for (int i = 0; i < pointerCount; i++) { - PointerTracker tracker = getPointerTracker(me.getPointerId(i)); - tracker.onMoveEvent((int)me.getX(i), (int)me.getY(i), eventTime); - } - } else { - PointerTracker tracker = getPointerTracker(id); - switch (action) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: - onDownEvent(tracker, x, y, eventTime); - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_POINTER_UP: - onUpEvent(tracker, x, y, eventTime); - break; - case MotionEvent.ACTION_CANCEL: - onCancelEvent(tracker, x, y, eventTime); - break; - } - } - - return true; - } - - private void onDownEvent(PointerTracker tracker, int x, int y, long eventTime) { - if (tracker.isOnModifierKey(x, y)) { - // Before processing a down event of modifier key, all pointers already being tracked - // should be released. - mPointerQueue.releaseAllPointersExcept(null, eventTime); - } - tracker.onDownEvent(x, y, eventTime); - mPointerQueue.add(tracker); - } - - private void onUpEvent(PointerTracker tracker, int x, int y, long eventTime) { - if (tracker.isModifier()) { - // Before processing an up event of modifier key, all pointers already being tracked - // should be released. - mPointerQueue.releaseAllPointersExcept(tracker, eventTime); - } else { - int index = mPointerQueue.lastIndexOf(tracker); - if (index >= 0) { - mPointerQueue.releaseAllPointersOlderThan(tracker, eventTime); - } else { - Log.w(TAG, "onUpEvent: corresponding down event not found for pointer " - + tracker.mPointerId); - } - } - tracker.onUpEvent(x, y, eventTime); - mPointerQueue.remove(tracker); - } - - private void onCancelEvent(PointerTracker tracker, int x, int y, long eventTime) { - tracker.onCancelEvent(x, y, eventTime); - mPointerQueue.remove(tracker); - } - - protected void swipeRight() { - mKeyboardActionListener.swipeRight(); - } - - protected void swipeLeft() { - mKeyboardActionListener.swipeLeft(); - } - - protected void swipeUp() { - mKeyboardActionListener.swipeUp(); - } - - protected void swipeDown() { - mKeyboardActionListener.swipeDown(); - } - - public void closing() { - mPreviewPopup.dismiss(); - mHandler.cancelAllMessages(); - - dismissPopupKeyboard(); - mBuffer = null; - mCanvas = null; - mMiniKeyboardCache.clear(); - } - - @Override - public void onDetachedFromWindow() { - super.onDetachedFromWindow(); - closing(); - } - - private void dismissPopupKeyboard() { - if (mMiniKeyboardPopup.isShowing()) { - mMiniKeyboardPopup.dismiss(); - mMiniKeyboard = null; - mMiniKeyboardOriginX = 0; - mMiniKeyboardOriginY = 0; - invalidateAllKeys(); - } - } - - public boolean handleBack() { - if (mMiniKeyboardPopup.isShowing()) { - dismissPopupKeyboard(); - return true; - } - return false; - } -} diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboardView.java b/java/src/com/android/inputmethod/latin/LatinKeyboardView.java deleted file mode 100644 index 22d39f7aa..000000000 --- a/java/src/com/android/inputmethod/latin/LatinKeyboardView.java +++ /dev/null @@ -1,375 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.inputmethod.latin; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.inputmethodservice.Keyboard; -import android.inputmethodservice.Keyboard.Key; -import android.os.Handler; -import android.os.Message; -import android.os.SystemClock; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.view.MotionEvent; - -import java.util.List; - -public class LatinKeyboardView extends LatinKeyboardBaseView { - - static final int KEYCODE_OPTIONS = -100; - static final int KEYCODE_OPTIONS_LONGPRESS = -101; - static final int KEYCODE_VOICE = -102; - static final int KEYCODE_F1 = -103; - static final int KEYCODE_NEXT_LANGUAGE = -104; - static final int KEYCODE_PREV_LANGUAGE = -105; - - private Keyboard mPhoneKeyboard; - - /** Whether we've started dropping move events because we found a big jump */ - private boolean mDroppingEvents; - /** - * Whether multi-touch disambiguation needs to be disabled if a real multi-touch event has - * occured - */ - private boolean mDisableDisambiguation; - /** The distance threshold at which we start treating the touch session as a multi-touch */ - private int mJumpThresholdSquare = Integer.MAX_VALUE; - /** The y coordinate of the last row */ - private int mLastRowY; - - public LatinKeyboardView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public LatinKeyboardView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - public void setPhoneKeyboard(Keyboard phoneKeyboard) { - mPhoneKeyboard = phoneKeyboard; - } - - @Override - public void setPreviewEnabled(boolean previewEnabled) { - if (getKeyboard() == mPhoneKeyboard) { - // Phone keyboard never shows popup preview (except language switch). - super.setPreviewEnabled(false); - } else { - super.setPreviewEnabled(previewEnabled); - } - } - - @Override - public void setKeyboard(Keyboard k) { - super.setKeyboard(k); - // One-seventh of the keyboard width seems like a reasonable threshold - mJumpThresholdSquare = k.getMinWidth() / 7; - mJumpThresholdSquare *= mJumpThresholdSquare; - // Assuming there are 4 rows, this is the coordinate of the last row - mLastRowY = (k.getHeight() * 3) / 4; - setKeyboardLocal(k); - } - - @Override - protected boolean onLongPress(Key key) { - int primaryCode = key.codes[0]; - if (primaryCode == KEYCODE_OPTIONS) { - return invokeOnKey(KEYCODE_OPTIONS_LONGPRESS); - } else if (primaryCode == '0' && getKeyboard() == mPhoneKeyboard) { - // Long pressing on 0 in phone number keypad gives you a '+'. - return invokeOnKey('+'); - } else { - return super.onLongPress(key); - } - } - - private boolean invokeOnKey(int primaryCode) { - getOnKeyboardActionListener().onKey(primaryCode, null, - LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE, - LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE); - return true; - } - - @Override - protected CharSequence adjustCase(CharSequence label) { - Keyboard keyboard = getKeyboard(); - if (keyboard.isShifted() - && keyboard instanceof LatinKeyboard - && ((LatinKeyboard) keyboard).isAlphaKeyboard() - && !TextUtils.isEmpty(label) && label.length() < 3 - && Character.isLowerCase(label.charAt(0))) { - label = label.toString().toUpperCase(); - } - return label; - } - - public boolean setShiftLocked(boolean shiftLocked) { - Keyboard keyboard = getKeyboard(); - if (keyboard instanceof LatinKeyboard) { - ((LatinKeyboard)keyboard).setShiftLocked(shiftLocked); - invalidateAllKeys(); - return true; - } - return false; - } - - /** - * This function checks to see if we need to handle any sudden jumps in the pointer location - * that could be due to a multi-touch being treated as a move by the firmware or hardware. - * Once a sudden jump is detected, all subsequent move events are discarded - * until an UP is received.<P> - * When a sudden jump is detected, an UP event is simulated at the last position and when - * the sudden moves subside, a DOWN event is simulated for the second key. - * @param me the motion event - * @return true if the event was consumed, so that it doesn't continue to be handled by - * KeyboardView. - */ - private boolean handleSuddenJump(MotionEvent me) { - final int action = me.getAction(); - final int x = (int) me.getX(); - final int y = (int) me.getY(); - boolean result = false; - - // Real multi-touch event? Stop looking for sudden jumps - if (me.getPointerCount() > 1) { - mDisableDisambiguation = true; - } - if (mDisableDisambiguation) { - // If UP, reset the multi-touch flag - if (action == MotionEvent.ACTION_UP) mDisableDisambiguation = false; - return false; - } - - switch (action) { - case MotionEvent.ACTION_DOWN: - // Reset the "session" - mDroppingEvents = false; - mDisableDisambiguation = false; - break; - case MotionEvent.ACTION_MOVE: - // Is this a big jump? - final int distanceSquare = (mLastX - x) * (mLastX - x) + (mLastY - y) * (mLastY - y); - // Check the distance and also if the move is not entirely within the bottom row - // If it's only in the bottom row, it might be an intentional slide gesture - // for language switching - if (distanceSquare > mJumpThresholdSquare - && (mLastY < mLastRowY || y < mLastRowY)) { - // If we're not yet dropping events, start dropping and send an UP event - if (!mDroppingEvents) { - mDroppingEvents = true; - // Send an up event - MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(), - MotionEvent.ACTION_UP, - mLastX, mLastY, me.getMetaState()); - super.onTouchEvent(translated); - translated.recycle(); - } - result = true; - } else if (mDroppingEvents) { - // If moves are small and we're already dropping events, continue dropping - result = true; - } - break; - case MotionEvent.ACTION_UP: - if (mDroppingEvents) { - // Send a down event first, as we dropped a bunch of sudden jumps and assume that - // the user is releasing the touch on the second key. - MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(), - MotionEvent.ACTION_DOWN, - x, y, me.getMetaState()); - super.onTouchEvent(translated); - translated.recycle(); - mDroppingEvents = false; - // Let the up event get processed as well, result = false - } - break; - } - // Track the previous coordinate - mLastX = x; - mLastY = y; - return result; - } - - @Override - public boolean onTouchEvent(MotionEvent me) { - LatinKeyboard keyboard = (LatinKeyboard) getKeyboard(); - if (DEBUG_LINE) { - mLastX = (int) me.getX(); - mLastY = (int) me.getY(); - invalidate(); - } - - // If there was a sudden jump, return without processing the actual motion event. - if (handleSuddenJump(me)) - return true; - - // Reset any bounding box controls in the keyboard - if (me.getAction() == MotionEvent.ACTION_DOWN) { - keyboard.keyReleased(); - } - - if (me.getAction() == MotionEvent.ACTION_UP) { - int languageDirection = keyboard.getLanguageChangeDirection(); - if (languageDirection != 0) { - getOnKeyboardActionListener().onKey( - languageDirection == 1 ? KEYCODE_NEXT_LANGUAGE : KEYCODE_PREV_LANGUAGE, - null, mLastX, mLastY); - me.setAction(MotionEvent.ACTION_CANCEL); - keyboard.keyReleased(); - return super.onTouchEvent(me); - } - } - - return super.onTouchEvent(me); - } - - /**************************** INSTRUMENTATION *******************************/ - - static final boolean DEBUG_AUTO_PLAY = false; - static final boolean DEBUG_LINE = false; - private static final int MSG_TOUCH_DOWN = 1; - private static final int MSG_TOUCH_UP = 2; - - Handler mHandler2; - - private String mStringToPlay; - private int mStringIndex; - private boolean mDownDelivered; - private Key[] mAsciiKeys = new Key[256]; - private boolean mPlaying; - private int mLastX; - private int mLastY; - private Paint mPaint; - - private void setKeyboardLocal(Keyboard k) { - if (DEBUG_AUTO_PLAY) { - findKeys(); - if (mHandler2 == null) { - mHandler2 = new Handler() { - @Override - public void handleMessage(Message msg) { - removeMessages(MSG_TOUCH_DOWN); - removeMessages(MSG_TOUCH_UP); - if (mPlaying == false) return; - - switch (msg.what) { - case MSG_TOUCH_DOWN: - if (mStringIndex >= mStringToPlay.length()) { - mPlaying = false; - return; - } - char c = mStringToPlay.charAt(mStringIndex); - while (c > 255 || mAsciiKeys[c] == null) { - mStringIndex++; - if (mStringIndex >= mStringToPlay.length()) { - mPlaying = false; - return; - } - c = mStringToPlay.charAt(mStringIndex); - } - int x = mAsciiKeys[c].x + 10; - int y = mAsciiKeys[c].y + 26; - MotionEvent me = MotionEvent.obtain(SystemClock.uptimeMillis(), - SystemClock.uptimeMillis(), - MotionEvent.ACTION_DOWN, x, y, 0); - LatinKeyboardView.this.dispatchTouchEvent(me); - me.recycle(); - sendEmptyMessageDelayed(MSG_TOUCH_UP, 500); // Deliver up in 500ms if nothing else - // happens - mDownDelivered = true; - break; - case MSG_TOUCH_UP: - char cUp = mStringToPlay.charAt(mStringIndex); - int x2 = mAsciiKeys[cUp].x + 10; - int y2 = mAsciiKeys[cUp].y + 26; - mStringIndex++; - - MotionEvent me2 = MotionEvent.obtain(SystemClock.uptimeMillis(), - SystemClock.uptimeMillis(), - MotionEvent.ACTION_UP, x2, y2, 0); - LatinKeyboardView.this.dispatchTouchEvent(me2); - me2.recycle(); - sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 500); // Deliver up in 500ms if nothing else - // happens - mDownDelivered = false; - break; - } - } - }; - - } - } - } - - private void findKeys() { - List<Key> keys = getKeyboard().getKeys(); - // Get the keys on this keyboard - for (int i = 0; i < keys.size(); i++) { - int code = keys.get(i).codes[0]; - if (code >= 0 && code <= 255) { - mAsciiKeys[code] = keys.get(i); - } - } - } - - public void startPlaying(String s) { - if (DEBUG_AUTO_PLAY) { - if (s == null) return; - mStringToPlay = s.toLowerCase(); - mPlaying = true; - mDownDelivered = false; - mStringIndex = 0; - mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 10); - } - } - - @Override - public void draw(Canvas c) { - LatinIMEUtil.GCUtils.getInstance().reset(); - boolean tryGC = true; - for (int i = 0; i < LatinIMEUtil.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) { - try { - super.draw(c); - tryGC = false; - } catch (OutOfMemoryError e) { - tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait("LatinKeyboardView", e); - } - } - if (DEBUG_AUTO_PLAY) { - if (mPlaying) { - mHandler2.removeMessages(MSG_TOUCH_DOWN); - mHandler2.removeMessages(MSG_TOUCH_UP); - if (mDownDelivered) { - mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_UP, 20); - } else { - mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 20); - } - } - } - if (DEBUG_LINE) { - if (mPaint == null) { - mPaint = new Paint(); - mPaint.setColor(0x80FFFFFF); - mPaint.setAntiAlias(false); - } - c.drawLine(mLastX, 0, mLastX, getHeight(), mPaint); - c.drawLine(0, mLastY, getWidth(), mLastY, mPaint); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/MiniKeyboardKeyDetector.java b/java/src/com/android/inputmethod/latin/MiniKeyboardKeyDetector.java deleted file mode 100644 index 356e62d48..000000000 --- a/java/src/com/android/inputmethod/latin/MiniKeyboardKeyDetector.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2010 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.inputmethod.latin; - -import android.inputmethodservice.Keyboard.Key; - -class MiniKeyboardKeyDetector extends KeyDetector { - private static final int MAX_NEARBY_KEYS = 1; - - private final int mSlideAllowanceSquare; - private final int mSlideAllowanceSquareTop; - - public MiniKeyboardKeyDetector(float slideAllowance) { - super(); - mSlideAllowanceSquare = (int)(slideAllowance * slideAllowance); - // Top slide allowance is slightly longer (sqrt(2) times) than other edges. - mSlideAllowanceSquareTop = mSlideAllowanceSquare * 2; - } - - @Override - protected int getMaxNearbyKeys() { - return MAX_NEARBY_KEYS; - } - - @Override - public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys) { - final Key[] keys = getKeys(); - final int touchX = getTouchX(x); - final int touchY = getTouchY(y); - int closestKeyIndex = LatinKeyboardBaseView.NOT_A_KEY; - int closestKeyDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare; - final int keyCount = keys.length; - for (int i = 0; i < keyCount; i++) { - final Key key = keys[i]; - int dist = key.squaredDistanceFrom(touchX, touchY); - if (dist < closestKeyDist) { - closestKeyIndex = i; - closestKeyDist = dist; - } - } - if (allKeys != null && closestKeyIndex != LatinKeyboardBaseView.NOT_A_KEY) - allKeys[0] = keys[closestKeyIndex].codes[0]; - return closestKeyIndex; - } -} diff --git a/java/src/com/android/inputmethod/latin/ModifierKeyState.java b/java/src/com/android/inputmethod/latin/ModifierKeyState.java deleted file mode 100644 index 097e87abe..000000000 --- a/java/src/com/android/inputmethod/latin/ModifierKeyState.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2010 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.inputmethod.latin; - -class ModifierKeyState { - private static final int RELEASING = 0; - private static final int PRESSING = 1; - private static final int MOMENTARY = 2; - - private int mState = RELEASING; - - public void onPress() { - mState = PRESSING; - } - - public void onRelease() { - mState = RELEASING; - } - - public void onOtherKeyPressed() { - if (mState == PRESSING) - mState = MOMENTARY; - } - - public boolean isMomentary() { - return mState == MOMENTARY; - } -} diff --git a/java/src/com/android/inputmethod/latin/PointerTracker.java b/java/src/com/android/inputmethod/latin/PointerTracker.java deleted file mode 100644 index 90218e4ab..000000000 --- a/java/src/com/android/inputmethod/latin/PointerTracker.java +++ /dev/null @@ -1,531 +0,0 @@ -/* - * Copyright (C) 2010 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.inputmethod.latin; - -import com.android.inputmethod.latin.LatinKeyboardBaseView.OnKeyboardActionListener; -import com.android.inputmethod.latin.LatinKeyboardBaseView.UIHandler; - -import android.content.res.Resources; -import android.inputmethodservice.Keyboard; -import android.inputmethodservice.Keyboard.Key; -import android.util.Log; -import android.view.MotionEvent; - -public class PointerTracker { - private static final String TAG = "PointerTracker"; - private static final boolean DEBUG = false; - private static final boolean DEBUG_MOVE = false; - - public interface UIProxy { - public void invalidateKey(Key key); - public void showPreview(int keyIndex, PointerTracker tracker); - public boolean hasDistinctMultitouch(); - } - - public final int mPointerId; - - // Timing constants - private final int mDelayBeforeKeyRepeatStart; - private final int mLongPressKeyTimeout; - private final int mMultiTapKeyTimeout; - - // Miscellaneous constants - private static final int NOT_A_KEY = LatinKeyboardBaseView.NOT_A_KEY; - private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE }; - - private final UIProxy mProxy; - private final UIHandler mHandler; - private final KeyDetector mKeyDetector; - private OnKeyboardActionListener mListener; - private final boolean mHasDistinctMultitouch; - - private Key[] mKeys; - private int mKeyHysteresisDistanceSquared = -1; - - private final KeyState mKeyState; - - // true if event is already translated to a key action (long press or mini-keyboard) - private boolean mKeyAlreadyProcessed; - - // true if this pointer is repeatable key - private boolean mIsRepeatableKey; - - // For multi-tap - private int mLastSentIndex; - private int mTapCount; - private long mLastTapTime; - private boolean mInMultiTap; - private final StringBuilder mPreviewLabel = new StringBuilder(1); - - // pressed key - private int mPreviousKey = NOT_A_KEY; - - // This class keeps track of a key index and a position where this pointer is. - private static class KeyState { - private final KeyDetector mKeyDetector; - - // The position and time at which first down event occurred. - private int mStartX; - private int mStartY; - private long mDownTime; - - // The current key index where this pointer is. - private int mKeyIndex = NOT_A_KEY; - // The position where mKeyIndex was recognized for the first time. - private int mKeyX; - private int mKeyY; - - // Last pointer position. - private int mLastX; - private int mLastY; - - public KeyState(KeyDetector keyDetecor) { - mKeyDetector = keyDetecor; - } - - public int getKeyIndex() { - return mKeyIndex; - } - - public int getKeyX() { - return mKeyX; - } - - public int getKeyY() { - return mKeyY; - } - - public int getStartX() { - return mStartX; - } - - public int getStartY() { - return mStartY; - } - - public long getDownTime() { - return mDownTime; - } - - public int getLastX() { - return mLastX; - } - - public int getLastY() { - return mLastY; - } - - public int onDownKey(int x, int y, long eventTime) { - mStartX = x; - mStartY = y; - mDownTime = eventTime; - - return onMoveToNewKey(onMoveKeyInternal(x, y), x, y); - } - - private int onMoveKeyInternal(int x, int y) { - mLastX = x; - mLastY = y; - return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null); - } - - public int onMoveKey(int x, int y) { - return onMoveKeyInternal(x, y); - } - - public int onMoveToNewKey(int keyIndex, int x, int y) { - mKeyIndex = keyIndex; - mKeyX = x; - mKeyY = y; - return keyIndex; - } - - public int onUpKey(int x, int y) { - return onMoveKeyInternal(x, y); - } - - public void onSetKeyboard() { - mKeyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(mKeyX, mKeyY, null); - } - } - - public PointerTracker(int id, UIHandler handler, KeyDetector keyDetector, UIProxy proxy, - Resources res) { - if (proxy == null || handler == null || keyDetector == null) - throw new NullPointerException(); - mPointerId = id; - mProxy = proxy; - mHandler = handler; - mKeyDetector = keyDetector; - mKeyState = new KeyState(keyDetector); - mHasDistinctMultitouch = proxy.hasDistinctMultitouch(); - mDelayBeforeKeyRepeatStart = res.getInteger(R.integer.config_delay_before_key_repeat_start); - mLongPressKeyTimeout = res.getInteger(R.integer.config_long_press_key_timeout); - mMultiTapKeyTimeout = res.getInteger(R.integer.config_multi_tap_key_timeout); - resetMultiTap(); - } - - public void setOnKeyboardActionListener(OnKeyboardActionListener listener) { - mListener = listener; - } - - public void setKeyboard(Key[] keys, float keyHysteresisDistance) { - if (keys == null || keyHysteresisDistance < 0) - throw new IllegalArgumentException(); - mKeys = keys; - mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance); - // Update current key index because keyboard layout has been changed. - mKeyState.onSetKeyboard(); - } - - private boolean isValidKeyIndex(int keyIndex) { - return keyIndex >= 0 && keyIndex < mKeys.length; - } - - public Key getKey(int keyIndex) { - return isValidKeyIndex(keyIndex) ? mKeys[keyIndex] : null; - } - - private boolean isModifierInternal(int keyIndex) { - Key key = getKey(keyIndex); - if (key == null) - return false; - int primaryCode = key.codes[0]; - return primaryCode == Keyboard.KEYCODE_SHIFT - || primaryCode == Keyboard.KEYCODE_MODE_CHANGE; - } - - public boolean isModifier() { - return isModifierInternal(mKeyState.getKeyIndex()); - } - - public boolean isOnModifierKey(int x, int y) { - return isModifierInternal(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null)); - } - - public boolean isSpaceKey(int keyIndex) { - Key key = getKey(keyIndex); - return key != null && key.codes[0] == LatinIME.KEYCODE_SPACE; - } - - public void updateKey(int keyIndex) { - if (mKeyAlreadyProcessed) - return; - int oldKeyIndex = mPreviousKey; - mPreviousKey = keyIndex; - if (keyIndex != oldKeyIndex) { - if (isValidKeyIndex(oldKeyIndex)) { - // if new key index is not a key, old key was just released inside of the key. - final boolean inside = (keyIndex == NOT_A_KEY); - mKeys[oldKeyIndex].onReleased(inside); - mProxy.invalidateKey(mKeys[oldKeyIndex]); - } - if (isValidKeyIndex(keyIndex)) { - mKeys[keyIndex].onPressed(); - mProxy.invalidateKey(mKeys[keyIndex]); - } - } - } - - public void setAlreadyProcessed() { - mKeyAlreadyProcessed = true; - } - - public void onTouchEvent(int action, int x, int y, long eventTime) { - switch (action) { - case MotionEvent.ACTION_MOVE: - onMoveEvent(x, y, eventTime); - break; - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: - onDownEvent(x, y, eventTime); - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_POINTER_UP: - onUpEvent(x, y, eventTime); - break; - case MotionEvent.ACTION_CANCEL: - onCancelEvent(x, y, eventTime); - break; - } - } - - public void onDownEvent(int x, int y, long eventTime) { - if (DEBUG) - debugLog("onDownEvent:", x, y); - int keyIndex = mKeyState.onDownKey(x, y, eventTime); - mKeyAlreadyProcessed = false; - mIsRepeatableKey = false; - checkMultiTap(eventTime, keyIndex); - if (mListener != null) { - if (isValidKeyIndex(keyIndex)) { - mListener.onPress(mKeys[keyIndex].codes[0]); - // This onPress call may have changed keyboard layout and have updated mKeyIndex. - // If that's the case, mKeyIndex has been updated in setKeyboard(). - keyIndex = mKeyState.getKeyIndex(); - } - } - if (isValidKeyIndex(keyIndex)) { - if (mKeys[keyIndex].repeatable) { - repeatKey(keyIndex); - mHandler.startKeyRepeatTimer(mDelayBeforeKeyRepeatStart, keyIndex, this); - mIsRepeatableKey = true; - } - mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this); - } - showKeyPreviewAndUpdateKey(keyIndex); - } - - public void onMoveEvent(int x, int y, long eventTime) { - if (DEBUG_MOVE) - debugLog("onMoveEvent:", x, y); - if (mKeyAlreadyProcessed) - return; - KeyState keyState = mKeyState; - final int keyIndex = keyState.onMoveKey(x, y); - final Key oldKey = getKey(keyState.getKeyIndex()); - if (isValidKeyIndex(keyIndex)) { - if (oldKey == null) { - keyState.onMoveToNewKey(keyIndex, x, y); - mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this); - } else if (!isMinorMoveBounce(x, y, keyIndex)) { - if (mListener != null) - mListener.onRelease(oldKey.codes[0]); - resetMultiTap(); - keyState.onMoveToNewKey(keyIndex, x, y); - mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this); - } - } else { - if (oldKey != null) { - if (mListener != null) - mListener.onRelease(oldKey.codes[0]); - keyState.onMoveToNewKey(keyIndex, x ,y); - mHandler.cancelLongPressTimer(); - } else if (!isMinorMoveBounce(x, y, keyIndex)) { - resetMultiTap(); - keyState.onMoveToNewKey(keyIndex, x ,y); - mHandler.cancelLongPressTimer(); - } - } - showKeyPreviewAndUpdateKey(mKeyState.getKeyIndex()); - } - - public void onUpEvent(int x, int y, long eventTime) { - if (DEBUG) - debugLog("onUpEvent :", x, y); - if (mKeyAlreadyProcessed) - return; - mHandler.cancelKeyTimers(); - mHandler.cancelPopupPreview(); - int keyIndex = mKeyState.onUpKey(x, y); - if (isMinorMoveBounce(x, y, keyIndex)) { - // Use previous fixed key index and coordinates. - keyIndex = mKeyState.getKeyIndex(); - x = mKeyState.getKeyX(); - y = mKeyState.getKeyY(); - } - showKeyPreviewAndUpdateKey(NOT_A_KEY); - if (!mIsRepeatableKey) { - detectAndSendKey(keyIndex, x, y, eventTime); - } - - if (isValidKeyIndex(keyIndex)) - mProxy.invalidateKey(mKeys[keyIndex]); - } - - public void onCancelEvent(int x, int y, long eventTime) { - if (DEBUG) - debugLog("onCancelEvt:", x, y); - mHandler.cancelKeyTimers(); - mHandler.cancelPopupPreview(); - showKeyPreviewAndUpdateKey(NOT_A_KEY); - int keyIndex = mKeyState.getKeyIndex(); - if (isValidKeyIndex(keyIndex)) - mProxy.invalidateKey(mKeys[keyIndex]); - } - - public void repeatKey(int keyIndex) { - Key key = getKey(keyIndex); - if (key != null) { - // While key is repeating, because there is no need to handle multi-tap key, we can - // pass -1 as eventTime argument. - detectAndSendKey(keyIndex, key.x, key.y, -1); - } - } - - public int getLastX() { - return mKeyState.getLastX(); - } - - public int getLastY() { - return mKeyState.getLastY(); - } - - public long getDownTime() { - return mKeyState.getDownTime(); - } - - // These package scope methods are only for debugging purpose. - /* package */ int getStartX() { - return mKeyState.getStartX(); - } - - /* package */ int getStartY() { - return mKeyState.getStartY(); - } - - private boolean isMinorMoveBounce(int x, int y, int newKey) { - if (mKeys == null || mKeyHysteresisDistanceSquared < 0) - throw new IllegalStateException("keyboard and/or hysteresis not set"); - int curKey = mKeyState.getKeyIndex(); - if (newKey == curKey) { - return true; - } else if (isValidKeyIndex(curKey)) { - return getSquareDistanceToKeyEdge(x, y, mKeys[curKey]) < mKeyHysteresisDistanceSquared; - } else { - return false; - } - } - - private static int getSquareDistanceToKeyEdge(int x, int y, Key key) { - final int left = key.x; - final int right = key.x + key.width; - final int top = key.y; - final int bottom = key.y + key.height; - final int edgeX = x < left ? left : (x > right ? right : x); - final int edgeY = y < top ? top : (y > bottom ? bottom : y); - final int dx = x - edgeX; - final int dy = y - edgeY; - return dx * dx + dy * dy; - } - - private void showKeyPreviewAndUpdateKey(int keyIndex) { - updateKey(keyIndex); - // The modifier key, such as shift key, should not be shown as preview when multi-touch is - // supported. On the other hand, if multi-touch is not supported, the modifier key should - // be shown as preview. - if (mHasDistinctMultitouch && isModifier()) { - mProxy.showPreview(NOT_A_KEY, this); - } else { - mProxy.showPreview(keyIndex, this); - } - } - - private void detectAndSendKey(int index, int x, int y, long eventTime) { - final OnKeyboardActionListener listener = mListener; - final Key key = getKey(index); - - if (key == null) { - if (listener != null) - listener.onCancel(); - } else { - if (key.text != null) { - if (listener != null) { - listener.onText(key.text); - listener.onRelease(NOT_A_KEY); - } - } else { - int code = key.codes[0]; - //TextEntryState.keyPressedAt(key, x, y); - int[] codes = mKeyDetector.newCodeArray(); - mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes); - // Multi-tap - if (mInMultiTap) { - if (mTapCount != -1) { - mListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE, x, y); - } else { - mTapCount = 0; - } - code = key.codes[mTapCount]; - } - /* - * Swap the first and second values in the codes array if the primary code is not - * the first value but the second value in the array. This happens when key - * debouncing is in effect. - */ - if (codes.length >= 2 && codes[0] != code && codes[1] == code) { - codes[1] = codes[0]; - codes[0] = code; - } - if (listener != null) { - listener.onKey(code, codes, x, y); - listener.onRelease(code); - } - } - mLastSentIndex = index; - mLastTapTime = eventTime; - } - } - - /** - * Handle multi-tap keys by producing the key label for the current multi-tap state. - */ - public CharSequence getPreviewText(Key key) { - if (mInMultiTap) { - // Multi-tap - mPreviewLabel.setLength(0); - mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]); - return mPreviewLabel; - } else { - return key.label; - } - } - - private void resetMultiTap() { - mLastSentIndex = NOT_A_KEY; - mTapCount = 0; - mLastTapTime = -1; - mInMultiTap = false; - } - - private void checkMultiTap(long eventTime, int keyIndex) { - Key key = getKey(keyIndex); - if (key == null) - return; - - final boolean isMultiTap = - (eventTime < mLastTapTime + mMultiTapKeyTimeout && keyIndex == mLastSentIndex); - if (key.codes.length > 1) { - mInMultiTap = true; - if (isMultiTap) { - mTapCount = (mTapCount + 1) % key.codes.length; - return; - } else { - mTapCount = -1; - return; - } - } - if (!isMultiTap) { - resetMultiTap(); - } - } - - private void debugLog(String title, int x, int y) { - int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null); - Key key = getKey(keyIndex); - final String code; - if (key == null) { - code = "----"; - } else { - int primaryCode = key.codes[0]; - code = String.format((primaryCode < 0) ? "%4d" : "0x%02x", primaryCode); - } - Log.d(TAG, String.format("%s%s[%d] %3d,%3d %3d(%s) %s", title, - (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, keyIndex, code, - (isModifier() ? "modifier" : ""))); - } -} diff --git a/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java b/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java deleted file mode 100644 index 325ce674c..000000000 --- a/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2010 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.inputmethod.latin; - -import android.inputmethodservice.Keyboard.Key; - -import java.util.Arrays; - -class ProximityKeyDetector extends KeyDetector { - private static final int MAX_NEARBY_KEYS = 12; - - // working area - private int[] mDistances = new int[MAX_NEARBY_KEYS]; - - @Override - protected int getMaxNearbyKeys() { - return MAX_NEARBY_KEYS; - } - - @Override - public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys) { - final Key[] keys = getKeys(); - final int touchX = getTouchX(x); - final int touchY = getTouchY(y); - int primaryIndex = LatinKeyboardBaseView.NOT_A_KEY; - int closestKey = LatinKeyboardBaseView.NOT_A_KEY; - int closestKeyDist = mProximityThresholdSquare + 1; - int[] distances = mDistances; - Arrays.fill(distances, Integer.MAX_VALUE); - int [] nearestKeyIndices = mKeyboard.getNearestKeys(touchX, touchY); - final int keyCount = nearestKeyIndices.length; - for (int i = 0; i < keyCount; i++) { - final Key key = keys[nearestKeyIndices[i]]; - int dist = 0; - boolean isInside = key.isInside(touchX, touchY); - if (isInside) { - primaryIndex = nearestKeyIndices[i]; - } - - if (((mProximityCorrectOn - && (dist = key.squaredDistanceFrom(touchX, touchY)) < mProximityThresholdSquare) - || isInside) - && key.codes[0] > 32) { - // Find insertion point - final int nCodes = key.codes.length; - if (dist < closestKeyDist) { - closestKeyDist = dist; - closestKey = nearestKeyIndices[i]; - } - - if (allKeys == null) continue; - - for (int j = 0; j < distances.length; j++) { - if (distances[j] > dist) { - // Make space for nCodes codes - System.arraycopy(distances, j, distances, j + nCodes, - distances.length - j - nCodes); - System.arraycopy(allKeys, j, allKeys, j + nCodes, - allKeys.length - j - nCodes); - System.arraycopy(key.codes, 0, allKeys, j, nCodes); - Arrays.fill(distances, j, j + nCodes, dist); - break; - } - } - } - } - if (primaryIndex == LatinKeyboardBaseView.NOT_A_KEY) { - primaryIndex = closestKey; - } - return primaryIndex; - } -} diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java new file mode 100644 index 000000000..3ee4eb891 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -0,0 +1,478 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin; + +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.voice.SettingsUtil; +import com.android.inputmethod.voice.VoiceIMEConnector; +import com.android.inputmethod.voice.VoiceInput; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.text.TextUtils; +import android.util.Log; +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InputMethodSubtype; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +public class SubtypeSwitcher { + // This flag indicates if we support language switching by swipe on space bar. + // We may or may not draw the current language on space bar regardless of this flag. + public static final boolean USE_SPACEBAR_LANGUAGE_SWITCHER = false; + private static final boolean DBG = false; + private static final String TAG = "SubtypeSwitcher"; + + private static final char LOCALE_SEPARATER = '_'; + private static final String KEYBOARD_MODE = "keyboard"; + private static final String VOICE_MODE = "voice"; + private final TextUtils.SimpleStringSplitter mLocaleSplitter = + new TextUtils.SimpleStringSplitter(LOCALE_SEPARATER); + + private static final SubtypeSwitcher sInstance = new SubtypeSwitcher(); + private /* final */ LatinIME mService; + private /* final */ SharedPreferences mPrefs; + private /* final */ InputMethodManager mImm; + private /* final */ Resources mResources; + private final ArrayList<InputMethodSubtype> mEnabledKeyboardSubtypesOfCurrentInputMethod = + new ArrayList<InputMethodSubtype>(); + private final ArrayList<String> mEnabledLanguagesOfCurrentInputMethod = new ArrayList<String>(); + + /*-----------------------------------------------------------*/ + // Variants which should be changed only by reload functions. + private Locale mSystemLocale; + private Locale mInputLocale; + private String mInputLocaleStr; + private String mMode; + private List<InputMethodSubtype> mAllEnabledSubtypesOfCurrentInputMethod; + private VoiceInput mVoiceInput; + private boolean mNeedsToDisplayLanguage; + private boolean mIsSystemLanguageSameAsInputLanguage; + /*-----------------------------------------------------------*/ + + public static SubtypeSwitcher getInstance() { + return sInstance; + } + + public static void init(LatinIME service, SharedPreferences prefs) { + sInstance.mPrefs = prefs; + sInstance.resetParams(service); + if (USE_SPACEBAR_LANGUAGE_SWITCHER) { + sInstance.initLanguageSwitcher(service); + } + + sInstance.updateAllParameters(); + } + + private SubtypeSwitcher() { + } + + private void resetParams(LatinIME service) { + mService = service; + mResources = service.getResources(); + mImm = (InputMethodManager) service.getSystemService(Context.INPUT_METHOD_SERVICE); + mEnabledKeyboardSubtypesOfCurrentInputMethod.clear(); + mEnabledLanguagesOfCurrentInputMethod.clear(); + mSystemLocale = null; + mInputLocale = null; + mInputLocaleStr = null; + mMode = null; + mAllEnabledSubtypesOfCurrentInputMethod = null; + // TODO: Voice input should be created here + mVoiceInput = null; + } + + // Update all parameters stored in SubtypeSwitcher. + // Only configuration changed event is allowed to call this because this is heavy. + private void updateAllParameters() { + mSystemLocale = mResources.getConfiguration().locale; + updateSubtype(mImm.getCurrentInputMethodSubtype()); + updateParametersOnStartInputView(); + } + + // Update parameters which are changed outside LatinIME. This parameters affect UI so they + // should be updated every time onStartInputview. + public void updateParametersOnStartInputView() { + if (USE_SPACEBAR_LANGUAGE_SWITCHER) { + updateForSpaceBarLanguageSwitch(); + } else { + updateEnabledSubtypes(); + } + } + + // Reload enabledSubtypes from the framework. + private void updateEnabledSubtypes() { + boolean foundCurrentSubtypeBecameDisabled = true; + mAllEnabledSubtypesOfCurrentInputMethod = mImm.getEnabledInputMethodSubtypeList(null); + mEnabledLanguagesOfCurrentInputMethod.clear(); + mEnabledKeyboardSubtypesOfCurrentInputMethod.clear(); + for (InputMethodSubtype ims: mAllEnabledSubtypesOfCurrentInputMethod) { + final String locale = ims.getLocale(); + final String mode = ims.getMode(); + mLocaleSplitter.setString(locale); + if (mLocaleSplitter.hasNext()) { + mEnabledLanguagesOfCurrentInputMethod.add(mLocaleSplitter.next()); + } + if (locale.equals(mInputLocaleStr) && mode.equals(mMode)) { + foundCurrentSubtypeBecameDisabled = false; + } + if (KEYBOARD_MODE.equals(ims.getMode())) { + mEnabledKeyboardSubtypesOfCurrentInputMethod.add(ims); + } + } + mNeedsToDisplayLanguage = !(getEnabledKeyboardLocaleCount() <= 1 + && mIsSystemLanguageSameAsInputLanguage); + if (foundCurrentSubtypeBecameDisabled) { + if (DBG) { + Log.w(TAG, "Last subtype was disabled. Update to the current one."); + } + updateSubtype(mImm.getCurrentInputMethodSubtype()); + } + } + + // Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function. + public void updateSubtype(InputMethodSubtype newSubtype) { + final String newLocale; + final String newMode; + if (newSubtype == null) { + // Normally, newSubtype shouldn't be null. But just in case newSubtype was null, + // fallback to the default locale and mode. + Log.w(TAG, "Couldn't get the current subtype."); + newLocale = "en_US"; + newMode =KEYBOARD_MODE; + } else { + newLocale = newSubtype.getLocale(); + newMode = newSubtype.getMode(); + } + if (DBG) { + Log.w(TAG, "Update subtype to:" + newLocale + "," + newMode + + ", from: " + mInputLocaleStr + ", " + mMode); + } + boolean languageChanged = false; + if (!newLocale.equals(mInputLocaleStr)) { + if (mInputLocaleStr != null) { + languageChanged = true; + } + updateInputLocale(newLocale); + } + boolean modeChanged = false; + String oldMode = mMode; + if (!newMode.equals(mMode)) { + if (mMode != null) { + modeChanged = true; + } + mMode = newMode; + } + if (isKeyboardMode()) { + if (modeChanged) { + if (VOICE_MODE.equals(oldMode) && mVoiceInput != null) { + mVoiceInput.cancel(); + } + } + if (languageChanged) { + mService.onKeyboardLanguageChanged(); + } + } else if (isVoiceMode()) { + // If needsToShowWarningDialog is true, voice input need to show warning before + // show recognition view. + if (languageChanged || modeChanged + || VoiceIMEConnector.getInstance().needsToShowWarningDialog()) { + if (mVoiceInput != null) { + // TODO: Call proper function to trigger VoiceIME + mService.onKey(Keyboard.CODE_VOICE, null, 0, 0); + } + } + } else { + Log.w(TAG, "Unknown subtype mode: " + mMode); + } + } + + // Update the current input locale from Locale string. + private void updateInputLocale(String inputLocaleStr) { + // example: inputLocaleStr = "en_US" "en" "" + // "en_US" --> language: en & country: US + // "en" --> language: en + // "" --> the system locale + mLocaleSplitter.setString(inputLocaleStr); + if (mLocaleSplitter.hasNext()) { + String language = mLocaleSplitter.next(); + if (mLocaleSplitter.hasNext()) { + mInputLocale = new Locale(language, mLocaleSplitter.next()); + } else { + mInputLocale = new Locale(language); + } + mInputLocaleStr = inputLocaleStr; + } else { + mInputLocale = mSystemLocale; + String country = mSystemLocale.getCountry(); + mInputLocaleStr = mSystemLocale.getLanguage() + + (TextUtils.isEmpty(country) ? "" : "_" + mSystemLocale.getLanguage()); + } + mIsSystemLanguageSameAsInputLanguage = getSystemLocale().getLanguage().equalsIgnoreCase( + getInputLocale().getLanguage()); + mNeedsToDisplayLanguage = !(getEnabledKeyboardLocaleCount() <= 1 + && mIsSystemLanguageSameAsInputLanguage); + } + + ////////////////////////////////// + // Language Switching functions // + ////////////////////////////////// + + public int getEnabledKeyboardLocaleCount() { + if (USE_SPACEBAR_LANGUAGE_SWITCHER) { + return mLanguageSwitcher.getLocaleCount(); + } else { + return mEnabledKeyboardSubtypesOfCurrentInputMethod.size(); + } + } + + public boolean needsToDisplayLanguage() { + return mNeedsToDisplayLanguage; + } + + public Locale getInputLocale() { + if (USE_SPACEBAR_LANGUAGE_SWITCHER) { + return mLanguageSwitcher.getInputLocale(); + } else { + return mInputLocale; + } + } + + public String getInputLocaleStr() { + if (USE_SPACEBAR_LANGUAGE_SWITCHER) { + String inputLanguage = null; + inputLanguage = mLanguageSwitcher.getInputLanguage(); + // Should return system locale if there is no Language available. + if (inputLanguage == null) { + inputLanguage = getSystemLocale().getLanguage(); + } + return inputLanguage; + } else { + return mInputLocaleStr; + } + } + + public String[] getEnabledLanguages() { + if (USE_SPACEBAR_LANGUAGE_SWITCHER) { + return mLanguageSwitcher.getEnabledLanguages(); + } else { + return mEnabledLanguagesOfCurrentInputMethod.toArray( + new String[mEnabledLanguagesOfCurrentInputMethod.size()]); + } + } + + public Locale getSystemLocale() { + if (USE_SPACEBAR_LANGUAGE_SWITCHER) { + return mLanguageSwitcher.getSystemLocale(); + } else { + return mSystemLocale; + } + } + + public boolean isSystemLanguageSameAsInputLanguage() { + if (USE_SPACEBAR_LANGUAGE_SWITCHER) { + return getSystemLocale().getLanguage().equalsIgnoreCase( + getInputLocaleStr().substring(0, 2)); + } else { + return mIsSystemLanguageSameAsInputLanguage; + } + } + + public void onConfigurationChanged(Configuration conf) { + final Locale systemLocale = conf.locale; + // If system configuration was changed, update all parameters. + if (!TextUtils.equals(systemLocale.toString(), mSystemLocale.toString())) { + if (USE_SPACEBAR_LANGUAGE_SWITCHER) { + // 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. + mLanguageSwitcher.loadLocales(mPrefs); + mLanguageSwitcher.setSystemLocale(systemLocale); + } else { + updateAllParameters(); + } + } + } + + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (USE_SPACEBAR_LANGUAGE_SWITCHER) { + if (LatinIME.PREF_SELECTED_LANGUAGES.equals(key)) { + mLanguageSwitcher.loadLocales(sharedPreferences); + } + } + } + + /** + * Change system locale for this application + * @param newLocale + * @return oldLocale + */ + public Locale changeSystemLocale(Locale newLocale) { + Configuration conf = mResources.getConfiguration(); + Locale oldLocale = conf.locale; + conf.locale = newLocale; + mResources.updateConfiguration(conf, mResources.getDisplayMetrics()); + return oldLocale; + } + + public boolean isKeyboardMode() { + return KEYBOARD_MODE.equals(mMode); + } + + + /////////////////////////// + // Voice Input functions // + /////////////////////////// + + public boolean setVoiceInput(VoiceInput vi) { + if (mVoiceInput == null && vi != null) { + mVoiceInput = vi; + if (isVoiceMode()) { + if (DBG) { + Log.d(TAG, "Set and call voice input."); + } + mService.onKey(Keyboard.CODE_VOICE, null, 0, 0); + return true; + } + } + return false; + } + + public boolean isVoiceMode() { + return VOICE_MODE.equals(mMode); + } + + ////////////////////////////////////// + // SpaceBar Language Switch support // + ////////////////////////////////////// + + private LanguageSwitcher mLanguageSwitcher; + + public static String getFullDisplayName(Locale locale, boolean returnsNameInThisLocale) { + if (returnsNameInThisLocale) { + return toTitleCase(locale.getDisplayName(locale)); + } else { + return toTitleCase(locale.getDisplayName()); + } + } + + public static String getDisplayLanguage(Locale locale) { + return toTitleCase(locale.getDisplayLanguage(locale)); + } + + public static String getShortDisplayLanguage(Locale locale) { + return toTitleCase(locale.getLanguage()); + } + + private static String toTitleCase(String s) { + if (s.length() == 0) { + return s; + } + return Character.toUpperCase(s.charAt(0)) + s.substring(1); + } + + private void updateForSpaceBarLanguageSwitch() { + // We need to update mNeedsToDisplayLanguage in onStartInputView because + // getEnabledKeyboardLocaleCount could have been changed. + mNeedsToDisplayLanguage = !(getEnabledKeyboardLocaleCount() <= 1 + && getSystemLocale().getLanguage().equalsIgnoreCase( + getInputLocale().getLanguage())); + } + + public String getInputLanguageName() { + return getDisplayLanguage(getInputLocale()); + } + + public String getNextInputLanguageName() { + if (USE_SPACEBAR_LANGUAGE_SWITCHER) { + return getDisplayLanguage(mLanguageSwitcher.getNextInputLocale()); + } else { + return ""; + } + } + + public String getPreviousInputLanguageName() { + if (USE_SPACEBAR_LANGUAGE_SWITCHER) { + return getDisplayLanguage(mLanguageSwitcher.getPrevInputLocale()); + } else { + return ""; + } + } + + // A list of locales which are supported by default for voice input, unless we get a + // different list from Gservices. + private 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 "; + + public boolean isVoiceSupported(String locale) { + // 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( + mService.getContentResolver(), + SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES, + DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES); + List<String> voiceInputSupportedLocales = Arrays.asList( + supportedLocalesString.split("\\s+")); + return voiceInputSupportedLocales.contains(locale); + } + + public void loadSettings() { + if (USE_SPACEBAR_LANGUAGE_SWITCHER) { + mLanguageSwitcher.loadLocales(mPrefs); + } + } + + public void toggleLanguage(boolean reset, boolean next) { + if (USE_SPACEBAR_LANGUAGE_SWITCHER) { + if (reset) { + mLanguageSwitcher.reset(); + } else { + if (next) { + mLanguageSwitcher.next(); + } else { + mLanguageSwitcher.prev(); + } + } + mLanguageSwitcher.persist(mPrefs); + } + } + + private void initLanguageSwitcher(LatinIME service) { + final Configuration conf = service.getResources().getConfiguration(); + mLanguageSwitcher = new LanguageSwitcher(service); + mLanguageSwitcher.loadLocales(mPrefs); + mLanguageSwitcher.setSystemLocale(conf.locale); + } +} diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 3b898941f..4c9b7509a 100755..100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -16,17 +16,17 @@ package com.android.inputmethod.latin; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - import android.content.Context; import android.text.AutoText; import android.text.TextUtils; import android.util.Log; import android.view.View; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + /** * This class loads a dictionary and provides a list of suggestions for a given sequence of * characters. This includes corrections and completions. @@ -81,6 +81,7 @@ public class Suggest implements Dictionary.WordCallback { private boolean mAutoTextEnabled; + private double mAutoCompleteThreshold; private int[] mPriorities = new int[mPrefMaxSuggestions]; private int[] mBigramPriorities = new int[PREF_MAX_BIGRAMS]; @@ -163,6 +164,10 @@ public class Suggest implements Dictionary.WordCallback { mUserBigramDictionary = userBigramDictionary; } + public void setAutoCompleteThreshold(double threshold) { + mAutoCompleteThreshold = threshold; + } + /** * Number of suggestions to generate from the input key sequence. This has * to be a number between 1 and 100 (inclusive). @@ -301,8 +306,14 @@ public class Suggest implements Dictionary.WordCallback { } mMainDict.getWords(wordComposer, this, mNextLettersFrequencies); if ((mCorrectionMode == CORRECTION_FULL || mCorrectionMode == CORRECTION_FULL_BIGRAM) - && mSuggestions.size() > 0) { - mHaveCorrection = true; + && mSuggestions.size() > 0 && mPriorities.length > 0) { + // TODO: when the normalized score of the first suggestion is nearly equals to + // the normalized score of the second suggestion, behave less aggressive. + final double normalizedScore = LatinIMEUtil.calcNormalizedScore( + mOriginalWord, mSuggestions.get(0), mPriorities[0]); + if (normalizedScore >= mAutoCompleteThreshold) { + mHaveCorrection = true; + } } } if (mOriginalWord != null) { @@ -326,8 +337,25 @@ public class Suggest implements Dictionary.WordCallback { String suggestedWord = mSuggestions.get(i).toString().toLowerCase(); CharSequence autoText = AutoText.get(suggestedWord, 0, suggestedWord.length(), view); - // Is there an AutoText correction? + // Is there an AutoText (also known as Quick Fixes) correction? boolean canAdd = autoText != null; + // Capitalize as needed + final int autoTextLength = autoText != null ? autoText.length() : 0; + if (autoTextLength > 0 && (mIsAllUpperCase || mIsFirstCharCapitalized)) { + int poolSize = mStringPool.size(); + StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove( + poolSize - 1) : new StringBuilder(getApproxMaxWordLength()); + sb.setLength(0); + if (mIsAllUpperCase) { + sb.append(autoText.toString().toUpperCase()); + } else if (mIsFirstCharCapitalized) { + sb.append(Character.toUpperCase(autoText.charAt(0))); + if (autoTextLength > 1) { + sb.append(autoText.subSequence(1, autoTextLength)); + } + } + autoText = sb.toString(); + } // Is that correction already the current prediction (or original word)? canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i)); // Is that correction already the next predicted word? @@ -395,6 +423,7 @@ public class Suggest implements Dictionary.WordCallback { return false; } + @Override public boolean addWord(final char[] word, final int offset, final int length, int freq, final int dicTypeId, final Dictionary.DataType dataType) { Dictionary.DataType dataTypeForLog = dataType; @@ -450,8 +479,7 @@ public class Suggest implements Dictionary.WordCallback { return true; } - System.arraycopy(priorities, pos, priorities, pos + 1, - prefMaxSuggestions - pos - 1); + System.arraycopy(priorities, pos, priorities, pos + 1, prefMaxSuggestions - pos - 1); priorities[pos] = freq; int poolSize = mStringPool.size(); StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1) diff --git a/java/src/com/android/inputmethod/latin/SwipeTracker.java b/java/src/com/android/inputmethod/latin/SwipeTracker.java deleted file mode 100644 index 970e91965..000000000 --- a/java/src/com/android/inputmethod/latin/SwipeTracker.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2010 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.inputmethod.latin; - -import android.view.MotionEvent; - -class SwipeTracker { - private static final int NUM_PAST = 4; - private static final int LONGEST_PAST_TIME = 200; - - final EventRingBuffer mBuffer = new EventRingBuffer(NUM_PAST); - - private float mYVelocity; - private float mXVelocity; - - public void addMovement(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - mBuffer.clear(); - return; - } - long time = ev.getEventTime(); - final int count = ev.getHistorySize(); - for (int i = 0; i < count; i++) { - addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i), ev.getHistoricalEventTime(i)); - } - addPoint(ev.getX(), ev.getY(), time); - } - - private void addPoint(float x, float y, long time) { - final EventRingBuffer buffer = mBuffer; - while (buffer.size() > 0) { - long lastT = buffer.getTime(0); - if (lastT >= time - LONGEST_PAST_TIME) - break; - buffer.dropOldest(); - } - buffer.add(x, y, time); - } - - public void computeCurrentVelocity(int units) { - computeCurrentVelocity(units, Float.MAX_VALUE); - } - - public void computeCurrentVelocity(int units, float maxVelocity) { - final EventRingBuffer buffer = mBuffer; - final float oldestX = buffer.getX(0); - final float oldestY = buffer.getY(0); - final long oldestTime = buffer.getTime(0); - - float accumX = 0; - float accumY = 0; - final int count = buffer.size(); - for (int pos = 1; pos < count; pos++) { - final int dur = (int)(buffer.getTime(pos) - oldestTime); - if (dur == 0) continue; - float dist = buffer.getX(pos) - oldestX; - float vel = (dist / dur) * units; // pixels/frame. - if (accumX == 0) accumX = vel; - else accumX = (accumX + vel) * .5f; - - dist = buffer.getY(pos) - oldestY; - vel = (dist / dur) * units; // pixels/frame. - if (accumY == 0) accumY = vel; - else accumY = (accumY + vel) * .5f; - } - mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity) - : Math.min(accumX, maxVelocity); - mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity) - : Math.min(accumY, maxVelocity); - } - - public float getXVelocity() { - return mXVelocity; - } - - public float getYVelocity() { - return mYVelocity; - } - - static class EventRingBuffer { - private final int bufSize; - private final float xBuf[]; - private final float yBuf[]; - private final long timeBuf[]; - private int top; // points new event - private int end; // points oldest event - private int count; // the number of valid data - - public EventRingBuffer(int max) { - this.bufSize = max; - xBuf = new float[max]; - yBuf = new float[max]; - timeBuf = new long[max]; - clear(); - } - - public void clear() { - top = end = count = 0; - } - - public int size() { - return count; - } - - // Position 0 points oldest event - private int index(int pos) { - return (end + pos) % bufSize; - } - - private int advance(int index) { - return (index + 1) % bufSize; - } - - public void add(float x, float y, long time) { - xBuf[top] = x; - yBuf[top] = y; - timeBuf[top] = time; - top = advance(top); - if (count < bufSize) { - count++; - } else { - end = advance(end); - } - } - - public float getX(int pos) { - return xBuf[index(pos)]; - } - - public float getY(int pos) { - return yBuf[index(pos)]; - } - - public long getTime(int pos) { - return timeBuf[index(pos)]; - } - - public void dropOldest() { - count--; - end = advance(end); - } - } -}
\ No newline at end of file diff --git a/java/src/com/android/inputmethod/latin/TextEntryState.java b/java/src/com/android/inputmethod/latin/TextEntryState.java index 9011191f1..34babdcb5 100644 --- a/java/src/com/android/inputmethod/latin/TextEntryState.java +++ b/java/src/com/android/inputmethod/latin/TextEntryState.java @@ -16,8 +16,9 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.keyboard.Key; + import android.content.Context; -import android.inputmethodservice.Keyboard.Key; import android.text.format.DateFormat; import android.util.Log; @@ -61,7 +62,7 @@ public class TextEntryState { SPACE_AFTER_PICKED, UNDO_COMMIT, CORRECTING, - PICKED_CORRECTION; + PICKED_CORRECTION, } private static State sState = State.UNKNOWN; @@ -96,7 +97,7 @@ public class TextEntryState { } try { sKeyLocationFile.close(); - // Write to log file + // Write to log file // Write timestamp, settings, String out = DateFormat.format("MM:dd hh:mm:ss", Calendar.getInstance().getTime()) .toString() @@ -168,6 +169,13 @@ public class TextEntryState { displayState(); } + public static void onAbortCorrection() { + if (isCorrecting()) { + sState = State.START; + } + displayState(); + } + public static void typedCharacter(char c, boolean isSeparator) { boolean isSpace = c == ' '; switch (sState) { @@ -253,13 +261,13 @@ public class TextEntryState { } public static void keyPressedAt(Key key, int x, int y) { - if (LOGGING && sKeyLocationFile != null && key.codes[0] >= 32) { - String out = - "KEY: " + (char) key.codes[0] - + " X: " + x + if (LOGGING && sKeyLocationFile != null && key.mCodes[0] >= 32) { + String out = + "KEY: " + (char) key.mCodes[0] + + " X: " + x + " Y: " + y - + " MX: " + (key.x + key.width / 2) - + " MY: " + (key.y + key.height / 2) + + " MX: " + (key.mX + key.mWidth / 2) + + " MY: " + (key.mY + key.mHeight / 2) + "\n"; try { sKeyLocationFile.write(out.getBytes()); diff --git a/java/src/com/android/inputmethod/latin/Tutorial.java b/java/src/com/android/inputmethod/latin/Tutorial.java index d3eaf30c6..20767de4d 100644 --- a/java/src/com/android/inputmethod/latin/Tutorial.java +++ b/java/src/com/android/inputmethod/latin/Tutorial.java @@ -16,6 +16,9 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.keyboard.KeyboardSwitcher; +import com.android.inputmethod.keyboard.LatinKeyboardView; + import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Handler; @@ -32,20 +35,24 @@ import android.widget.PopupWindow; import android.widget.TextView; import java.util.ArrayList; -import java.util.List; public class Tutorial implements OnTouchListener { - - private List<Bubble> mBubbles = new ArrayList<Bubble>(); - private View mInputView; - private LatinIME mIme; - private int[] mLocation = new int[2]; - + + public interface TutorialListener { + public void onTutorialDone(); + } + + private final ArrayList<Bubble> mBubbles = new ArrayList<Bubble>(); + private final KeyboardSwitcher mKeyboardSwitcher; + private final View mInputView; + private final TutorialListener mListener; + private final int[] mLocation = new int[2]; + private static final int MSG_SHOW_BUBBLE = 0; - + private int mBubbleIndex; - - Handler mHandler = new Handler() { + + private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -57,20 +64,18 @@ public class Tutorial implements OnTouchListener { } }; - class Bubble { - Drawable bubbleBackground; - int x; - int y; - int width; - int gravity; - CharSequence text; - boolean dismissOnTouch; - boolean dismissOnClose; - PopupWindow window; - TextView textView; - View inputView; - - Bubble(Context context, View inputView, + private class Bubble { + private final Drawable bubbleBackground; + private final int x; + private final int y; + private final int width; + private final int gravity; + private final CharSequence text; + private final PopupWindow window; + private final TextView textView; + private final View inputView; + + private Bubble(Context context, View inputView, int backgroundResource, int bx, int by, int textResource1, int textResource2) { bubbleBackground = context.getResources().getDrawable(backgroundResource); x = bx; @@ -81,8 +86,6 @@ public class Tutorial implements OnTouchListener { .append(context.getResources().getText(textResource1)) .append("\n") .append(context.getResources().getText(textResource2)); - this.dismissOnTouch = true; - this.dismissOnClose = false; this.inputView = inputView; window = new PopupWindow(context); window.setBackgroundDrawable(null); @@ -124,7 +127,7 @@ public class Tutorial implements OnTouchListener { return l.getHeight(); } - void show(int offx, int offy) { + private void show(int offx, int offy) { int textHeight = chooseSize(window, inputView, text, textView); offy -= textView.getPaddingTop() + textHeight; if (inputView.getVisibility() == View.VISIBLE @@ -133,6 +136,7 @@ public class Tutorial implements OnTouchListener { if ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) offy -= window.getHeight(); if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) offx -= window.getWidth(); textView.setOnTouchListener(new View.OnTouchListener() { + @Override public boolean onTouch(View view, MotionEvent me) { Tutorial.this.next(); return true; @@ -144,63 +148,66 @@ public class Tutorial implements OnTouchListener { } } } - - void hide() { + + private void hide() { if (window.isShowing()) { textView.setOnTouchListener(null); window.dismiss(); } } - - boolean isShowing() { + + private boolean isShowing() { return window.isShowing(); } } - - public Tutorial(LatinIME ime, LatinKeyboardView inputView) { + + public Tutorial(TutorialListener listener, KeyboardSwitcher keyboardSwitcher) { + mListener = listener; + mKeyboardSwitcher = keyboardSwitcher; + LatinKeyboardView inputView = keyboardSwitcher.getInputView(); + mInputView = inputView; Context context = inputView.getContext(); - mIme = ime; int inputWidth = inputView.getWidth(); final int x = inputWidth / 20; // Half of 1/10th + ArrayList<Bubble> bubbles = mBubbles; Bubble bWelcome = new Bubble(context, inputView, R.drawable.dialog_bubble_step02, x, 0, R.string.tip_to_open_keyboard, R.string.touch_to_continue); - mBubbles.add(bWelcome); + bubbles.add(bWelcome); Bubble bAccents = new Bubble(context, inputView, R.drawable.dialog_bubble_step02, x, 0, R.string.tip_to_view_accents, R.string.touch_to_continue); - mBubbles.add(bAccents); + bubbles.add(bAccents); Bubble b123 = new Bubble(context, inputView, R.drawable.dialog_bubble_step07, x, 0, R.string.tip_to_open_symbols, R.string.touch_to_continue); - mBubbles.add(b123); + bubbles.add(b123); Bubble bABC = new Bubble(context, inputView, R.drawable.dialog_bubble_step07, x, 0, R.string.tip_to_close_symbols, R.string.touch_to_continue); - mBubbles.add(bABC); + bubbles.add(bABC); Bubble bSettings = new Bubble(context, inputView, R.drawable.dialog_bubble_step07, x, 0, R.string.tip_to_launch_settings, R.string.touch_to_continue); - mBubbles.add(bSettings); + bubbles.add(bSettings); Bubble bDone = new Bubble(context, inputView, R.drawable.dialog_bubble_step02, x, 0, R.string.tip_to_start_typing, R.string.touch_to_finish); - mBubbles.add(bDone); - mInputView = inputView; + bubbles.add(bDone); } - - void start() { + + public void start() { mInputView.getLocationInWindow(mLocation); mBubbleIndex = -1; mInputView.setOnTouchListener(this); next(); } - boolean next() { + private void next() { if (mBubbleIndex >= 0) { // If the bubble is not yet showing, don't move to the next. if (!mBubbles.get(mBubbleIndex).isShowing()) { - return true; + return; } // Hide all previous bubbles as well, as they may have had a delayed show for (int i = 0; i <= mBubbleIndex; i++) { @@ -210,31 +217,31 @@ public class Tutorial implements OnTouchListener { mBubbleIndex++; if (mBubbleIndex >= mBubbles.size()) { mInputView.setOnTouchListener(null); - mIme.sendDownUpKeyEvents(-1); // Inform the setupwizard that tutorial is in last bubble - mIme.tutorialDone(); - return false; + mListener.onTutorialDone(); + return; } if (mBubbleIndex == 3 || mBubbleIndex == 4) { - mIme.mKeyboardSwitcher.toggleSymbols(); + mKeyboardSwitcher.changeKeyboardMode(); } mHandler.sendMessageDelayed( mHandler.obtainMessage(MSG_SHOW_BUBBLE, mBubbles.get(mBubbleIndex)), 500); - return true; + return; } - - void hide() { - for (int i = 0; i < mBubbles.size(); i++) { - mBubbles.get(i).hide(); + + private void hide() { + for (Bubble bubble : mBubbles) { + bubble.hide(); } mInputView.setOnTouchListener(null); } - boolean close() { + public boolean close() { mHandler.removeMessages(MSG_SHOW_BUBBLE); hide(); return true; } + @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { next(); diff --git a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java index 67d9c0bcf..6d2f6b611 100644 --- a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java @@ -16,10 +16,6 @@ package com.android.inputmethod.latin; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; - import android.content.ContentValues; import android.content.Context; import android.database.Cursor; @@ -30,6 +26,10 @@ import android.os.AsyncTask; import android.provider.BaseColumns; import android.util.Log; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; + /** * Stores all the pairs user types in databases. Prune the database if the size * gets too big. Unlike AutoDictionary, it even stores the pairs that are already diff --git a/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java index 49b95e9aa..a522303e6 100644 --- a/java/src/com/android/inputmethod/latin/UserDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserDictionary.java @@ -97,6 +97,7 @@ public class UserDictionary extends ExpandableDictionary { final ContentResolver contentResolver = getContext().getContentResolver(); new Thread("addWord") { + @Override public void run() { contentResolver.insert(Words.CONTENT_URI, values); } |