aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/latin
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
-rw-r--r--java/src/com/android/inputmethod/latin/AccessibilityUtils.java211
-rw-r--r--java/src/com/android/inputmethod/latin/AssetFileAddress.java52
-rw-r--r--java/src/com/android/inputmethod/latin/AutoCorrection.java143
-rw-r--r--java/src/com/android/inputmethod/latin/AutoDictionary.java1
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java184
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java141
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java96
-rw-r--r--java/src/com/android/inputmethod/latin/CandidateView.java3
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java21
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java82
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableDictionary.java59
-rw-r--r--java/src/com/android/inputmethod/latin/InputLanguageSelection.java4
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java691
-rw-r--r--java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java28
-rw-r--r--java/src/com/android/inputmethod/latin/Settings.java46
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java225
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java391
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestedWords.java16
-rw-r--r--java/src/com/android/inputmethod/latin/TextEntryState.java358
-rw-r--r--java/src/com/android/inputmethod/latin/UserDictionary.java7
-rw-r--r--java/src/com/android/inputmethod/latin/Utils.java207
-rw-r--r--java/src/com/android/inputmethod/latin/WhitelistDictionary.java99
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java58
23 files changed, 2089 insertions, 1034 deletions
diff --git a/java/src/com/android/inputmethod/latin/AccessibilityUtils.java b/java/src/com/android/inputmethod/latin/AccessibilityUtils.java
new file mode 100644
index 000000000..cd3f9e0ad
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/AccessibilityUtils.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2011 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.SharedPreferences;
+import android.content.res.TypedArray;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardSwitcher;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Utility functions for accessibility support.
+ */
+public class AccessibilityUtils {
+ /** Shared singleton instance. */
+ private static final AccessibilityUtils sInstance = new AccessibilityUtils();
+ private /* final */ LatinIME mService;
+ private /* final */ AccessibilityManager mAccessibilityManager;
+ private /* final */ Map<Integer, CharSequence> mDescriptions;
+
+ /**
+ * Returns a shared instance of AccessibilityUtils.
+ *
+ * @return A shared instance of AccessibilityUtils.
+ */
+ public static AccessibilityUtils getInstance() {
+ return sInstance;
+ }
+
+ /**
+ * Initializes (or re-initializes) the shared instance of AccessibilityUtils
+ * with the specified parent service and preferences.
+ *
+ * @param service The parent input method service.
+ * @param prefs The parent preferences.
+ */
+ public static void init(LatinIME service, SharedPreferences prefs) {
+ sInstance.initialize(service, prefs);
+ }
+
+ private AccessibilityUtils() {
+ // This class is not publicly instantiable.
+ }
+
+ /**
+ * Initializes (or re-initializes) with the specified parent service and
+ * preferences.
+ *
+ * @param service The parent input method service.
+ * @param prefs The parent preferences.
+ */
+ private void initialize(LatinIME service, SharedPreferences prefs) {
+ mService = service;
+ mAccessibilityManager = (AccessibilityManager) service.getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ mDescriptions = null;
+ }
+
+ /**
+ * Returns true if accessibility is enabled.
+ *
+ * @return {@code true} if accessibility is enabled.
+ */
+ public boolean isAccessibilityEnabled() {
+ return mAccessibilityManager.isEnabled();
+ }
+
+ /**
+ * Speaks a key's action after it has been released. Does not speak letter
+ * keys since typed keys are already spoken aloud by TalkBack.
+ * <p>
+ * No-op if accessibility is not enabled.
+ * </p>
+ *
+ * @param primaryCode The primary code of the released key.
+ * @param switcher The input method's {@link KeyboardSwitcher}.
+ */
+ public void onRelease(int primaryCode, KeyboardSwitcher switcher) {
+ if (!isAccessibilityEnabled()) {
+ return;
+ }
+
+ int resId = -1;
+
+ switch (primaryCode) {
+ case Keyboard.CODE_SHIFT: {
+ if (switcher.isShiftedOrShiftLocked()) {
+ resId = R.string.description_shift_on;
+ } else {
+ resId = R.string.description_shift_off;
+ }
+ break;
+ }
+
+ case Keyboard.CODE_SWITCH_ALPHA_SYMBOL: {
+ if (switcher.isAlphabetMode()) {
+ resId = R.string.description_symbols_off;
+ } else {
+ resId = R.string.description_symbols_on;
+ }
+ break;
+ }
+ }
+
+ if (resId >= 0) {
+ speakDescription(mService.getResources().getText(resId));
+ }
+ }
+
+ /**
+ * Speaks a key's description for accessibility. If a key has an explicit
+ * description defined in keycodes.xml, that will be used. Otherwise, if the
+ * key is a Unicode character, then its character will be used.
+ * <p>
+ * No-op if accessibility is not enabled.
+ * </p>
+ *
+ * @param primaryCode The primary code of the pressed key.
+ * @param switcher The input method's {@link KeyboardSwitcher}.
+ */
+ public void onPress(int primaryCode, KeyboardSwitcher switcher) {
+ if (!isAccessibilityEnabled()) {
+ return;
+ }
+
+ // TODO Use the current keyboard state to read "Switch to symbols"
+ // instead of just "Symbols" (and similar for shift key).
+ CharSequence description = describeKey(primaryCode);
+ if (description == null && Character.isDefined((char) primaryCode)) {
+ description = Character.toString((char) primaryCode);
+ }
+
+ if (description != null) {
+ speakDescription(description);
+ }
+ }
+
+ /**
+ * Returns a text description for a given key code. If the key does not have
+ * an explicit description, returns <code>null</code>.
+ *
+ * @param keyCode An integer key code.
+ * @return A {@link CharSequence} describing the key or <code>null</code> if
+ * no description is available.
+ */
+ private CharSequence describeKey(int keyCode) {
+ // If not loaded yet, load key descriptions from XML file.
+ if (mDescriptions == null) {
+ mDescriptions = loadDescriptions();
+ }
+
+ return mDescriptions.get(keyCode);
+ }
+
+ /**
+ * Loads key descriptions from resources.
+ */
+ private Map<Integer, CharSequence> loadDescriptions() {
+ final Map<Integer, CharSequence> descriptions = new HashMap<Integer, CharSequence>();
+ final TypedArray array = mService.getResources().obtainTypedArray(R.array.key_descriptions);
+
+ // Key descriptions are stored as a key code followed by a string.
+ for (int i = 0; i < array.length() - 1; i += 2) {
+ int code = array.getInteger(i, 0);
+ CharSequence desc = array.getText(i + 1);
+
+ descriptions.put(code, desc);
+ }
+
+ array.recycle();
+
+ return descriptions;
+ }
+
+ /**
+ * Sends a character sequence to be read aloud.
+ *
+ * @param description The {@link CharSequence} to be read aloud.
+ */
+ private void speakDescription(CharSequence description) {
+ // TODO We need to add an AccessibilityEvent type for IMEs.
+ final AccessibilityEvent event = AccessibilityEvent.obtain(
+ AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
+ event.setPackageName(mService.getPackageName());
+ event.setClassName(getClass().getName());
+ event.setAddedCount(description.length());
+ event.getText().add(description);
+
+ mAccessibilityManager.sendAccessibilityEvent(event);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/AssetFileAddress.java b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
new file mode 100644
index 000000000..074ecacc5
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2011 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 java.io.File;
+
+/**
+ * Immutable class to hold the address of an asset.
+ * As opposed to a normal file, an asset is usually represented as a contiguous byte array in
+ * the package file. Open it correctly thus requires the name of the package it is in, but
+ * also the offset in the file and the length of this data. This class encapsulates these three.
+ */
+class AssetFileAddress {
+ public final String mFilename;
+ public final long mOffset;
+ public final long mLength;
+
+ public AssetFileAddress(final String filename, final long offset, final long length) {
+ mFilename = filename;
+ mOffset = offset;
+ mLength = length;
+ }
+
+ public static AssetFileAddress makeFromFileName(final String filename) {
+ if (null == filename) return null;
+ File f = new File(filename);
+ if (null == f || !f.isFile()) return null;
+ return new AssetFileAddress(filename, 0l, f.length());
+ }
+
+ public static AssetFileAddress makeFromFileNameAndOffset(final String filename,
+ final long offset, final long length) {
+ if (null == filename) return null;
+ File f = new File(filename);
+ if (null == f || !f.isFile()) return null;
+ return new AssetFileAddress(filename, offset, length);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java
new file mode 100644
index 000000000..d3119792c
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2011 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.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+public class AutoCorrection {
+ private static final boolean DBG = LatinImeLogger.sDBG;
+ private static final String TAG = AutoCorrection.class.getSimpleName();
+ private boolean mHasAutoCorrection;
+ private CharSequence mAutoCorrectionWord;
+ private double mNormalizedScore;
+
+ public void init() {
+ mHasAutoCorrection = false;
+ mAutoCorrectionWord = null;
+ mNormalizedScore = Integer.MIN_VALUE;
+ }
+
+ public boolean hasAutoCorrection() {
+ return mHasAutoCorrection;
+ }
+
+ public CharSequence getAutoCorrectionWord() {
+ return mAutoCorrectionWord;
+ }
+
+ public double getNormalizedScore() {
+ return mNormalizedScore;
+ }
+
+ public void updateAutoCorrectionStatus(Map<String, Dictionary> dictionaries,
+ WordComposer wordComposer, ArrayList<CharSequence> suggestions, int[] sortedScores,
+ CharSequence typedWord, double autoCorrectionThreshold, int correctionMode,
+ CharSequence quickFixedWord, CharSequence whitelistedWord) {
+ if (hasAutoCorrectionForWhitelistedWord(whitelistedWord)) {
+ mHasAutoCorrection = true;
+ mAutoCorrectionWord = whitelistedWord;
+ } else if (hasAutoCorrectionForTypedWord(
+ dictionaries, wordComposer, suggestions, typedWord, correctionMode)) {
+ mHasAutoCorrection = true;
+ mAutoCorrectionWord = typedWord;
+ } else if (hasAutoCorrectionForQuickFix(quickFixedWord)) {
+ mHasAutoCorrection = true;
+ mAutoCorrectionWord = quickFixedWord;
+ } else if (hasAutoCorrectionForBinaryDictionary(wordComposer, suggestions, correctionMode,
+ sortedScores, typedWord, autoCorrectionThreshold)) {
+ mHasAutoCorrection = true;
+ mAutoCorrectionWord = suggestions.get(0);
+ }
+ }
+
+ public static boolean isValidWord(
+ Map<String, Dictionary> dictionaries, CharSequence word, boolean ignoreCase) {
+ if (TextUtils.isEmpty(word)) {
+ return false;
+ }
+ final CharSequence lowerCasedWord = word.toString().toLowerCase();
+ for (final String key : dictionaries.keySet()) {
+ if (key.equals(Suggest.DICT_KEY_WHITELIST)) continue;
+ final Dictionary dictionary = dictionaries.get(key);
+ if (dictionary.isValidWord(word)
+ || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean isValidWordForAutoCorrection(
+ Map<String, Dictionary> dictionaries, CharSequence word, boolean ignoreCase) {
+ final Dictionary whiteList = dictionaries.get(Suggest.DICT_KEY_WHITELIST);
+ // If "word" is in the whitelist dictionary, it should not be auto corrected.
+ if (whiteList != null && whiteList.isValidWord(word)) {
+ return false;
+ }
+ return isValidWord(dictionaries, word, ignoreCase);
+ }
+
+ private static boolean hasAutoCorrectionForWhitelistedWord(CharSequence whiteListedWord) {
+ return whiteListedWord != null;
+ }
+
+ private boolean hasAutoCorrectionForTypedWord(Map<String, Dictionary> dictionaries,
+ WordComposer wordComposer, ArrayList<CharSequence> suggestions, CharSequence typedWord,
+ int correctionMode) {
+ if (TextUtils.isEmpty(typedWord)) return false;
+ boolean isValidWord = isValidWordForAutoCorrection(dictionaries, typedWord, false);
+ return wordComposer.size() > 1 && suggestions.size() > 0 && isValidWord
+ && (correctionMode == Suggest.CORRECTION_FULL
+ || correctionMode == Suggest.CORRECTION_FULL_BIGRAM);
+ }
+
+ private static boolean hasAutoCorrectionForQuickFix(CharSequence quickFixedWord) {
+ return quickFixedWord != null;
+ }
+
+ private boolean hasAutoCorrectionForBinaryDictionary(WordComposer wordComposer,
+ ArrayList<CharSequence> suggestions, int correctionMode, int[] sortedScores,
+ CharSequence typedWord, double autoCorrectionThreshold) {
+ if (wordComposer.size() > 1 && (correctionMode == Suggest.CORRECTION_FULL
+ || correctionMode == Suggest.CORRECTION_FULL_BIGRAM)
+ && typedWord != null && suggestions.size() > 0 && sortedScores.length > 0) {
+ final CharSequence autoCorrectionCandidate = suggestions.get(0);
+ final int autoCorrectionCandidateScore = sortedScores[0];
+ // TODO: when the normalized score of the first suggestion is nearly equals to
+ // the normalized score of the second suggestion, behave less aggressive.
+ mNormalizedScore = Utils.calcNormalizedScore(
+ typedWord,autoCorrectionCandidate, autoCorrectionCandidateScore);
+ if (DBG) {
+ Log.d(TAG, "Normalized " + typedWord + "," + autoCorrectionCandidate + ","
+ + autoCorrectionCandidateScore + ", " + mNormalizedScore
+ + "(" + autoCorrectionThreshold + ")");
+ }
+ if (mNormalizedScore >= autoCorrectionThreshold) {
+ if (DBG) {
+ Log.d(TAG, "Auto corrected by S-threshold.");
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/java/src/com/android/inputmethod/latin/AutoDictionary.java b/java/src/com/android/inputmethod/latin/AutoDictionary.java
index 307b81d43..a00b0915c 100644
--- a/java/src/com/android/inputmethod/latin/AutoDictionary.java
+++ b/java/src/com/android/inputmethod/latin/AutoDictionary.java
@@ -27,6 +27,7 @@ import android.provider.BaseColumns;
import android.util.Log;
import java.util.HashMap;
+import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index d87672c0e..fa90fce67 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -16,29 +16,37 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.keyboard.ProximityInfo;
+
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.util.Log;
import java.io.File;
import java.util.Arrays;
+import java.util.Locale;
/**
* Implements a static, compacted, binary dictionary of standard words.
*/
public class BinaryDictionary extends Dictionary {
+ public static final String DICTIONARY_PACK_AUTHORITY =
+ "com.android.inputmethod.latin.dictionarypack";
+
/**
- * There is difference between what java and native code can handle.
+ * There is a difference between what java and native code can handle.
* This value should only be used in BinaryDictionary.java
* It is necessary to keep it at this value because some languages e.g. German have
* really long words.
*/
- protected static final int MAX_WORD_LENGTH = 48;
+ public static final int MAX_WORD_LENGTH = 48;
+ public static final int MAX_WORDS = 18;
private static final String TAG = "BinaryDictionary";
- private static final int MAX_ALTERNATIVES = 16;
- private static final int MAX_WORDS = 18;
+ private static final int MAX_PROXIMITY_CHARS_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE;
private static final int MAX_BIGRAMS = 60;
private static final int TYPED_LETTER_MULTIPLIER = 2;
@@ -47,28 +55,45 @@ public class BinaryDictionary extends Dictionary {
private int mDicTypeId;
private int mNativeDict;
private long mDictLength;
- private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_ALTERNATIVES];
+ private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_PROXIMITY_CHARS_SIZE];
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];
+ private final int[] mScores = new int[MAX_WORDS];
+ private final int[] mBigramScores = new int[MAX_BIGRAMS];
- static {
- try {
- System.loadLibrary("jni_latinime2");
- } catch (UnsatisfiedLinkError ule) {
- Log.e(TAG, "Could not load native library jni_latinime");
+ private final KeyboardSwitcher mKeyboardSwitcher = KeyboardSwitcher.getInstance();
+
+ public static class Flag {
+ public final String mName;
+ public final int mValue;
+
+ public Flag(String name, int value) {
+ mName = name;
+ mValue = value;
}
}
+ public static final Flag FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING =
+ new Flag("requiresGermanUmlautProcessing", 0x1);
+
+ private static final Flag[] ALL_FLAGS = {
+ // Here should reside all flags that trigger some special processing
+ // These *must* match the definition in UnigramDictionary enum in
+ // unigram_dictionary.h so please update both at the same time.
+ FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING,
+ };
+
+ private int mFlags = 0;
+
private BinaryDictionary() {
}
/**
- * Initialize a dictionary from a raw resource file
+ * Initializes a dictionary from a raw resource file
* @param context application context for reading resources
* @param resId the resource containing the raw binary dictionary
- * @return initialized instance of BinaryDictionary
+ * @param dicTypeId the type of the dictionary being created, out of the list in Suggest.DIC_*
+ * @return an initialized instance of BinaryDictionary
*/
public static BinaryDictionary initDictionary(Context context, int resId, int dicTypeId) {
synchronized (sInstance) {
@@ -93,12 +118,12 @@ public class BinaryDictionary extends Dictionary {
return null;
}
}
+ sInstance.mFlags = initFlags(ALL_FLAGS, SubtypeSwitcher.getInstance());
return sInstance;
}
- // For unit test
- /* package */ static BinaryDictionary initDictionary(File dictionary, long startOffset,
- long length, int dicTypeId) {
+ /* package for test */ static BinaryDictionary initDictionary(File dictionary, long startOffset,
+ long length, int dicTypeId, Flag[] flagArray) {
synchronized (sInstance) {
sInstance.closeInternal();
if (dictionary.isFile()) {
@@ -109,6 +134,51 @@ public class BinaryDictionary extends Dictionary {
return null;
}
}
+ sInstance.mFlags = initFlags(flagArray, null);
+ return sInstance;
+ }
+
+ private static int initFlags(Flag[] flagArray, SubtypeSwitcher switcher) {
+ int flags = 0;
+ for (Flag entry : flagArray) {
+ if (switcher == null || switcher.currentSubtypeContainsExtraValueKey(entry.mName))
+ flags |= entry.mValue;
+ }
+ return flags;
+ }
+
+ static {
+ Utils.loadNativeLibrary();
+ }
+
+ /**
+ * Initializes a dictionary from a dictionary pack.
+ *
+ * This searches for a content provider providing a dictionary pack for the specified
+ * locale. If none is found, it falls back to using the resource passed as fallBackResId
+ * as a dictionary.
+ * @param context application context for reading resources
+ * @param dicTypeId the type of the dictionary being created, out of the list in Suggest.DIC_*
+ * @param locale the locale for which to create the dictionary
+ * @param fallBackResId the id of the resource to use as a fallback if no pack is found
+ * @return an initialized instance of BinaryDictionary
+ */
+ public static BinaryDictionary initDictionaryFromManager(Context context, int dicTypeId,
+ Locale locale, int fallbackResId) {
+ if (null == locale) {
+ Log.e(TAG, "No locale defined for dictionary");
+ return initDictionary(context, fallbackResId, dicTypeId);
+ }
+ synchronized (sInstance) {
+ sInstance.closeInternal();
+
+ final AssetFileAddress dictFile = BinaryDictionaryGetter.getDictionaryFile(locale,
+ context, fallbackResId);
+ if (null != dictFile) {
+ sInstance.loadDictionary(dictFile.mFilename, dictFile.mOffset, dictFile.mLength);
+ sInstance.mDicTypeId = dicTypeId;
+ }
+ }
return sInstance;
}
@@ -117,89 +187,99 @@ public class BinaryDictionary extends Dictionary {
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[] nextLettersFrequencies, int nextLettersSize);
+ private native int getSuggestionsNative(int dict, int proximityInfo, int[] xCoordinates,
+ int[] yCoordinates, int[] inputCodes, int codesSize, int flags, char[] outputChars,
+ int[] scores);
private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength,
- int[] inputCodes, int inputCodesLength, char[] outputChars, int[] frequencies,
+ int[] inputCodes, int inputCodesLength, char[] outputChars, int[] scores,
int maxWordLength, int maxBigrams, int maxAlternatives);
private final void loadDictionary(String path, long startOffset, long length) {
mNativeDict = openNative(path, startOffset, length,
- TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER,
- MAX_WORD_LENGTH, MAX_WORDS, MAX_ALTERNATIVES);
+ TYPED_LETTER_MULTIPLIER, FULL_WORD_SCORE_MULTIPLIER,
+ MAX_WORD_LENGTH, MAX_WORDS, MAX_PROXIMITY_CHARS_SIZE);
mDictLength = length;
}
@Override
public void getBigrams(final WordComposer codes, final CharSequence previousWord,
- final WordCallback callback, int[] nextLettersFrequencies) {
+ final WordCallback callback) {
if (mNativeDict == 0) return;
char[] chars = previousWord.toString().toCharArray();
Arrays.fill(mOutputChars_bigrams, (char) 0);
- Arrays.fill(mFrequencies_bigrams, 0);
+ Arrays.fill(mBigramScores, 0);
int codesSize = codes.size();
Arrays.fill(mInputCodes, -1);
int[] alternatives = codes.getCodesAt(0);
System.arraycopy(alternatives, 0, mInputCodes, 0,
- Math.min(alternatives.length, MAX_ALTERNATIVES));
+ Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE));
int count = getBigramsNative(mNativeDict, chars, chars.length, mInputCodes, codesSize,
- mOutputChars_bigrams, mFrequencies_bigrams, MAX_WORD_LENGTH, MAX_BIGRAMS,
- MAX_ALTERNATIVES);
+ mOutputChars_bigrams, mBigramScores, MAX_WORD_LENGTH, MAX_BIGRAMS,
+ MAX_PROXIMITY_CHARS_SIZE);
for (int j = 0; j < count; ++j) {
- if (mFrequencies_bigrams[j] < 1) break;
+ if (mBigramScores[j] < 1) break;
final int start = j * MAX_WORD_LENGTH;
int len = 0;
while (len < MAX_WORD_LENGTH && mOutputChars_bigrams[start + len] != 0) {
++len;
}
if (len > 0) {
- callback.addWord(mOutputChars_bigrams, start, len, mFrequencies_bigrams[j],
+ callback.addWord(mOutputChars_bigrams, start, len, mBigramScores[j],
mDicTypeId, DataType.BIGRAM);
}
}
}
@Override
- public void getWords(final WordComposer codes, final WordCallback callback,
- int[] nextLettersFrequencies) {
- if (mNativeDict == 0) return;
-
- 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);
- System.arraycopy(alternatives, 0, mInputCodes, i * MAX_ALTERNATIVES,
- Math.min(alternatives.length, MAX_ALTERNATIVES));
- }
- Arrays.fill(mOutputChars, (char) 0);
- Arrays.fill(mFrequencies, 0);
-
- int count = getSuggestionsNative(mNativeDict, mInputCodes, codesSize, mOutputChars,
- mFrequencies, nextLettersFrequencies,
- nextLettersFrequencies != null ? nextLettersFrequencies.length : 0);
+ public void getWords(final WordComposer codes, final WordCallback callback) {
+ final int count = getSuggestions(codes, mKeyboardSwitcher.getLatinKeyboard(),
+ mOutputChars, mScores);
for (int j = 0; j < count; ++j) {
- if (mFrequencies[j] < 1) break;
+ if (mScores[j] < 1) break;
final int start = j * MAX_WORD_LENGTH;
int len = 0;
while (len < MAX_WORD_LENGTH && mOutputChars[start + len] != 0) {
++len;
}
if (len > 0) {
- callback.addWord(mOutputChars, start, len, mFrequencies[j], mDicTypeId,
+ callback.addWord(mOutputChars, start, len, mScores[j], mDicTypeId,
DataType.UNIGRAM);
}
}
}
+ /* package for test */ boolean isValidDictionary() {
+ return mNativeDict != 0;
+ }
+
+ /* package for test */ int getSuggestions(final WordComposer codes, final Keyboard keyboard,
+ char[] outputChars, int[] scores) {
+ if (!isValidDictionary()) return -1;
+
+ final int codesSize = codes.size();
+ // Won't deal with really long words.
+ if (codesSize > MAX_WORD_LENGTH - 1) return -1;
+
+ Arrays.fill(mInputCodes, WordComposer.NOT_A_CODE);
+ for (int i = 0; i < codesSize; i++) {
+ int[] alternatives = codes.getCodesAt(i);
+ System.arraycopy(alternatives, 0, mInputCodes, i * MAX_PROXIMITY_CHARS_SIZE,
+ Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE));
+ }
+ Arrays.fill(outputChars, (char) 0);
+ Arrays.fill(scores, 0);
+
+ return getSuggestionsNative(
+ mNativeDict, keyboard.getProximityInfo(),
+ codes.getXCoordinates(), codes.getYCoordinates(), mInputCodes, codesSize,
+ mFlags, outputChars, scores);
+ }
+
@Override
public boolean isValidWord(CharSequence word) {
if (word == null) return false;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
new file mode 100644
index 000000000..d0464dd94
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2011 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.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.text.TextUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+
+/**
+ * Group class for static methods to help with creation and getting of the binary dictionary
+ * file from the dictionary provider
+ */
+public class BinaryDictionaryFileDumper {
+ /**
+ * The size of the temporary buffer to copy files.
+ */
+ static final int FILE_READ_BUFFER_SIZE = 1024;
+
+ // Prevents this class to be accidentally instantiated.
+ private BinaryDictionaryFileDumper() {
+ }
+
+ /**
+ * Generates a file name that matches the locale passed as an argument.
+ * The file name is basically the result of the .toString() method, except we replace
+ * any @File.separator with an underscore to avoid generating a file name that may not
+ * be created.
+ * @param locale the locale for which to get the file name
+ * @param context the context to use for getting the directory
+ * @return the name of the file to be created
+ */
+ private static String getCacheFileNameForLocale(Locale locale, Context context) {
+ // The following assumes two things :
+ // 1. That File.separator is not the same character as "_"
+ // I don't think any android system will ever use "_" as a path separator
+ // 2. That no two locales differ by only a File.separator versus a "_"
+ // Since "_" can't be part of locale components this should be safe.
+ // Examples:
+ // en -> en
+ // en_US_POSIX -> en_US_POSIX
+ // en__foo/bar -> en__foo_bar
+ final String[] separator = { File.separator };
+ final String[] empty = { "_" };
+ final CharSequence basename = TextUtils.replace(locale.toString(), separator, empty);
+ return context.getFilesDir() + File.separator + basename;
+ }
+
+ /**
+ * Return for a given locale the provider URI to query to get the dictionary.
+ */
+ public static Uri getProviderUri(Locale locale) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(BinaryDictionary.DICTIONARY_PACK_AUTHORITY).appendPath(
+ locale.toString()).build();
+ }
+
+ /**
+ * Queries a content provider for dictionary data for some locale and returns it as a file name.
+ *
+ * This will query a content provider for dictionary data for a given locale, and return
+ * the name of a file suitable to be mmap'ed. It will copy it to local storage if needed.
+ * It should also check the dictionary version to avoid unnecessary copies but this is
+ * still in TODO state.
+ * This will make the data from the content provider the cached dictionary for this locale,
+ * overwriting any previous cached data.
+ * @returns the name of the file, or null if no data could be obtained.
+ * @throw FileNotFoundException if the provider returns non-existent data.
+ * @throw IOException if the provider-returned data could not be read.
+ */
+ public static String getDictionaryFileFromContentProvider(Locale locale, Context context)
+ throws FileNotFoundException, IOException {
+ // TODO: check whether the dictionary is the same or not and if it is, return the cached
+ // file.
+ final ContentResolver resolver = context.getContentResolver();
+ final Uri dictionaryPackUri = getProviderUri(locale);
+ final InputStream stream = resolver.openInputStream(dictionaryPackUri);
+ if (null == stream) return null;
+ return copyFileTo(stream, getCacheFileNameForLocale(locale, context));
+ }
+
+ /**
+ * Accepts a file as dictionary data for some locale and returns the name of a file.
+ *
+ * This will make the data in the input file the cached dictionary for this locale, overwriting
+ * any previous cached data.
+ */
+ public static String getDictionaryFileFromFile(String fileName, Locale locale,
+ Context context) throws FileNotFoundException, IOException {
+ return copyFileTo(new FileInputStream(fileName), getCacheFileNameForLocale(locale,
+ context));
+ }
+
+ /**
+ * Accepts a resource number as dictionary data for some locale and returns the name of a file.
+ *
+ * This will make the resource the cached dictionary for this locale, overwriting any previous
+ * cached data.
+ */
+ public static String getDictionaryFileFromResource(int resource, Locale locale,
+ Context context) throws FileNotFoundException, IOException {
+ return copyFileTo(context.getResources().openRawResource(resource),
+ getCacheFileNameForLocale(locale, context));
+ }
+
+ /**
+ * Copies the data in an input stream to a target file, creating the file if necessary and
+ * overwriting it if it already exists.
+ */
+ private static String copyFileTo(final InputStream input, final String outputFileName)
+ throws FileNotFoundException, IOException {
+ final byte[] buffer = new byte[FILE_READ_BUFFER_SIZE];
+ final FileOutputStream output = new FileOutputStream(outputFileName);
+ for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer))
+ output.write(buffer, 0, readBytes);
+ input.close();
+ return outputFileName;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
new file mode 100644
index 000000000..72512c7e1
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2011 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.AssetFileDescriptor;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Locale;
+
+/**
+ * Helper class to get the address of a mmap'able dictionary file.
+ */
+class BinaryDictionaryGetter {
+
+ /**
+ * Used for Log actions from this class
+ */
+ private static final String TAG = BinaryDictionaryGetter.class.getSimpleName();
+
+ // Prevents this from being instantiated
+ private BinaryDictionaryGetter() {}
+
+ /**
+ * Returns a file address from a resource, or null if it cannot be opened.
+ */
+ private static AssetFileAddress loadFallbackResource(Context context, int fallbackResId) {
+ final AssetFileDescriptor afd = context.getResources().openRawResourceFd(fallbackResId);
+ if (afd == null) {
+ Log.e(TAG, "Found the resource but cannot read it. Is it compressed? resId="
+ + fallbackResId);
+ return null;
+ }
+ return AssetFileAddress.makeFromFileNameAndOffset(
+ context.getApplicationInfo().sourceDir, afd.getStartOffset(), afd.getLength());
+ }
+
+ /**
+ * Returns a file address for a given locale, trying relevant methods in order.
+ *
+ * Tries to get a binary dictionary from various sources, in order:
+ * - Uses a private method of getting a private dictionary, as implemented by the
+ * PrivateBinaryDictionaryGetter class.
+ * If that fails:
+ * - Uses a content provider to get a public dictionary, as per the protocol described
+ * in BinaryDictionaryFileDumper.
+ * If that fails:
+ * - Gets a file name from the fallback resource passed as an argument.
+ * If that fails:
+ * - Returns null.
+ * @return The address of a valid file, or null.
+ * @throws FileNotFoundException if a dictionary provider returned a file name, but the
+ * file cannot be found.
+ * @throws IOException if there was an I/O problem reading or copying a file.
+ */
+ public static AssetFileAddress getDictionaryFile(Locale locale, Context context,
+ int fallbackResId) {
+ // Try first to query a private file signed the same way.
+ final AssetFileAddress privateFile =
+ PrivateBinaryDictionaryGetter.getDictionaryFile(locale, context);
+ if (null != privateFile) {
+ return privateFile;
+ } else {
+ try {
+ // If that was no-go, try to find a publicly exported dictionary.
+ final String fileName = BinaryDictionaryFileDumper.
+ getDictionaryFileFromContentProvider(locale, context);
+ return AssetFileAddress.makeFromFileName(fileName);
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Unable to create dictionary file from provider for locale "
+ + locale.toString() + ": falling back to internal dictionary");
+ return loadFallbackResource(context, fallbackResId);
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to read source data for locale "
+ + locale.toString() + ": falling back to internal dictionary");
+ return loadFallbackResource(context, fallbackResId);
+ }
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java
index 9699ad136..5719b9012 100644
--- a/java/src/com/android/inputmethod/latin/CandidateView.java
+++ b/java/src/com/android/inputmethod/latin/CandidateView.java
@@ -347,9 +347,6 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
if (mShowingAddToDictionary && index == 0) {
addToDictionary(word);
} else {
- if (!mSuggestions.mIsApplicationSpecifiedCompletions) {
- TextEntryState.acceptedSuggestion(mSuggestions.getWord(0), word);
- }
mService.pickSuggestionManually(index, word);
}
}
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 74933595c..ac43d6477 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -29,7 +29,7 @@ public abstract class Dictionary {
/**
* The weight to give to a word if it's length is the same as the number of typed characters.
*/
- protected static final int FULL_WORD_FREQ_MULTIPLIER = 2;
+ protected static final int FULL_WORD_SCORE_MULTIPLIER = 2;
public static enum DataType {
UNIGRAM, BIGRAM
@@ -42,17 +42,17 @@ public abstract class Dictionary {
public interface WordCallback {
/**
* Adds a word to a list of suggestions. The word is expected to be ordered based on
- * the provided frequency.
+ * the provided score.
* @param word the character array containing the word
* @param wordOffset starting offset of the word in the character array
* @param wordLength length of valid characters in the character array
- * @param frequency the frequency of occurrence. This is normalized between 1 and 255, but
+ * @param score the score of occurrence. This is normalized between 1 and 255, but
* can exceed those limits
* @param dicTypeId of the dictionary where word was from
* @param dataType tells type of this data
* @return true if the word was added, false if no more words are required
*/
- boolean addWord(char[] word, int wordOffset, int wordLength, int frequency, int dicTypeId,
+ boolean addWord(char[] word, int wordOffset, int wordLength, int score, int dicTypeId,
DataType dataType);
}
@@ -61,14 +61,9 @@ public abstract class Dictionary {
* words are added through the callback object.
* @param composer the key sequence to match
* @param callback the callback object to send matched words to as possible candidates
- * @param nextLettersFrequencies array of frequencies of next letters that could follow the
- * word so far. For instance, "bracke" can be followed by "t", so array['t'] will have
- * a non-zero value on returning from this method.
- * Pass in null if you don't want the dictionary to look up next letters.
* @see WordCallback#addWord(char[], int, int)
*/
- abstract public void getWords(final WordComposer composer, final WordCallback callback,
- int[] nextLettersFrequencies);
+ abstract public void getWords(final WordComposer composer, final WordCallback callback);
/**
* Searches for pairs in the bigram dictionary that matches the previous word and all the
@@ -76,13 +71,9 @@ public abstract class Dictionary {
* @param composer the key sequence to match
* @param previousWord the word before
* @param callback the callback object to send possible word following previous word
- * @param nextLettersFrequencies array of frequencies of next letters that could follow the
- * word so far. For instance, "bracke" can be followed by "t", so array['t'] will have
- * a non-zero value on returning from this method.
- * Pass in null if you don't want the dictionary to look up next letters.
*/
public void getBigrams(final WordComposer composer, final CharSequence previousWord,
- final WordCallback callback, int[] nextLettersFrequencies) {
+ final WordCallback callback) {
// empty base implementation
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
new file mode 100644
index 000000000..c7864621d
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2011 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.net.Uri;
+
+/**
+ * Takes action to reload the necessary data when a dictionary pack was added/removed.
+ */
+public class DictionaryPackInstallBroadcastReceiver extends BroadcastReceiver {
+
+ final LatinIME mService;
+
+ public DictionaryPackInstallBroadcastReceiver(final LatinIME service) {
+ mService = service;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ final PackageManager manager = context.getPackageManager();
+
+ // We need to reread the dictionary if a new dictionary package is installed.
+ if (action.equals(Intent.ACTION_PACKAGE_ADDED)) {
+ final Uri packageUri = intent.getData();
+ if (null == packageUri) return; // No package name : we can't do anything
+ final String packageName = packageUri.getSchemeSpecificPart();
+ if (null == packageName) return;
+ final PackageInfo packageInfo;
+ try {
+ packageInfo = manager.getPackageInfo(packageName, PackageManager.GET_PROVIDERS);
+ } catch (android.content.pm.PackageManager.NameNotFoundException e) {
+ return; // No package info : we can't do anything
+ }
+ final ProviderInfo[] providers = packageInfo.providers;
+ if (null == providers) return; // No providers : it is not a dictionary.
+
+ // Search for some dictionary pack in the just-installed package. If found, reread.
+ for (ProviderInfo info : providers) {
+ if (BinaryDictionary.DICTIONARY_PACK_AUTHORITY.equals(info.authority)) {
+ mService.resetSuggestMainDict();
+ return;
+ }
+ }
+ // If we come here none of the authorities matched the one we searched for.
+ // We can exit safely.
+ return;
+ } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
+ && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ // When the dictionary package is removed, we need to reread dictionary (to use the
+ // next-priority one, or stop using a dictionary at all if this was the only one,
+ // since this is the user request).
+ // If we are replacing the package, we will receive ADDED right away so no need to
+ // remove the dictionary at the moment, since we will do it when we receive the
+ // ADDED broadcast.
+
+ // TODO: Only reload dictionary on REMOVED when the removed package is the one we
+ // read dictionary from?
+ mService.resetSuggestMainDict();
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 0fc86c335..d87fbce51 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -37,7 +37,6 @@ public class ExpandableDictionary extends Dictionary {
private int mDicTypeId;
private int mMaxDepth;
private int mInputLength;
- private int[] mNextLettersFrequencies;
private StringBuilder sb = new StringBuilder(MAX_WORD_LENGTH);
private static final char QUOTE = '\'';
@@ -191,8 +190,7 @@ public class ExpandableDictionary extends Dictionary {
}
@Override
- public void getWords(final WordComposer codes, final WordCallback callback,
- int[] nextLettersFrequencies) {
+ public void getWords(final WordComposer codes, final WordCallback callback) {
synchronized (mUpdatingLock) {
// If we need to update, start off a background task
if (mRequiresReload) startDictionaryLoadingTaskLocked();
@@ -201,7 +199,6 @@ public class ExpandableDictionary extends Dictionary {
}
mInputLength = codes.size();
- mNextLettersFrequencies = nextLettersFrequencies;
if (mCodes.length < mInputLength) mCodes = new int[mInputLength][];
// Cache the codes so that we don't have to lookup an array list
for (int i = 0; i < mInputLength; i++) {
@@ -228,11 +225,21 @@ public class ExpandableDictionary extends Dictionary {
/**
* Returns the word's frequency or -1 if not found
*/
- public int getWordFrequency(CharSequence word) {
+ protected int getWordFrequency(CharSequence word) {
Node node = searchNode(mRoots, word, 0, word.length());
return (node == null) ? -1 : node.mFrequency;
}
+ private static int computeSkippedWordFinalFreq(int freq, int snr, int inputLength) {
+ // The computation itself makes sense for >= 2, but the == 2 case returns 0
+ // anyway so we may as well test against 3 instead and return the constant
+ if (inputLength >= 3) {
+ return (freq * snr * (inputLength - 2)) / (inputLength - 1);
+ } else {
+ return 0;
+ }
+ }
+
/**
* Recursively traverse the tree for words that match the input. Input consists of
* a list of arrays. Each item in the list is one input character position. An input
@@ -246,13 +253,14 @@ public class ExpandableDictionary extends Dictionary {
* @param completion whether the traversal is now in completion mode - meaning that we've
* exhausted the input and we're looking for all possible suffixes.
* @param snr current weight of the word being formed
- * @param inputIndex position in the input characters. This can be off from the depth in
+ * @param inputIndex position in the input characters. This can be off from the depth in
* case we skip over some punctuations such as apostrophe in the traversal. That is, if you type
* "wouldve", it could be matching "would've", so the depth will be one more than the
* inputIndex
* @param callback the callback class for adding a word
*/
- protected void getWordsRec(NodeArray roots, final WordComposer codes, final char[] word,
+ // TODO: Share this routine with the native code for BinaryDictionary
+ protected void getWordsRec(NodeArray roots, final WordComposer codes, final char[] word,
final int depth, boolean completion, int snr, int inputIndex, int skipPos,
WordCallback callback) {
final int count = roots.mLength;
@@ -278,14 +286,15 @@ public class ExpandableDictionary extends Dictionary {
if (completion) {
word[depth] = c;
if (terminal) {
- if (!callback.addWord(word, 0, depth + 1, freq * snr, mDicTypeId,
- DataType.UNIGRAM)) {
- return;
+ final int finalFreq;
+ if (skipPos < 0) {
+ finalFreq = freq * snr;
+ } else {
+ finalFreq = computeSkippedWordFinalFreq(freq, snr, mInputLength);
}
- // Add to frequency of next letters for predictive correction
- if (mNextLettersFrequencies != null && depth >= inputIndex && skipPos < 0
- && mNextLettersFrequencies.length > word[inputIndex]) {
- mNextLettersFrequencies[word[inputIndex]]++;
+ if (!callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId,
+ DataType.UNIGRAM)) {
+ return;
}
}
if (children != null) {
@@ -296,7 +305,7 @@ public class ExpandableDictionary extends Dictionary {
// Skip the ' and continue deeper
word[depth] = c;
if (children != null) {
- getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex,
+ getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex,
skipPos, callback);
}
} else {
@@ -313,10 +322,16 @@ public class ExpandableDictionary extends Dictionary {
if (codeSize == inputIndex + 1) {
if (terminal) {
- if (INCLUDE_TYPED_WORD_IF_VALID
+ if (INCLUDE_TYPED_WORD_IF_VALID
|| !same(word, depth + 1, codes.getTypedWord())) {
- int finalFreq = freq * snr * addedAttenuation;
- if (skipPos < 0) finalFreq *= FULL_WORD_FREQ_MULTIPLIER;
+ final int finalFreq;
+ if (skipPos < 0) {
+ finalFreq = freq * snr * addedAttenuation
+ * FULL_WORD_SCORE_MULTIPLIER;
+ } else {
+ finalFreq = computeSkippedWordFinalFreq(freq,
+ snr * addedAttenuation, mInputLength);
+ }
callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId,
DataType.UNIGRAM);
}
@@ -327,7 +342,7 @@ public class ExpandableDictionary extends Dictionary {
skipPos, callback);
}
} else if (children != null) {
- getWordsRec(children, codes, word, depth + 1,
+ getWordsRec(children, codes, word, depth + 1,
false, snr * addedAttenuation, inputIndex + 1,
skipPos, callback);
}
@@ -427,7 +442,7 @@ public class ExpandableDictionary extends Dictionary {
@Override
public void getBigrams(final WordComposer codes, final CharSequence previousWord,
- final WordCallback callback, int[] nextLettersFrequencies) {
+ final WordCallback callback) {
if (!reloadDictionaryIfRequired()) {
runReverseLookUp(previousWord, callback);
}
@@ -516,7 +531,7 @@ public class ExpandableDictionary extends Dictionary {
}
}
- static char toLowerCase(char c) {
+ private static char toLowerCase(char c) {
char baseChar = c;
if (c < BASE_CHARS.length) {
baseChar = BASE_CHARS[c];
@@ -535,7 +550,7 @@ public class ExpandableDictionary extends Dictionary {
* if c is not a combined character, or the base character if it
* is combined.
*/
- static final char BASE_CHARS[] = {
+ private static final char BASE_CHARS[] = {
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
diff --git a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
index a9f2c2c22..be5e015aa 100644
--- a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
+++ b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
@@ -106,8 +106,8 @@ public class InputLanguageSelection extends PreferenceActivity {
conf.locale = locale;
res.updateConfiguration(conf, res.getDisplayMetrics());
- int mainDicResId = LatinIME.getMainDictionaryResourceId(res);
- BinaryDictionary bd = BinaryDictionary.initDictionary(this, mainDicResId, Suggest.DIC_MAIN);
+ BinaryDictionary bd = BinaryDictionary.initDictionaryFromManager(this, Suggest.DIC_MAIN,
+ locale, Utils.getMainDictionaryResourceId(res));
// Is the dictionary larger than a placeholder? Arbitrarily chose a lower limit of
// 4000-5000 words, whereas the LARGE_DICTIONARY is about 20000+ words.
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index acbccf6e6..ae2315437 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -16,16 +16,21 @@
package com.android.inputmethod.latin;
-import com.android.inputmethod.compat.InputMethodSubtype;
+import com.android.inputmethod.compat.CompatUtils;
+import com.android.inputmethod.compat.EditorInfoCompatUtils;
+import com.android.inputmethod.compat.InputConnectionCompatUtils;
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.compat.InputMethodServiceCompatWrapper;
+import com.android.inputmethod.compat.InputTypeCompatUtils;
+import com.android.inputmethod.compat.VibratorCompatWrapper;
+import com.android.inputmethod.deprecated.VoiceProxy;
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.LatinKeyboard;
import com.android.inputmethod.keyboard.LatinKeyboardView;
import com.android.inputmethod.latin.Utils.RingCharBuffer;
-import com.android.inputmethod.voice.VoiceIMEConnector;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
@@ -41,9 +46,9 @@ import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.os.Debug;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Message;
import android.os.SystemClock;
-import android.os.Vibrator;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.text.InputType;
@@ -62,13 +67,10 @@ import android.view.ViewParent;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.CompletionInfo;
-// @@@ import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
-// @@@ import android.view.inputmethod.InputMethodSubtype;
import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
@@ -82,13 +84,35 @@ import java.util.Locale;
/**
* Input method implementation for Qwerty'ish keyboard.
*/
-public class LatinIME extends InputMethodService implements KeyboardActionListener,
- SharedPreferences.OnSharedPreferenceChangeListener {
- private static final String TAG = "LatinIME";
+public class LatinIME extends InputMethodServiceCompatWrapper implements KeyboardActionListener {
+ private static final String TAG = LatinIME.class.getSimpleName();
private static final boolean PERF_DEBUG = false;
private static final boolean TRACE = false;
private static boolean DEBUG = LatinImeLogger.sDBG;
+ /**
+ * The private IME option used to indicate that no microphone should be
+ * shown for a given text field. For instance, this is specified by the
+ * search dialog when the dialog is already showing a voice search button.
+ *
+ * @deprecated Use {@link LatinIME#IME_OPTION_NO_MICROPHONE} with package name prefixed.
+ */
+ @SuppressWarnings("dep-ann")
+ public static final String IME_OPTION_NO_MICROPHONE_COMPAT = "nm";
+
+ /**
+ * The private IME option used to indicate that no microphone should be
+ * shown for a given text field. For instance, this is specified by the
+ * search dialog when the dialog is already showing a voice search button.
+ */
+ public static final String IME_OPTION_NO_MICROPHONE = "noMicrophoneKey";
+
+ /**
+ * The private IME option used to indicate that no settings key should be
+ * shown for a given text field.
+ */
+ public static final String IME_OPTION_NO_SETTINGS_KEY = "noSettingsKey";
+
private static final int DELAY_UPDATE_SUGGESTIONS = 180;
private static final int DELAY_UPDATE_OLD_SUGGESTIONS = 300;
private static final int DELAY_UPDATE_SHIFT_STATE = 300;
@@ -99,6 +123,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Key events coming any faster than this are long-presses.
private static final int QUICK_PRESS = 200;
+ /**
+ * The name of the scheme used by the Package Manager to warn of a new package installation,
+ * replacement or removal.
+ */
+ private static final String SCHEME_PACKAGE = "package";
+
private int mSuggestionVisibility;
private static final int SUGGESTION_VISIBILILTY_SHOW_VALUE
= R.string.prefs_suggestion_visibility_show_value;
@@ -120,13 +150,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private AlertDialog mOptionsDialog;
- private InputMethodManager mImm;
+ private InputMethodManagerCompatWrapper mImm;
private Resources mResources;
private SharedPreferences mPrefs;
private String mInputMethodId;
private KeyboardSwitcher mKeyboardSwitcher;
private SubtypeSwitcher mSubtypeSwitcher;
- private VoiceIMEConnector mVoiceConnector;
+ private VoiceProxy mVoiceProxy;
private UserDictionary mUserDictionary;
private UserBigramDictionary mUserBigramDictionary;
@@ -139,6 +169,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private boolean mIsSettingsSuggestionStripOn;
private boolean mApplicationSpecifiedCompletionOn;
+ private AccessibilityUtils mAccessibilityUtils;
+
private final StringBuilder mComposing = new StringBuilder();
private WordComposer mWord = new WordComposer();
private CharSequence mBestWord;
@@ -146,7 +178,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private boolean mHasDictionary;
private boolean mJustAddedAutoSpace;
private boolean mAutoCorrectEnabled;
- private boolean mReCorrectionEnabled;
+ private boolean mRecorrectionEnabled;
private boolean mBigramSuggestionEnabled;
private boolean mAutoCorrectOn;
private boolean mVibrateOn;
@@ -159,6 +191,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private int mConfigDelayBeforeFadeoutLanguageOnSpacebar;
private int mConfigDurationOfFadeoutLanguageOnSpacebar;
private float mConfigFinalFadeoutFactorOfLanguageOnSpacebar;
+ private long mConfigDoubleSpacesTurnIntoPeriodTimeout;
private int mCorrectionMode;
private int mCommittedLength;
@@ -170,7 +203,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Indicates whether the suggestion strip is to be on in landscape
private boolean mJustAccepted;
- private boolean mJustReverted;
private int mDeleteCount;
private long mLastKeyTime;
@@ -182,60 +214,47 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
/* package */ String mWordSeparators;
private String mSentenceSeparators;
private String mSuggestPuncs;
- // TODO: Move this flag to VoiceIMEConnector
+ // TODO: Move this flag to VoiceProxy
private boolean mConfigurationChanging;
+ // Object for reacting to adding/removing a dictionary pack.
+ private BroadcastReceiver mDictionaryPackInstallReceiver =
+ new DictionaryPackInstallBroadcastReceiver(this);
+
// Keeps track of most recently inserted text (multi-character key) for reverting
private CharSequence mEnteredText;
- private boolean mRefreshKeyboardRequired;
private final ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>();
- public abstract static class WordAlternatives {
- protected CharSequence mChosenWord;
+ public class WordAlternatives {
+ private final CharSequence mChosenWord;
+ private final WordComposer mWordComposer;
- public WordAlternatives() {
- // Nothing
- }
-
- public WordAlternatives(CharSequence chosenWord) {
+ public WordAlternatives(CharSequence chosenWord, WordComposer wordComposer) {
mChosenWord = chosenWord;
+ mWordComposer = wordComposer;
}
- @Override
- public int hashCode() {
- return mChosenWord.hashCode();
- }
-
- public abstract CharSequence getOriginalWord();
-
public CharSequence getChosenWord() {
return mChosenWord;
}
- public abstract SuggestedWords.Builder getAlternatives();
- }
-
- public class TypedWordAlternatives extends WordAlternatives {
- private WordComposer word;
-
- public TypedWordAlternatives() {
- // Nothing
+ public CharSequence getOriginalWord() {
+ return mWordComposer.getTypedWord();
}
- public TypedWordAlternatives(CharSequence chosenWord, WordComposer wordComposer) {
- super(chosenWord);
- word = wordComposer;
+ public SuggestedWords.Builder getAlternatives() {
+ return getTypedSuggestions(mWordComposer);
}
@Override
- public CharSequence getOriginalWord() {
- return word.getTypedWord();
+ public int hashCode() {
+ return mChosenWord.hashCode();
}
@Override
- public SuggestedWords.Builder getAlternatives() {
- return getTypedSuggestions(word);
+ public boolean equals(Object o) {
+ return o instanceof CharSequence && TextUtils.equals(mChosenWord, (CharSequence)o);
}
}
@@ -248,6 +267,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private static final int MSG_VOICE_RESULTS = 3;
private static final int MSG_FADEOUT_LANGUAGE_ON_SPACEBAR = 4;
private static final int MSG_DISMISS_LANGUAGE_ON_SPACEBAR = 5;
+ private static final int MSG_SPACE_TYPED = 6;
@Override
public void handleMessage(Message msg) {
@@ -264,7 +284,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
switcher.updateShiftState();
break;
case MSG_VOICE_RESULTS:
- mVoiceConnector.handleVoiceResults(preferCapitalization()
+ mVoiceProxy.handleVoiceResults(preferCapitalization()
|| (switcher.isAlphabetMode() && switcher.isShiftedOrShiftLocked()));
break;
case MSG_FADEOUT_LANGUAGE_ON_SPACEBAR:
@@ -324,7 +344,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
removeMessages(MSG_DISMISS_LANGUAGE_ON_SPACEBAR);
final LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
if (inputView != null) {
- final LatinKeyboard keyboard = inputView.getLatinKeyboard();
+ final LatinKeyboard keyboard = mKeyboardSwitcher.getLatinKeyboard();
// The language is never displayed when the delay is zero.
if (mConfigDelayBeforeFadeoutLanguageOnSpacebar != 0)
inputView.setSpacebarTextFadeFactor(localeChanged ? 1.0f
@@ -336,6 +356,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
}
+
+ public void startDoubleSpacesTimer() {
+ removeMessages(MSG_SPACE_TYPED);
+ sendMessageDelayed(obtainMessage(MSG_SPACE_TYPED),
+ mConfigDoubleSpacesTurnIntoPeriodTimeout);
+ }
+
+ public void cancelDoubleSpacesTimer() {
+ removeMessages(MSG_SPACE_TYPED);
+ }
+
+ public boolean isAcceptingDoubleSpaces() {
+ return hasMessages(MSG_SPACE_TYPED);
+ }
}
@Override
@@ -345,13 +379,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
LatinImeLogger.init(this, prefs);
SubtypeSwitcher.init(this, prefs);
KeyboardSwitcher.init(this, prefs);
+ AccessibilityUtils.init(this, prefs);
super.onCreate();
- mImm = ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE));
- mInputMethodId = Utils.getInputMethodId(mImm, getApplicationInfo().packageName);
+ mImm = InputMethodManagerCompatWrapper.getInstance(this);
+ mInputMethodId = Utils.getInputMethodId(mImm, getPackageName());
mSubtypeSwitcher = SubtypeSwitcher.getInstance();
mKeyboardSwitcher = KeyboardSwitcher.getInstance();
+ mAccessibilityUtils = AccessibilityUtils.getInstance();
final Resources res = getResources();
mResources = res;
@@ -359,10 +395,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// If the option should not be shown, do not read the recorrection preference
// but always use the default setting defined in the resources.
if (res.getBoolean(R.bool.config_enable_show_recorrection_option)) {
- mReCorrectionEnabled = prefs.getBoolean(Settings.PREF_RECORRECTION_ENABLED,
+ mRecorrectionEnabled = prefs.getBoolean(Settings.PREF_RECORRECTION_ENABLED,
res.getBoolean(R.bool.config_default_recorrection_enabled));
} else {
- mReCorrectionEnabled = res.getBoolean(R.bool.config_default_recorrection_enabled);
+ mRecorrectionEnabled = res.getBoolean(R.bool.config_default_recorrection_enabled);
}
mConfigEnableShowSubtypeSettings = res.getBoolean(
@@ -375,6 +411,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
R.integer.config_duration_of_fadeout_language_on_spacebar);
mConfigFinalFadeoutFactorOfLanguageOnSpacebar = res.getInteger(
R.integer.config_final_fadeout_percentage_of_language_on_spacebar) / 100.0f;
+ mConfigDoubleSpacesTurnIntoPeriodTimeout = res.getInteger(
+ R.integer.config_double_spaces_turn_into_period_timeout);
Utils.GCUtils.getInstance().reset();
boolean tryGC = true;
@@ -390,30 +428,26 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mOrientation = res.getConfiguration().orientation;
initSuggestPuncList();
- // register to receive ringer mode change and network state change.
+ // Register to receive ringer mode change and network state change.
+ // Also receive installation and removal of a dictionary pack.
final IntentFilter filter = new IntentFilter();
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(mReceiver, filter);
- mVoiceConnector = VoiceIMEConnector.init(this, prefs, mHandler);
- prefs.registerOnSharedPreferenceChangeListener(this);
- }
+ mVoiceProxy = VoiceProxy.init(this, prefs, mHandler);
- /**
- * Returns a main dictionary resource id
- * @return main dictionary resource id
- */
- public static int getMainDictionaryResourceId(Resources res) {
- final String MAIN_DIC_NAME = "main";
- String packageName = LatinIME.class.getPackage().getName();
- return res.getIdentifier(MAIN_DIC_NAME, "raw", packageName);
+ final IntentFilter packageFilter = new IntentFilter();
+ packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ packageFilter.addDataScheme(SCHEME_PACKAGE);
+ registerReceiver(mDictionaryPackInstallReceiver, packageFilter);
}
private void initSuggest() {
- updateAutoTextEnabled();
- String locale = mSubtypeSwitcher.getInputLocaleStr();
+ final String localeStr = mSubtypeSwitcher.getInputLocaleStr();
+ final Locale keyboardLocale = new Locale(localeStr);
- Locale savedLocale = mSubtypeSwitcher.changeSystemLocale(new Locale(locale));
+ final Locale savedLocale = mSubtypeSwitcher.changeSystemLocale(keyboardLocale);
if (mSuggest != null) {
mSuggest.close();
}
@@ -421,20 +455,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mQuickFixes = isQuickFixesEnabled(prefs);
final Resources res = mResources;
- int mainDicResId = getMainDictionaryResourceId(res);
- mSuggest = new Suggest(this, mainDicResId);
+ int mainDicResId = Utils.getMainDictionaryResourceId(res);
+ mSuggest = new Suggest(this, mainDicResId, keyboardLocale);
loadAndSetAutoCorrectionThreshold(prefs);
+ updateAutoTextEnabled();
- mUserDictionary = new UserDictionary(this, locale);
+ mUserDictionary = new UserDictionary(this, localeStr);
mSuggest.setUserDictionary(mUserDictionary);
mContactsDictionary = new ContactsDictionary(this, Suggest.DIC_CONTACTS);
mSuggest.setContactsDictionary(mContactsDictionary);
- mAutoDictionary = new AutoDictionary(this, this, locale, Suggest.DIC_AUTO);
+ mAutoDictionary = new AutoDictionary(this, this, localeStr, Suggest.DIC_AUTO);
mSuggest.setAutoDictionary(mAutoDictionary);
- mUserBigramDictionary = new UserBigramDictionary(this, this, locale, Suggest.DIC_USER);
+ mUserBigramDictionary = new UserBigramDictionary(this, this, localeStr, Suggest.DIC_USER);
mSuggest.setUserBigramDictionary(mUserBigramDictionary);
updateCorrectionMode();
@@ -444,6 +479,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mSubtypeSwitcher.changeSystemLocale(savedLocale);
}
+ /* package private */ void resetSuggestMainDict() {
+ final String localeStr = mSubtypeSwitcher.getInputLocaleStr();
+ final Locale keyboardLocale = new Locale(localeStr);
+ int mainDicResId = Utils.getMainDictionaryResourceId(mResources);
+ mSuggest.resetMainDict(this, mainDicResId, keyboardLocale);
+ }
+
@Override
public void onDestroy() {
if (mSuggest != null) {
@@ -451,7 +493,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mSuggest = null;
}
unregisterReceiver(mReceiver);
- mVoiceConnector.destroy();
+ unregisterReceiver(mDictionaryPackInstallReceiver);
+ mVoiceProxy.destroy();
LatinImeLogger.commit();
LatinImeLogger.onDestroy();
super.onDestroy();
@@ -472,7 +515,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mConfigurationChanging = true;
super.onConfigurationChanged(conf);
- mVoiceConnector.onConfigurationChanged(conf);
+ mVoiceProxy.onConfigurationChanged(conf);
mConfigurationChanging = false;
}
@@ -489,7 +532,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (container.getPaddingRight() != 0) {
HorizontalScrollView scrollView =
(HorizontalScrollView) container.findViewById(R.id.candidates_scroll_view);
- // @@@ scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER);
+ setOverScrollModeNever(scrollView);
container.setGravity(Gravity.CENTER_HORIZONTAL);
}
mCandidateView = (CandidateView) container.findViewById(R.id.candidates);
@@ -498,17 +541,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return container;
}
- private static boolean isPasswordVariation(int variation) {
- return variation == InputType.TYPE_TEXT_VARIATION_PASSWORD
- || variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
- // @@@ || variation == InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD;
- }
-
- private static boolean isEmailVariation(int variation) {
- return variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
- // @@@ || variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
- }
-
@Override
public void onStartInputView(EditorInfo attribute, boolean restarting) {
final KeyboardSwitcher switcher = mKeyboardSwitcher;
@@ -524,20 +556,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mSubtypeSwitcher.updateParametersOnStartInputView();
- if (mRefreshKeyboardRequired) {
- mRefreshKeyboardRequired = false;
- onRefreshKeyboard();
- }
-
- TextEntryState.newSession(this);
+ TextEntryState.reset();
// Most such things we decide below in initializeInputAttributesAndGetMode, but we need to
// know now whether this is a password text field, because we need to know now whether we
// want to enable the voice button.
- mVoiceConnector.resetVoiceStates(isPasswordVariation(
- attribute.inputType & InputType.TYPE_MASK_VARIATION));
+ final VoiceProxy voiceIme = mVoiceProxy;
+ voiceIme.resetVoiceStates(InputTypeCompatUtils.isPasswordInputType(attribute.inputType)
+ || InputTypeCompatUtils.isVisiblePasswordInputType(attribute.inputType));
- final int mode = initializeInputAttributesAndGetMode(attribute.inputType);
+ initializeInputAttributes(attribute);
inputView.closing();
mEnteredText = null;
@@ -548,9 +576,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
loadSettings(attribute);
if (mSubtypeSwitcher.isKeyboardMode()) {
- switcher.loadKeyboard(mode, attribute.imeOptions,
- mVoiceConnector.isVoiceButtonEnabled(),
- mVoiceConnector.isVoiceButtonOnPrimary());
+ switcher.loadKeyboard(attribute,
+ mSubtypeSwitcher.isShortcutImeEnabled() && voiceIme.isVoiceButtonEnabled(),
+ voiceIme.isVoiceButtonOnPrimary());
switcher.updateShiftState();
}
@@ -561,18 +589,24 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
updateCorrectionMode();
+ final boolean accessibilityEnabled = mAccessibilityUtils.isAccessibilityEnabled();
+
inputView.setPreviewEnabled(mPopupOn);
inputView.setProximityCorrectionEnabled(true);
+ inputView.setAccessibilityEnabled(accessibilityEnabled);
// If we just entered a text field, maybe it has some old text that requires correction
- checkReCorrectionOnStart();
+ checkRecorrectionOnStart();
inputView.setForeground(true);
- mVoiceConnector.onStartInputView(inputView.getWindowToken());
+ voiceIme.onStartInputView(inputView.getWindowToken());
if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
}
- private int initializeInputAttributesAndGetMode(int inputType) {
+ private void initializeInputAttributes(EditorInfo attribute) {
+ if (attribute == null)
+ return;
+ final int inputType = attribute.inputType;
final int variation = inputType & InputType.TYPE_MASK_VARIATION;
mAutoSpace = false;
mInputTypeNoAutoCorrect = false;
@@ -580,73 +614,52 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mApplicationSpecifiedCompletionOn = false;
mApplicationSpecifiedCompletions = null;
- final int mode;
- switch (inputType & InputType.TYPE_MASK_CLASS) {
- case InputType.TYPE_CLASS_NUMBER:
- case InputType.TYPE_CLASS_DATETIME:
- mode = KeyboardId.MODE_NUMBER;
- break;
- case InputType.TYPE_CLASS_PHONE:
- mode = KeyboardId.MODE_PHONE;
- break;
- case InputType.TYPE_CLASS_TEXT:
- mIsSettingsSuggestionStripOn = true;
- // Make sure that passwords are not displayed in candidate view
- if (isPasswordVariation(variation)) {
- mIsSettingsSuggestionStripOn = false;
- }
- if (isEmailVariation(variation)
- || variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) {
- mAutoSpace = false;
- } else {
- mAutoSpace = true;
- }
- if (isEmailVariation(variation)) {
- mIsSettingsSuggestionStripOn = false;
- mode = KeyboardId.MODE_EMAIL;
- } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
- mIsSettingsSuggestionStripOn = false;
- mode = KeyboardId.MODE_URL;
- } else if (variation == InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
- mode = KeyboardId.MODE_IM;
- } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
- mIsSettingsSuggestionStripOn = false;
- mode = KeyboardId.MODE_TEXT;
- } else if (variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) {
- 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 ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) {
- mInputTypeNoAutoCorrect = true;
- }
- } else {
- mode = KeyboardId.MODE_TEXT;
- }
-
- // If NO_SUGGESTIONS is set, don't do prediction.
- if ((inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) {
- mIsSettingsSuggestionStripOn = false;
- mInputTypeNoAutoCorrect = true;
- }
- // If it's not multiline and the autoCorrect flag is not set, then don't correct
- if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0 &&
- (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) == 0) {
+ if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) {
+ mIsSettingsSuggestionStripOn = true;
+ // Make sure that passwords are not displayed in candidate view
+ if (InputTypeCompatUtils.isPasswordInputType(inputType)
+ || InputTypeCompatUtils.isVisiblePasswordInputType(inputType)) {
+ mIsSettingsSuggestionStripOn = false;
+ }
+ if (InputTypeCompatUtils.isEmailVariation(variation)
+ || variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) {
+ mAutoSpace = false;
+ } else {
+ mAutoSpace = true;
+ }
+ if (InputTypeCompatUtils.isEmailVariation(variation)) {
+ mIsSettingsSuggestionStripOn = false;
+ } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
+ mIsSettingsSuggestionStripOn = false;
+ } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
+ mIsSettingsSuggestionStripOn = false;
+ } else if (variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) {
+ // If it's a browser edit field and auto correct is not ON explicitly, then
+ // disable auto correction, but keep suggestions on.
+ if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) {
mInputTypeNoAutoCorrect = true;
}
- if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
- mIsSettingsSuggestionStripOn = false;
- mApplicationSpecifiedCompletionOn = isFullscreenMode();
- }
- break;
- default:
- mode = KeyboardId.MODE_TEXT;
- break;
+ }
+
+ // If NO_SUGGESTIONS is set, don't do prediction.
+ if ((inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) {
+ mIsSettingsSuggestionStripOn = false;
+ mInputTypeNoAutoCorrect = true;
+ }
+ // If it's not multiline and the autoCorrect flag is not set, then don't correct
+ if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0
+ && (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) == 0) {
+ mInputTypeNoAutoCorrect = true;
+ }
+ if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
+ mIsSettingsSuggestionStripOn = false;
+ mApplicationSpecifiedCompletionOn = isFullscreenMode();
+ }
}
- return mode;
}
- private void checkReCorrectionOnStart() {
- if (!mReCorrectionEnabled) return;
+ private void checkRecorrectionOnStart() {
+ if (!mRecorrectionEnabled) return;
final InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
@@ -680,7 +693,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
LatinImeLogger.commit();
mKeyboardSwitcher.onAutoCorrectionStateChanged(false);
- mVoiceConnector.flushVoiceInputLogs(mConfigurationChanging);
+ mVoiceProxy.flushVoiceInputLogs(mConfigurationChanging);
KeyboardView inputView = mKeyboardSwitcher.getInputView();
if (inputView != null) inputView.closing();
@@ -701,7 +714,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onUpdateExtractedText(int token, ExtractedText text) {
super.onUpdateExtractedText(token, text);
- mVoiceConnector.showPunctuationHintIfNecessary();
+ mVoiceProxy.showPunctuationHintIfNecessary();
}
@Override
@@ -714,19 +727,30 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (DEBUG) {
Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart
+ ", ose=" + oldSelEnd
+ + ", lss=" + mLastSelectionStart
+ + ", lse=" + mLastSelectionEnd
+ ", nss=" + newSelStart
+ ", nse=" + newSelEnd
+ ", cs=" + candidatesStart
+ ", ce=" + candidatesEnd);
}
- mVoiceConnector.setCursorAndSelection(newSelEnd, newSelStart);
+ mVoiceProxy.setCursorAndSelection(newSelEnd, newSelStart);
// If the current selection in the text view changes, we should
// clear whatever candidate text we have.
- if ((((mComposing.length() > 0 && mHasValidSuggestions)
- || mVoiceConnector.isVoiceInputHighlighted()) && (newSelStart != candidatesEnd
- || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart)) {
+ final boolean selectionChanged = (newSelStart != candidatesEnd
+ || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart;
+ final boolean candidatesCleared = candidatesStart == -1 && candidatesEnd == -1;
+ if (((mComposing.length() > 0 && mHasValidSuggestions)
+ || mVoiceProxy.isVoiceInputHighlighted())
+ && (selectionChanged || candidatesCleared)) {
+ if (candidatesCleared) {
+ // If the composing span has been cleared, save the typed word in the history for
+ // recorrection before we reset the candidate strip. Then, we'll be able to show
+ // suggestions for recorrection right away.
+ saveWordInHistory(mComposing);
+ }
mComposing.setLength(0);
mHasValidSuggestions = false;
mHandler.postUpdateSuggestions();
@@ -735,17 +759,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (ic != null) {
ic.finishComposingText();
}
- mVoiceConnector.setVoiceInputHighlighted(false);
+ mVoiceProxy.setVoiceInputHighlighted(false);
} else if (!mHasValidSuggestions && !mJustAccepted) {
- switch (TextEntryState.getState()) {
- case ACCEPTED_DEFAULT:
- TextEntryState.reset();
- // $FALL-THROUGH$
- case SPACE_AFTER_PICKED:
+ if (TextEntryState.isAcceptedDefault() || TextEntryState.isSpaceAfterPicked()) {
+ if (TextEntryState.isAcceptedDefault())
+ TextEntryState.reset();
mJustAddedAutoSpace = false; // The user moved the cursor.
- break;
- default:
- break;
}
}
mJustAccepted = false;
@@ -755,18 +774,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mLastSelectionStart = newSelStart;
mLastSelectionEnd = newSelEnd;
- if (mReCorrectionEnabled && isShowingSuggestionsStrip()) {
+ if (mRecorrectionEnabled && isShowingSuggestionsStrip()) {
// Don't look for corrections if the keyboard is not visible
if (mKeyboardSwitcher.isInputViewShown()) {
// Check if we should go in or out of correction mode.
- if (isSuggestionsRequested() && !mJustReverted
+ if (isSuggestionsRequested()
&& (candidatesStart == candidatesEnd || newSelStart != oldSelStart
- || TextEntryState.isCorrecting())
+ || TextEntryState.isRecorrecting())
&& (newSelStart < newSelEnd - 1 || !mHasValidSuggestions)) {
if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) {
mHandler.postUpdateOldSuggestions();
} else {
- abortCorrection(false);
+ abortRecorrection(false);
// Show the punctuation suggestions list if the current one is not
// and if not showing "Touch again to save".
if (mCandidateView != null && !isShowingPunctuationList()
@@ -789,7 +808,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
*/
@Override
public void onExtractedTextClicked() {
- if (mReCorrectionEnabled && isSuggestionsRequested()) return;
+ if (mRecorrectionEnabled && isSuggestionsRequested()) return;
super.onExtractedTextClicked();
}
@@ -805,7 +824,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
*/
@Override
public void onExtractedCursorMovement(int dx, int dy) {
- if (mReCorrectionEnabled && isSuggestionsRequested()) return;
+ if (mRecorrectionEnabled && isSuggestionsRequested()) return;
super.onExtractedCursorMovement(dx, dy);
}
@@ -820,20 +839,19 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mOptionsDialog.dismiss();
mOptionsDialog = null;
}
- mVoiceConnector.hideVoiceWindow(mConfigurationChanging);
+ mVoiceProxy.hideVoiceWindow(mConfigurationChanging);
mWordHistory.clear();
super.hideWindow();
- TextEntryState.endSession();
}
@Override
public void onDisplayCompletions(CompletionInfo[] applicationSpecifiedCompletions) {
if (DEBUG) {
Log.i(TAG, "Received completions:");
- final int count = (applicationSpecifiedCompletions != null)
- ? applicationSpecifiedCompletions.length : 0;
- for (int i = 0; i < count; i++) {
- Log.i(TAG, " #" + i + ": " + applicationSpecifiedCompletions[i]);
+ if (applicationSpecifiedCompletions != null) {
+ for (int i = 0; i < applicationSpecifiedCompletions.length; i++) {
+ Log.i(TAG, " #" + i + ": " + applicationSpecifiedCompletions[i]);
+ }
}
}
if (mApplicationSpecifiedCompletionOn) {
@@ -898,8 +916,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (DEBUG) {
Log.d(TAG, "Touchable region " + x + ", " + y + ", " + width + ", " + height);
}
- //@@@outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION;
- //@@@outInsets.touchableRegion.set(x, y, width, height);
+ setTouchableRegionCompat(outInsets, x, y, width, height);
}
}
@@ -964,7 +981,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
mCommittedLength = mComposing.length();
TextEntryState.acceptedTyped(mComposing);
- addToDictionaries(mComposing, AutoDictionary.FREQUENCY_FOR_TYPED);
+ addToAutoAndUserBigramDictionaries(mComposing, AutoDictionary.FREQUENCY_FOR_TYPED);
}
updateSuggestions();
}
@@ -1012,7 +1029,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
private void doubleSpace() {
- //if (!mAutoPunctuate) return;
if (mCorrectionMode == Suggest.CORRECTION_NONE) return;
final InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
@@ -1020,13 +1036,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (lastThree != null && lastThree.length() == 3
&& Character.isLetterOrDigit(lastThree.charAt(0))
&& lastThree.charAt(1) == Keyboard.CODE_SPACE
- && lastThree.charAt(2) == Keyboard.CODE_SPACE) {
+ && lastThree.charAt(2) == Keyboard.CODE_SPACE
+ && mHandler.isAcceptingDoubleSpaces()) {
+ mHandler.cancelDoubleSpacesTimer();
ic.beginBatchEdit();
ic.deleteSurroundingText(2, 0);
ic.commitText(". ", 1);
ic.endBatchEdit();
mKeyboardSwitcher.updateShiftState();
mJustAddedAutoSpace = true;
+ } else {
+ mHandler.startDoubleSpacesTimer();
}
}
@@ -1106,6 +1126,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
mLastKeyTime = when;
KeyboardSwitcher switcher = mKeyboardSwitcher;
+ final boolean accessibilityEnabled = switcher.isAccessibilityEnabled();
final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
switch (primaryCode) {
case Keyboard.CODE_DELETE:
@@ -1115,12 +1136,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
break;
case Keyboard.CODE_SHIFT:
// Shift key is handled in onPress() when device has distinct multi-touch panel.
- if (!distinctMultiTouch)
+ if (!distinctMultiTouch || accessibilityEnabled)
switcher.toggleShift();
break;
case Keyboard.CODE_SWITCH_ALPHA_SYMBOL:
// Symbol key is handled in onPress() when device has distinct multi-touch panel.
- if (!distinctMultiTouch)
+ if (!distinctMultiTouch || accessibilityEnabled)
switcher.changeKeyboardMode();
break;
case Keyboard.CODE_CANCEL:
@@ -1158,10 +1179,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (isWordSeparator(primaryCode)) {
handleSeparator(primaryCode);
} else {
- handleCharacter(primaryCode, keyCodes);
+ handleCharacter(primaryCode, keyCodes, x, y);
}
- // Cancel the just reverted state
- mJustReverted = false;
}
switcher.onKey(primaryCode);
// Reset after any single keystroke
@@ -1170,10 +1189,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onTextInput(CharSequence text) {
- mVoiceConnector.commitVoiceInput();
+ mVoiceProxy.commitVoiceInput();
InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
- abortCorrection(false);
+ abortRecorrection(false);
ic.beginBatchEdit();
commitTyped(ic);
maybeRemovePreviousPeriod(text);
@@ -1181,7 +1200,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
ic.endBatchEdit();
mKeyboardSwitcher.updateShiftState();
mKeyboardSwitcher.onKey(Keyboard.CODE_DUMMY);
- mJustReverted = false;
mJustAddedAutoSpace = false;
mEnteredText = text;
}
@@ -1193,13 +1211,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
private void handleBackspace() {
- if (mVoiceConnector.logAndRevertVoiceInput()) return;
+ if (mVoiceProxy.logAndRevertVoiceInput()) return;
final InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
ic.beginBatchEdit();
- mVoiceConnector.handleBackspace();
+ mVoiceProxy.handleBackspace();
boolean deleteChar = false;
if (mHasValidSuggestions) {
@@ -1221,7 +1239,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.postUpdateShiftKeyState();
TextEntryState.backspace();
- if (TextEntryState.getState() == TextEntryState.State.UNDO_COMMIT) {
+ if (TextEntryState.isUndoCommit()) {
revertLastWord(deleteChar);
ic.endBatchEdit();
return;
@@ -1246,16 +1264,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
}
- mJustReverted = false;
ic.endBatchEdit();
}
private void handleTab() {
final int imeOptions = getCurrentInputEditorInfo().imeOptions;
- final int navigationFlags = 0;
- // @@@ final int navigationFlags =
- // @@@ EditorInfo.IME_FLAG_NAVIGATE_NEXT | EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
- if ((imeOptions & navigationFlags) == 0) {
+ if (!EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions)
+ && !EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions)) {
sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB);
return;
}
@@ -1264,33 +1279,32 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (ic == null)
return;
- /* @@@
// 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
+ if (EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions)
&& !isManualTemporaryUpperCase) {
+ EditorInfoCompatUtils.performEditorActionNext(ic);
ic.performEditorAction(EditorInfo.IME_ACTION_NEXT);
- } else if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS) != 0
+ } else if (EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions)
&& isManualTemporaryUpperCase) {
- ic.performEditorAction(EditorInfo.IME_ACTION_PREVIOUS);
+ EditorInfoCompatUtils.performEditorActionPrevious(ic);
}
- */
}
- private void abortCorrection(boolean force) {
- if (force || TextEntryState.isCorrecting()) {
- TextEntryState.onAbortCorrection();
+ private void abortRecorrection(boolean force) {
+ if (force || TextEntryState.isRecorrecting()) {
+ TextEntryState.onAbortRecorrection();
setCandidatesViewShown(isCandidateStripVisible());
getCurrentInputConnection().finishComposingText();
clearSuggestions();
}
}
- private void handleCharacter(int primaryCode, int[] keyCodes) {
- mVoiceConnector.handleCharacter();
+ private void handleCharacter(int primaryCode, int[] keyCodes, int x, int y) {
+ mVoiceProxy.handleCharacter();
- if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isCorrecting()) {
- abortCorrection(false);
+ if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isRecorrecting()) {
+ abortRecorrection(false);
}
int code = primaryCode;
@@ -1300,6 +1314,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mComposing.setLength(0);
saveWordInHistory(mBestWord);
mWord.reset();
+ clearSuggestions();
}
}
KeyboardSwitcher switcher = mKeyboardSwitcher;
@@ -1327,7 +1342,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mWord.setFirstCharCapitalized(true);
}
mComposing.append((char) code);
- mWord.add(code, keyCodes);
+ mWord.add(code, keyCodes, x, y);
InputConnection ic = getCurrentInputConnection();
if (ic != null) {
// If it's the first letter, make note of auto-caps state
@@ -1346,7 +1361,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
private void handleSeparator(int primaryCode) {
- mVoiceConnector.handleSeparator();
+ mVoiceProxy.handleSeparator();
// Should dismiss the "Touch again to save" message when handling separator
if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) {
@@ -1358,14 +1373,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final InputConnection ic = getCurrentInputConnection();
if (ic != null) {
ic.beginBatchEdit();
- abortCorrection(false);
+ abortRecorrection(false);
}
if (mHasValidSuggestions) {
// In certain languages where single quote is a separator, it's better
// not to auto correct, but accept the typed word. For instance,
// in Italian dov' should not be expanded to dove' because the elision
// requires the last vowel to be removed.
- if (mAutoCorrectOn && primaryCode != '\'' && !mJustReverted) {
+ if (mAutoCorrectOn && primaryCode != '\'') {
pickedDefault = pickDefaultSuggestion();
// Picked the suggestion by the space key. We consider this
// as "added an auto space".
@@ -1384,14 +1399,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Handle the case of ". ." -> " .." with auto-space if necessary
// before changing the TextEntryState.
- if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED
- && primaryCode == Keyboard.CODE_PERIOD) {
+ if (TextEntryState.isPunctuationAfterAccepted() && primaryCode == Keyboard.CODE_PERIOD) {
reswapPeriodAndSpace();
}
TextEntryState.typedCharacter((char) primaryCode, true);
- if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED
- && primaryCode != Keyboard.CODE_ENTER) {
+ if (TextEntryState.isPunctuationAfterAccepted() && primaryCode != Keyboard.CODE_ENTER) {
swapPunctuationAndSpace();
} else if (isSuggestionsRequested() && primaryCode == Keyboard.CODE_SPACE) {
doubleSpace();
@@ -1400,13 +1413,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
CharSequence typedWord = mWord.getTypedWord();
TextEntryState.backToAcceptedDefault(typedWord);
if (!TextUtils.isEmpty(typedWord) && !typedWord.equals(mBestWord)) {
- if (ic != null) {
- /* @@@
- CorrectionInfo correctionInfo = new CorrectionInfo(
- mLastSelectionEnd - typedWord.length(), typedWord, mBestWord);
- ic.commitCorrection(correctionInfo);
- */
- }
+ InputConnectionCompatUtils.commitCorrection(
+ ic, mLastSelectionEnd - typedWord.length(), typedWord, mBestWord);
if (mCandidateView != null)
mCandidateView.onAutoCorrectionInverted(mBestWord);
}
@@ -1420,17 +1428,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void handleClose() {
commitTyped(getCurrentInputConnection());
- mVoiceConnector.handleClose();
+ mVoiceProxy.handleClose();
requestHideSelf(0);
LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
if (inputView != null)
inputView.closing();
- TextEntryState.endSession();
}
private void saveWordInHistory(CharSequence result) {
if (mWord.size() <= 1) {
- mWord.reset();
return;
}
// Skip if result is null. It happens in some edge case.
@@ -1440,7 +1446,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Make a copy of the CharSequence, since it is/could be a mutable CharSequence
final String resultCopy = result.toString();
- TypedWordAlternatives entry = new TypedWordAlternatives(resultCopy,
+ WordAlternatives entry = new WordAlternatives(resultCopy,
new WordComposer(mWord));
mWordHistory.add(entry);
}
@@ -1463,7 +1469,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private boolean isCandidateStripVisible() {
if (mCandidateView == null)
return false;
- if (mCandidateView.isShowingAddToDictionaryHint() || TextEntryState.isCorrecting())
+ if (mCandidateView.isShowingAddToDictionaryHint() || TextEntryState.isRecorrecting())
return true;
if (!isShowingSuggestionsStrip())
return false;
@@ -1473,26 +1479,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
public void switchToKeyboardView() {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- if (DEBUG) {
- Log.d(TAG, "Switch to keyboard view.");
- }
- 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);
- }
- setCandidatesViewShown(isCandidateStripVisible());
- updateInputViewShown();
- mHandler.postUpdateSuggestions();
+ if (DEBUG) {
+ Log.d(TAG, "Switch to keyboard view.");
+ }
+ 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);
+ }
+ setCandidatesViewShown(isCandidateStripVisible());
+ updateInputViewShown();
+ mHandler.postUpdateSuggestions();
}
public void clearSuggestions() {
@@ -1500,7 +1501,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
public void setSuggestions(SuggestedWords words) {
- if (mVoiceConnector.getAndResetIsShowingHint()) {
+ if (mVoiceProxy.getAndResetIsShowingHint()) {
setCandidatesView(mCandidateViewContainer);
}
@@ -1514,11 +1515,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
public void updateSuggestions() {
- mKeyboardSwitcher.setPreferredLetters(null);
-
// Check if we have a suggestion engine attached.
if ((mSuggest == null || !isSuggestionsRequested())
- && !mVoiceConnector.isVoiceInputHighlighted()) {
+ && !mVoiceProxy.isVoiceInputHighlighted()) {
return;
}
@@ -1534,36 +1533,30 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
private void showCorrections(WordAlternatives alternatives) {
- mKeyboardSwitcher.setPreferredLetters(null);
SuggestedWords.Builder builder = alternatives.getAlternatives();
builder.setTypedWordValid(false).setHasMinimalSuggestion(false);
showSuggestions(builder.build(), alternatives.getOriginalWord());
}
private void showSuggestions(WordComposer word) {
- // TODO Maybe need better way of retrieving previous word
+ // TODO: May need a better way of retrieving previous word
CharSequence prevWord = EditingUtils.getPreviousWord(getCurrentInputConnection(),
mWordSeparators);
SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(
mKeyboardSwitcher.getInputView(), word, prevWord);
- int[] nextLettersFrequencies = mSuggest.getNextLettersFrequencies();
- mKeyboardSwitcher.setPreferredLetters(nextLettersFrequencies);
-
- boolean correctionAvailable = !mInputTypeNoAutoCorrect && !mJustReverted
- && mSuggest.hasAutoCorrection();
+ boolean correctionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasAutoCorrection();
final CharSequence typedWord = word.getTypedWord();
- // If we're in basic correct
- final boolean typedWordValid = mSuggest.isValidWord(typedWord) ||
- (preferCapitalization()
- && mSuggest.isValidWord(typedWord.toString().toLowerCase()));
+ // Here, we want to promote a whitelisted word if exists.
+ final boolean typedWordValid = AutoCorrection.isValidWordForAutoCorrection(
+ mSuggest.getUnigramDictionaries(), typedWord, preferCapitalization());
if (mCorrectionMode == Suggest.CORRECTION_FULL
|| mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) {
correctionAvailable |= typedWordValid;
}
// Don't auto-correct words with multiple capital letter
correctionAvailable &= !word.isMostlyCaps();
- correctionAvailable &= !TextEntryState.isCorrecting();
+ correctionAvailable &= !TextEntryState.isRecorrecting();
// Basically, we update the suggestion strip only when suggestion count > 1. However,
// there is an exception: We update the suggestion strip whenever typed word's length
@@ -1610,7 +1603,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mJustAccepted = true;
pickSuggestion(mBestWord);
// Add the word to the auto dictionary if it's not a known word
- addToDictionaries(mBestWord, AutoDictionary.FREQUENCY_FOR_TYPED);
+ addToAutoAndUserBigramDictionaries(mBestWord, AutoDictionary.FREQUENCY_FOR_TYPED);
return true;
}
@@ -1619,9 +1612,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void pickSuggestionManually(int index, CharSequence suggestion) {
SuggestedWords suggestions = mCandidateView.getSuggestions();
- mVoiceConnector.flushAndLogAllTextModificationCounters(index, suggestion, mWordSeparators);
+ mVoiceProxy.flushAndLogAllTextModificationCounters(index, suggestion, mWordSeparators);
- final boolean correcting = TextEntryState.isCorrecting();
+ final boolean recorrecting = TextEntryState.isRecorrecting();
InputConnection ic = getCurrentInputConnection();
if (ic != null) {
ic.beginBatchEdit();
@@ -1663,24 +1656,35 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
pickSuggestion(suggestion);
// Add the word to the auto dictionary if it's not a known word
if (index == 0) {
- addToDictionaries(suggestion, AutoDictionary.FREQUENCY_FOR_PICKED);
+ addToAutoAndUserBigramDictionaries(suggestion, AutoDictionary.FREQUENCY_FOR_PICKED);
} else {
- addToBigramDictionary(suggestion, 1);
+ addToOnlyBigramDictionary(suggestion, 1);
}
LatinImeLogger.logOnManualSuggestion(mComposing.toString(), suggestion.toString(),
index, suggestions.mWords);
TextEntryState.acceptedSuggestion(mComposing.toString(), suggestion);
// Follow it with a space
- if (mAutoSpace && !correcting) {
+ if (mAutoSpace && !recorrecting) {
sendSpace();
mJustAddedAutoSpace = true;
}
- final boolean showingAddToDictionaryHint = index == 0 && mCorrectionMode > 0
- && !mSuggest.isValidWord(suggestion)
- && !mSuggest.isValidWord(suggestion.toString().toLowerCase());
-
- if (!correcting) {
+ // We should show the hint if the user pressed the first entry AND either:
+ // - There is no dictionary (we know that because we tried to load it => null != mSuggest
+ // AND mHasDictionary is false)
+ // - There is a dictionary and the word is not in it
+ // Please note that if mSuggest is null, it means that everything is off: suggestion
+ // and correction, so we shouldn't try to show the hint
+ // We used to look at mCorrectionMode here, but showing the hint should have nothing
+ // to do with the autocorrection setting.
+ final boolean showingAddToDictionaryHint = index == 0 && mSuggest != null
+ // If there is no dictionary the hint should be shown.
+ && (!mHasDictionary
+ // If "suggestion" is not in the dictionary, the hint should be shown.
+ || !AutoCorrection.isValidWord(
+ mSuggest.getUnigramDictionaries(), suggestion, true));
+
+ if (!recorrecting) {
// Fool the state watcher so that a subsequent backspace will not do a revert, unless
// we just did a correction, in which case we need to stay in
// TextEntryState.State.PICKED_SUGGESTION state.
@@ -1712,13 +1716,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return;
InputConnection ic = getCurrentInputConnection();
if (ic != null) {
- mVoiceConnector.rememberReplacedWord(suggestion, mWordSeparators);
+ mVoiceProxy.rememberReplacedWord(suggestion, mWordSeparators);
ic.commitText(suggestion, 1);
}
saveWordInHistory(suggestion);
mHasValidSuggestions = false;
mCommittedLength = suggestion.length();
- switcher.setPreferredLetters(null);
}
/**
@@ -1731,31 +1734,30 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// If we didn't find a match, search for result in typed word history
WordComposer foundWord = null;
WordAlternatives alternatives = null;
+ // Search old suggestions to suggest re-corrected suggestions.
for (WordAlternatives entry : mWordHistory) {
if (TextUtils.equals(entry.getChosenWord(), touching.mWord)) {
- if (entry instanceof TypedWordAlternatives) {
- foundWord = ((TypedWordAlternatives) entry).word;
- }
+ foundWord = entry.mWordComposer;
alternatives = entry;
break;
}
}
- // If we didn't find a match, at least suggest corrections.
+ // If we didn't find a match, at least suggest corrections as re-corrected suggestions.
if (foundWord == null
- && (mSuggest.isValidWord(touching.mWord)
- || mSuggest.isValidWord(touching.mWord.toString().toLowerCase()))) {
+ && (AutoCorrection.isValidWord(
+ mSuggest.getUnigramDictionaries(), touching.mWord, true))) {
foundWord = new WordComposer();
for (int i = 0; i < touching.mWord.length(); i++) {
foundWord.add(touching.mWord.charAt(i), new int[] {
touching.mWord.charAt(i)
- });
+ }, WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
}
foundWord.setFirstCharCapitalized(Character.isUpperCase(touching.mWord.charAt(0)));
}
// Found a match, show suggestions
if (foundWord != null || alternatives != null) {
if (alternatives == null) {
- alternatives = new TypedWordAlternatives(touching.mWord, foundWord);
+ alternatives = new WordAlternatives(touching.mWord, foundWord);
}
showCorrections(alternatives);
if (foundWord != null) {
@@ -1769,7 +1771,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
private void setOldSuggestions() {
- mVoiceConnector.setShowingVoiceSuggestions(false);
+ mVoiceProxy.setShowingVoiceSuggestions(false);
if (mCandidateView != null && mCandidateView.isShowingAddToDictionaryHint()) {
return;
}
@@ -1783,21 +1785,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (touching != null && touching.mWord.length() > 1) {
ic.beginBatchEdit();
- if (!mVoiceConnector.applyVoiceAlternatives(touching)
+ if (!mVoiceProxy.applyVoiceAlternatives(touching)
&& !applyTypedAlternatives(touching)) {
- abortCorrection(true);
+ abortRecorrection(true);
} else {
- TextEntryState.selectedForCorrection();
+ TextEntryState.selectedForRecorrection();
EditingUtils.underlineWord(ic, touching);
}
ic.endBatchEdit();
} else {
- abortCorrection(true);
+ abortRecorrection(true);
setPunctuationSuggestions(); // Show the punctuation suggestions list
}
} else {
- abortCorrection(true);
+ abortRecorrection(true);
}
}
@@ -1806,21 +1808,22 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
setCandidatesViewShown(isCandidateStripVisible());
}
- private void addToDictionaries(CharSequence suggestion, int frequencyDelta) {
+ private void addToAutoAndUserBigramDictionaries(CharSequence suggestion, int frequencyDelta) {
checkAddToDictionary(suggestion, frequencyDelta, false);
}
- private void addToBigramDictionary(CharSequence suggestion, int frequencyDelta) {
+ private void addToOnlyBigramDictionary(CharSequence suggestion, int frequencyDelta) {
checkAddToDictionary(suggestion, frequencyDelta, true);
}
/**
* Adds to the UserBigramDictionary and/or AutoDictionary
- * @param addToBigramDictionary true if it should be added to bigram dictionary if possible
+ * @param selectedANotTypedWord true if it should be added to bigram dictionary if possible
*/
private void checkAddToDictionary(CharSequence suggestion, int frequencyDelta,
- boolean addToBigramDictionary) {
+ boolean selectedANotTypedWord) {
if (suggestion == null || suggestion.length() < 1) return;
+
// Only auto-add to dictionary if auto-correct is ON. Otherwise we'll be
// adding words in situations where the user or application really didn't
// want corrections enabled or learned.
@@ -1828,9 +1831,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|| mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM)) {
return;
}
- if (!addToBigramDictionary && mAutoDictionary.isValidWord(suggestion)
- || (!mSuggest.isValidWord(suggestion.toString())
- && !mSuggest.isValidWord(suggestion.toString().toLowerCase()))) {
+
+ final boolean selectedATypedWordAndItsInAutoDic =
+ !selectedANotTypedWord && mAutoDictionary.isValidWord(suggestion);
+ final boolean isValidWord = AutoCorrection.isValidWord(
+ mSuggest.getUnigramDictionaries(), suggestion, true);
+ final boolean needsToAddToAutoDictionary = selectedATypedWordAndItsInAutoDic
+ || !isValidWord;
+ if (needsToAddToAutoDictionary) {
mAutoDictionary.addWord(suggestion.toString(), frequencyDelta);
}
@@ -1870,7 +1878,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final int length = mComposing.length();
if (!mHasValidSuggestions && length > 0) {
final InputConnection ic = getCurrentInputConnection();
- mJustReverted = true;
final CharSequence punctuation = ic.getTextBeforeCursor(1, 0);
if (deleteChar) ic.deleteSurroundingText(1, 0);
int toDelete = mCommittedLength;
@@ -1898,7 +1905,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.postUpdateSuggestions();
} else {
sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
- mJustReverted = false;
}
}
@@ -1936,28 +1942,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mSubtypeSwitcher.toggleLanguage(reset, next);
}
// Reload keyboard because the current language has been changed.
- KeyboardSwitcher switcher = mKeyboardSwitcher;
- final EditorInfo attribute = getCurrentInputEditorInfo();
- final int mode = initializeInputAttributesAndGetMode((attribute != null)
- ? attribute.inputType : 0);
- final int imeOptions = (attribute != null) ? attribute.imeOptions : 0;
- switcher.loadKeyboard(mode, imeOptions, mVoiceConnector.isVoiceButtonEnabled(),
- mVoiceConnector.isVoiceButtonOnPrimary());
+ mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(),
+ mSubtypeSwitcher.isShortcutImeEnabled() && mVoiceProxy.isVoiceButtonEnabled(),
+ mVoiceProxy.isVoiceButtonOnPrimary());
initSuggest();
- switcher.updateShiftState();
- }
-
- @Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
- String key) {
- mSubtypeSwitcher.onSharedPreferenceChanged(sharedPreferences, key);
- if (Settings.PREF_SELECTED_LANGUAGES.equals(key)) {
- mRefreshKeyboardRequired = true;
- } else if (Settings.PREF_RECORRECTION_ENABLED.equals(key)) {
- mReCorrectionEnabled = sharedPreferences.getBoolean(
- Settings.PREF_RECORRECTION_ENABLED,
- mResources.getBoolean(R.bool.config_default_recorrection_enabled));
- }
+ mKeyboardSwitcher.updateShiftState();
}
@Override
@@ -1967,7 +1956,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
@Override
- public void onPress(int primaryCode) {
+ public void onPress(int primaryCode, boolean withSliding) {
if (mKeyboardSwitcher.isVibrateAndSoundFeedbackRequired()) {
vibrate();
playKeyClick(primaryCode);
@@ -1975,25 +1964,27 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
KeyboardSwitcher switcher = mKeyboardSwitcher;
final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) {
- switcher.onPressShift();
+ switcher.onPressShift(withSliding);
} else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
switcher.onPressSymbol();
} else {
switcher.onOtherKeyPressed();
}
+ mAccessibilityUtils.onPress(primaryCode, switcher);
}
@Override
- public void onRelease(int primaryCode) {
+ public void onRelease(int primaryCode, boolean withSliding) {
KeyboardSwitcher switcher = mKeyboardSwitcher;
// Reset any drag flags in the keyboard
switcher.keyReleased();
final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) {
- switcher.onReleaseShift();
+ switcher.onReleaseShift(withSliding);
} else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
switcher.onReleaseSymbol();
}
+ mAccessibilityUtils.onRelease(primaryCode, switcher);
}
@@ -2073,6 +2064,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
private void updateCorrectionMode() {
+ // TODO: cleanup messy flags
mHasDictionary = mSuggest != null ? mSuggest.hasMainDictionary() : false;
mAutoCorrectOn = (mAutoCorrectEnabled || mQuickFixes)
&& !mInputTypeNoAutoCorrect && mHasDictionary;
@@ -2124,10 +2116,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void loadSettings(EditorInfo attribute) {
// Get the settings preferences
final SharedPreferences prefs = mPrefs;
- Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
- // @@@ mVibrateOn = vibrator != null && vibrator.hasVibrator()
- mVibrateOn = vibrator != null
- && prefs.getBoolean(Settings.PREF_VIBRATE_ON, false);
+ final boolean hasVibrator = VibratorCompatWrapper.getInstance(this).hasVibrator();
+ mVibrateOn = hasVibrator && prefs.getBoolean(Settings.PREF_VIBRATE_ON, false);
mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON,
mResources.getBoolean(R.bool.config_default_sound_enabled));
@@ -2139,7 +2129,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mBigramSuggestionEnabled = mAutoCorrectEnabled && isBigramSuggestionEnabled(prefs);
loadAndSetAutoCorrectionThreshold(prefs);
- mVoiceConnector.loadSettings(attribute, prefs);
+ mVoiceProxy.loadSettings(attribute, prefs);
updateCorrectionMode();
updateAutoTextEnabled();
@@ -2250,13 +2240,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
di.dismiss();
switch (position) {
case 0:
- Intent intent = new Intent(
- android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ Intent intent = CompatUtils.getInputLanguageSelectionIntent(
+ mInputMethodId, Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
- intent.putExtra(android.provider.Settings.EXTRA_INPUT_METHOD_ID,
- mInputMethodId);
startActivity(intent);
break;
case 1:
@@ -2293,6 +2280,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void showOptionsMenuInternal(CharSequence title, CharSequence[] items,
DialogInterface.OnClickListener listener) {
+ final IBinder windowToken = mKeyboardSwitcher.getInputView().getWindowToken();
+ if (windowToken == null) return;
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setCancelable(true);
builder.setIcon(R.drawable.ic_dialog_keyboard);
@@ -2303,7 +2292,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mOptionsDialog.setCanceledOnTouchOutside(true);
Window window = mOptionsDialog.getWindow();
WindowManager.LayoutParams lp = window.getAttributes();
- lp.token = mKeyboardSwitcher.getInputView().getWindowToken();
+ lp.token = windowToken;
lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
window.setAttributes(lp);
window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
@@ -2347,8 +2336,4 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
for (int i = 0; i < CPS_BUFFER_SIZE; i++) total += mCpsIntervals[i];
System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total));
}
-
- public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) {
- SubtypeSwitcher.getInstance().updateSubtype(subtype);
- }
}
diff --git a/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java
new file mode 100644
index 000000000..90726b0d8
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2011 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 java.util.Locale;
+
+class PrivateBinaryDictionaryGetter {
+ private PrivateBinaryDictionaryGetter() {}
+ public static AssetFileAddress getDictionaryFile(Locale locale, Context context) {
+ return null;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 59c68f208..488ab09c3 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -16,17 +16,17 @@
package com.android.inputmethod.latin;
-import com.android.inputmethod.voice.VoiceIMEConnector;
-import com.android.inputmethod.voice.VoiceInputLogger;
+import com.android.inputmethod.compat.CompatUtils;
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.deprecated.VoiceProxy;
+import com.android.inputmethod.compat.VibratorCompatWrapper;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.backup.BackupManager;
import android.content.DialogInterface;
-import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
-import android.os.Vibrator;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
@@ -82,7 +82,7 @@ public class Settings extends PreferenceActivity
private AlertDialog mDialog;
- private VoiceInputLogger mLogger;
+ private VoiceProxy.VoiceLoggerWrapper mVoiceLogger;
private boolean mOkClicked = false;
private String mVoiceModeOff;
@@ -111,7 +111,7 @@ public class Settings extends PreferenceActivity
mVoiceModeOff = getString(R.string.voice_mode_off);
mVoiceOn = !(prefs.getString(PREF_VOICE_SETTINGS_KEY, mVoiceModeOff)
.equals(mVoiceModeOff));
- mLogger = VoiceInputLogger.getLogger(this);
+ mVoiceLogger = VoiceProxy.VoiceLoggerWrapper.getInstance(this);
mAutoCorrectionThreshold = (ListPreference) findPreference(PREF_AUTO_CORRECTION_THRESHOLD);
mBigramSuggestion = (CheckBoxPreference) findPreference(PREF_BIGRAM_SUGGESTIONS);
@@ -134,10 +134,7 @@ public class Settings extends PreferenceActivity
generalSettings.removePreference(mVoicePreference);
}
- Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
- if (vibrator == null
- // @@@ || !vibrator.hasVibrator()
- ) {
+ if (!VibratorCompatWrapper.getInstance(this).hasVibrator()) {
generalSettings.removePreference(findPreference(PREF_VIBRATE_ON));
}
@@ -186,7 +183,7 @@ public class Settings extends PreferenceActivity
((PreferenceGroup) findPreference(PREF_PREDICTION_SETTINGS_KEY))
.removePreference(mQuickFixes);
}
- if (!VoiceIMEConnector.VOICE_INSTALLED
+ if (!VoiceProxy.VOICE_INSTALLED
|| !SpeechRecognizer.isRecognitionAvailable(this)) {
getPreferenceScreen().removePreference(mVoicePreference);
} else {
@@ -224,16 +221,9 @@ public class Settings extends PreferenceActivity
@Override
public boolean onPreferenceClick(Preference pref) {
if (pref == mInputLanguageSelection) {
- final String action;
- if (android.os.Build.VERSION.SDK_INT
- >= /* android.os.Build.VERSION_CODES.HONEYCOMB */ 11) {
- // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS
- // TODO: Can this be a constant instead of literal String constant?
- action = "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
- } else {
- action = "com.android.inputmethod.latin.INPUT_LANGUAGE_SELECTION";
- }
- startActivity(new Intent(action));
+ startActivity(CompatUtils.getInputLanguageSelectionIntent(
+ Utils.getInputMethodId(InputMethodManagerCompatWrapper.getInstance(this),
+ getApplicationInfo().packageName), 0));
return true;
}
return false;
@@ -279,10 +269,10 @@ public class Settings extends PreferenceActivity
public void onClick(DialogInterface dialog, int whichButton) {
if (whichButton == DialogInterface.BUTTON_NEGATIVE) {
mVoicePreference.setValue(mVoiceModeOff);
- mLogger.settingsWarningDialogCancel();
+ mVoiceLogger.settingsWarningDialogCancel();
} else if (whichButton == DialogInterface.BUTTON_POSITIVE) {
mOkClicked = true;
- mLogger.settingsWarningDialogOk();
+ mVoiceLogger.settingsWarningDialogOk();
}
updateVoicePreference();
}
@@ -313,7 +303,7 @@ public class Settings extends PreferenceActivity
AlertDialog dialog = builder.create();
mDialog = dialog;
dialog.setOnDismissListener(this);
- mLogger.settingsWarningDialogShown();
+ mVoiceLogger.settingsWarningDialogShown();
return dialog;
default:
Log.e(TAG, "unknown dialog " + id);
@@ -323,7 +313,7 @@ public class Settings extends PreferenceActivity
@Override
public void onDismiss(DialogInterface dialog) {
- mLogger.settingsWarningDialogDismissed();
+ mVoiceLogger.settingsWarningDialogDismissed();
if (!mOkClicked) {
// This assumes that onPreferenceClick gets called first, and this if the user
// agreed after the warning, we set the mOkClicked value to true.
@@ -333,10 +323,6 @@ public class Settings extends PreferenceActivity
private void updateVoicePreference() {
boolean isChecked = !mVoicePreference.getValue().equals(mVoiceModeOff);
- if (isChecked) {
- mLogger.voiceInputSettingEnabled();
- } else {
- mLogger.voiceInputSettingDisabled();
- }
+ mVoiceLogger.voiceInputSettingEnabled(isChecked);
}
}
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index f45c2b7f8..6610a65ef 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -16,13 +16,12 @@
package com.android.inputmethod.latin;
-import com.android.inputmethod.compat.InputMethodSubtype;
+import com.android.inputmethod.compat.InputMethodInfoCompatWrapper;
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper;
+import com.android.inputmethod.deprecated.VoiceProxy;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.LatinKeyboard;
-import com.android.inputmethod.keyboard.LatinKeyboardView;
-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.Intent;
@@ -33,11 +32,10 @@ import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
+import android.os.AsyncTask;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
import java.util.ArrayList;
import java.util.Arrays;
@@ -47,7 +45,7 @@ import java.util.Map;
public class SubtypeSwitcher {
private static boolean DBG = LatinImeLogger.sDBG;
- private static final String TAG = "SubtypeSwitcher";
+ private static final String TAG = SubtypeSwitcher.class.getSimpleName();
private static final char LOCALE_SEPARATER = '_';
private static final String KEYBOARD_MODE = "keyboard";
@@ -60,26 +58,27 @@ public class SubtypeSwitcher {
private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
private /* final */ LatinIME mService;
private /* final */ SharedPreferences mPrefs;
- private /* final */ InputMethodManager mImm;
+ private /* final */ InputMethodManagerCompatWrapper mImm;
private /* final */ Resources mResources;
private /* final */ ConnectivityManager mConnectivityManager;
private /* final */ boolean mConfigUseSpacebarLanguageSwitcher;
- private final ArrayList<InputMethodSubtype> mEnabledKeyboardSubtypesOfCurrentInputMethod =
- new ArrayList<InputMethodSubtype>();
+ private final ArrayList<InputMethodSubtypeCompatWrapper>
+ mEnabledKeyboardSubtypesOfCurrentInputMethod =
+ new ArrayList<InputMethodSubtypeCompatWrapper>();
private final ArrayList<String> mEnabledLanguagesOfCurrentInputMethod = new ArrayList<String>();
/*-----------------------------------------------------------*/
// Variants which should be changed only by reload functions.
private boolean mNeedsToDisplayLanguage;
private boolean mIsSystemLanguageSameAsInputLanguage;
- private InputMethodInfo mShortcutInputMethodInfo;
- private InputMethodSubtype mShortcutSubtype;
- private List<InputMethodSubtype> mAllEnabledSubtypesOfCurrentInputMethod;
+ private InputMethodInfoCompatWrapper mShortcutInputMethodInfo;
+ private InputMethodSubtypeCompatWrapper mShortcutSubtype;
+ private List<InputMethodSubtypeCompatWrapper> mAllEnabledSubtypesOfCurrentInputMethod;
+ private InputMethodSubtypeCompatWrapper mCurrentSubtype;
private Locale mSystemLocale;
private Locale mInputLocale;
private String mInputLocaleStr;
- private String mMode;
- private VoiceInput mVoiceInput;
+ private VoiceProxy.VoiceInputWrapper mVoiceInputWrapper;
/*-----------------------------------------------------------*/
private boolean mIsNetworkConnected;
@@ -103,7 +102,7 @@ public class SubtypeSwitcher {
mService = service;
mPrefs = prefs;
mResources = service.getResources();
- mImm = (InputMethodManager) service.getSystemService(Context.INPUT_METHOD_SERVICE);
+ mImm = InputMethodManagerCompatWrapper.getInstance(service);
mConnectivityManager = (ConnectivityManager) service.getSystemService(
Context.CONNECTIVITY_SERVICE);
mEnabledKeyboardSubtypesOfCurrentInputMethod.clear();
@@ -111,11 +110,10 @@ public class SubtypeSwitcher {
mSystemLocale = null;
mInputLocale = null;
mInputLocaleStr = null;
- // Mode is initialized to KEYBOARD_MODE, in case that LatinIME can't obtain currentSubtype
- mMode = KEYBOARD_MODE;
+ mCurrentSubtype = null;
mAllEnabledSubtypesOfCurrentInputMethod = null;
// TODO: Voice input should be created here
- mVoiceInput = null;
+ mVoiceInputWrapper = null;
mConfigUseSpacebarLanguageSwitcher = mResources.getBoolean(
R.bool.config_use_spacebar_language_switcher);
if (mConfigUseSpacebarLanguageSwitcher)
@@ -129,7 +127,7 @@ public class SubtypeSwitcher {
// Only configuration changed event is allowed to call this because this is heavy.
private void updateAllParameters() {
mSystemLocale = mResources.getConfiguration().locale;
- // @@@ updateSubtype(mImm.getCurrentInputMethodSubtype());
+ updateSubtype(mImm.getCurrentInputMethodSubtype());
updateParametersOnStartInputView();
}
@@ -146,19 +144,20 @@ public class SubtypeSwitcher {
// Reload enabledSubtypes from the framework.
private void updateEnabledSubtypes() {
+ final String currentMode = getCurrentSubtypeMode();
boolean foundCurrentSubtypeBecameDisabled = true;
- // @@@ mAllEnabledSubtypesOfCurrentInputMethod = mImm.getEnabledInputMethodSubtypeList(
- // null, true);
+ mAllEnabledSubtypesOfCurrentInputMethod = mImm.getEnabledInputMethodSubtypeList(
+ null, true);
mEnabledLanguagesOfCurrentInputMethod.clear();
mEnabledKeyboardSubtypesOfCurrentInputMethod.clear();
- for (InputMethodSubtype ims: mAllEnabledSubtypesOfCurrentInputMethod) {
+ for (InputMethodSubtypeCompatWrapper 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)) {
+ if (locale.equals(mInputLocaleStr) && mode.equals(currentMode)) {
foundCurrentSubtypeBecameDisabled = false;
}
if (KEYBOARD_MODE.equals(ims.getMode())) {
@@ -169,10 +168,10 @@ public class SubtypeSwitcher {
&& mIsSystemLanguageSameAsInputLanguage);
if (foundCurrentSubtypeBecameDisabled) {
if (DBG) {
- Log.w(TAG, "Current subtype: " + mInputLocaleStr + ", " + mMode);
+ Log.w(TAG, "Current subtype: " + mInputLocaleStr + ", " + currentMode);
Log.w(TAG, "Last subtype was disabled. Update to the current one.");
}
- // @@@ updateSubtype(mImm.getCurrentInputMethodSubtype());
+ updateSubtype(mImm.getCurrentInputMethodSubtype());
}
}
@@ -185,11 +184,10 @@ public class SubtypeSwitcher {
+ ", " + mShortcutSubtype.getMode())));
}
// TODO: Update an icon for shortcut IME
- /*
- Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts =
+ final Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>> shortcuts =
mImm.getShortcutInputMethodsAndSubtypes();
- for (InputMethodInfo imi: shortcuts.keySet()) {
- List<InputMethodSubtype> subtypes = shortcuts.get(imi);
+ for (InputMethodInfoCompatWrapper imi : shortcuts.keySet()) {
+ List<InputMethodSubtypeCompatWrapper> subtypes = shortcuts.get(imi);
// TODO: Returns the first found IMI for now. Should handle all shortcuts as
// appropriate.
mShortcutInputMethodInfo = imi;
@@ -197,17 +195,24 @@ public class SubtypeSwitcher {
// as appropriate.
mShortcutSubtype = subtypes.size() > 0 ? subtypes.get(0) : null;
break;
- }
- */
+ }
+ if (DBG) {
+ Log.d(TAG, "Update shortcut IME to : "
+ + (mShortcutInputMethodInfo == null
+ ? "<null>" : mShortcutInputMethodInfo.getId()) + ", "
+ + (mShortcutSubtype == null ? "<null>" : (mShortcutSubtype.getLocale()
+ + ", " + mShortcutSubtype.getMode())));
+ }
}
// Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function.
- public void updateSubtype(InputMethodSubtype newSubtype) {
+ public void updateSubtype(InputMethodSubtypeCompatWrapper newSubtype) {
final String newLocale;
final String newMode;
- if (newSubtype == null) {
+ final String oldMode = getCurrentSubtypeMode();
+ if (newSubtype == null || newSubtype.getOriginalObject() == null) {
// Normally, newSubtype shouldn't be null. But just in case newSubtype was null,
- // fallback to the default locale and mode.
+ // fallback to the default locale.
Log.w(TAG, "Couldn't get the current subtype.");
newLocale = "en_US";
newMode = KEYBOARD_MODE;
@@ -217,7 +222,7 @@ public class SubtypeSwitcher {
}
if (DBG) {
Log.w(TAG, "Update subtype to:" + newLocale + "," + newMode
- + ", from: " + mInputLocaleStr + ", " + mMode);
+ + ", from: " + mInputLocaleStr + ", " + oldMode);
}
boolean languageChanged = false;
if (!newLocale.equals(mInputLocaleStr)) {
@@ -227,42 +232,41 @@ public class SubtypeSwitcher {
updateInputLocale(newLocale);
}
boolean modeChanged = false;
- String oldMode = mMode;
- if (!newMode.equals(mMode)) {
- if (mMode != null) {
+ if (!newMode.equals(oldMode)) {
+ if (oldMode != null) {
modeChanged = true;
}
- mMode = newMode;
}
+ mCurrentSubtype = newSubtype;
// If the old mode is voice input, we need to reset or cancel its status.
// We cancel its status when we change mode, while we reset otherwise.
if (isKeyboardMode()) {
if (modeChanged) {
- if (VOICE_MODE.equals(oldMode) && mVoiceInput != null) {
- mVoiceInput.cancel();
+ if (VOICE_MODE.equals(oldMode) && mVoiceInputWrapper != null) {
+ mVoiceInputWrapper.cancel();
}
}
if (modeChanged || languageChanged) {
updateShortcutIME();
mService.onRefreshKeyboard();
}
- } else if (isVoiceMode() && mVoiceInput != null) {
+ } else if (isVoiceMode() && mVoiceInputWrapper != null) {
if (VOICE_MODE.equals(oldMode)) {
- mVoiceInput.reset();
+ mVoiceInputWrapper.reset();
}
// If needsToShowWarningDialog is true, voice input need to show warning before
// show recognition view.
if (languageChanged || modeChanged
- || VoiceIMEConnector.getInstance().needsToShowWarningDialog()) {
+ || VoiceProxy.getInstance().needsToShowWarningDialog()) {
triggerVoiceIME();
}
} else {
- Log.w(TAG, "Unknown subtype mode: " + mMode);
- if (VOICE_MODE.equals(oldMode) && mVoiceInput != null) {
+ Log.w(TAG, "Unknown subtype mode: " + newMode);
+ if (VOICE_MODE.equals(oldMode) && mVoiceInputWrapper != null) {
// We need to reset the voice input to release the resources and to reset its status
// as it is not the current input mode.
- mVoiceInput.reset();
+ mVoiceInputWrapper.reset();
}
}
}
@@ -303,15 +307,34 @@ public class SubtypeSwitcher {
if (token == null || mShortcutInputMethodInfo == null) {
return;
}
- // @@@ mImm.setInputMethodAndSubtype(token, mShortcutInfo.getId(), mShortcutSubtype);
+ final String imiId = mShortcutInputMethodInfo.getId();
+ final InputMethodSubtypeCompatWrapper subtype = mShortcutSubtype;
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ mImm.setInputMethodAndSubtype(token, imiId, subtype);
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ // Calls in this method need to be done in the same thread as the thread which
+ // called switchToShortcutIME().
+
+ // Notify an event that the current subtype was changed. This event will be
+ // handled if "onCurrentInputMethodSubtypeChanged" can't be implemented
+ // when the API level is 10 or previous.
+ mService.notifyOnCurrentInputMethodSubtypeChanged(subtype);
+ }
+ }.execute();
}
public Drawable getShortcutIcon() {
return getSubtypeIcon(mShortcutInputMethodInfo, mShortcutSubtype);
}
- private Drawable getSubtypeIcon(InputMethodInfo imi, InputMethodSubtype subtype) {
- /*
+ private Drawable getSubtypeIcon(
+ InputMethodInfoCompatWrapper imi, InputMethodSubtypeCompatWrapper subtype) {
final PackageManager pm = mService.getPackageManager();
if (imi != null) {
final String imiPackageName = imi.getPackageName();
@@ -333,8 +356,7 @@ public class SubtypeSwitcher {
Log.w(TAG, "IME can't be found: " + imiPackageName);
}
}
- }
- */
+ }
return null;
}
@@ -346,11 +368,28 @@ public class SubtypeSwitcher {
return false;
}
- public boolean isShortcutAvailable() {
+ public boolean isShortcutImeEnabled() {
+ if (mShortcutInputMethodInfo == null)
+ return false;
+ if (mShortcutSubtype == null)
+ return true;
+ final boolean allowsImplicitlySelectedSubtypes = true;
+ for (final InputMethodSubtypeCompatWrapper enabledSubtype :
+ mImm.getEnabledInputMethodSubtypeList(
+ mShortcutInputMethodInfo, allowsImplicitlySelectedSubtypes)) {
+ if (enabledSubtype.equals(mShortcutSubtype))
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isShortcutImeReady() {
if (mShortcutInputMethodInfo == null)
return false;
- if (mShortcutSubtype != null && contains(mShortcutSubtype.getExtraValue().split(","),
- SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY)) {
+ if (mShortcutSubtype == null)
+ return true;
+ if (contains(mShortcutSubtype.getExtraValue().split(","),
+ SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY)) {
return mIsNetworkConnected;
}
return true;
@@ -361,12 +400,10 @@ public class SubtypeSwitcher {
ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
mIsNetworkConnected = !noConnection;
- final LatinKeyboardView inputView = KeyboardSwitcher.getInstance().getInputView();
- if (inputView != null) {
- final LatinKeyboard keyboard = inputView.getLatinKeyboard();
- if (keyboard != null) {
- keyboard.updateShortcutKey(isShortcutAvailable(), inputView);
- }
+ final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance();
+ final LatinKeyboard keyboard = switcher.getLatinKeyboard();
+ if (keyboard != null) {
+ keyboard.updateShortcutKey(isShortcutImeReady(), switcher.getInputView());
}
}
@@ -462,14 +499,6 @@ public class SubtypeSwitcher {
}
}
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- if (mConfigUseSpacebarLanguageSwitcher) {
- if (Settings.PREF_SELECTED_LANGUAGES.equals(key)) {
- mLanguageSwitcher.loadLocales(sharedPreferences);
- }
- }
- }
-
/**
* Change system locale for this application
* @param newLocale
@@ -484,7 +513,7 @@ public class SubtypeSwitcher {
}
public boolean isKeyboardMode() {
- return KEYBOARD_MODE.equals(mMode);
+ return KEYBOARD_MODE.equals(getCurrentSubtypeMode());
}
@@ -492,9 +521,9 @@ public class SubtypeSwitcher {
// Voice Input functions //
///////////////////////////
- public boolean setVoiceInput(VoiceInput vi) {
- if (mVoiceInput == null && vi != null) {
- mVoiceInput = vi;
+ public boolean setVoiceInputWrapper(VoiceProxy.VoiceInputWrapper vi) {
+ if (mVoiceInputWrapper == null && vi != null) {
+ mVoiceInputWrapper = vi;
if (isVoiceMode()) {
if (DBG) {
Log.d(TAG, "Set and call voice input.: " + getInputLocaleStr());
@@ -507,12 +536,12 @@ public class SubtypeSwitcher {
}
public boolean isVoiceMode() {
- return VOICE_MODE.equals(mMode);
+ return null == mCurrentSubtype ? false : VOICE_MODE.equals(getCurrentSubtypeMode());
}
private void triggerVoiceIME() {
if (!mService.isInputViewShown()) return;
- VoiceIMEConnector.getInstance().startListening(false,
+ VoiceProxy.getInstance().startListening(false,
KeyboardSwitcher.getInstance().getInputView().getWindowToken());
}
@@ -573,19 +602,29 @@ public class SubtypeSwitcher {
}
}
- // 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 ";
+ /////////////////////////////
+ // Other utility functions //
+ /////////////////////////////
+
+ public String getCurrentSubtypeExtraValue() {
+ // If null, return what an empty ExtraValue would return : the empty string.
+ return null != mCurrentSubtype ? mCurrentSubtype.getExtraValue() : "";
+ }
+
+ public boolean currentSubtypeContainsExtraValueKey(String key) {
+ // If null, return what an empty ExtraValue would return : false.
+ return null != mCurrentSubtype ? mCurrentSubtype.containsExtraValueKey(key) : false;
+ }
+
+ public String getCurrentSubtypeExtraValueOf(String key) {
+ // If null, return what an empty ExtraValue would return : null.
+ return null != mCurrentSubtype ? mCurrentSubtype.getExtraValueOf(key) : null;
+ }
+
+ public String getCurrentSubtypeMode() {
+ return null != mCurrentSubtype ? mCurrentSubtype.getMode() : KEYBOARD_MODE;
+ }
+
public boolean isVoiceSupported(String locale) {
// Get the current list of supported locales and check the current locale against that
@@ -593,10 +632,8 @@ public class SubtypeSwitcher {
// 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);
+ String supportedLocalesString = VoiceProxy.getSupportedLocalesString(
+ mService.getContentResolver());
List<String> voiceInputSupportedLocales = Arrays.asList(
supportedLocalesString.split("\\s+"));
return voiceInputSupportedLocales.contains(locale);
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index c9e57d0a5..0cc9d4198 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -25,6 +25,11 @@ import android.view.View;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
/**
* This class loads a dictionary and provides a list of suggestions for a given sequence of
@@ -43,7 +48,7 @@ public class Suggest implements Dictionary.WordCallback {
/**
* Words that appear in both bigram and unigram data gets multiplier ranging from
- * BIGRAM_MULTIPLIER_MIN to BIGRAM_MULTIPLIER_MAX depending on the frequency score from
+ * BIGRAM_MULTIPLIER_MIN to BIGRAM_MULTIPLIER_MAX depending on the score from
* bigram data.
*/
public static final double BIGRAM_MULTIPLIER_MIN = 1.2;
@@ -63,19 +68,23 @@ public class Suggest implements Dictionary.WordCallback {
// If you add a type of dictionary, increment DIC_TYPE_LAST_ID
public static final int DIC_TYPE_LAST_ID = 4;
+ public static final String DICT_KEY_MAIN = "main";
+ public static final String DICT_KEY_CONTACTS = "contacts";
+ public static final String DICT_KEY_AUTO = "auto";
+ public static final String DICT_KEY_USER = "user";
+ public static final String DICT_KEY_USER_BIGRAM = "user_bigram";
+ public static final String DICT_KEY_WHITELIST ="whitelist";
+
static final int LARGE_DICTIONARY_THRESHOLD = 200 * 1000;
private static final boolean DBG = LatinImeLogger.sDBG;
- private BinaryDictionary mMainDict;
-
- private Dictionary mUserDictionary;
-
- private Dictionary mAutoDictionary;
-
- private Dictionary mContactsDictionary;
+ private AutoCorrection mAutoCorrection;
- private Dictionary mUserBigramDictionary;
+ private BinaryDictionary mMainDict;
+ private WhitelistDictionary mWhiteListDictionary;
+ private final Map<String, Dictionary> mUnigramDictionaries = new HashMap<String, Dictionary>();
+ private final Map<String, Dictionary> mBigramDictionaries = new HashMap<String, Dictionary>();
private int mPrefMaxSuggestions = 12;
@@ -84,20 +93,13 @@ public class Suggest implements Dictionary.WordCallback {
private boolean mQuickFixesEnabled;
private double mAutoCorrectionThreshold;
- private int[] mPriorities = new int[mPrefMaxSuggestions];
- private int[] mBigramPriorities = new int[PREF_MAX_BIGRAMS];
-
- // Handle predictive correction for only the first 1280 characters for performance reasons
- // If we support scripts that need latin characters beyond that, we should probably use some
- // kind of a sparse array or language specific list with a mapping lookup table.
- // 1280 is the size of the BASE_CHARS array in ExpandableDictionary, which is a basic set of
- // latin characters.
- private int[] mNextLettersFrequencies = new int[1280];
+ private int[] mScores = new int[mPrefMaxSuggestions];
+ private int[] mBigramScores = new int[PREF_MAX_BIGRAMS];
+
private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>();
ArrayList<CharSequence> mBigramSuggestions = new ArrayList<CharSequence>();
private ArrayList<CharSequence> mStringPool = new ArrayList<CharSequence>();
- private boolean mHasAutoCorrection;
- private String mLowerOriginalWord;
+ private CharSequence mTypedWord;
// TODO: Remove these member variables by passing more context to addWord() callback method
private boolean mIsFirstCharCapitalized;
@@ -105,17 +107,44 @@ public class Suggest implements Dictionary.WordCallback {
private int mCorrectionMode = CORRECTION_BASIC;
- public Suggest(Context context, int dictionaryResId) {
- mMainDict = BinaryDictionary.initDictionary(context, dictionaryResId, DIC_MAIN);
- initPool();
+ public Suggest(Context context, int dictionaryResId, Locale locale) {
+ init(context, BinaryDictionary.initDictionaryFromManager(context, DIC_MAIN, locale,
+ dictionaryResId));
+ }
+
+ /* package for test */ Suggest(File dictionary, long startOffset, long length,
+ BinaryDictionary.Flag[] flagArray) {
+ init(null, BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN,
+ flagArray));
}
- // For unit test
- /* package */ Suggest(File dictionary, long startOffset, long length) {
- mMainDict = BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN);
+ private void init(Context context, BinaryDictionary mainDict) {
+ if (mainDict != null) {
+ mMainDict = mainDict;
+ mUnigramDictionaries.put(DICT_KEY_MAIN, mainDict);
+ mBigramDictionaries.put(DICT_KEY_MAIN, mainDict);
+ }
+ mWhiteListDictionary = WhitelistDictionary.init(context);
+ if (mWhiteListDictionary != null) {
+ mUnigramDictionaries.put(DICT_KEY_WHITELIST, mWhiteListDictionary);
+ }
+ mAutoCorrection = new AutoCorrection();
initPool();
}
+ public void resetMainDict(Context context, int dictionaryResId, Locale locale) {
+ final BinaryDictionary newMainDict = BinaryDictionary.initDictionaryFromManager(context,
+ DIC_MAIN, locale, dictionaryResId);
+ mMainDict = newMainDict;
+ if (null == newMainDict) {
+ mUnigramDictionaries.remove(DICT_KEY_MAIN);
+ mBigramDictionaries.remove(DICT_KEY_MAIN);
+ } else {
+ mUnigramDictionaries.put(DICT_KEY_MAIN, newMainDict);
+ mBigramDictionaries.put(DICT_KEY_MAIN, newMainDict);
+ }
+ }
+
private void initPool() {
for (int i = 0; i < mPrefMaxSuggestions; i++) {
StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
@@ -139,6 +168,10 @@ public class Suggest implements Dictionary.WordCallback {
return mMainDict != null && mMainDict.getSize() > LARGE_DICTIONARY_THRESHOLD;
}
+ public Map<String, Dictionary> getUnigramDictionaries() {
+ return mUnigramDictionaries;
+ }
+
public int getApproxMaxWordLength() {
return APPROX_MAX_WORD_LENGTH;
}
@@ -148,22 +181,28 @@ public class Suggest implements Dictionary.WordCallback {
* before the main dictionary, if set.
*/
public void setUserDictionary(Dictionary userDictionary) {
- mUserDictionary = userDictionary;
+ if (userDictionary != null)
+ mUnigramDictionaries.put(DICT_KEY_USER, userDictionary);
}
/**
* Sets an optional contacts dictionary resource to be loaded.
*/
- public void setContactsDictionary(Dictionary userDictionary) {
- mContactsDictionary = userDictionary;
+ public void setContactsDictionary(Dictionary contactsDictionary) {
+ if (contactsDictionary != null) {
+ mUnigramDictionaries.put(DICT_KEY_CONTACTS, contactsDictionary);
+ mBigramDictionaries.put(DICT_KEY_CONTACTS, contactsDictionary);
+ }
}
public void setAutoDictionary(Dictionary autoDictionary) {
- mAutoDictionary = autoDictionary;
+ if (autoDictionary != null)
+ mUnigramDictionaries.put(DICT_KEY_AUTO, autoDictionary);
}
public void setUserBigramDictionary(Dictionary userBigramDictionary) {
- mUserBigramDictionary = userBigramDictionary;
+ if (userBigramDictionary != null)
+ mBigramDictionaries.put(DICT_KEY_USER_BIGRAM, userBigramDictionary);
}
public void setAutoCorrectionThreshold(double threshold) {
@@ -185,8 +224,8 @@ public class Suggest implements Dictionary.WordCallback {
throw new IllegalArgumentException("maxSuggestions must be between 1 and 100");
}
mPrefMaxSuggestions = maxSuggestions;
- mPriorities = new int[mPrefMaxSuggestions];
- mBigramPriorities = new int[PREF_MAX_BIGRAMS];
+ mScores = new int[mPrefMaxSuggestions];
+ mBigramScores = new int[PREF_MAX_BIGRAMS];
collectGarbage(mSuggestions, mPrefMaxSuggestions);
while (mStringPool.size() < mPrefMaxSuggestions) {
StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
@@ -207,35 +246,50 @@ public class Suggest implements Dictionary.WordCallback {
return getSuggestedWordBuilder(view, wordComposer, prevWordForBigram).build();
}
+ private CharSequence capitalizeWord(boolean all, boolean first, CharSequence word) {
+ if (TextUtils.isEmpty(word) || !(all || first)) return word;
+ final int wordLength = word.length();
+ final int poolSize = mStringPool.size();
+ final StringBuilder sb =
+ poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1)
+ : new StringBuilder(getApproxMaxWordLength());
+ sb.setLength(0);
+ if (all) {
+ sb.append(word.toString().toUpperCase());
+ } else if (first) {
+ sb.append(Character.toUpperCase(word.charAt(0)));
+ if (wordLength > 1) {
+ sb.append(word.subSequence(1, wordLength));
+ }
+ }
+ return sb;
+ }
+
// TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder
public SuggestedWords.Builder getSuggestedWordBuilder(View view, WordComposer wordComposer,
CharSequence prevWordForBigram) {
LatinImeLogger.onStartSuggestion(prevWordForBigram);
- mHasAutoCorrection = false;
+ mAutoCorrection.init();
mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
mIsAllUpperCase = wordComposer.isAllUpperCase();
collectGarbage(mSuggestions, mPrefMaxSuggestions);
- Arrays.fill(mPriorities, 0);
- Arrays.fill(mNextLettersFrequencies, 0);
+ Arrays.fill(mScores, 0);
// Save a lowercase version of the original word
CharSequence typedWord = wordComposer.getTypedWord();
if (typedWord != null) {
final String typedWordString = typedWord.toString();
typedWord = typedWordString;
- mLowerOriginalWord = typedWordString.toLowerCase();
// Treating USER_TYPED as UNIGRAM suggestion for logging now.
LatinImeLogger.onAddSuggestedWord(typedWordString, Suggest.DIC_USER_TYPED,
Dictionary.DataType.UNIGRAM);
- } else {
- mLowerOriginalWord = "";
}
+ mTypedWord = typedWord;
- double normalizedScore = Integer.MIN_VALUE;
if (wordComposer.size() == 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM
|| mCorrectionMode == CORRECTION_BASIC)) {
// At first character typed, search only the bigrams
- Arrays.fill(mBigramPriorities, 0);
+ Arrays.fill(mBigramScores, 0);
collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS);
if (!TextUtils.isEmpty(prevWordForBigram)) {
@@ -243,17 +297,8 @@ public class Suggest implements Dictionary.WordCallback {
if (mMainDict != null && mMainDict.isValidWord(lowerPrevWord)) {
prevWordForBigram = lowerPrevWord;
}
- if (mUserBigramDictionary != null) {
- mUserBigramDictionary.getBigrams(wordComposer, prevWordForBigram, this,
- mNextLettersFrequencies);
- }
- if (mContactsDictionary != null) {
- mContactsDictionary.getBigrams(wordComposer, prevWordForBigram, this,
- mNextLettersFrequencies);
- }
- if (mMainDict != null) {
- mMainDict.getBigrams(wordComposer, prevWordForBigram, this,
- mNextLettersFrequencies);
+ for (final Dictionary dictionary : mBigramDictionaries.values()) {
+ dictionary.getBigrams(wordComposer, prevWordForBigram, this);
}
char currentChar = wordComposer.getTypedWord().charAt(0);
char currentCharUpper = Character.toUpperCase(currentChar);
@@ -276,120 +321,85 @@ public class Suggest implements Dictionary.WordCallback {
} else if (wordComposer.size() > 1) {
// At second character typed, search the unigrams (scores being affected by bigrams)
- if (mUserDictionary != null || mContactsDictionary != null) {
- if (mUserDictionary != null) {
- mUserDictionary.getWords(wordComposer, this, mNextLettersFrequencies);
- }
- if (mContactsDictionary != null) {
- mContactsDictionary.getWords(wordComposer, this, mNextLettersFrequencies);
- }
-
- if (mSuggestions.size() > 0 && isValidWord(typedWord)
- && (mCorrectionMode == CORRECTION_FULL
- || mCorrectionMode == CORRECTION_FULL_BIGRAM)) {
- if (DBG) {
- Log.d(TAG, "Auto corrected by CORRECTION_FULL.");
- }
- mHasAutoCorrection = true;
- }
- }
- if (mMainDict != null) mMainDict.getWords(wordComposer, this, mNextLettersFrequencies);
- if ((mCorrectionMode == CORRECTION_FULL || mCorrectionMode == CORRECTION_FULL_BIGRAM)
- && 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.
- normalizedScore = Utils.calcNormalizedScore(
- typedWord, mSuggestions.get(0), mPriorities[0]);
- if (DBG) {
- Log.d(TAG, "Normalized " + typedWord + "," + mSuggestions.get(0) + ","
- + mPriorities[0] + ", " + normalizedScore
- + "(" + mAutoCorrectionThreshold + ")");
- }
- if (normalizedScore >= mAutoCorrectionThreshold) {
- if (DBG) {
- Log.d(TAG, "Auto corrected by S-threthhold.");
- }
- mHasAutoCorrection = true;
- }
+ for (final String key : mUnigramDictionaries.keySet()) {
+ // Skip AutoDictionary and WhitelistDictionary to lookup
+ if (key.equals(DICT_KEY_AUTO) || key.equals(DICT_KEY_WHITELIST))
+ continue;
+ final Dictionary dictionary = mUnigramDictionaries.get(key);
+ dictionary.getWords(wordComposer, this);
}
}
+ CharSequence autoText = null;
+ final String typedWordString = typedWord == null ? null : typedWord.toString();
if (typedWord != null) {
- mSuggestions.add(0, typedWord.toString());
- }
- if (mQuickFixesEnabled) {
- int i = 0;
- int max = 6;
- // Don't autotext the suggestions from the dictionaries
- if (mCorrectionMode == CORRECTION_BASIC) max = 1;
- while (i < mSuggestions.size() && i < max) {
- String suggestedWord = mSuggestions.get(i).toString().toLowerCase();
- CharSequence autoText =
- AutoText.get(suggestedWord, 0, suggestedWord.length(), view);
+ // Apply quick fix only for the typed word.
+ if (mQuickFixesEnabled) {
+ final String lowerCaseTypedWord = typedWordString.toLowerCase();
+ CharSequence tempAutoText = capitalizeWord(
+ mIsAllUpperCase, mIsFirstCharCapitalized, AutoText.get(
+ lowerCaseTypedWord, 0, lowerCaseTypedWord.length(), view));
+ // TODO: cleanup canAdd
// 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();
- }
+ boolean canAdd = tempAutoText != null;
// Is that correction already the current prediction (or original word)?
- canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i));
+ canAdd &= !TextUtils.equals(tempAutoText, typedWord);
// Is that correction already the next predicted word?
- if (canAdd && i + 1 < mSuggestions.size() && mCorrectionMode != CORRECTION_BASIC) {
- canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i + 1));
+ if (canAdd && mSuggestions.size() > 0 && mCorrectionMode != CORRECTION_BASIC) {
+ canAdd &= !TextUtils.equals(tempAutoText, mSuggestions.get(0));
}
if (canAdd) {
if (DBG) {
Log.d(TAG, "Auto corrected by AUTOTEXT.");
}
- mHasAutoCorrection = true;
- mSuggestions.add(i + 1, autoText);
- i++;
+ autoText = tempAutoText;
}
- i++;
}
}
+
+ CharSequence whitelistedWord = capitalizeWord(mIsAllUpperCase, mIsFirstCharCapitalized,
+ mWhiteListDictionary.getWhiteListedWord(typedWordString));
+
+ mAutoCorrection.updateAutoCorrectionStatus(mUnigramDictionaries, wordComposer,
+ mSuggestions, mScores, typedWord, mAutoCorrectionThreshold, mCorrectionMode,
+ autoText, whitelistedWord);
+
+ if (autoText != null) {
+ mSuggestions.add(0, autoText);
+ }
+
+ if (whitelistedWord != null) {
+ mSuggestions.add(0, whitelistedWord);
+ }
+
+ if (typedWord != null) {
+ mSuggestions.add(0, typedWordString);
+ }
removeDupes();
+
if (DBG) {
- ArrayList<SuggestedWords.SuggestedWordInfo> frequencyInfoList =
+ double normalizedScore = mAutoCorrection.getNormalizedScore();
+ ArrayList<SuggestedWords.SuggestedWordInfo> scoreInfoList =
new ArrayList<SuggestedWords.SuggestedWordInfo>();
- frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false));
- final int priorityLength = mPriorities.length;
- for (int i = 0; i < priorityLength; ++i) {
+ scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false));
+ for (int i = 0; i < mScores.length; ++i) {
if (normalizedScore > 0) {
- final String priorityThreshold = Integer.toString(mPriorities[i]) + " (" +
- normalizedScore + ")";
- frequencyInfoList.add(
- new SuggestedWords.SuggestedWordInfo(priorityThreshold, false));
+ final String scoreThreshold = String.format("%d (%4.2f)", mScores[i],
+ normalizedScore);
+ scoreInfoList.add(
+ new SuggestedWords.SuggestedWordInfo(scoreThreshold, false));
normalizedScore = 0.0;
} else {
- final String priority = Integer.toString(mPriorities[i]);
- frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo(priority, false));
+ final String score = Integer.toString(mScores[i]);
+ scoreInfoList.add(new SuggestedWords.SuggestedWordInfo(score, false));
}
}
- for (int i = priorityLength; i < mSuggestions.size(); ++i) {
- frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false));
+ for (int i = mScores.length; i < mSuggestions.size(); ++i) {
+ scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false));
}
- return new SuggestedWords.Builder().addWords(mSuggestions, frequencyInfoList);
- } else {
- return new SuggestedWords.Builder().addWords(mSuggestions, null);
+ return new SuggestedWords.Builder().addWords(mSuggestions, scoreInfoList);
}
- }
-
- public int[] getNextLettersFrequencies() {
- return mNextLettersFrequencies;
+ return new SuggestedWords.Builder().addWords(mSuggestions, null);
}
private void removeDupes() {
@@ -420,45 +430,43 @@ public class Suggest implements Dictionary.WordCallback {
}
public boolean hasAutoCorrection() {
- return mHasAutoCorrection;
- }
-
- private static boolean compareCaseInsensitive(final String lowerOriginalWord,
- final char[] word, final int offset, final int length) {
- final int originalLength = lowerOriginalWord.length();
- if (originalLength == length && Character.isUpperCase(word[offset])) {
- for (int i = 0; i < originalLength; i++) {
- if (lowerOriginalWord.charAt(i) != Character.toLowerCase(word[offset+i])) {
- return false;
- }
- }
- return true;
- }
- return false;
+ return mAutoCorrection.hasAutoCorrection();
}
@Override
- public boolean addWord(final char[] word, final int offset, final int length, int freq,
+ public boolean addWord(final char[] word, final int offset, final int length, int score,
final int dicTypeId, final Dictionary.DataType dataType) {
Dictionary.DataType dataTypeForLog = dataType;
- ArrayList<CharSequence> suggestions;
- int[] priorities;
- int prefMaxSuggestions;
+ final ArrayList<CharSequence> suggestions;
+ final int[] sortedScores;
+ final int prefMaxSuggestions;
if(dataType == Dictionary.DataType.BIGRAM) {
suggestions = mBigramSuggestions;
- priorities = mBigramPriorities;
+ sortedScores = mBigramScores;
prefMaxSuggestions = PREF_MAX_BIGRAMS;
} else {
suggestions = mSuggestions;
- priorities = mPriorities;
+ sortedScores = mScores;
prefMaxSuggestions = mPrefMaxSuggestions;
}
int pos = 0;
// Check if it's the same word, only caps are different
- if (compareCaseInsensitive(mLowerOriginalWord, word, offset, length)) {
- pos = 0;
+ if (Utils.equalsIgnoreCase(mTypedWord, word, offset, length)) {
+ // TODO: remove this surrounding if clause and move this logic to
+ // getSuggestedWordBuilder.
+ if (suggestions.size() > 0) {
+ final String currentHighestWord = suggestions.get(0).toString();
+ // If the current highest word is also equal to typed word, we need to compare
+ // frequency to determine the insertion position. This does not ensure strictly
+ // correct ordering, but ensures the top score is on top which is enough for
+ // removing duplicates correctly.
+ if (Utils.equalsIgnoreCase(currentHighestWord, word, offset, length)
+ && score <= sortedScores[0]) {
+ pos = 1;
+ }
+ }
} else {
if (dataType == Dictionary.DataType.UNIGRAM) {
// Check if the word was already added before (by bigram data)
@@ -466,24 +474,24 @@ public class Suggest implements Dictionary.WordCallback {
if(bigramSuggestion >= 0) {
dataTypeForLog = Dictionary.DataType.BIGRAM;
// turn freq from bigram into multiplier specified above
- double multiplier = (((double) mBigramPriorities[bigramSuggestion])
+ double multiplier = (((double) mBigramScores[bigramSuggestion])
/ MAXIMUM_BIGRAM_FREQUENCY)
* (BIGRAM_MULTIPLIER_MAX - BIGRAM_MULTIPLIER_MIN)
+ BIGRAM_MULTIPLIER_MIN;
/* Log.d(TAG,"bigram num: " + bigramSuggestion
+ " wordB: " + mBigramSuggestions.get(bigramSuggestion).toString()
- + " currentPriority: " + freq + " bigramPriority: "
- + mBigramPriorities[bigramSuggestion]
+ + " currentScore: " + score + " bigramScore: "
+ + mBigramScores[bigramSuggestion]
+ " multiplier: " + multiplier); */
- freq = (int)Math.round((freq * multiplier));
+ score = (int)Math.round((score * multiplier));
}
}
- // Check the last one's priority and bail
- if (priorities[prefMaxSuggestions - 1] >= freq) return true;
+ // Check the last one's score and bail
+ if (sortedScores[prefMaxSuggestions - 1] >= score) return true;
while (pos < prefMaxSuggestions) {
- if (priorities[pos] < freq
- || (priorities[pos] == freq && length < suggestions.get(pos).length())) {
+ if (sortedScores[pos] < score
+ || (sortedScores[pos] == score && length < suggestions.get(pos).length())) {
break;
}
pos++;
@@ -493,8 +501,8 @@ public class Suggest implements Dictionary.WordCallback {
return true;
}
- System.arraycopy(priorities, pos, priorities, pos + 1, prefMaxSuggestions - pos - 1);
- priorities[pos] = freq;
+ System.arraycopy(sortedScores, pos, sortedScores, pos + 1, prefMaxSuggestions - pos - 1);
+ sortedScores[pos] = score;
int poolSize = mStringPool.size();
StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1)
: new StringBuilder(getApproxMaxWordLength());
@@ -541,16 +549,6 @@ public class Suggest implements Dictionary.WordCallback {
return -1;
}
- public boolean isValidWord(final CharSequence word) {
- if (word == null || word.length() == 0 || mMainDict == null) {
- return false;
- }
- return mMainDict.isValidWord(word)
- || (mUserDictionary != null && mUserDictionary.isValidWord(word))
- || (mAutoDictionary != null && mAutoDictionary.isValidWord(word))
- || (mContactsDictionary != null && mContactsDictionary.isValidWord(word));
- }
-
private void collectGarbage(ArrayList<CharSequence> suggestions, int prefMaxSuggestions) {
int poolSize = mStringPool.size();
int garbageSize = suggestions.size();
@@ -569,25 +567,12 @@ public class Suggest implements Dictionary.WordCallback {
}
public void close() {
- if (mMainDict != null) {
- mMainDict.close();
- mMainDict = null;
- }
- if (mUserDictionary != null) {
- mUserDictionary.close();
- mUserDictionary = null;
- }
- if (mUserBigramDictionary != null) {
- mUserBigramDictionary.close();
- mUserBigramDictionary = null;
- }
- if (mContactsDictionary != null) {
- mContactsDictionary.close();
- mContactsDictionary = null;
- }
- if (mAutoDictionary != null) {
- mAutoDictionary.close();
- mAutoDictionary = null;
+ final Set<Dictionary> dictionaries = new HashSet<Dictionary>();
+ dictionaries.addAll(mUnigramDictionaries.values());
+ dictionaries.addAll(mBigramDictionaries.values());
+ for (final Dictionary dictionary : dictionaries) {
+ dictionary.close();
}
+ mMainDict = null;
}
}
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index f774ce3a5..fe7aac7c2 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -24,23 +24,20 @@ import java.util.HashSet;
import java.util.List;
public class SuggestedWords {
- public static final SuggestedWords EMPTY = new SuggestedWords(null, false, false, false, null);
+ public static final SuggestedWords EMPTY = new SuggestedWords(null, false, false, null);
public final List<CharSequence> mWords;
- public final boolean mIsApplicationSpecifiedCompletions;
public final boolean mTypedWordValid;
public final boolean mHasMinimalSuggestion;
public final List<SuggestedWordInfo> mSuggestedWordInfoList;
- private SuggestedWords(List<CharSequence> words, boolean isApplicationSpecifiedCompletions,
- boolean typedWordValid, boolean hasMinamlSuggestion,
- List<SuggestedWordInfo> suggestedWordInfoList) {
+ private SuggestedWords(List<CharSequence> words, boolean typedWordValid,
+ boolean hasMinamlSuggestion, List<SuggestedWordInfo> suggestedWordInfoList) {
if (words != null) {
mWords = words;
} else {
mWords = Collections.emptyList();
}
- mIsApplicationSpecifiedCompletions = isApplicationSpecifiedCompletions;
mTypedWordValid = typedWordValid;
mHasMinimalSuggestion = hasMinamlSuggestion;
mSuggestedWordInfoList = suggestedWordInfoList;
@@ -64,7 +61,6 @@ public class SuggestedWords {
public static class Builder {
private List<CharSequence> mWords = new ArrayList<CharSequence>();
- private boolean mIsCompletions;
private boolean mTypedWordValid;
private boolean mHasMinimalSuggestion;
private List<SuggestedWordInfo> mSuggestedWordInfoList =
@@ -109,7 +105,6 @@ public class SuggestedWords {
public Builder setApplicationSpecifiedCompletions(CompletionInfo[] infos) {
for (CompletionInfo info : infos)
addWord(info.getText());
- mIsCompletions = true;
return this;
}
@@ -141,15 +136,14 @@ public class SuggestedWords {
alreadySeen.add(prevWord);
}
}
- mIsCompletions = false;
mTypedWordValid = false;
mHasMinimalSuggestion = false;
return this;
}
public SuggestedWords build() {
- return new SuggestedWords(mWords, mIsCompletions, mTypedWordValid,
- mHasMinimalSuggestion, mSuggestedWordInfoList);
+ return new SuggestedWords(mWords, mTypedWordValid, mHasMinimalSuggestion,
+ mSuggestedWordInfoList);
}
public int size() {
diff --git a/java/src/com/android/inputmethod/latin/TextEntryState.java b/java/src/com/android/inputmethod/latin/TextEntryState.java
index f571f26d5..63196430b 100644
--- a/java/src/com/android/inputmethod/latin/TextEntryState.java
+++ b/java/src/com/android/inputmethod/latin/TextEntryState.java
@@ -16,117 +16,39 @@
package com.android.inputmethod.latin;
-import com.android.inputmethod.keyboard.Key;
-
-import android.content.Context;
-import android.text.format.DateFormat;
import android.util.Log;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Calendar;
-
public class TextEntryState {
-
- private static final boolean DBG = false;
-
- private static final String TAG = "TextEntryState";
-
- private static boolean LOGGING = false;
-
- private static int sBackspaceCount = 0;
-
- private static int sAutoSuggestCount = 0;
-
- private static int sAutoSuggestUndoneCount = 0;
-
- private static int sManualSuggestCount = 0;
-
- private static int sWordNotInDictionaryCount = 0;
-
- private static int sSessionCount = 0;
-
- private static int sTypedChars;
-
- private static int sActualChars;
-
- public enum State {
- UNKNOWN,
- START,
- IN_WORD,
- ACCEPTED_DEFAULT,
- PICKED_SUGGESTION,
- PUNCTUATION_AFTER_WORD,
- PUNCTUATION_AFTER_ACCEPTED,
- SPACE_AFTER_ACCEPTED,
- SPACE_AFTER_PICKED,
- UNDO_COMMIT,
- CORRECTING,
- PICKED_CORRECTION,
+ private static final String TAG = TextEntryState.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static final int UNKNOWN = 0;
+ private static final int START = 1;
+ private static final int IN_WORD = 2;
+ private static final int ACCEPTED_DEFAULT = 3;
+ private static final int PICKED_SUGGESTION = 4;
+ private static final int PUNCTUATION_AFTER_WORD = 5;
+ private static final int PUNCTUATION_AFTER_ACCEPTED = 6;
+ private static final int SPACE_AFTER_ACCEPTED = 7;
+ private static final int SPACE_AFTER_PICKED = 8;
+ private static final int UNDO_COMMIT = 9;
+ private static final int RECORRECTING = 10;
+ private static final int PICKED_RECORRECTION = 11;
+
+ private static int sState = UNKNOWN;
+ private static int sPreviousState = UNKNOWN;
+
+ private static void setState(final int newState) {
+ sPreviousState = sState;
+ sState = newState;
}
- private static State sState = State.UNKNOWN;
-
- private static FileOutputStream sKeyLocationFile;
- private static FileOutputStream sUserActionFile;
-
- public static void newSession(Context context) {
- sSessionCount++;
- sAutoSuggestCount = 0;
- sBackspaceCount = 0;
- sAutoSuggestUndoneCount = 0;
- sManualSuggestCount = 0;
- sWordNotInDictionaryCount = 0;
- sTypedChars = 0;
- sActualChars = 0;
- sState = State.START;
-
- if (LOGGING) {
- try {
- sKeyLocationFile = context.openFileOutput("key.txt", Context.MODE_APPEND);
- sUserActionFile = context.openFileOutput("action.txt", Context.MODE_APPEND);
- } catch (IOException ioe) {
- Log.e("TextEntryState", "Couldn't open file for output: " + ioe);
- }
- }
- }
-
- public static void endSession() {
- if (sKeyLocationFile == null) {
- return;
- }
- try {
- sKeyLocationFile.close();
- // Write to log file
- // Write timestamp, settings,
- String out = DateFormat.format("MM:dd hh:mm:ss", Calendar.getInstance().getTime())
- .toString()
- + " BS: " + sBackspaceCount
- + " auto: " + sAutoSuggestCount
- + " manual: " + sManualSuggestCount
- + " typed: " + sWordNotInDictionaryCount
- + " undone: " + sAutoSuggestUndoneCount
- + " saved: " + ((float) (sActualChars - sTypedChars) / sActualChars)
- + "\n";
- sUserActionFile.write(out.getBytes());
- sUserActionFile.close();
- sKeyLocationFile = null;
- sUserActionFile = null;
- } catch (IOException ioe) {
- // ignore
- }
- }
-
public static void acceptedDefault(CharSequence typedWord, CharSequence actualWord) {
if (typedWord == null) return;
- if (!typedWord.equals(actualWord)) {
- sAutoSuggestCount++;
- }
- sTypedChars += typedWord.length();
- sActualChars += actualWord.length();
- sState = State.ACCEPTED_DEFAULT;
+ setState(ACCEPTED_DEFAULT);
LatinImeLogger.logOnAutoSuggestion(typedWord.toString(), actualWord.toString());
- displayState();
+ if (DEBUG)
+ displayState("acceptedDefault", "typedWord", typedWord, "actualWord", actualWord);
}
// State.ACCEPTED_DEFAULT will be changed to other sub-states
@@ -138,151 +60,167 @@ public class TextEntryState {
case SPACE_AFTER_ACCEPTED:
case PUNCTUATION_AFTER_ACCEPTED:
case IN_WORD:
- sState = State.ACCEPTED_DEFAULT;
+ setState(ACCEPTED_DEFAULT);
break;
default:
break;
}
- displayState();
+ if (DEBUG) displayState("backToAcceptedDefault", "typedWord", typedWord);
}
- public static void acceptedTyped(@SuppressWarnings("unused") CharSequence typedWord) {
- sWordNotInDictionaryCount++;
- sState = State.PICKED_SUGGESTION;
- displayState();
+ public static void acceptedTyped(CharSequence typedWord) {
+ setState(PICKED_SUGGESTION);
+ if (DEBUG) displayState("acceptedTyped", "typedWord", typedWord);
}
public static void acceptedSuggestion(CharSequence typedWord, CharSequence actualWord) {
- sManualSuggestCount++;
- State oldState = sState;
- if (typedWord.equals(actualWord)) {
- acceptedTyped(typedWord);
- }
- if (oldState == State.CORRECTING || oldState == State.PICKED_CORRECTION) {
- sState = State.PICKED_CORRECTION;
+ if (sState == RECORRECTING || sState == PICKED_RECORRECTION) {
+ setState(PICKED_RECORRECTION);
} else {
- sState = State.PICKED_SUGGESTION;
+ setState(PICKED_SUGGESTION);
}
- displayState();
+ if (DEBUG)
+ displayState("acceptedSuggestion", "typedWord", typedWord, "actualWord", actualWord);
}
- public static void selectedForCorrection() {
- sState = State.CORRECTING;
- displayState();
+ public static void selectedForRecorrection() {
+ setState(RECORRECTING);
+ if (DEBUG) displayState("selectedForRecorrection");
}
- public static void onAbortCorrection() {
- if (isCorrecting()) {
- sState = State.START;
+ public static void onAbortRecorrection() {
+ if (sState == RECORRECTING || sState == PICKED_RECORRECTION) {
+ setState(START);
}
- displayState();
+ if (DEBUG) displayState("onAbortRecorrection");
}
public static void typedCharacter(char c, boolean isSeparator) {
- boolean isSpace = c == ' ';
+ final boolean isSpace = (c == ' ');
switch (sState) {
- case IN_WORD:
- if (isSpace || isSeparator) {
- sState = State.START;
- } else {
- // State hasn't changed.
- }
- break;
- case ACCEPTED_DEFAULT:
- case SPACE_AFTER_PICKED:
- if (isSpace) {
- sState = State.SPACE_AFTER_ACCEPTED;
- } else if (isSeparator) {
- sState = State.PUNCTUATION_AFTER_ACCEPTED;
- } else {
- sState = State.IN_WORD;
- }
- break;
- case PICKED_SUGGESTION:
- case PICKED_CORRECTION:
- if (isSpace) {
- sState = State.SPACE_AFTER_PICKED;
- } else if (isSeparator) {
- // Swap
- sState = State.PUNCTUATION_AFTER_ACCEPTED;
- } else {
- sState = State.IN_WORD;
- }
- break;
- case START:
- case UNKNOWN:
- case SPACE_AFTER_ACCEPTED:
- case PUNCTUATION_AFTER_ACCEPTED:
- case PUNCTUATION_AFTER_WORD:
- if (!isSpace && !isSeparator) {
- sState = State.IN_WORD;
- } else {
- sState = State.START;
- }
- break;
- case UNDO_COMMIT:
- if (isSpace || isSeparator) {
- sState = State.ACCEPTED_DEFAULT;
- } else {
- sState = State.IN_WORD;
- }
- break;
- case CORRECTING:
- sState = State.START;
- break;
+ case IN_WORD:
+ if (isSpace || isSeparator) {
+ setState(START);
+ } else {
+ // State hasn't changed.
+ }
+ break;
+ case ACCEPTED_DEFAULT:
+ case SPACE_AFTER_PICKED:
+ case PUNCTUATION_AFTER_ACCEPTED:
+ if (isSpace) {
+ setState(SPACE_AFTER_ACCEPTED);
+ } else if (isSeparator) {
+ // Swap
+ setState(PUNCTUATION_AFTER_ACCEPTED);
+ } else {
+ setState(IN_WORD);
+ }
+ break;
+ case PICKED_SUGGESTION:
+ case PICKED_RECORRECTION:
+ if (isSpace) {
+ setState(SPACE_AFTER_PICKED);
+ } else if (isSeparator) {
+ // Swap
+ setState(PUNCTUATION_AFTER_ACCEPTED);
+ } else {
+ setState(IN_WORD);
+ }
+ break;
+ case START:
+ case UNKNOWN:
+ case SPACE_AFTER_ACCEPTED:
+ case PUNCTUATION_AFTER_WORD:
+ if (!isSpace && !isSeparator) {
+ setState(IN_WORD);
+ } else {
+ setState(START);
+ }
+ break;
+ case UNDO_COMMIT:
+ if (isSpace || isSeparator) {
+ setState(ACCEPTED_DEFAULT);
+ } else {
+ setState(IN_WORD);
+ }
+ break;
+ case RECORRECTING:
+ setState(START);
+ break;
}
- displayState();
+ if (DEBUG) displayState("typedCharacter", "char", c, "isSeparator", isSeparator);
}
public static void backspace() {
- if (sState == State.ACCEPTED_DEFAULT) {
- sState = State.UNDO_COMMIT;
- sAutoSuggestUndoneCount++;
+ if (sState == ACCEPTED_DEFAULT) {
+ setState(UNDO_COMMIT);
LatinImeLogger.logOnAutoSuggestionCanceled();
- } else if (sState == State.UNDO_COMMIT) {
- sState = State.IN_WORD;
+ } else if (sState == UNDO_COMMIT) {
+ setState(IN_WORD);
}
- sBackspaceCount++;
- displayState();
+ if (DEBUG) displayState("backspace");
}
public static void reset() {
- sState = State.START;
- displayState();
+ setState(START);
+ if (DEBUG) displayState("reset");
}
- public static State getState() {
- if (DBG) {
- Log.d(TAG, "Returning state = " + sState);
- }
- return sState;
+ public static boolean isAcceptedDefault() {
+ return sState == ACCEPTED_DEFAULT;
}
- public static boolean isCorrecting() {
- return sState == State.CORRECTING || sState == State.PICKED_CORRECTION;
+ public static boolean isSpaceAfterPicked() {
+ return sState == SPACE_AFTER_PICKED;
}
- public static void keyPressedAt(Key key, int x, int y) {
- if (LOGGING && sKeyLocationFile != null && key.mCode >= 32) {
- String out =
- "KEY: " + (char) key.mCode
- + " X: " + x
- + " Y: " + y
- + " MX: " + (key.mX + key.mWidth / 2)
- + " MY: " + (key.mY + key.mHeight / 2)
- + "\n";
- try {
- sKeyLocationFile.write(out.getBytes());
- } catch (IOException ioe) {
- // TODO: May run out of space
- }
+ public static boolean isUndoCommit() {
+ return sState == UNDO_COMMIT;
+ }
+
+ public static boolean isPunctuationAfterAccepted() {
+ return sState == PUNCTUATION_AFTER_ACCEPTED;
+ }
+
+ public static boolean isRecorrecting() {
+ return sState == RECORRECTING || sState == PICKED_RECORRECTION;
+ }
+
+ public static String getState() {
+ return stateName(sState);
+ }
+
+ private static String stateName(int state) {
+ switch (state) {
+ case START: return "START";
+ case IN_WORD: return "IN_WORD";
+ case ACCEPTED_DEFAULT: return "ACCEPTED_DEFAULT";
+ case PICKED_SUGGESTION: return "PICKED_SUGGESTION";
+ case PUNCTUATION_AFTER_WORD: return "PUNCTUATION_AFTER_WORD";
+ case PUNCTUATION_AFTER_ACCEPTED: return "PUNCTUATION_AFTER_ACCEPTED";
+ case SPACE_AFTER_ACCEPTED: return "SPACE_AFTER_ACCEPTED";
+ case SPACE_AFTER_PICKED: return "SPACE_AFTER_PICKED";
+ case UNDO_COMMIT: return "UNDO_COMMIT";
+ case RECORRECTING: return "RECORRECTING";
+ case PICKED_RECORRECTION: return "PICKED_RECORRECTION";
+ default: return "UNKNOWN";
}
}
- private static void displayState() {
- if (DBG) {
- Log.d(TAG, "State = " + sState);
+ private static void displayState(String title, Object ... args) {
+ final StringBuilder sb = new StringBuilder(title);
+ sb.append(':');
+ for (int i = 0; i < args.length; i += 2) {
+ sb.append(' ');
+ sb.append(args[i]);
+ sb.append('=');
+ sb.append(args[i+1].toString());
}
+ sb.append(" state=");
+ sb.append(stateName(sState));
+ sb.append(" previous=");
+ sb.append(stateName(sPreviousState));
+ Log.d(TAG, sb.toString());
}
}
-
diff --git a/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java
index 56ee5b9e7..c06bd736e 100644
--- a/java/src/com/android/inputmethod/latin/UserDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserDictionary.java
@@ -126,9 +126,8 @@ public class UserDictionary extends ExpandableDictionary {
}
@Override
- public synchronized void getWords(final WordComposer codes, final WordCallback callback,
- int[] nextLettersFrequencies) {
- super.getWords(codes, callback, nextLettersFrequencies);
+ public synchronized void getWords(final WordComposer codes, final WordCallback callback) {
+ super.getWords(codes, callback);
}
@Override
@@ -138,7 +137,7 @@ public class UserDictionary extends ExpandableDictionary {
private void addWords(Cursor cursor) {
clearDictionary();
-
+ if (cursor == null) return;
final int maxWordLength = getMaxWordLength();
if (cursor.moveToFirst()) {
final int indexWord = cursor.getColumnIndex(Words.WORD);
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index d33d962c0..35b2b123c 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -16,15 +16,21 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.compat.InputMethodInfoCompatWrapper;
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.compat.InputTypeCompatUtils;
+import com.android.inputmethod.keyboard.KeyboardId;
+
+import android.content.res.Resources;
import android.inputmethodservice.InputMethodService;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
+import android.text.InputType;
import android.text.format.DateUtils;
import android.util.Log;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.EditorInfo;
import java.io.BufferedReader;
import java.io.File;
@@ -41,6 +47,10 @@ public class Utils {
private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4;
private static boolean DBG = LatinImeLogger.sDBG;
+ private Utils() {
+ // Intentional empty constructor for utility class.
+ }
+
/**
* Cancel an {@link AsyncTask}.
*
@@ -55,7 +65,7 @@ public class Utils {
}
public static class GCUtils {
- private static final String TAG = "GCUtils";
+ private static final String GC_TAG = GCUtils.class.getSimpleName();
public static final int GC_TRY_COUNT = 2;
// GC_TRY_LOOP_MAX is used for the hard limit of GC wait,
// GC_TRY_LOOP_MAX should be greater than GC_TRY_COUNT.
@@ -84,7 +94,7 @@ public class Utils {
Thread.sleep(GC_INTERVAL);
return true;
} catch (InterruptedException e) {
- Log.e(TAG, "Sleep was interrupted.");
+ Log.e(GC_TAG, "Sleep was interrupted.");
LatinImeLogger.logOnException(metaData, t);
return false;
}
@@ -92,16 +102,15 @@ public class Utils {
}
}
- public static boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm) {
- return imm.getEnabledInputMethodList().size() > 1;
- // @@@ return imm.getEnabledInputMethodList().size() > 1
+ public static boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManagerCompatWrapper imm) {
+ return imm.getEnabledInputMethodList().size() > 1
// imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled
// input method subtype (The current IME should be LatinIME.)
- // || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
+ || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
}
- public static String getInputMethodId(InputMethodManager imm, String packageName) {
- for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) {
+ public static String getInputMethodId(InputMethodManagerCompatWrapper imm, String packageName) {
+ for (final InputMethodInfoCompatWrapper imi : imm.getEnabledInputMethodList()) {
if (imi.getPackageName().equals(packageName))
return imi.getId();
}
@@ -262,20 +271,42 @@ public class Utils {
return dp[sl][tl];
}
+ // Get the current stack trace
+ public static String getStackTrace() {
+ StringBuilder sb = new StringBuilder();
+ try {
+ throw new RuntimeException();
+ } catch (RuntimeException e) {
+ StackTraceElement[] frames = e.getStackTrace();
+ // Start at 1 because the first frame is here and we don't care about it
+ for (int j = 1; j < frames.length; ++j) sb.append(frames[j].toString() + "\n");
+ }
+ return sb.toString();
+ }
+
// In dictionary.cpp, getSuggestion() method,
// suggestion scores are computed using the below formula.
- // original score (called 'frequency')
+ // original score
// := 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.
+ // Then, the following processing is applied.
+ // - If the dictionary word is matched up to the point of the user entry
+ // (full match up to min(before.length(), after.length())
+ // => Then multiply by FULL_MATCHED_WORDS_PROMOTION_RATE (this is defined 1.2)
+ // - If the word is a true full match except for differences in accents or
+ // capitalization, then treat it as if the score was 255.
+ // - If before.length() == after.length()
+ // => multiply by mFullWordMultiplier (this is defined 2))
+ // So, maximum original score is pow(2, min(before.length(), after.length())) * 255 * 2 * 1.2
+ // For historical reasons we ignore the 1.2 modifier (because the measure for a good
+ // autocorrection threshold was done at a time when it didn't exist). This doesn't change
+ // the result.
+ // So, we can normalize original score by dividing pow(2, min(b.l(),a.l())) * 255 * 2.
private static final int MAX_INITIAL_SCORE = 255;
private static final int TYPED_LETTER_MULTIPLIER = 2;
- private static final int FULL_WORD_MULTIPLYER = 2;
+ private static final int FULL_WORD_MULTIPLIER = 2;
public static double calcNormalizedScore(CharSequence before, CharSequence after, int score) {
final int beforeLength = before.length();
final int afterLength = after.length();
@@ -285,7 +316,7 @@ public class Utils {
// correction.
final double maximumScore = MAX_INITIAL_SCORE
* Math.pow(TYPED_LETTER_MULTIPLIER, Math.min(beforeLength, afterLength))
- * FULL_WORD_MULTIPLYER;
+ * FULL_WORD_MULTIPLIER;
// add a weight based on edit distance.
// distance <= max(afterLength, beforeLength) == afterLength,
// so, 0 <= distance / afterLength <= 1
@@ -294,7 +325,7 @@ public class Utils {
}
public static class UsabilityStudyLogUtils {
- private static final String TAG = "UsabilityStudyLogUtils";
+ private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
private static final String FILENAME = "log.txt";
private static final UsabilityStudyLogUtils sInstance =
new UsabilityStudyLogUtils();
@@ -331,7 +362,7 @@ public class Utils {
try {
mWriter = getPrintWriter(mDirectory, FILENAME, false);
} catch (IOException e) {
- Log.e(TAG, "Can't create log file.");
+ Log.e(USABILITY_TAG, "Can't create log file.");
}
}
}
@@ -368,7 +399,7 @@ public class Utils {
final String printString = String.format("%s\t%d\t%s\n",
mDateFormat.format(mDate), currentTime, log);
if (LatinImeLogger.sDBG) {
- Log.d(TAG, "Write: " + log);
+ Log.d(USABILITY_TAG, "Write: " + log);
}
mWriter.print(printString);
}
@@ -389,10 +420,10 @@ public class Utils {
sb.append(line);
}
} catch (IOException e) {
- Log.e(TAG, "Can't read log file.");
+ Log.e(USABILITY_TAG, "Can't read log file.");
} finally {
if (LatinImeLogger.sDBG) {
- Log.d(TAG, "output all logs\n" + sb.toString());
+ Log.d(USABILITY_TAG, "output all logs\n" + sb.toString());
}
mIms.getCurrentInputConnection().commitText(sb.toString(), 0);
try {
@@ -411,7 +442,7 @@ public class Utils {
public void run() {
if (mFile != null && mFile.exists()) {
if (LatinImeLogger.sDBG) {
- Log.d(TAG, "Delete log file.");
+ Log.d(USABILITY_TAG, "Delete log file.");
}
mFile.delete();
mWriter.close();
@@ -440,4 +471,134 @@ public class Utils {
return new PrintWriter(new FileOutputStream(mFile), true /* autoFlush */);
}
}
+
+ public static int getKeyboardMode(EditorInfo attribute) {
+ if (attribute == null)
+ return KeyboardId.MODE_TEXT;
+
+ final int inputType = attribute.inputType;
+ final int variation = inputType & InputType.TYPE_MASK_VARIATION;
+
+ switch (inputType & InputType.TYPE_MASK_CLASS) {
+ case InputType.TYPE_CLASS_NUMBER:
+ case InputType.TYPE_CLASS_DATETIME:
+ return KeyboardId.MODE_NUMBER;
+ case InputType.TYPE_CLASS_PHONE:
+ return KeyboardId.MODE_PHONE;
+ case InputType.TYPE_CLASS_TEXT:
+ if (InputTypeCompatUtils.isEmailVariation(variation)) {
+ return KeyboardId.MODE_EMAIL;
+ } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
+ return KeyboardId.MODE_URL;
+ } else if (variation == InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
+ return KeyboardId.MODE_IM;
+ } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
+ return KeyboardId.MODE_TEXT;
+ } else if (variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) {
+ return KeyboardId.MODE_WEB;
+ } else {
+ return KeyboardId.MODE_TEXT;
+ }
+ default:
+ return KeyboardId.MODE_TEXT;
+ }
+ }
+
+ public static boolean containsInCsv(String key, String csv) {
+ if (csv == null)
+ return false;
+ for (String option : csv.split(",")) {
+ if (option.equals(key))
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean inPrivateImeOptions(String packageName, String key,
+ EditorInfo attribute) {
+ if (attribute == null)
+ return false;
+ return containsInCsv(packageName != null ? packageName + "." + key : key,
+ attribute.privateImeOptions);
+ }
+
+ /**
+ * Returns a main dictionary resource id
+ * @return main dictionary resource id
+ */
+ public static int getMainDictionaryResourceId(Resources res) {
+ final String MAIN_DIC_NAME = "main";
+ String packageName = LatinIME.class.getPackage().getName();
+ return res.getIdentifier(MAIN_DIC_NAME, "raw", packageName);
+ }
+
+ public static void loadNativeLibrary() {
+ try {
+ System.loadLibrary("jni_latinime");
+ } catch (UnsatisfiedLinkError ule) {
+ Log.e(TAG, "Could not load native library jni_latinime");
+ }
+ }
+
+ /**
+ * Returns true if a and b are equal ignoring the case of the character.
+ * @param a first character to check
+ * @param b second character to check
+ * @return {@code true} if a and b are equal, {@code false} otherwise.
+ */
+ public static boolean equalsIgnoreCase(char a, char b) {
+ // Some language, such as Turkish, need testing both cases.
+ return a == b
+ || Character.toLowerCase(a) == Character.toLowerCase(b)
+ || Character.toUpperCase(a) == Character.toUpperCase(b);
+ }
+
+ /**
+ * Returns true if a and b are equal ignoring the case of the characters, including if they are
+ * both null.
+ * @param a first CharSequence to check
+ * @param b second CharSequence to check
+ * @return {@code true} if a and b are equal, {@code false} otherwise.
+ */
+ public static boolean equalsIgnoreCase(CharSequence a, CharSequence b) {
+ if (a == b)
+ return true; // including both a and b are null.
+ if (a == null || b == null)
+ return false;
+ final int length = a.length();
+ if (length != b.length())
+ return false;
+ for (int i = 0; i < length; i++) {
+ if (!equalsIgnoreCase(a.charAt(i), b.charAt(i)))
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns true if a and b are equal ignoring the case of the characters, including if a is null
+ * and b is zero length.
+ * @param a CharSequence to check
+ * @param b character array to check
+ * @param offset start offset of array b
+ * @param length length of characters in array b
+ * @return {@code true} if a and b are equal, {@code false} otherwise.
+ * @throws IndexOutOfBoundsException
+ * if {@code offset < 0 || length < 0 || offset + length > data.length}.
+ * @throws NullPointerException if {@code b == null}.
+ */
+ public static boolean equalsIgnoreCase(CharSequence a, char[] b, int offset, int length) {
+ if (offset < 0 || length < 0 || length > b.length - offset)
+ throw new IndexOutOfBoundsException("array.length=" + b.length + " offset=" + offset
+ + " length=" + length);
+ if (a == null)
+ return length == 0; // including a is null and b is zero length.
+ if (a.length() != length)
+ return false;
+ for (int i = 0; i < length; i++) {
+ if (!equalsIgnoreCase(a.charAt(i), b[offset + i]))
+ return false;
+ }
+ return true;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
new file mode 100644
index 000000000..2389d4e3c
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2011 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.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.HashMap;
+
+public class WhitelistDictionary extends Dictionary {
+
+ private static final boolean DBG = LatinImeLogger.sDBG;
+ private static final String TAG = WhitelistDictionary.class.getSimpleName();
+
+ private final HashMap<String, Pair<Integer, String>> mWhitelistWords =
+ new HashMap<String, Pair<Integer, String>>();
+
+ private static final WhitelistDictionary sInstance = new WhitelistDictionary();
+
+ private WhitelistDictionary() {
+ }
+
+ public static WhitelistDictionary init(Context context) {
+ synchronized (sInstance) {
+ if (context != null) {
+ sInstance.initWordlist(
+ context.getResources().getStringArray(R.array.wordlist_whitelist));
+ } else {
+ sInstance.mWhitelistWords.clear();
+ }
+ }
+ return sInstance;
+ }
+
+ private void initWordlist(String[] wordlist) {
+ mWhitelistWords.clear();
+ final int N = wordlist.length;
+ if (N % 3 != 0) {
+ if (DBG) {
+ Log.d(TAG, "The number of the whitelist is invalid.");
+ }
+ return;
+ }
+ try {
+ for (int i = 0; i < N; i += 3) {
+ final int score = Integer.valueOf(wordlist[i]);
+ final String before = wordlist[i + 1];
+ final String after = wordlist[i + 2];
+ if (before != null && after != null) {
+ mWhitelistWords.put(
+ before.toLowerCase(), new Pair<Integer, String>(score, after));
+ }
+ }
+ } catch (NumberFormatException e) {
+ if (DBG) {
+ Log.d(TAG, "The score of the word is invalid.");
+ }
+ }
+ }
+
+ public String getWhiteListedWord(String before) {
+ if (before == null) return null;
+ final String lowerCaseBefore = before.toLowerCase();
+ if(mWhitelistWords.containsKey(lowerCaseBefore)) {
+ if (DBG) {
+ Log.d(TAG, "--- found whiteListedWord: " + lowerCaseBefore);
+ }
+ return mWhitelistWords.get(lowerCaseBefore).second;
+ }
+ return null;
+ }
+
+ // Not used for WhitelistDictionary. We use getWhitelistedWord() in Suggest.java instead
+ @Override
+ public void getWords(WordComposer composer, WordCallback callback) {
+ }
+
+ @Override
+ public boolean isValidWord(CharSequence word) {
+ if (TextUtils.isEmpty(word)) return false;
+ return !TextUtils.isEmpty(getWhiteListedWord(word.toString()));
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 2e415b771..02583895b 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -16,22 +16,32 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.keyboard.KeyDetector;
+
import java.util.ArrayList;
/**
* A place to store the currently composing word with information such as adjacent key codes as well
*/
public class WordComposer {
+
+ public static final int NOT_A_CODE = KeyDetector.NOT_A_CODE;
+ public static final int NOT_A_COORDINATE = -1;
+
/**
* The list of unicode values for each keystroke (including surrounding keys)
*/
private final ArrayList<int[]> mCodes;
-
+
+ private int mTypedLength;
+ private final int[] mXCoordinates;
+ private final int[] mYCoordinates;
+
/**
* The word chosen from the candidate list, until it is committed.
*/
private String mPreferredWord;
-
+
private final StringBuilder mTypedWord;
private int mCapsCount;
@@ -44,17 +54,24 @@ public class WordComposer {
private boolean mIsFirstCharCapitalized;
public WordComposer() {
- mCodes = new ArrayList<int[]>(12);
- mTypedWord = new StringBuilder(20);
+ final int N = BinaryDictionary.MAX_WORD_LENGTH;
+ mCodes = new ArrayList<int[]>(N);
+ mTypedWord = new StringBuilder(N);
+ mTypedLength = 0;
+ mXCoordinates = new int[N];
+ mYCoordinates = new int[N];
}
- WordComposer(WordComposer copy) {
- mCodes = new ArrayList<int[]>(copy.mCodes);
- mPreferredWord = copy.mPreferredWord;
- mTypedWord = new StringBuilder(copy.mTypedWord);
- mCapsCount = copy.mCapsCount;
- mAutoCapitalized = copy.mAutoCapitalized;
- mIsFirstCharCapitalized = copy.mIsFirstCharCapitalized;
+ WordComposer(WordComposer source) {
+ mCodes = new ArrayList<int[]>(source.mCodes);
+ mPreferredWord = source.mPreferredWord;
+ mTypedWord = new StringBuilder(source.mTypedWord);
+ mCapsCount = source.mCapsCount;
+ mAutoCapitalized = source.mAutoCapitalized;
+ mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
+ mTypedLength = source.mTypedLength;
+ mXCoordinates = source.mXCoordinates;
+ mYCoordinates = source.mYCoordinates;
}
/**
@@ -62,6 +79,7 @@ public class WordComposer {
*/
public void reset() {
mCodes.clear();
+ mTypedLength = 0;
mIsFirstCharCapitalized = false;
mPreferredWord = null;
mTypedWord.setLength(0);
@@ -85,15 +103,28 @@ public class WordComposer {
return mCodes.get(index);
}
+ public int[] getXCoordinates() {
+ return mXCoordinates;
+ }
+
+ public int[] getYCoordinates() {
+ return mYCoordinates;
+ }
+
/**
* Add a new keystroke, with codes[0] containing the pressed key's unicode and the rest of
* the array containing unicode for adjacent keys, sorted by reducing probability/proximity.
* @param codes the array of unicode values
*/
- public void add(int primaryCode, int[] codes) {
+ public void add(int primaryCode, int[] codes, int x, int y) {
mTypedWord.append((char) primaryCode);
correctPrimaryJuxtapos(primaryCode, codes);
mCodes.add(codes);
+ if (mTypedLength < BinaryDictionary.MAX_WORD_LENGTH) {
+ mXCoordinates[mTypedLength] = x;
+ mYCoordinates[mTypedLength] = y;
+ }
+ ++mTypedLength;
if (Character.isUpperCase((char) primaryCode)) mCapsCount++;
}
@@ -124,6 +155,9 @@ public class WordComposer {
mTypedWord.deleteCharAt(lastPos);
if (Character.isUpperCase(last)) mCapsCount--;
}
+ if (mTypedLength > 0) {
+ --mTypedLength;
+ }
}
/**