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/AutoCorrection.java143
-rw-r--r--java/src/com/android/inputmethod/latin/AutoDictionary.java1
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java165
-rw-r--r--java/src/com/android/inputmethod/latin/CandidateView.java17
-rw-r--r--java/src/com/android/inputmethod/latin/DebugSettings.java10
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java13
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableDictionary.java59
-rw-r--r--java/src/com/android/inputmethod/latin/InputLanguageSelection.java2
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java499
-rw-r--r--java/src/com/android/inputmethod/latin/Settings.java4
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java104
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java309
-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.java150
-rw-r--r--java/src/com/android/inputmethod/latin/WhitelistDictionary.java99
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java58
19 files changed, 1447 insertions, 778 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/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java
new file mode 100644
index 000000000..092f7ad63
--- /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[] priorities,
+ 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,
+ priorities, 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[] priorities,
+ CharSequence typedWord, double autoCorrectionThreshold) {
+ if (wordComposer.size() > 1 && (correctionMode == Suggest.CORRECTION_FULL
+ || correctionMode == Suggest.CORRECTION_FULL_BIGRAM)
+ && typedWord != null && suggestions.size() > 0 && priorities.length > 0) {
+ final CharSequence autoCorrectionCandidate = suggestions.get(0);
+ final int autoCorrectionCandidateScore = priorities[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 813f7d32f..08ddd25fa 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -16,10 +16,15 @@
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;
/**
@@ -33,11 +38,11 @@ public class BinaryDictionary extends Dictionary {
* 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;
@@ -46,19 +51,32 @@ 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];
- static {
- try {
- System.loadLibrary("jni_latinime");
- } catch (UnsatisfiedLinkError ule) {
- Log.e(TAG, "Could not load native library jni_latinime");
+ private final KeyboardSwitcher mKeyboardSwitcher = KeyboardSwitcher.getInstance();
+ private final SubtypeSwitcher mSubtypeSwitcher = SubtypeSwitcher.getInstance();
+
+ private static class Flags {
+ private static class FlagEntry {
+ public final String mName;
+ public final int mValue;
+ public FlagEntry(String name, int value) {
+ mName = name;
+ mValue = value;
+ }
}
+ public static final FlagEntry[] 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.
+ new FlagEntry("requiresGermanUmlautProcessing", 0x1)
+ };
}
+ private int mFlags = 0;
private BinaryDictionary() {
}
@@ -72,47 +90,80 @@ public class BinaryDictionary extends Dictionary {
public static BinaryDictionary initDictionary(Context context, int resId, int dicTypeId) {
synchronized (sInstance) {
sInstance.closeInternal();
- if (resId != 0) {
- sInstance.loadDictionary(context, resId);
+ try {
+ final AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId);
+ if (afd == null) {
+ Log.e(TAG, "Found the resource but it is compressed. resId=" + resId);
+ return null;
+ }
+ final String sourceDir = context.getApplicationInfo().sourceDir;
+ final File packagePath = new File(sourceDir);
+ // TODO: Come up with a way to handle a directory.
+ if (!packagePath.isFile()) {
+ Log.e(TAG, "sourceDir is not a file: " + sourceDir);
+ return null;
+ }
+ sInstance.loadDictionary(sourceDir, afd.getStartOffset(), afd.getLength());
+ sInstance.mDicTypeId = dicTypeId;
+ } catch (android.content.res.Resources.NotFoundException e) {
+ Log.e(TAG, "Could not find the resource. resId=" + resId);
+ return null;
+ }
+ }
+ sInstance.initFlags();
+ return sInstance;
+ }
+
+ /* package for test */ static BinaryDictionary initDictionary(File dictionary, long startOffset,
+ long length, int dicTypeId) {
+ synchronized (sInstance) {
+ sInstance.closeInternal();
+ if (dictionary.isFile()) {
+ sInstance.loadDictionary(dictionary.getAbsolutePath(), startOffset, length);
sInstance.mDicTypeId = dicTypeId;
+ } else {
+ Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath());
+ return null;
}
}
return sInstance;
}
+ private void initFlags() {
+ int flags = 0;
+ for (Flags.FlagEntry entry : Flags.ALL_FLAGS) {
+ if (mSubtypeSwitcher.currentSubtypeContainsExtraValueKey(entry.mName))
+ flags |= entry.mValue;
+ }
+ mFlags = flags;
+ }
+
+ static {
+ Utils.loadNativeLibrary();
+ }
+
private native int openNative(String sourceDir, long dictOffset, long dictSize,
int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength,
int maxWords, int maxAlternatives);
private native void closeNative(int dict);
private native boolean isValidWordNative(int nativeData, char[] word, int wordLength);
- private native int getSuggestionsNative(int dict, int[] inputCodes, int codesSize,
- char[] outputChars, int[] frequencies,
- int[] nextLettersFrequencies, int nextLettersSize);
+ private native int getSuggestionsNative(int dict, int proximityInfo, int[] xCoordinates,
+ int[] yCoordinates, int[] inputCodes, int codesSize, int flags, char[] outputChars,
+ int[] frequencies);
private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength,
int[] inputCodes, int inputCodesLength, char[] outputChars, int[] frequencies,
int maxWordLength, int maxBigrams, int maxAlternatives);
- private final void loadDictionary(Context context, int resId) {
- try {
- final AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId);
- if (afd == null) {
- Log.e(TAG, "Found the resource but it is compressed. resId=" + resId);
- return;
- }
- mNativeDict = openNative(context.getApplicationInfo().sourceDir,
- afd.getStartOffset(), afd.getLength(),
+ 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);
- mDictLength = afd.getLength();
- } catch (android.content.res.Resources.NotFoundException e) {
- Log.e(TAG, "Could not find the resource. resId=" + resId);
- return;
- }
+ 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();
@@ -123,11 +174,11 @@ public class BinaryDictionary extends Dictionary {
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);
+ MAX_PROXIMITY_CHARS_SIZE);
for (int j = 0; j < count; ++j) {
if (mFrequencies_bigrams[j] < 1) break;
@@ -144,26 +195,9 @@ public class BinaryDictionary extends Dictionary {
}
@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, mFrequencies);
for (int j = 0; j < count; ++j) {
if (mFrequencies[j] < 1) break;
@@ -179,6 +213,33 @@ public class BinaryDictionary extends Dictionary {
}
}
+ /* package for test */ boolean isValidDictionary() {
+ return mNativeDict != 0;
+ }
+
+ /* package for test */ int getSuggestions(final WordComposer codes, final Keyboard keyboard,
+ char[] outputChars, int[] frequencies) {
+ 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(frequencies, 0);
+
+ return getSuggestionsNative(
+ mNativeDict, keyboard.getProximityInfo(),
+ codes.getXCoordinates(), codes.getYCoordinates(), mInputCodes, codesSize,
+ mFlags, outputChars, frequencies);
+ }
+
@Override
public boolean isValidWord(CharSequence word) {
if (word == null) return false;
diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java
index fc45c7c75..5719b9012 100644
--- a/java/src/com/android/inputmethod/latin/CandidateView.java
+++ b/java/src/com/android/inputmethod/latin/CandidateView.java
@@ -54,7 +54,7 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
private static final int MAX_SUGGESTIONS = 16;
- private static boolean DBG = LatinImeLogger.sDBG;
+ private static final boolean DBG = LatinImeLogger.sDBG;
private final ArrayList<View> mWords = new ArrayList<View>();
private final boolean mConfigCandidateHighlightFontColorEnabled;
@@ -226,10 +226,14 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
}
final String debugString = info.getDebugString();
if (DBG) {
- if (!TextUtils.isEmpty(debugString)) {
+ if (TextUtils.isEmpty(debugString)) {
+ dv.setVisibility(GONE);
+ } else {
dv.setText(debugString);
dv.setVisibility(VISIBLE);
}
+ } else {
+ dv.setVisibility(GONE);
}
} else {
dv.setVisibility(GONE);
@@ -249,8 +253,10 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
final TextView tv = (TextView)mWords.get(1).findViewById(R.id.candidate_word);
final Spannable word = new SpannableString(autoCorrectedWord);
final int wordLength = word.length();
- word.setSpan(mInvertedBackgroundColorSpan, 0, wordLength, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
- word.setSpan(mInvertedForegroundColorSpan, 0, wordLength, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ word.setSpan(mInvertedBackgroundColorSpan, 0, wordLength,
+ Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ word.setSpan(mInvertedForegroundColorSpan, 0, wordLength,
+ Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
tv.setText(word);
mShowingAutoCorrectionInverted = true;
}
@@ -341,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/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java
index 03211f36b..2f1e7c2b8 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettings.java
@@ -20,6 +20,7 @@ import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
+import android.os.Process;
import android.preference.CheckBoxPreference;
import android.preference.PreferenceActivity;
import android.util.Log;
@@ -30,6 +31,7 @@ public class DebugSettings extends PreferenceActivity
private static final String TAG = "DebugSettings";
private static final String DEBUG_MODE_KEY = "debug_mode";
+ private boolean mServiceNeedsRestart = false;
private CheckBoxPreference mDebugMode;
@Override
@@ -39,16 +41,24 @@ public class DebugSettings extends PreferenceActivity
SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
prefs.registerOnSharedPreferenceChangeListener(this);
+ mServiceNeedsRestart = false;
mDebugMode = (CheckBoxPreference) findPreference(DEBUG_MODE_KEY);
updateDebugMode();
}
@Override
+ protected void onStop() {
+ super.onStop();
+ if (mServiceNeedsRestart) Process.killProcess(Process.myPid());
+ }
+
+ @Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
if (key.equals(DEBUG_MODE_KEY)) {
if (mDebugMode != null) {
mDebugMode.setChecked(prefs.getBoolean(DEBUG_MODE_KEY, false));
updateDebugMode();
+ mServiceNeedsRestart = true;
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 74933595c..56f0cc503 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -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/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 0fc86c335..0318175f6 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_FREQ_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..5587c685f 100644
--- a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
+++ b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
@@ -106,7 +106,7 @@ public class InputLanguageSelection extends PreferenceActivity {
conf.locale = locale;
res.updateConfiguration(conf, res.getDisplayMetrics());
- int mainDicResId = LatinIME.getMainDictionaryResourceId(res);
+ int mainDicResId = Utils.getMainDictionaryResourceId(res);
BinaryDictionary bd = BinaryDictionary.initDictionary(this, mainDicResId, Suggest.DIC_MAIN);
// Is the dictionary larger than a placeholder? Arbitrarily chose a lower limit of
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 5ce1b7e95..6e76cadf2 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -18,7 +18,6 @@ package com.android.inputmethod.latin;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardActionListener;
-import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.KeyboardView;
import com.android.inputmethod.keyboard.LatinKeyboard;
@@ -40,6 +39,7 @@ 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;
@@ -81,13 +81,34 @@ 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 InputMethodService 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.
+ */
+ 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;
@@ -138,6 +159,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;
@@ -145,7 +168,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;
@@ -158,6 +181,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;
@@ -169,7 +193,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;
@@ -186,7 +209,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// 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>();
@@ -247,6 +269,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) {
@@ -323,7 +346,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
@@ -335,6 +358,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
@@ -344,13 +381,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);
+ mInputMethodId = Utils.getInputMethodId(mImm, getPackageName());
mSubtypeSwitcher = SubtypeSwitcher.getInstance();
mKeyboardSwitcher = KeyboardSwitcher.getInstance();
+ mAccessibilityUtils = AccessibilityUtils.getInstance();
final Resources res = getResources();
mResources = res;
@@ -358,10 +397,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,
- res.getBoolean(R.bool.default_recorrection_enabled));
+ mRecorrectionEnabled = prefs.getBoolean(Settings.PREF_RECORRECTION_ENABLED,
+ res.getBoolean(R.bool.config_default_recorrection_enabled));
} else {
- mReCorrectionEnabled = res.getBoolean(R.bool.default_recorrection_enabled);
+ mRecorrectionEnabled = res.getBoolean(R.bool.config_default_recorrection_enabled);
}
mConfigEnableShowSubtypeSettings = res.getBoolean(
@@ -374,6 +413,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;
@@ -395,21 +436,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(mReceiver, filter);
mVoiceConnector = VoiceIMEConnector.init(this, prefs, mHandler);
- prefs.registerOnSharedPreferenceChangeListener(this);
- }
-
- /**
- * 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);
}
private void initSuggest() {
- updateAutoTextEnabled();
String locale = mSubtypeSwitcher.getInputLocaleStr();
Locale savedLocale = mSubtypeSwitcher.changeSystemLocale(new Locale(locale));
@@ -420,9 +449,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mQuickFixes = isQuickFixesEnabled(prefs);
final Resources res = mResources;
- int mainDicResId = getMainDictionaryResourceId(res);
+ int mainDicResId = Utils.getMainDictionaryResourceId(res);
mSuggest = new Suggest(this, mainDicResId);
loadAndSetAutoCorrectionThreshold(prefs);
+ updateAutoTextEnabled();
mUserDictionary = new UserDictionary(this, locale);
mSuggest.setUserDictionary(mUserDictionary);
@@ -497,17 +527,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;
@@ -523,20 +542,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 VoiceIMEConnector voiceIme = mVoiceConnector;
+ voiceIme.resetVoiceStates(Utils.isPasswordInputType(attribute.inputType)
+ || Utils.isVisiblePasswordInputType(attribute.inputType));
- final int mode = initializeInputAttributesAndGetMode(attribute.inputType);
+ initializeInputAttributes(attribute);
inputView.closing();
mEnteredText = null;
@@ -547,9 +562,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();
}
@@ -560,18 +575,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;
@@ -579,73 +600,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 (Utils.isPasswordInputType(inputType)
+ || Utils.isVisiblePasswordInputType(inputType)) {
+ mIsSettingsSuggestionStripOn = false;
+ }
+ if (Utils.isEmailVariation(variation)
+ || variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) {
+ mAutoSpace = false;
+ } else {
+ mAutoSpace = true;
+ }
+ if (Utils.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;
@@ -713,6 +713,8 @@ 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
@@ -723,9 +725,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// 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)
+ || mVoiceConnector.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();
@@ -736,15 +747,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
mVoiceConnector.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;
@@ -754,18 +760,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()
@@ -788,7 +794,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
*/
@Override
public void onExtractedTextClicked() {
- if (mReCorrectionEnabled && isSuggestionsRequested()) return;
+ if (mRecorrectionEnabled && isSuggestionsRequested()) return;
super.onExtractedTextClicked();
}
@@ -804,7 +810,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);
}
@@ -822,17 +828,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mVoiceConnector.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) {
@@ -963,7 +968,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();
}
@@ -1011,7 +1016,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;
@@ -1019,13 +1023,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();
}
}
@@ -1105,6 +1113,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:
@@ -1114,12 +1123,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:
@@ -1157,10 +1166,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
@@ -1172,7 +1179,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mVoiceConnector.commitVoiceInput();
InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
- abortCorrection(false);
+ abortRecorrection(false);
ic.beginBatchEdit();
commitTyped(ic);
maybeRemovePreviousPeriod(text);
@@ -1180,7 +1187,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
ic.endBatchEdit();
mKeyboardSwitcher.updateShiftState();
mKeyboardSwitcher.onKey(Keyboard.CODE_DUMMY);
- mJustReverted = false;
mJustAddedAutoSpace = false;
mEnteredText = text;
}
@@ -1220,7 +1226,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;
@@ -1245,7 +1251,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
}
- mJustReverted = false;
ic.endBatchEdit();
}
@@ -1273,20 +1278,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
- 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) {
+ private void handleCharacter(int primaryCode, int[] keyCodes, int x, int y) {
mVoiceConnector.handleCharacter();
- if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isCorrecting()) {
- abortCorrection(false);
+ if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isRecorrecting()) {
+ abortRecorrection(false);
}
int code = primaryCode;
@@ -1296,6 +1301,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mComposing.setLength(0);
saveWordInHistory(mBestWord);
mWord.reset();
+ clearSuggestions();
}
}
KeyboardSwitcher switcher = mKeyboardSwitcher;
@@ -1323,7 +1329,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
@@ -1354,14 +1360,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".
@@ -1380,14 +1386,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();
@@ -1419,12 +1423,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
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.
@@ -1457,7 +1459,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;
@@ -1467,26 +1469,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() {
@@ -1508,8 +1505,6 @@ 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()) {
@@ -1528,36 +1523,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
@@ -1604,7 +1593,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;
}
@@ -1615,7 +1604,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
SuggestedWords suggestions = mCandidateView.getSuggestions();
mVoiceConnector.flushAndLogAllTextModificationCounters(index, suggestion, mWordSeparators);
- final boolean correcting = TextEntryState.isCorrecting();
+ final boolean recorrecting = TextEntryState.isRecorrecting();
InputConnection ic = getCurrentInputConnection();
if (ic != null) {
ic.beginBatchEdit();
@@ -1657,24 +1646,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,7 +1712,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
saveWordInHistory(suggestion);
mHasValidSuggestions = false;
mCommittedLength = suggestion.length();
- switcher.setPreferredLetters(null);
}
/**
@@ -1725,6 +1724,7 @@ 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) {
@@ -1734,15 +1734,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
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)));
}
@@ -1779,19 +1779,19 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (!mVoiceConnector.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);
}
}
@@ -1800,21 +1800,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.
@@ -1822,9 +1823,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);
}
@@ -1864,7 +1870,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;
@@ -1892,7 +1897,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.postUpdateSuggestions();
} else {
sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
- mJustReverted = false;
}
}
@@ -1930,28 +1934,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(),
+ mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(),
+ mSubtypeSwitcher.isShortcutImeEnabled() && mVoiceConnector.isVoiceButtonEnabled(),
mVoiceConnector.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.default_recorrection_enabled));
- }
+ mKeyboardSwitcher.updateShiftState();
}
@Override
@@ -1961,7 +1948,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);
@@ -1969,25 +1956,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);
}
@@ -2067,6 +2056,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;
@@ -2082,7 +2072,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void updateAutoTextEnabled() {
if (mSuggest == null) return;
- mSuggest.setAutoTextEnabled(mQuickFixes
+ mSuggest.setQuickFixesEnabled(mQuickFixes
&& SubtypeSwitcher.getInstance().isSystemLanguageSameAsInputLanguage());
}
@@ -2121,7 +2111,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
mVibrateOn = vibrator != null && vibrator.hasVibrator()
&& prefs.getBoolean(Settings.PREF_VIBRATE_ON, false);
- mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON, false);
+ mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON,
+ mResources.getBoolean(R.bool.config_default_sound_enabled));
mPopupOn = isPopupEnabled(prefs);
mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true);
@@ -2272,10 +2263,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
di.dismiss();
switch (position) {
case 0:
- launchSettings();
+ mImm.showInputMethodPicker();
break;
case 1:
- mImm.showInputMethodPicker();
+ launchSettings();
break;
}
}
@@ -2285,6 +2276,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);
@@ -2295,7 +2288,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);
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 12338ce61..341d5add0 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -225,7 +225,9 @@ public class Settings extends PreferenceActivity
final String action;
if (android.os.Build.VERSION.SDK_INT
>= /* android.os.Build.VERSION_CODES.HONEYCOMB */ 11) {
- action = "android.settings.INPUT_METHOD_AND_SUBTYPE_ENABLER";
+ // 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";
}
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index f4262cc99..dc14d770a 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -18,7 +18,6 @@ package com.android.inputmethod.latin;
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;
@@ -47,7 +46,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";
@@ -75,10 +74,10 @@ public class SubtypeSwitcher {
private InputMethodInfo mShortcutInputMethodInfo;
private InputMethodSubtype mShortcutSubtype;
private List<InputMethodSubtype> mAllEnabledSubtypesOfCurrentInputMethod;
+ private InputMethodSubtype mCurrentSubtype;
private Locale mSystemLocale;
private Locale mInputLocale;
private String mInputLocaleStr;
- private String mMode;
private VoiceInput mVoiceInput;
/*-----------------------------------------------------------*/
@@ -111,8 +110,7 @@ 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;
@@ -146,6 +144,7 @@ public class SubtypeSwitcher {
// Reload enabledSubtypes from the framework.
private void updateEnabledSubtypes() {
+ final String currentMode = getCurrentSubtypeMode();
boolean foundCurrentSubtypeBecameDisabled = true;
mAllEnabledSubtypesOfCurrentInputMethod = mImm.getEnabledInputMethodSubtypeList(
null, true);
@@ -158,7 +157,7 @@ public class SubtypeSwitcher {
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,7 +168,7 @@ 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());
@@ -210,9 +209,10 @@ public class SubtypeSwitcher {
public void updateSubtype(InputMethodSubtype newSubtype) {
final String newLocale;
final String newMode;
+ final String oldMode = getCurrentSubtypeMode();
if (newSubtype == 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;
@@ -222,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)) {
@@ -232,13 +232,12 @@ 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.
@@ -263,7 +262,7 @@ public class SubtypeSwitcher {
triggerVoiceIME();
}
} else {
- Log.w(TAG, "Unknown subtype mode: " + mMode);
+ Log.w(TAG, "Unknown subtype mode: " + newMode);
if (VOICE_MODE.equals(oldMode) && mVoiceInput != 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.
@@ -356,11 +355,27 @@ public class SubtypeSwitcher {
return false;
}
- public boolean isShortcutAvailable() {
+ public boolean isShortcutImeEnabled() {
if (mShortcutInputMethodInfo == null)
return false;
- if (mShortcutSubtype != null && contains(mShortcutSubtype.getExtraValue().split(","),
- SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY)) {
+ if (mShortcutSubtype == null)
+ return true;
+ final boolean allowsImplicitlySelectedSubtypes = true;
+ for (final InputMethodSubtype enabledSubtype : mImm.getEnabledInputMethodSubtypeList(
+ mShortcutInputMethodInfo, allowsImplicitlySelectedSubtypes)) {
+ if (enabledSubtype.equals(mShortcutSubtype))
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isShortcutImeReady() {
+ if (mShortcutInputMethodInfo == null)
+ return false;
+ if (mShortcutSubtype == null)
+ return true;
+ if (contains(mShortcutSubtype.getExtraValue().split(","),
+ SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY)) {
return mIsNetworkConnected;
}
return true;
@@ -371,12 +386,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());
}
}
@@ -426,8 +439,15 @@ public class SubtypeSwitcher {
if (mConfigUseSpacebarLanguageSwitcher) {
return mLanguageSwitcher.getEnabledLanguages();
} else {
+ int enabledLanguageCount = mEnabledLanguagesOfCurrentInputMethod.size();
+ // Workaround for explicitly specifying the voice language
+ if (enabledLanguageCount == 1) {
+ mEnabledLanguagesOfCurrentInputMethod.add(
+ mEnabledLanguagesOfCurrentInputMethod.get(0));
+ ++enabledLanguageCount;
+ }
return mEnabledLanguagesOfCurrentInputMethod.toArray(
- new String[mEnabledLanguagesOfCurrentInputMethod.size()]);
+ new String[enabledLanguageCount]);
}
}
@@ -465,14 +485,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
@@ -487,7 +499,7 @@ public class SubtypeSwitcher {
}
public boolean isKeyboardMode() {
- return KEYBOARD_MODE.equals(mMode);
+ return KEYBOARD_MODE.equals(getCurrentSubtypeMode());
}
@@ -510,7 +522,7 @@ public class SubtypeSwitcher {
}
public boolean isVoiceMode() {
- return VOICE_MODE.equals(mMode);
+ return null == mCurrentSubtype ? false : VOICE_MODE.equals(getCurrentSubtypeMode());
}
private void triggerVoiceIME() {
@@ -576,6 +588,30 @@ public class SubtypeSwitcher {
}
}
+ /////////////////////////////
+ // 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;
+ }
+
+
// 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 =
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index ced355bb2..0de474e59 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -22,8 +22,13 @@ import android.text.TextUtils;
import android.util.Log;
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.Map;
+import java.util.Set;
/**
* This class loads a dictionary and provides a list of suggestions for a given sequence of
@@ -62,40 +67,37 @@ 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;
- static final int LARGE_DICTIONARY_THRESHOLD = 200 * 1000;
-
- private static boolean DBG = LatinImeLogger.sDBG;
-
- private BinaryDictionary mMainDict;
+ 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";
- private Dictionary mUserDictionary;
+ static final int LARGE_DICTIONARY_THRESHOLD = 200 * 1000;
- private Dictionary mAutoDictionary;
+ private static final boolean DBG = LatinImeLogger.sDBG;
- 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;
private static final int PREF_MAX_BIGRAMS = 60;
- private boolean mAutoTextEnabled;
+ 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 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;
// TODO: Remove these member variables by passing more context to addWord() callback method
@@ -105,7 +107,24 @@ public class Suggest implements Dictionary.WordCallback {
private int mCorrectionMode = CORRECTION_BASIC;
public Suggest(Context context, int dictionaryResId) {
- mMainDict = BinaryDictionary.initDictionary(context, dictionaryResId, DIC_MAIN);
+ init(context, BinaryDictionary.initDictionary(context, dictionaryResId, DIC_MAIN));
+ }
+
+ /* package for test */ Suggest(File dictionary, long startOffset, long length) {
+ init(null, 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();
}
@@ -116,8 +135,8 @@ public class Suggest implements Dictionary.WordCallback {
}
}
- public void setAutoTextEnabled(boolean enabled) {
- mAutoTextEnabled = enabled;
+ public void setQuickFixesEnabled(boolean enabled) {
+ mQuickFixesEnabled = enabled;
}
public int getCorrectionMode() {
@@ -132,6 +151,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;
}
@@ -141,22 +164,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) {
@@ -200,16 +229,34 @@ 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);
// Save a lowercase version of the original word
CharSequence typedWord = wordComposer.getTypedWord();
@@ -235,17 +282,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);
@@ -268,97 +306,86 @@ 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.
- final double normalizedScore = Utils.calcNormalizedScore(
- typedWord, mSuggestions.get(0), mPriorities[0]);
- if (LatinImeLogger.sDBG) {
- 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 (mAutoTextEnabled) {
- 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, mPriorities, 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();
- return new SuggestedWords.Builder().addWords(mSuggestions, null);
- }
- public int[] getNextLettersFrequencies() {
- return mNextLettersFrequencies;
+ if (DBG) {
+ double normalizedScore = mAutoCorrection.getNormalizedScore();
+ ArrayList<SuggestedWords.SuggestedWordInfo> frequencyInfoList =
+ new ArrayList<SuggestedWords.SuggestedWordInfo>();
+ frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false));
+ final int priorityLength = mPriorities.length;
+ for (int i = 0; i < priorityLength; ++i) {
+ if (normalizedScore > 0) {
+ final String priorityThreshold = Integer.toString(mPriorities[i]) + " (" +
+ normalizedScore + ")";
+ frequencyInfoList.add(
+ new SuggestedWords.SuggestedWordInfo(priorityThreshold, false));
+ normalizedScore = 0.0;
+ } else {
+ final String priority = Integer.toString(mPriorities[i]);
+ frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo(priority, false));
+ }
+ }
+ for (int i = priorityLength; i < mSuggestions.size(); ++i) {
+ frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false));
+ }
+ return new SuggestedWords.Builder().addWords(mSuggestions, frequencyInfoList);
+ }
+ return new SuggestedWords.Builder().addWords(mSuggestions, null);
}
private void removeDupes() {
@@ -389,15 +416,15 @@ public class Suggest implements Dictionary.WordCallback {
}
public boolean hasAutoCorrection() {
- return mHasAutoCorrection;
+ return mAutoCorrection.hasAutoCorrection();
}
- private boolean compareCaseInsensitive(final String mLowerOriginalWord,
+ private static boolean compareCaseInsensitive(final String lowerOriginalWord,
final char[] word, final int offset, final int length) {
- final int originalLength = mLowerOriginalWord.length();
+ final int originalLength = lowerOriginalWord.length();
if (originalLength == length && Character.isUpperCase(word[offset])) {
for (int i = 0; i < originalLength; i++) {
- if (mLowerOriginalWord.charAt(i) != Character.toLowerCase(word[offset+i])) {
+ if (lowerOriginalWord.charAt(i) != Character.toLowerCase(word[offset+i])) {
return false;
}
}
@@ -427,7 +454,20 @@ public class Suggest implements Dictionary.WordCallback {
// Check if it's the same word, only caps are different
if (compareCaseInsensitive(mLowerOriginalWord, word, offset, length)) {
- pos = 0;
+ // TODO: remove this surrounding if clause and move this logic to
+ // getSuggestedWordBuilder.
+ if (suggestions.size() > 0) {
+ final String currentHighestWordLowerCase =
+ suggestions.get(0).toString().toLowerCase();
+ // 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 (compareCaseInsensitive(currentHighestWordLowerCase, word, offset, length)
+ && freq <= priorities[0]) {
+ pos = 1;
+ }
+ }
} else {
if (dataType == Dictionary.DataType.UNIGRAM) {
// Check if the word was already added before (by bigram data)
@@ -510,16 +550,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();
@@ -538,25 +568,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 e980d3a30..727e3f16d 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -16,13 +16,18 @@
package com.android.inputmethod.latin;
+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.EditorInfo;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
@@ -41,6 +46,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 +64,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 +93,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;
}
@@ -261,6 +270,19 @@ 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')
@@ -268,13 +290,22 @@ public class Utils {
// (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 frequency 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();
@@ -284,7 +315,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
@@ -293,7 +324,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();
@@ -330,7 +361,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.");
}
}
}
@@ -367,7 +398,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);
}
@@ -388,10 +419,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 {
@@ -410,7 +441,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();
@@ -439,4 +470,95 @@ 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 (Utils.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 isEmailVariation(int variation) {
+ return variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+ || variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
+ }
+
+ // Please refer to TextView.isPasswordInputType
+ public static boolean isPasswordInputType(int inputType) {
+ final int variation =
+ inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION);
+ return (variation
+ == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD))
+ || (variation
+ == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD))
+ || (variation
+ == (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
+ }
+
+ // Please refer to TextView.isVisiblePasswordInputType
+ public static boolean isVisiblePasswordInputType(int inputType) {
+ final int variation =
+ inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION);
+ return variation
+ == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
+ }
+
+ 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) {
+ return res.getIdentifier("main", "raw", LatinIME.class.getPackage().getName());
+ }
+
+ public static void loadNativeLibrary() {
+ try {
+ System.loadLibrary("jni_latinime");
+ } catch (UnsatisfiedLinkError ule) {
+ Log.e(TAG, "Could not load native library jni_latinime");
+ }
+ }
}
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;
+ }
}
/**