aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/latin
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
-rw-r--r--java/src/com/android/inputmethod/latin/AccessibilityUtils.java211
-rw-r--r--java/src/com/android/inputmethod/latin/AssetFileAddress.java52
-rw-r--r--java/src/com/android/inputmethod/latin/AutoCorrection.java10
-rw-r--r--java/src/com/android/inputmethod/latin/AutoDictionary.java11
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java162
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java151
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java97
-rw-r--r--java/src/com/android/inputmethod/latin/CandidateView.java183
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsDictionary.java21
-rw-r--r--java/src/com/android/inputmethod/latin/DebugSettings.java8
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java10
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryCollection.java71
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFactory.java177
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java89
-rw-r--r--java/src/com/android/inputmethod/latin/EditingUtils.java147
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableDictionary.java71
-rw-r--r--java/src/com/android/inputmethod/latin/Flag.java64
-rw-r--r--java/src/com/android/inputmethod/latin/InputLanguageSelection.java211
-rw-r--r--java/src/com/android/inputmethod/latin/LanguageSwitcher.java193
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java1295
-rw-r--r--java/src/com/android/inputmethod/latin/LatinImeLogger.java7
-rw-r--r--java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java29
-rw-r--r--java/src/com/android/inputmethod/latin/Settings.java331
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java389
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java184
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestedWords.java8
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java43
-rw-r--r--java/src/com/android/inputmethod/latin/TextEntryState.java18
-rw-r--r--java/src/com/android/inputmethod/latin/UserBigramDictionary.java12
-rw-r--r--java/src/com/android/inputmethod/latin/Utils.java267
-rw-r--r--java/src/com/android/inputmethod/latin/WhitelistDictionary.java1
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java14
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellChecker.java114
33 files changed, 2555 insertions, 2096 deletions
diff --git a/java/src/com/android/inputmethod/latin/AccessibilityUtils.java b/java/src/com/android/inputmethod/latin/AccessibilityUtils.java
deleted file mode 100644
index cd3f9e0ad..000000000
--- a/java/src/com/android/inputmethod/latin/AccessibilityUtils.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.res.TypedArray;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Utility functions for accessibility support.
- */
-public class AccessibilityUtils {
- /** Shared singleton instance. */
- private static final AccessibilityUtils sInstance = new AccessibilityUtils();
- private /* final */ LatinIME mService;
- private /* final */ AccessibilityManager mAccessibilityManager;
- private /* final */ Map<Integer, CharSequence> mDescriptions;
-
- /**
- * Returns a shared instance of AccessibilityUtils.
- *
- * @return A shared instance of AccessibilityUtils.
- */
- public static AccessibilityUtils getInstance() {
- return sInstance;
- }
-
- /**
- * Initializes (or re-initializes) the shared instance of AccessibilityUtils
- * with the specified parent service and preferences.
- *
- * @param service The parent input method service.
- * @param prefs The parent preferences.
- */
- public static void init(LatinIME service, SharedPreferences prefs) {
- sInstance.initialize(service, prefs);
- }
-
- private AccessibilityUtils() {
- // This class is not publicly instantiable.
- }
-
- /**
- * Initializes (or re-initializes) with the specified parent service and
- * preferences.
- *
- * @param service The parent input method service.
- * @param prefs The parent preferences.
- */
- private void initialize(LatinIME service, SharedPreferences prefs) {
- mService = service;
- mAccessibilityManager = (AccessibilityManager) service.getSystemService(
- Context.ACCESSIBILITY_SERVICE);
- mDescriptions = null;
- }
-
- /**
- * Returns true if accessibility is enabled.
- *
- * @return {@code true} if accessibility is enabled.
- */
- public boolean isAccessibilityEnabled() {
- return mAccessibilityManager.isEnabled();
- }
-
- /**
- * Speaks a key's action after it has been released. Does not speak letter
- * keys since typed keys are already spoken aloud by TalkBack.
- * <p>
- * No-op if accessibility is not enabled.
- * </p>
- *
- * @param primaryCode The primary code of the released key.
- * @param switcher The input method's {@link KeyboardSwitcher}.
- */
- public void onRelease(int primaryCode, KeyboardSwitcher switcher) {
- if (!isAccessibilityEnabled()) {
- return;
- }
-
- int resId = -1;
-
- switch (primaryCode) {
- case Keyboard.CODE_SHIFT: {
- if (switcher.isShiftedOrShiftLocked()) {
- resId = R.string.description_shift_on;
- } else {
- resId = R.string.description_shift_off;
- }
- break;
- }
-
- case Keyboard.CODE_SWITCH_ALPHA_SYMBOL: {
- if (switcher.isAlphabetMode()) {
- resId = R.string.description_symbols_off;
- } else {
- resId = R.string.description_symbols_on;
- }
- break;
- }
- }
-
- if (resId >= 0) {
- speakDescription(mService.getResources().getText(resId));
- }
- }
-
- /**
- * Speaks a key's description for accessibility. If a key has an explicit
- * description defined in keycodes.xml, that will be used. Otherwise, if the
- * key is a Unicode character, then its character will be used.
- * <p>
- * No-op if accessibility is not enabled.
- * </p>
- *
- * @param primaryCode The primary code of the pressed key.
- * @param switcher The input method's {@link KeyboardSwitcher}.
- */
- public void onPress(int primaryCode, KeyboardSwitcher switcher) {
- if (!isAccessibilityEnabled()) {
- return;
- }
-
- // TODO Use the current keyboard state to read "Switch to symbols"
- // instead of just "Symbols" (and similar for shift key).
- CharSequence description = describeKey(primaryCode);
- if (description == null && Character.isDefined((char) primaryCode)) {
- description = Character.toString((char) primaryCode);
- }
-
- if (description != null) {
- speakDescription(description);
- }
- }
-
- /**
- * Returns a text description for a given key code. If the key does not have
- * an explicit description, returns <code>null</code>.
- *
- * @param keyCode An integer key code.
- * @return A {@link CharSequence} describing the key or <code>null</code> if
- * no description is available.
- */
- private CharSequence describeKey(int keyCode) {
- // If not loaded yet, load key descriptions from XML file.
- if (mDescriptions == null) {
- mDescriptions = loadDescriptions();
- }
-
- return mDescriptions.get(keyCode);
- }
-
- /**
- * Loads key descriptions from resources.
- */
- private Map<Integer, CharSequence> loadDescriptions() {
- final Map<Integer, CharSequence> descriptions = new HashMap<Integer, CharSequence>();
- final TypedArray array = mService.getResources().obtainTypedArray(R.array.key_descriptions);
-
- // Key descriptions are stored as a key code followed by a string.
- for (int i = 0; i < array.length() - 1; i += 2) {
- int code = array.getInteger(i, 0);
- CharSequence desc = array.getText(i + 1);
-
- descriptions.put(code, desc);
- }
-
- array.recycle();
-
- return descriptions;
- }
-
- /**
- * Sends a character sequence to be read aloud.
- *
- * @param description The {@link CharSequence} to be read aloud.
- */
- private void speakDescription(CharSequence description) {
- // TODO We need to add an AccessibilityEvent type for IMEs.
- final AccessibilityEvent event = AccessibilityEvent.obtain(
- AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
- event.setPackageName(mService.getPackageName());
- event.setClassName(getClass().getName());
- event.setAddedCount(description.length());
- event.getText().add(description);
-
- mAccessibilityManager.sendAccessibilityEvent(event);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/AssetFileAddress.java b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
new file mode 100644
index 000000000..074ecacc5
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import java.io.File;
+
+/**
+ * Immutable class to hold the address of an asset.
+ * As opposed to a normal file, an asset is usually represented as a contiguous byte array in
+ * the package file. Open it correctly thus requires the name of the package it is in, but
+ * also the offset in the file and the length of this data. This class encapsulates these three.
+ */
+class AssetFileAddress {
+ public final String mFilename;
+ public final long mOffset;
+ public final long mLength;
+
+ public AssetFileAddress(final String filename, final long offset, final long length) {
+ mFilename = filename;
+ mOffset = offset;
+ mLength = length;
+ }
+
+ public static AssetFileAddress makeFromFileName(final String filename) {
+ if (null == filename) return null;
+ File f = new File(filename);
+ if (null == f || !f.isFile()) return null;
+ return new AssetFileAddress(filename, 0l, f.length());
+ }
+
+ public static AssetFileAddress makeFromFileNameAndOffset(final String filename,
+ final long offset, final long length) {
+ if (null == filename) return null;
+ File f = new File(filename);
+ if (null == f || !f.isFile()) return null;
+ return new AssetFileAddress(filename, offset, length);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java
index 092f7ad63..d3119792c 100644
--- a/java/src/com/android/inputmethod/latin/AutoCorrection.java
+++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java
@@ -48,7 +48,7 @@ public class AutoCorrection {
}
public void updateAutoCorrectionStatus(Map<String, Dictionary> dictionaries,
- WordComposer wordComposer, ArrayList<CharSequence> suggestions, int[] priorities,
+ WordComposer wordComposer, ArrayList<CharSequence> suggestions, int[] sortedScores,
CharSequence typedWord, double autoCorrectionThreshold, int correctionMode,
CharSequence quickFixedWord, CharSequence whitelistedWord) {
if (hasAutoCorrectionForWhitelistedWord(whitelistedWord)) {
@@ -62,7 +62,7 @@ public class AutoCorrection {
mHasAutoCorrection = true;
mAutoCorrectionWord = quickFixedWord;
} else if (hasAutoCorrectionForBinaryDictionary(wordComposer, suggestions, correctionMode,
- priorities, typedWord, autoCorrectionThreshold)) {
+ sortedScores, typedWord, autoCorrectionThreshold)) {
mHasAutoCorrection = true;
mAutoCorrectionWord = suggestions.get(0);
}
@@ -114,13 +114,13 @@ public class AutoCorrection {
}
private boolean hasAutoCorrectionForBinaryDictionary(WordComposer wordComposer,
- ArrayList<CharSequence> suggestions, int correctionMode, int[] priorities,
+ ArrayList<CharSequence> suggestions, int correctionMode, int[] sortedScores,
CharSequence typedWord, double autoCorrectionThreshold) {
if (wordComposer.size() > 1 && (correctionMode == Suggest.CORRECTION_FULL
|| correctionMode == Suggest.CORRECTION_FULL_BIGRAM)
- && typedWord != null && suggestions.size() > 0 && priorities.length > 0) {
+ && typedWord != null && suggestions.size() > 0 && sortedScores.length > 0) {
final CharSequence autoCorrectionCandidate = suggestions.get(0);
- final int autoCorrectionCandidateScore = priorities[0];
+ final int autoCorrectionCandidateScore = sortedScores[0];
// TODO: when the normalized score of the first suggestion is nearly equals to
// the normalized score of the second suggestion, behave less aggressive.
mNormalizedScore = Utils.calcNormalizedScore(
diff --git a/java/src/com/android/inputmethod/latin/AutoDictionary.java b/java/src/com/android/inputmethod/latin/AutoDictionary.java
index 54c6f309c..460930f16 100644
--- a/java/src/com/android/inputmethod/latin/AutoDictionary.java
+++ b/java/src/com/android/inputmethod/latin/AutoDictionary.java
@@ -27,7 +27,6 @@ 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;
@@ -42,13 +41,8 @@ public class AutoDictionary extends ExpandableDictionary {
static final int FREQUENCY_FOR_PICKED = 3;
// Weight added to a user typing a new word that doesn't get corrected (or is reverted)
static final int FREQUENCY_FOR_TYPED = 1;
- // A word that is frequently typed and gets promoted to the user dictionary, uses this
- // frequency.
- static final int FREQUENCY_FOR_AUTO_ADD = 250;
// If the user touches a typed word 2 times or more, it will become valid.
private static final int VALIDITY_THRESHOLD = 2 * FREQUENCY_FOR_PICKED;
- // If the user touches a typed word 4 times or more, it will be added to the user dict.
- private static final int PROMOTION_THRESHOLD = 4 * FREQUENCY_FOR_PICKED;
private LatinIME mIme;
// Locale for which this auto dictionary is storing words
@@ -152,11 +146,6 @@ public class AutoDictionary extends ExpandableDictionary {
freq = freq < 0 ? addFrequency : freq + addFrequency;
super.addWord(word, freq);
- if (freq >= PROMOTION_THRESHOLD) {
- mIme.promoteToUserDictionary(word, FREQUENCY_FOR_AUTO_ADD);
- freq = 0;
- }
-
synchronized (mPendingWritesLock) {
// Write a null frequency if it is to be deleted from the db
mPendingWrites.put(word, freq == 0 ? null : new Integer(freq));
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 08ddd25fa..9748d6006 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -21,10 +21,7 @@ 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;
/**
@@ -32,8 +29,11 @@ import java.util.Arrays;
*/
public class BinaryDictionary extends Dictionary {
+ public static final String DICTIONARY_PACK_AUTHORITY =
+ "com.android.inputmethod.latin.dictionarypack";
+
/**
- * There is difference between what java and native code can handle.
+ * There is a difference between what java and native code can handle.
* This value should only be used in BinaryDictionary.java
* It is necessary to keep it at this value because some languages e.g. German have
* really long words.
@@ -41,101 +41,59 @@ public class BinaryDictionary extends Dictionary {
public static final int MAX_WORD_LENGTH = 48;
public static final int MAX_WORDS = 18;
+ @SuppressWarnings("unused")
private static final String TAG = "BinaryDictionary";
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;
- private static final BinaryDictionary sInstance = new BinaryDictionary();
private int mDicTypeId;
private int mNativeDict;
- private long mDictLength;
private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_PROXIMITY_CHARS_SIZE];
private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS];
private final char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS];
- private final int[] mFrequencies = new int[MAX_WORDS];
- private final int[] mFrequencies_bigrams = new int[MAX_BIGRAMS];
+ private final int[] mScores = new int[MAX_WORDS];
+ private final int[] mBigramScores = new int[MAX_BIGRAMS];
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() {
- }
+ public static final Flag FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING =
+ new Flag(R.bool.config_require_umlaut_processing, 0x1);
- /**
- * Initialize a dictionary from a raw resource file
- * @param context application context for reading resources
- * @param resId the resource containing the raw binary dictionary
- * @return initialized instance of BinaryDictionary
- */
- public static BinaryDictionary initDictionary(Context context, int resId, int dicTypeId) {
- synchronized (sInstance) {
- sInstance.closeInternal();
- 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;
- }
+ // Can create a new flag from extravalue :
+ // public static final Flag FLAG_MYFLAG =
+ // new Flag("my_flag", 0x02);
- /* 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 static final Flag[] ALL_FLAGS = {
+ // Here should reside all flags that trigger some special processing
+ // These *must* match the definition in UnigramDictionary enum in
+ // unigram_dictionary.h so please update both at the same time.
+ FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING,
+ };
- private void initFlags() {
- int flags = 0;
- for (Flags.FlagEntry entry : Flags.ALL_FLAGS) {
- if (mSubtypeSwitcher.currentSubtypeContainsExtraValueKey(entry.mName))
- flags |= entry.mValue;
- }
- mFlags = flags;
+ private int mFlags = 0;
+
+ /**
+ * Constructor for the binary dictionary. This is supposed to be called from the
+ * dictionary factory.
+ * All implementations should pass null into flagArray, except for testing purposes.
+ * @param context the context to access the environment from.
+ * @param filename the name of the file to read through native code.
+ * @param offset the offset of the dictionary data within the file.
+ * @param length the length of the binary data.
+ * @param flagArray the flags to limit the dictionary to, or null for default.
+ */
+ public BinaryDictionary(final Context context,
+ final String filename, final long offset, final long length, Flag[] flagArray) {
+ // Note: at the moment a binary dictionary is always of the "main" type.
+ // Initializing this here will help transitioning out of the scheme where
+ // the Suggest class knows everything about every single dictionary.
+ mDicTypeId = Suggest.DIC_MAIN;
+ // TODO: Stop relying on the state of SubtypeSwitcher, get it as a parameter
+ mFlags = Flag.initFlags(null == flagArray ? ALL_FLAGS : flagArray, context,
+ SubtypeSwitcher.getInstance());
+ loadDictionary(filename, offset, length);
}
static {
@@ -149,16 +107,15 @@ public class BinaryDictionary extends Dictionary {
private native boolean isValidWordNative(int nativeData, char[] word, int wordLength);
private native int getSuggestionsNative(int dict, int proximityInfo, int[] xCoordinates,
int[] yCoordinates, int[] inputCodes, int codesSize, int flags, char[] outputChars,
- int[] frequencies);
+ int[] scores);
private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength,
- int[] inputCodes, int inputCodesLength, char[] outputChars, int[] frequencies,
+ int[] inputCodes, int inputCodesLength, char[] outputChars, int[] scores,
int maxWordLength, int maxBigrams, int maxAlternatives);
private final void loadDictionary(String path, long startOffset, long length) {
mNativeDict = openNative(path, startOffset, length,
- TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER,
+ TYPED_LETTER_MULTIPLIER, FULL_WORD_SCORE_MULTIPLIER,
MAX_WORD_LENGTH, MAX_WORDS, MAX_PROXIMITY_CHARS_SIZE);
- mDictLength = length;
}
@Override
@@ -168,27 +125,32 @@ public class BinaryDictionary extends Dictionary {
char[] chars = previousWord.toString().toCharArray();
Arrays.fill(mOutputChars_bigrams, (char) 0);
- Arrays.fill(mFrequencies_bigrams, 0);
+ Arrays.fill(mBigramScores, 0);
int codesSize = codes.size();
+ if (codesSize <= 0) {
+ // Do not return bigrams from BinaryDictionary when nothing was typed.
+ // Only use user-history bigrams (or whatever other bigram dictionaries decide).
+ return;
+ }
Arrays.fill(mInputCodes, -1);
int[] alternatives = codes.getCodesAt(0);
System.arraycopy(alternatives, 0, mInputCodes, 0,
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,
+ mOutputChars_bigrams, mBigramScores, MAX_WORD_LENGTH, MAX_BIGRAMS,
MAX_PROXIMITY_CHARS_SIZE);
for (int j = 0; j < count; ++j) {
- if (mFrequencies_bigrams[j] < 1) break;
+ if (mBigramScores[j] < 1) break;
final int start = j * MAX_WORD_LENGTH;
int len = 0;
while (len < MAX_WORD_LENGTH && mOutputChars_bigrams[start + len] != 0) {
++len;
}
if (len > 0) {
- callback.addWord(mOutputChars_bigrams, start, len, mFrequencies_bigrams[j],
+ callback.addWord(mOutputChars_bigrams, start, len, mBigramScores[j],
mDicTypeId, DataType.BIGRAM);
}
}
@@ -197,17 +159,17 @@ public class BinaryDictionary extends Dictionary {
@Override
public void getWords(final WordComposer codes, final WordCallback callback) {
final int count = getSuggestions(codes, mKeyboardSwitcher.getLatinKeyboard(),
- mOutputChars, mFrequencies);
+ mOutputChars, mScores);
for (int j = 0; j < count; ++j) {
- if (mFrequencies[j] < 1) break;
+ if (mScores[j] < 1) break;
final int start = j * MAX_WORD_LENGTH;
int len = 0;
while (len < MAX_WORD_LENGTH && mOutputChars[start + len] != 0) {
++len;
}
if (len > 0) {
- callback.addWord(mOutputChars, start, len, mFrequencies[j], mDicTypeId,
+ callback.addWord(mOutputChars, start, len, mScores[j], mDicTypeId,
DataType.UNIGRAM);
}
}
@@ -218,7 +180,7 @@ public class BinaryDictionary extends Dictionary {
}
/* package for test */ int getSuggestions(final WordComposer codes, final Keyboard keyboard,
- char[] outputChars, int[] frequencies) {
+ char[] outputChars, int[] scores) {
if (!isValidDictionary()) return -1;
final int codesSize = codes.size();
@@ -232,12 +194,13 @@ public class BinaryDictionary extends Dictionary {
Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE));
}
Arrays.fill(outputChars, (char) 0);
- Arrays.fill(frequencies, 0);
+ Arrays.fill(scores, 0);
+ final int proximityInfo = keyboard == null ? 0 : keyboard.getProximityInfo();
return getSuggestionsNative(
- mNativeDict, keyboard.getProximityInfo(),
+ mNativeDict, proximityInfo,
codes.getXCoordinates(), codes.getYCoordinates(), mInputCodes, codesSize,
- mFlags, outputChars, frequencies);
+ mFlags, outputChars, scores);
}
@Override
@@ -247,10 +210,6 @@ public class BinaryDictionary extends Dictionary {
return isValidWordNative(mNativeDict, chars, chars.length);
}
- public long getSize() {
- return mDictLength; // This value is initialized in loadDictionary()
- }
-
@Override
public synchronized void close() {
closeInternal();
@@ -260,7 +219,6 @@ public class BinaryDictionary extends Dictionary {
if (mNativeDict != 0) {
closeNative(mNativeDict);
mNativeDict = 0;
- mDictLength = 0;
}
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
new file mode 100644
index 000000000..76a230f82
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.net.Uri;
+import android.text.TextUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Group class for static methods to help with creation and getting of the binary dictionary
+ * file from the dictionary provider
+ */
+public class BinaryDictionaryFileDumper {
+ /**
+ * The size of the temporary buffer to copy files.
+ */
+ static final int FILE_READ_BUFFER_SIZE = 1024;
+
+ // Prevents this class to be accidentally instantiated.
+ private BinaryDictionaryFileDumper() {
+ }
+
+ /**
+ * Generates a file name that matches the locale passed as an argument.
+ * The file name is basically the result of the .toString() method, except we replace
+ * any @File.separator with an underscore to avoid generating a file name that may not
+ * be created.
+ * @param locale the locale for which to get the file name
+ * @param context the context to use for getting the directory
+ * @return the name of the file to be created
+ */
+ private static String getCacheFileNameForLocale(Locale locale, Context context) {
+ // The following assumes two things :
+ // 1. That File.separator is not the same character as "_"
+ // I don't think any android system will ever use "_" as a path separator
+ // 2. That no two locales differ by only a File.separator versus a "_"
+ // Since "_" can't be part of locale components this should be safe.
+ // Examples:
+ // en -> en
+ // en_US_POSIX -> en_US_POSIX
+ // en__foo/bar -> en__foo_bar
+ final String[] separator = { File.separator };
+ final String[] empty = { "_" };
+ final CharSequence basename = TextUtils.replace(locale.toString(), separator, empty);
+ return context.getFilesDir() + File.separator + basename;
+ }
+
+ /**
+ * Return for a given locale the provider URI to query to get the dictionary.
+ */
+ public static Uri getProviderUri(Locale locale) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(BinaryDictionary.DICTIONARY_PACK_AUTHORITY).appendPath(
+ locale.toString()).build();
+ }
+
+ /**
+ * Queries a content provider for dictionary data for some locale and returns the file addresses
+ *
+ * This will query a content provider for dictionary data for a given locale, and return
+ * the addresses of a file set the members of which are suitable to be mmap'ed. It will copy
+ * them to local storage if needed.
+ * It should also check the dictionary versions to avoid unnecessary copies but this is
+ * still in TODO state.
+ * This will make the data from the content provider the cached dictionary for this locale,
+ * overwriting any previous cached data.
+ * @returns the addresses of the files, or null if no data could be obtained.
+ * @throw FileNotFoundException if the provider returns non-existent data.
+ * @throw IOException if the provider-returned data could not be read.
+ */
+ public static List<AssetFileAddress> getDictSetFromContentProvider(Locale locale,
+ Context context) throws FileNotFoundException, IOException {
+ // TODO: check whether the dictionary is the same or not and if it is, return the cached
+ // file.
+ // TODO: This should be able to read a number of files from the dictionary pack, copy
+ // them all and return them.
+ final ContentResolver resolver = context.getContentResolver();
+ final Uri dictionaryPackUri = getProviderUri(locale);
+ final AssetFileDescriptor afd = resolver.openAssetFileDescriptor(dictionaryPackUri, "r");
+ if (null == afd) return null;
+ final String fileName =
+ copyFileTo(afd.createInputStream(), getCacheFileNameForLocale(locale, context));
+ return Arrays.asList(AssetFileAddress.makeFromFileName(fileName));
+ }
+
+ /**
+ * Accepts a file as dictionary data for some locale and returns the name of a file.
+ *
+ * This will make the data in the input file the cached dictionary for this locale, overwriting
+ * any previous cached data.
+ */
+ public static String getDictionaryFileFromFile(String fileName, Locale locale,
+ Context context) throws FileNotFoundException, IOException {
+ return copyFileTo(new FileInputStream(fileName), getCacheFileNameForLocale(locale,
+ context));
+ }
+
+ /**
+ * Accepts a resource number as dictionary data for some locale and returns the name of a file.
+ *
+ * This will make the resource the cached dictionary for this locale, overwriting any previous
+ * cached data.
+ */
+ public static String getDictionaryFileFromResource(int resource, Locale locale,
+ Context context) throws FileNotFoundException, IOException {
+ return copyFileTo(context.getResources().openRawResource(resource),
+ getCacheFileNameForLocale(locale, context));
+ }
+
+ /**
+ * Copies the data in an input stream to a target file, creating the file if necessary and
+ * overwriting it if it already exists.
+ * @param input the stream to be copied.
+ * @param outputFileName the name of a file to copy the data to. It is created if necessary.
+ */
+ private static String copyFileTo(final InputStream input, final String outputFileName)
+ throws FileNotFoundException, IOException {
+ final byte[] buffer = new byte[FILE_READ_BUFFER_SIZE];
+ final FileOutputStream output = new FileOutputStream(outputFileName);
+ for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer))
+ output.write(buffer, 0, readBytes);
+ input.close();
+ return outputFileName;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
new file mode 100644
index 000000000..7ce92920d
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Helper class to get the address of a mmap'able dictionary file.
+ */
+class BinaryDictionaryGetter {
+
+ /**
+ * Used for Log actions from this class
+ */
+ private static final String TAG = BinaryDictionaryGetter.class.getSimpleName();
+
+ // Prevents this from being instantiated
+ private BinaryDictionaryGetter() {}
+
+ /**
+ * Returns a file address from a resource, or null if it cannot be opened.
+ */
+ private static AssetFileAddress loadFallbackResource(Context context, int fallbackResId) {
+ final AssetFileDescriptor afd = context.getResources().openRawResourceFd(fallbackResId);
+ if (afd == null) {
+ Log.e(TAG, "Found the resource but cannot read it. Is it compressed? resId="
+ + fallbackResId);
+ return null;
+ }
+ return AssetFileAddress.makeFromFileNameAndOffset(
+ context.getApplicationInfo().sourceDir, afd.getStartOffset(), afd.getLength());
+ }
+
+ /**
+ * Returns a list of file addresses for a given locale, trying relevant methods in order.
+ *
+ * Tries to get binary dictionaries from various sources, in order:
+ * - Uses a private method of getting a private dictionaries, as implemented by the
+ * PrivateBinaryDictionaryGetter class.
+ * If that fails:
+ * - Uses a content provider to get a public dictionary set, as per the protocol described
+ * in BinaryDictionaryFileDumper.
+ * If that fails:
+ * - Gets a file name from the fallback resource passed as an argument.
+ * If that fails:
+ * - Returns null.
+ * @return The address of a valid file, or null.
+ */
+ public static List<AssetFileAddress> getDictionaryFiles(Locale locale, Context context,
+ int fallbackResId) {
+ // Try first to query a private package signed the same way for private files.
+ final List<AssetFileAddress> privateFiles =
+ PrivateBinaryDictionaryGetter.getDictionaryFiles(locale, context);
+ if (null != privateFiles) {
+ return privateFiles;
+ } else {
+ try {
+ // If that was no-go, try to find a publicly exported dictionary.
+ List<AssetFileAddress> listFromContentProvider =
+ BinaryDictionaryFileDumper.getDictSetFromContentProvider(locale, context);
+ if (null != listFromContentProvider) {
+ return listFromContentProvider;
+ }
+ // If the list is null, fall through and return the fallback
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Unable to create dictionary file from provider for locale "
+ + locale.toString() + ": falling back to internal dictionary");
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to read source data for locale "
+ + locale.toString() + ": falling back to internal dictionary");
+ }
+ return Arrays.asList(loadFallbackResource(context, fallbackResId));
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java
index c52f6b2c4..a8381020f 100644
--- a/java/src/com/android/inputmethod/latin/CandidateView.java
+++ b/java/src/com/android/inputmethod/latin/CandidateView.java
@@ -16,8 +16,6 @@
package com.android.inputmethod.latin;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
@@ -40,33 +38,41 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
-import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.TextView;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+
import java.util.ArrayList;
import java.util.List;
public class CandidateView extends LinearLayout implements OnClickListener, OnLongClickListener {
+ public interface Listener {
+ public boolean addWordToDictionary(String word);
+ public void pickSuggestionManually(int index, CharSequence word);
+ }
+
private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD);
private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
private static final int MAX_SUGGESTIONS = 16;
private static final boolean DBG = LatinImeLogger.sDBG;
- private final ArrayList<View> mWords = new ArrayList<View>();
+ private final ArrayList<TextView> mWords = new ArrayList<TextView>();
+ private final ArrayList<View> mDividers = new ArrayList<View>();
+ private final int mCandidatePadding;
private final boolean mConfigCandidateHighlightFontColorEnabled;
private final CharacterStyle mInvertedForegroundColorSpan;
private final CharacterStyle mInvertedBackgroundColorSpan;
- private final int mColorNormal;
- private final int mColorRecommended;
- private final int mColorOther;
+ private final int mColorTypedWord;
+ private final int mColorAutoCorrect;
+ private final int mColorSuggestedCandidate;
private final PopupWindow mPreviewPopup;
private final TextView mPreviewText;
- private LatinIME mService;
+ private Listener mListener;
private SuggestedWords mSuggestions = SuggestedWords.EMPTY;
private boolean mShowingAutoCorrectionInverted;
private boolean mShowingAddToDictionary;
@@ -133,37 +139,37 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
ViewGroup.LayoutParams.WRAP_CONTENT);
mPreviewPopup.setContentView(mPreviewText);
mPreviewPopup.setBackgroundDrawable(null);
- mPreviewPopup.setAnimationStyle(R.style.KeyPreviewAnimation);
mConfigCandidateHighlightFontColorEnabled =
res.getBoolean(R.bool.config_candidate_highlight_font_color_enabled);
- mColorNormal = res.getColor(R.color.candidate_normal);
- mColorRecommended = res.getColor(R.color.candidate_recommended);
- mColorOther = res.getColor(R.color.candidate_other);
- mInvertedForegroundColorSpan = new ForegroundColorSpan(mColorNormal ^ 0x00ffffff);
- mInvertedBackgroundColorSpan = new BackgroundColorSpan(mColorNormal);
+ mColorTypedWord = res.getColor(R.color.candidate_typed_word);
+ mColorAutoCorrect = res.getColor(R.color.candidate_auto_correct);
+ mColorSuggestedCandidate = res.getColor(R.color.candidate_suggested);
+ mInvertedForegroundColorSpan = new ForegroundColorSpan(mColorTypedWord ^ 0x00ffffff);
+ mInvertedBackgroundColorSpan = new BackgroundColorSpan(mColorTypedWord);
+ mCandidatePadding = res.getDimensionPixelOffset(R.dimen.candidate_padding);
for (int i = 0; i < MAX_SUGGESTIONS; i++) {
- View v = inflater.inflate(R.layout.candidate, null);
- TextView tv = (TextView)v.findViewById(R.id.candidate_word);
+ final TextView tv = (TextView)inflater.inflate(R.layout.candidate, null);
tv.setTag(i);
tv.setOnClickListener(this);
if (i == 0)
tv.setOnLongClickListener(this);
- ImageView divider = (ImageView)v.findViewById(R.id.candidate_divider);
- // Do not display divider of first candidate.
- divider.setVisibility(i == 0 ? GONE : VISIBLE);
- mWords.add(v);
+ mWords.add(tv);
+ if (i > 0) {
+ View divider = inflater.inflate(R.layout.candidate_divider, null);
+ mDividers.add(divider);
+ }
}
scrollTo(0, getScrollY());
}
/**
- * A connection back to the service to communicate with the text field
+ * A connection back to the input method.
* @param listener
*/
- public void setService(LatinIME listener) {
- mService = listener;
+ public void setListener(Listener listener) {
+ mListener = listener;
}
public void setSuggestions(SuggestedWords suggestions) {
@@ -177,68 +183,79 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
}
}
+ private CharSequence getStyledCandidateWord(CharSequence word, boolean isAutoCorrect) {
+ if (!isAutoCorrect)
+ return word;
+ final CharacterStyle style = mConfigCandidateHighlightFontColorEnabled ? BOLD_SPAN
+ : UNDERLINE_SPAN;
+ final Spannable spannedWord = new SpannableString(word);
+ spannedWord.setSpan(style, 0, word.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ return spannedWord;
+ }
+
+ private int getCandidateTextColor(boolean isAutoCorrect, boolean isSuggestedCandidate,
+ SuggestedWordInfo info) {
+ final int color;
+ if (isAutoCorrect && mConfigCandidateHighlightFontColorEnabled) {
+ color = mColorAutoCorrect;
+ } else if (isSuggestedCandidate) {
+ color = mColorSuggestedCandidate;
+ } else {
+ color = mColorTypedWord;
+ }
+ if (info != null && info.isPreviousSuggestedWord()) {
+ final int newAlpha = (int)(Color.alpha(color) * 0.5f);
+ return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color));
+ } else {
+ return color;
+ }
+ }
+
private void updateSuggestions() {
final SuggestedWords suggestions = mSuggestions;
+ final List<SuggestedWordInfo> suggestedWordInfoList = suggestions.mSuggestedWordInfoList;
+
clear();
- final int count = suggestions.size();
+ final int count = Math.min(mWords.size(), suggestions.size());
for (int i = 0; i < count; i++) {
- CharSequence word = suggestions.getWord(i);
+ final CharSequence word = suggestions.getWord(i);
if (word == null) continue;
- final int wordLength = word.length();
- final List<SuggestedWordInfo> suggestedWordInfoList =
- suggestions.mSuggestedWordInfoList;
-
- final View v = mWords.get(i);
- final TextView tv = (TextView)v.findViewById(R.id.candidate_word);
- final TextView dv = (TextView)v.findViewById(R.id.candidate_debug_info);
- tv.setTextColor(mColorNormal);
- // TODO: Needs safety net?
- if (suggestions.mHasMinimalSuggestion
+
+ final SuggestedWordInfo info = (suggestedWordInfoList != null)
+ ? suggestedWordInfoList.get(i) : null;
+ final boolean isAutoCorrect = suggestions.mHasMinimalSuggestion
&& ((i == 1 && !suggestions.mTypedWordValid)
- || (i == 0 && suggestions.mTypedWordValid))) {
- final CharacterStyle style;
- if (mConfigCandidateHighlightFontColorEnabled) {
- style = BOLD_SPAN;
- tv.setTextColor(mColorRecommended);
- } else {
- style = UNDERLINE_SPAN;
- }
- final Spannable spannedWord = new SpannableString(word);
- spannedWord.setSpan(style, 0, wordLength, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
- word = spannedWord;
- } else if (i != 0 || (wordLength == 1 && count > 1)) {
- // HACK: even if i == 0, we use mColorOther when this
- // suggestion's length is 1
- // and there are multiple suggestions, such as the default
- // punctuation list.
- if (mConfigCandidateHighlightFontColorEnabled)
- tv.setTextColor(mColorOther);
- }
- tv.setText(word);
- tv.setClickable(true);
-
- if (suggestedWordInfoList != null && suggestedWordInfoList.get(i) != null) {
- final SuggestedWordInfo info = suggestedWordInfoList.get(i);
- if (info.isPreviousSuggestedWord()) {
- int color = tv.getCurrentTextColor();
- tv.setTextColor(Color.argb((int)(Color.alpha(color) * 0.5f), Color.red(color),
- Color.green(color), Color.blue(color)));
- }
- final String debugString = info.getDebugString();
- if (DBG) {
- if (TextUtils.isEmpty(debugString)) {
- dv.setVisibility(GONE);
- } else {
- dv.setText(debugString);
- dv.setVisibility(VISIBLE);
- }
- } else {
- dv.setVisibility(GONE);
- }
+ || (i == 0 && suggestions.mTypedWordValid));
+ // HACK: even if i == 0, we use mColorOther when this suggestion's length is 1
+ // and there are multiple suggestions, such as the default punctuation list.
+ // TODO: Need to revisit this logic with bigram suggestions
+ final boolean isSuggestedCandidate = (i != 0);
+ final boolean isPunctuationSuggestions = (word.length() == 1 && count > 1);
+
+ final TextView tv = mWords.get(i);
+ tv.setTextColor(getCandidateTextColor(isAutoCorrect,
+ isSuggestedCandidate || isPunctuationSuggestions, info));
+ tv.setText(getStyledCandidateWord(word, isAutoCorrect));
+ if (i == 0) {
+ tv.setPadding(mCandidatePadding, 0, 0, 0);
+ } else if (i == count - 1) {
+ tv.setPadding(0, 0, mCandidatePadding, 0);
} else {
- dv.setVisibility(GONE);
+ tv.setPadding(0, 0, 0, 0);
+ }
+ if (i > 0)
+ addView(mDividers.get(i - 1));
+ addView(tv);
+
+ if (DBG && info != null) {
+ final TextView dv = new TextView(getContext(), null);
+ dv.setTextSize(10.0f);
+ dv.setTextColor(0xff808080);
+ dv.setText(info.getDebugString());
+ addView(dv);
+ LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)dv.getLayoutParams();
+ lp.gravity = Gravity.BOTTOM;
}
- addView(v);
}
scrollTo(0, getScrollY());
@@ -250,7 +267,7 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
// with color is disabled.
if (mConfigCandidateHighlightFontColorEnabled)
return;
- final TextView tv = (TextView)mWords.get(1).findViewById(R.id.candidate_word);
+ final TextView tv = mWords.get(1);
final Spannable word = new SpannableString(autoCorrectedWord);
final int wordLength = word.length();
word.setSpan(mInvertedBackgroundColorSpan, 0, wordLength,
@@ -276,7 +293,7 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
setSuggestions(builder.build());
mShowingAddToDictionary = true;
// Disable R.string.hint_add_to_dictionary button
- TextView tv = (TextView)getChildAt(1).findViewById(R.id.candidate_word);
+ TextView tv = mWords.get(1);
tv.setClickable(false);
}
@@ -305,11 +322,11 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
return;
final TextView previewText = mPreviewText;
- previewText.setTextColor(mColorNormal);
+ previewText.setTextColor(mColorTypedWord);
previewText.setText(word);
previewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
- View v = getChildAt(index);
+ View v = mWords.get(index);
final int[] offsetInWindow = new int[2];
v.getLocationInWindow(offsetInWindow);
final int posX = offsetInWindow[0];
@@ -325,7 +342,7 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
}
private void addToDictionary(CharSequence word) {
- if (mService.addWordToDictionary(word.toString())) {
+ if (mListener.addWordToDictionary(word.toString())) {
showPreview(0, getContext().getString(R.string.added_word, word));
}
}
@@ -351,7 +368,7 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
if (mShowingAddToDictionary && index == 0) {
addToDictionary(word);
} else {
- mService.pickSuggestionManually(index, word);
+ mListener.pickSuggestionManually(index, word);
}
}
diff --git a/java/src/com/android/inputmethod/latin/ContactsDictionary.java b/java/src/com/android/inputmethod/latin/ContactsDictionary.java
index 048f72dc5..66a041508 100644
--- a/java/src/com/android/inputmethod/latin/ContactsDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsDictionary.java
@@ -26,6 +26,8 @@ import android.provider.ContactsContract.Contacts;
import android.text.TextUtils;
import android.util.Log;
+import com.android.inputmethod.keyboard.Keyboard;
+
public class ContactsDictionary extends ExpandableDictionary {
private static final String[] PROJECTION = {
@@ -38,7 +40,7 @@ public class ContactsDictionary extends ExpandableDictionary {
/**
* Frequency for contacts information into the dictionary
*/
- private static final int FREQUENCY_FOR_CONTACTS = 128;
+ private static final int FREQUENCY_FOR_CONTACTS = 40;
private static final int FREQUENCY_FOR_CONTACTS_BIGRAM = 90;
private static final int INDEX_NAME = 1;
@@ -95,6 +97,14 @@ public class ContactsDictionary extends ExpandableDictionary {
mLastLoadedContacts = SystemClock.uptimeMillis();
}
+ @Override
+ public void getBigrams(final WordComposer codes, final CharSequence previousWord,
+ final WordCallback callback) {
+ // Do not return bigrams from Contacts when nothing was typed.
+ if (codes.size() <= 0) return;
+ super.getBigrams(codes, previousWord, callback);
+ }
+
private void addWords(Cursor cursor) {
clearDictionary();
@@ -104,7 +114,7 @@ public class ContactsDictionary extends ExpandableDictionary {
while (!cursor.isAfterLast()) {
String name = cursor.getString(INDEX_NAME);
- if (name != null) {
+ if (name != null && -1 == name.indexOf('@')) {
int len = name.length();
String prevWord = null;
@@ -115,8 +125,9 @@ public class ContactsDictionary extends ExpandableDictionary {
for (j = i + 1; j < len; j++) {
char c = name.charAt(j);
- if (!(c == '-' || c == '\'' ||
- Character.isLetter(c))) {
+ if (!(c == Keyboard.CODE_DASH
+ || c == Keyboard.CODE_SINGLE_QUOTE
+ || Character.isLetter(c))) {
break;
}
}
@@ -132,8 +143,6 @@ public class ContactsDictionary extends ExpandableDictionary {
if (wordLen < maxWordLength && wordLen > 1) {
super.addWord(word, FREQUENCY_FOR_CONTACTS);
if (!TextUtils.isEmpty(prevWord)) {
- // TODO Do not add email address
- // Not so critical
super.setBigram(prevWord, word,
FREQUENCY_FOR_CONTACTS_BIGRAM);
}
diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java
index 2f1e7c2b8..fd62d61c3 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettings.java
@@ -33,6 +33,7 @@ public class DebugSettings extends PreferenceActivity
private boolean mServiceNeedsRestart = false;
private CheckBoxPreference mDebugMode;
+ private CheckBoxPreference mUseSpacebarLanguageSwitch;
@Override
protected void onCreate(Bundle icicle) {
@@ -60,6 +61,13 @@ public class DebugSettings extends PreferenceActivity
updateDebugMode();
mServiceNeedsRestart = true;
}
+ } else if (key.equals(SubtypeSwitcher.USE_SPACEBAR_LANGUAGE_SWITCH_KEY)) {
+ if (mUseSpacebarLanguageSwitch != null) {
+ mUseSpacebarLanguageSwitch.setChecked(
+ prefs.getBoolean(SubtypeSwitcher.USE_SPACEBAR_LANGUAGE_SWITCH_KEY,
+ getResources().getBoolean(
+ R.bool.config_use_spacebar_language_switcher)));
+ }
}
}
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 56f0cc503..c7737b9a2 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -29,7 +29,7 @@ public abstract class Dictionary {
/**
* The weight to give to a word if it's length is the same as the number of typed characters.
*/
- protected static final int FULL_WORD_FREQ_MULTIPLIER = 2;
+ protected static final int FULL_WORD_SCORE_MULTIPLIER = 2;
public static enum DataType {
UNIGRAM, BIGRAM
@@ -42,17 +42,17 @@ public abstract class Dictionary {
public interface WordCallback {
/**
* Adds a word to a list of suggestions. The word is expected to be ordered based on
- * the provided frequency.
+ * the provided score.
* @param word the character array containing the word
* @param wordOffset starting offset of the word in the character array
* @param wordLength length of valid characters in the character array
- * @param frequency the frequency of occurrence. This is normalized between 1 and 255, but
+ * @param score the score of occurrence. This is normalized between 1 and 255, but
* can exceed those limits
* @param dicTypeId of the dictionary where word was from
* @param dataType tells type of this data
* @return true if the word was added, false if no more words are required
*/
- boolean addWord(char[] word, int wordOffset, int wordLength, int frequency, int dicTypeId,
+ boolean addWord(char[] word, int wordOffset, int wordLength, int score, int dicTypeId,
DataType dataType);
}
@@ -61,7 +61,7 @@ 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
- * @see WordCallback#addWord(char[], int, int)
+ * @see WordCallback#addWord(char[], int, int, int, int, DataType)
*/
abstract public void getWords(final WordComposer composer, final WordCallback callback);
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
new file mode 100644
index 000000000..3fcb6ed55
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Class for a collection of dictionaries that behave like one dictionary.
+ */
+public class DictionaryCollection extends Dictionary {
+
+ protected final List<Dictionary> mDictionaries;
+
+ public DictionaryCollection() {
+ mDictionaries = new CopyOnWriteArrayList<Dictionary>();
+ }
+
+ public DictionaryCollection(Dictionary... dictionaries) {
+ mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
+ }
+
+ public DictionaryCollection(Collection<Dictionary> dictionaries) {
+ mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
+ }
+
+ @Override
+ public void getWords(final WordComposer composer, final WordCallback callback) {
+ for (final Dictionary dict : mDictionaries)
+ dict.getWords(composer, callback);
+ }
+
+ @Override
+ public void getBigrams(final WordComposer composer, final CharSequence previousWord,
+ final WordCallback callback) {
+ for (final Dictionary dict : mDictionaries)
+ dict.getBigrams(composer, previousWord, callback);
+ }
+
+ @Override
+ public boolean isValidWord(CharSequence word) {
+ for (final Dictionary dict : mDictionaries)
+ if (dict.isValidWord(word)) return true;
+ return false;
+ }
+
+ @Override
+ public void close() {
+ for (final Dictionary dict : mDictionaries)
+ dict.close();
+ }
+
+ public void addDictionary(Dictionary newDict) {
+ mDictionaries.add(newDict);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
new file mode 100644
index 000000000..bba331868
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.util.Log;
+
+import java.io.File;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Factory for dictionary instances.
+ */
+public class DictionaryFactory {
+
+ private static String TAG = DictionaryFactory.class.getSimpleName();
+
+ /**
+ * Initializes a dictionary from a dictionary pack.
+ *
+ * This searches for a content provider providing a dictionary pack for the specified
+ * locale. If none is found, it falls back to using the resource passed as fallBackResId
+ * as a dictionary.
+ * @param context application context for reading resources
+ * @param locale the locale for which to create the dictionary
+ * @param fallbackResId the id of the resource to use as a fallback if no pack is found
+ * @return an initialized instance of Dictionary
+ */
+ public static Dictionary createDictionaryFromManager(Context context, Locale locale,
+ int fallbackResId) {
+ if (null == locale) {
+ Log.e(TAG, "No locale defined for dictionary");
+ return new DictionaryCollection(createBinaryDictionary(context, fallbackResId));
+ }
+
+ final List<Dictionary> dictList = new LinkedList<Dictionary>();
+ for (final AssetFileAddress f : BinaryDictionaryGetter.getDictionaryFiles(locale,
+ context, fallbackResId)) {
+ dictList.add(new BinaryDictionary(context, f.mFilename, f.mOffset, f.mLength, null));
+ }
+
+ if (null == dictList) return null;
+ return new DictionaryCollection(dictList);
+ }
+
+ /**
+ * Initializes a dictionary from a raw resource file
+ * @param context application context for reading resources
+ * @param resId the resource containing the raw binary dictionary
+ * @return an initialized instance of BinaryDictionary
+ */
+ protected static BinaryDictionary createBinaryDictionary(Context context, int resId) {
+ AssetFileDescriptor afd = null;
+ try {
+ afd = context.getResources().openRawResourceFd(resId);
+ if (afd == null) {
+ Log.e(TAG, "Found the resource but it is compressed. resId=" + resId);
+ return null;
+ }
+ if (!isFullDictionary(afd)) 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;
+ }
+ return new BinaryDictionary(context,
+ sourceDir, afd.getStartOffset(), afd.getLength(), null);
+ } catch (android.content.res.Resources.NotFoundException e) {
+ Log.e(TAG, "Could not find the resource. resId=" + resId);
+ return null;
+ } finally {
+ if (null != afd) {
+ try {
+ afd.close();
+ } catch (java.io.IOException e) {
+ /* IOException on close ? What am I supposed to do ? */
+ }
+ }
+ }
+ }
+
+ /**
+ * Create a dictionary from passed data. This is intended for unit tests only.
+ * @param context the test context to create this data from.
+ * @param dictionary the file to read
+ * @param startOffset the offset in the file where the data starts
+ * @param length the length of the data
+ * @param flagArray the flags to use with this data for testing
+ * @return the created dictionary, or null.
+ */
+ public static Dictionary createDictionaryForTest(Context context, File dictionary,
+ long startOffset, long length, Flag[] flagArray) {
+ if (dictionary.isFile()) {
+ return new BinaryDictionary(context, dictionary.getAbsolutePath(), startOffset, length,
+ flagArray);
+ } else {
+ Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath());
+ return null;
+ }
+ }
+
+ /**
+ * Find out whether a dictionary is available for this locale.
+ * @param context the context on which to check resources.
+ * @param locale the locale to check for.
+ * @return whether a (non-placeholder) dictionary is available or not.
+ */
+ public static boolean isDictionaryAvailable(Context context, Locale locale) {
+ final Resources res = context.getResources();
+ final Locale saveLocale = Utils.setSystemLocale(res, locale);
+
+ final int resourceId = Utils.getMainDictionaryResourceId(res);
+ final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
+ final boolean hasDictionary = isFullDictionary(afd);
+ try {
+ if (null != afd) afd.close();
+ } catch (java.io.IOException e) {
+ /* Um, what can we do here exactly? */
+ }
+
+ Utils.setSystemLocale(res, saveLocale);
+ return hasDictionary;
+ }
+
+ // TODO: Do not use the size of the dictionary as an unique dictionary ID.
+ public static Long getDictionaryId(Context context, Locale locale) {
+ final Resources res = context.getResources();
+ final Locale saveLocale = Utils.setSystemLocale(res, locale);
+
+ final int resourceId = Utils.getMainDictionaryResourceId(res);
+ final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
+ final Long size = (afd != null && afd.getLength() > PLACEHOLDER_LENGTH)
+ ? afd.getLength()
+ : null;
+ try {
+ if (null != afd) afd.close();
+ } catch (java.io.IOException e) {
+ }
+
+ Utils.setSystemLocale(res, saveLocale);
+ return size;
+ }
+
+ // TODO: Find the Right Way to find out whether the resource is a placeholder or not.
+ // Suggestion : strip the locale, open the placeholder file and store its offset.
+ // Upon opening the file, if it's the same offset, then it's the placeholder.
+ private static final long PLACEHOLDER_LENGTH = 34;
+ /**
+ * Finds out whether the data pointed out by an AssetFileDescriptor is a full
+ * dictionary (as opposed to null, or to a place holder).
+ * @param afd the file descriptor to test, or null
+ * @return true if the dictionary is a real full dictionary, false if it's null or a placeholder
+ */
+ protected static boolean isFullDictionary(final AssetFileDescriptor afd) {
+ return (afd != null && afd.getLength() > PLACEHOLDER_LENGTH);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
new file mode 100644
index 000000000..9d30af84b
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.net.Uri;
+
+/**
+ * Takes action to reload the necessary data when a dictionary pack was added/removed.
+ */
+public class DictionaryPackInstallBroadcastReceiver extends BroadcastReceiver {
+
+ final LatinIME mService;
+ /**
+ * The action of the intent for publishing that new dictionary data is available.
+ */
+ /* package */ static final String NEW_DICTIONARY_INTENT_ACTION =
+ "com.android.inputmethod.latin.dictionarypack.newdict";
+
+ public DictionaryPackInstallBroadcastReceiver(final LatinIME service) {
+ mService = service;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ final PackageManager manager = context.getPackageManager();
+
+ // We need to reread the dictionary if a new dictionary package is installed.
+ if (action.equals(Intent.ACTION_PACKAGE_ADDED)) {
+ final Uri packageUri = intent.getData();
+ if (null == packageUri) return; // No package name : we can't do anything
+ final String packageName = packageUri.getSchemeSpecificPart();
+ if (null == packageName) return;
+ final PackageInfo packageInfo;
+ try {
+ packageInfo = manager.getPackageInfo(packageName, PackageManager.GET_PROVIDERS);
+ } catch (android.content.pm.PackageManager.NameNotFoundException e) {
+ return; // No package info : we can't do anything
+ }
+ final ProviderInfo[] providers = packageInfo.providers;
+ if (null == providers) return; // No providers : it is not a dictionary.
+
+ // Search for some dictionary pack in the just-installed package. If found, reread.
+ for (ProviderInfo info : providers) {
+ if (BinaryDictionary.DICTIONARY_PACK_AUTHORITY.equals(info.authority)) {
+ mService.resetSuggestMainDict();
+ return;
+ }
+ }
+ // If we come here none of the authorities matched the one we searched for.
+ // We can exit safely.
+ return;
+ } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
+ && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ // When the dictionary package is removed, we need to reread dictionary (to use the
+ // next-priority one, or stop using a dictionary at all if this was the only one,
+ // since this is the user request).
+ // If we are replacing the package, we will receive ADDED right away so no need to
+ // remove the dictionary at the moment, since we will do it when we receive the
+ // ADDED broadcast.
+
+ // TODO: Only reload dictionary on REMOVED when the removed package is the one we
+ // read dictionary from?
+ mService.resetSuggestMainDict();
+ } else if (action.equals(NEW_DICTIONARY_INTENT_ACTION)) {
+ mService.resetSuggestMainDict();
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/EditingUtils.java b/java/src/com/android/inputmethod/latin/EditingUtils.java
index 90c250dcb..e56aa695d 100644
--- a/java/src/com/android/inputmethod/latin/EditingUtils.java
+++ b/java/src/com/android/inputmethod/latin/EditingUtils.java
@@ -16,13 +16,13 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.compat.InputConnectionCompatUtils;
+
import android.text.TextUtils;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
import java.util.regex.Pattern;
/**
@@ -34,11 +34,6 @@ public class EditingUtils {
*/
private static final int LOOKBACK_CHARACTER_NUM = 15;
- // Cache Method pointers
- private static boolean sMethodsInitialized;
- private static Method sMethodGetSelectedText;
- private static Method sMethodSetComposingRegion;
-
private EditingUtils() {
// Unintentional empty constructor for singleton.
}
@@ -78,7 +73,7 @@ public class EditingUtils {
/**
* @param connection connection to the current text field.
- * @param sep characters which may separate words
+ * @param separators characters which may separate words
* @return the word that surrounds the cursor, including up to one trailing
* separator. For example, if the field contains "he|llo world", where |
* represents the cursor, then "hello " will be returned.
@@ -166,23 +161,62 @@ public class EditingUtils {
private static final Pattern spaceRegex = Pattern.compile("\\s+");
+
public static CharSequence getPreviousWord(InputConnection connection,
String sentenceSeperators) {
//TODO: Should fix this. This could be slow!
CharSequence prev = connection.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
- if (prev == null) {
- return null;
- }
+ return getPreviousWord(prev, sentenceSeperators);
+ }
+
+ // Get the word before the whitespace preceding the non-whitespace preceding the cursor.
+ // Also, it won't return words that end in a separator.
+ // Example :
+ // "abc def|" -> abc
+ // "abc def |" -> abc
+ // "abc def. |" -> abc
+ // "abc def . |" -> def
+ // "abc|" -> null
+ // "abc |" -> null
+ // "abc. def|" -> null
+ public static CharSequence getPreviousWord(CharSequence prev, String sentenceSeperators) {
+ if (prev == null) return null;
String[] w = spaceRegex.split(prev);
- if (w.length >= 2 && w[w.length-2].length() > 0) {
- char lastChar = w[w.length-2].charAt(w[w.length-2].length() -1);
- if (sentenceSeperators.contains(String.valueOf(lastChar))) {
- return null;
- }
- return w[w.length-2];
- } else {
- return null;
- }
+
+ // If we can't find two words, or we found an empty word, return null.
+ if (w.length < 2 || w[w.length - 2].length() <= 0) return null;
+
+ // If ends in a separator, return null
+ char lastChar = w[w.length - 2].charAt(w[w.length - 2].length() - 1);
+ if (sentenceSeperators.contains(String.valueOf(lastChar))) return null;
+
+ return w[w.length - 2];
+ }
+
+ public static CharSequence getThisWord(InputConnection connection, String sentenceSeperators) {
+ final CharSequence prev = connection.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
+ return getThisWord(prev, sentenceSeperators);
+ }
+
+ // Get the word immediately before the cursor, even if there is whitespace between it and
+ // the cursor - but not if there is punctuation.
+ // Example :
+ // "abc def|" -> def
+ // "abc def |" -> def
+ // "abc def. |" -> null
+ // "abc def . |" -> null
+ public static CharSequence getThisWord(CharSequence prev, String sentenceSeperators) {
+ if (prev == null) return null;
+ String[] w = spaceRegex.split(prev);
+
+ // No word : return null
+ if (w.length < 1 || w[w.length - 1].length() <= 0) return null;
+
+ // If ends in a separator, return null
+ char lastChar = w[w.length - 1].charAt(w[w.length - 1].length() - 1);
+ if (sentenceSeperators.contains(String.valueOf(lastChar))) return null;
+
+ return w[w.length - 1];
}
public static class SelectedWord {
@@ -241,7 +275,8 @@ public class EditingUtils {
}
// Extract the selection alone
- CharSequence touching = getSelectedText(ic, selStart, selEnd);
+ CharSequence touching = InputConnectionCompatUtils.getSelectedText(
+ ic, selStart, selEnd);
if (TextUtils.isEmpty(touching)) return null;
// Is any part of the selection a separator? If so, return null.
final int length = touching.length();
@@ -255,74 +290,4 @@ public class EditingUtils {
}
return null;
}
-
- /**
- * Cache method pointers for performance
- */
- private static void initializeMethodsForReflection() {
- try {
- // These will either both exist or not, so no need for separate try/catch blocks.
- // If other methods are added later, use separate try/catch blocks.
- sMethodGetSelectedText = InputConnection.class.getMethod("getSelectedText", int.class);
- sMethodSetComposingRegion = InputConnection.class.getMethod("setComposingRegion",
- int.class, int.class);
- } catch (NoSuchMethodException exc) {
- // Ignore
- }
- sMethodsInitialized = true;
- }
-
- /**
- * Returns the selected text between the selStart and selEnd positions.
- */
- private static CharSequence getSelectedText(InputConnection ic, int selStart, int selEnd) {
- // Use reflection, for backward compatibility
- CharSequence result = null;
- if (!sMethodsInitialized) {
- initializeMethodsForReflection();
- }
- if (sMethodGetSelectedText != null) {
- try {
- result = (CharSequence) sMethodGetSelectedText.invoke(ic, 0);
- return result;
- } catch (InvocationTargetException exc) {
- // Ignore
- } catch (IllegalArgumentException e) {
- // Ignore
- } catch (IllegalAccessException e) {
- // Ignore
- }
- }
- // Reflection didn't work, try it the poor way, by moving the cursor to the start,
- // getting the text after the cursor and moving the text back to selected mode.
- // TODO: Verify that this works properly in conjunction with
- // LatinIME#onUpdateSelection
- ic.setSelection(selStart, selEnd);
- result = ic.getTextAfterCursor(selEnd - selStart, 0);
- ic.setSelection(selStart, selEnd);
- return result;
- }
-
- /**
- * Tries to set the text into composition mode if there is support for it in the framework.
- */
- public static void underlineWord(InputConnection ic, SelectedWord word) {
- // Use reflection, for backward compatibility
- // If method not found, there's nothing we can do. It still works but just wont underline
- // the word.
- if (!sMethodsInitialized) {
- initializeMethodsForReflection();
- }
- if (sMethodSetComposingRegion != null) {
- try {
- sMethodSetComposingRegion.invoke(ic, word.mStart, word.mEnd);
- } catch (InvocationTargetException exc) {
- // Ignore
- } catch (IllegalArgumentException e) {
- // Ignore
- } catch (IllegalAccessException e) {
- // Ignore
- }
- }
- }
}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 0318175f6..97a4a1816 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -19,6 +19,8 @@ package com.android.inputmethod.latin;
import android.content.Context;
import android.os.AsyncTask;
+import com.android.inputmethod.keyboard.Keyboard;
+
import java.util.LinkedList;
/**
@@ -32,14 +34,14 @@ public class ExpandableDictionary extends Dictionary {
*/
protected static final int MAX_WORD_LENGTH = 32;
+ // Bigram frequency is a fixed point number with 1 meaning 1.2 and 255 meaning 1.8.
+ protected static final int BIGRAM_MAX_FREQUENCY = 255;
+
private Context mContext;
private char[] mWordBuilder = new char[MAX_WORD_LENGTH];
private int mDicTypeId;
private int mMaxDepth;
private int mInputLength;
- private StringBuilder sb = new StringBuilder(MAX_WORD_LENGTH);
-
- private static final char QUOTE = '\'';
private boolean mRequiresReload;
@@ -98,6 +100,7 @@ public class ExpandableDictionary extends Dictionary {
public int addFrequency(int add) {
mFrequency += add;
+ if (mFrequency > BIGRAM_MAX_FREQUENCY) mFrequency = BIGRAM_MAX_FREQUENCY;
return mFrequency;
}
}
@@ -226,6 +229,7 @@ public class ExpandableDictionary extends Dictionary {
* Returns the word's frequency or -1 if not found
*/
protected int getWordFrequency(CharSequence word) {
+ // Case-sensitive search
Node node = searchNode(mRoots, word, 0, word.length());
return (node == null) ? -1 : node.mFrequency;
}
@@ -301,7 +305,8 @@ public class ExpandableDictionary extends Dictionary {
getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex,
skipPos, callback);
}
- } else if ((c == QUOTE && currentChars[0] != QUOTE) || depth == skipPos) {
+ } else if ((c == Keyboard.CODE_SINGLE_QUOTE
+ && currentChars[0] != Keyboard.CODE_SINGLE_QUOTE) || depth == skipPos) {
// Skip the ' and continue deeper
word[depth] = c;
if (children != null) {
@@ -327,7 +332,7 @@ public class ExpandableDictionary extends Dictionary {
final int finalFreq;
if (skipPos < 0) {
finalFreq = freq * snr * addedAttenuation
- * FULL_WORD_FREQ_MULTIPLIER;
+ * FULL_WORD_SCORE_MULTIPLIER;
} else {
finalFreq = computeSkippedWordFinalFreq(freq,
snr * addedAttenuation, mInputLength);
@@ -362,12 +367,16 @@ public class ExpandableDictionary extends Dictionary {
/**
* Adds bigrams to the in-memory trie structure that is being used to retrieve any word
- * @param frequency frequency for this bigrams
- * @param addFrequency if true, it adds to current frequency
+ * @param frequency frequency for this bigram
+ * @param addFrequency if true, it adds to current frequency, else it overwrites the old value
* @return returns the final frequency
*/
private int addOrSetBigram(String word1, String word2, int frequency, boolean addFrequency) {
- Node firstWord = searchWord(mRoots, word1, 0, null);
+ // We don't want results to be different according to case of the looked up left hand side
+ // word. We do want however to return the correct case for the right hand side.
+ // So we want to squash the case of the left hand side, and preserve that of the right
+ // hand side word.
+ Node firstWord = searchWord(mRoots, word1.toLowerCase(), 0, null);
Node secondWord = searchWord(mRoots, word2, 0, null);
LinkedList<NextWord> bigram = firstWord.mNGrams;
if (bigram == null || bigram.size() == 0) {
@@ -433,8 +442,12 @@ public class ExpandableDictionary extends Dictionary {
}
}
- private void runReverseLookUp(final CharSequence previousWord, final WordCallback callback) {
- Node prevWord = searchNode(mRoots, previousWord, 0, previousWord.length());
+ private void runBigramReverseLookUp(final CharSequence previousWord,
+ final WordCallback callback) {
+ // Search for the lowercase version of the word only, because that's where bigrams
+ // store their sons.
+ Node prevWord = searchNode(mRoots, previousWord.toString().toLowerCase(), 0,
+ previousWord.length());
if (prevWord != null && prevWord.mNGrams != null) {
reverseLookUp(prevWord.mNGrams, callback);
}
@@ -444,7 +457,7 @@ public class ExpandableDictionary extends Dictionary {
public void getBigrams(final WordComposer codes, final CharSequence previousWord,
final WordCallback callback) {
if (!reloadDictionaryIfRequired()) {
- runReverseLookUp(previousWord, callback);
+ runBigramReverseLookUp(previousWord, callback);
}
}
@@ -462,6 +475,9 @@ public class ExpandableDictionary extends Dictionary {
}
}
+ // Local to reverseLookUp, but do not allocate each time.
+ private final char[] mLookedUpString = new char[MAX_WORD_LENGTH];
+
/**
* reverseLookUp retrieves the full word given a list of terminal nodes and adds those words
* through callback.
@@ -474,30 +490,33 @@ public class ExpandableDictionary extends Dictionary {
for (NextWord nextWord : terminalNodes) {
node = nextWord.mWord;
freq = nextWord.getFrequency();
- // TODO Not the best way to limit suggestion threshold
- if (freq >= UserBigramDictionary.SUGGEST_THRESHOLD) {
- sb.setLength(0);
- do {
- sb.insert(0, node.mCode);
- node = node.mParent;
- } while(node != null);
-
- // TODO better way to feed char array?
- callback.addWord(sb.toString().toCharArray(), 0, sb.length(), freq, mDicTypeId,
- DataType.BIGRAM);
- }
+ int index = MAX_WORD_LENGTH;
+ do {
+ --index;
+ mLookedUpString[index] = node.mCode;
+ node = node.mParent;
+ } while (node != null);
+
+ callback.addWord(mLookedUpString, index, MAX_WORD_LENGTH - index, freq, mDicTypeId,
+ DataType.BIGRAM);
}
}
/**
- * Search for the terminal node of the word
+ * Recursively search for the terminal node of the word.
+ *
+ * One iteration takes the full word to search for and the current index of the recursion.
+ *
+ * @param children the node of the trie to search under.
+ * @param word the word to search for. Only read [offset..length] so there may be trailing chars
+ * @param offset the index in {@code word} this recursion should operate on.
+ * @param length the length of the input word.
* @return Returns the terminal node of the word if the word exists
*/
private Node searchNode(final NodeArray children, final CharSequence word, final int offset,
final int length) {
- // TODO Consider combining with addWordRec
final int count = children.mLength;
- char currentChar = word.charAt(offset);
+ final char currentChar = word.charAt(offset);
for (int j = 0; j < count; j++) {
final Node node = children.mData[j];
if (node.mCode == currentChar) {
diff --git a/java/src/com/android/inputmethod/latin/Flag.java b/java/src/com/android/inputmethod/latin/Flag.java
new file mode 100644
index 000000000..3cb8f7e17
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/Flag.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+public class Flag {
+ public final String mName;
+ public final int mResource;
+ public final int mMask;
+ public final int mSource;
+
+ static private final int SOURCE_CONFIG = 1;
+ static private final int SOURCE_EXTRAVALUE = 2;
+
+ public Flag(int resourceId, int mask) {
+ mName = null;
+ mResource = resourceId;
+ mSource = SOURCE_CONFIG;
+ mMask = mask;
+ }
+
+ public Flag(String name, int mask) {
+ mName = name;
+ mResource = 0;
+ mSource = SOURCE_EXTRAVALUE;
+ mMask = mask;
+ }
+
+ // If context/switcher are null, set all related flags in flagArray to on.
+ public static int initFlags(Flag[] flagArray, Context context, SubtypeSwitcher switcher) {
+ int flags = 0;
+ final Resources res = null == context ? null : context.getResources();
+ for (Flag entry : flagArray) {
+ switch (entry.mSource) {
+ case Flag.SOURCE_CONFIG:
+ if (res == null || res.getBoolean(entry.mResource))
+ flags |= entry.mMask;
+ break;
+ case Flag.SOURCE_EXTRAVALUE:
+ if (switcher == null ||
+ switcher.currentSubtypeContainsExtraValueKey(entry.mName))
+ flags |= entry.mMask;
+ break;
+ }
+ }
+ return flags;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
deleted file mode 100644
index b58a57e42..000000000
--- a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (C) 2008-2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.preference.CheckBoxPreference;
-import android.preference.PreferenceActivity;
-import android.preference.PreferenceGroup;
-import android.preference.PreferenceManager;
-import android.text.TextUtils;
-
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Locale;
-
-public class InputLanguageSelection extends PreferenceActivity {
-
- private SharedPreferences mPrefs;
- private String mSelectedLanguages;
- private ArrayList<Loc> mAvailableLanguages = new ArrayList<Loc>();
- private static final String[] BLACKLIST_LANGUAGES = {
- "ko", "ja", "zh", "el", "zz"
- };
-
- private static class Loc implements Comparable<Object> {
- private static Collator sCollator = Collator.getInstance();
-
- private String mLabel;
- public final Locale mLocale;
-
- public Loc(String label, Locale locale) {
- this.mLabel = label;
- this.mLocale = locale;
- }
-
- public void setLabel(String label) {
- this.mLabel = label;
- }
-
- @Override
- public String toString() {
- return this.mLabel;
- }
-
- @Override
- public int compareTo(Object o) {
- return sCollator.compare(this.mLabel, ((Loc) o).mLabel);
- }
- }
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- addPreferencesFromResource(R.xml.language_prefs);
- // Get the settings preferences
- mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
- mSelectedLanguages = mPrefs.getString(Settings.PREF_SELECTED_LANGUAGES, "");
- String[] languageList = mSelectedLanguages.split(",");
- mAvailableLanguages = getUniqueLocales();
- PreferenceGroup parent = getPreferenceScreen();
- for (int i = 0; i < mAvailableLanguages.size(); i++) {
- CheckBoxPreference pref = new CheckBoxPreference(this);
- Locale locale = mAvailableLanguages.get(i).mLocale;
- pref.setTitle(SubtypeSwitcher.getFullDisplayName(locale, true));
- boolean checked = isLocaleIn(locale, languageList);
- pref.setChecked(checked);
- if (hasDictionary(locale)) {
- pref.setSummary(R.string.has_dictionary);
- }
- parent.addPreference(pref);
- }
- }
-
- private boolean isLocaleIn(Locale locale, String[] list) {
- String lang = get5Code(locale);
- for (int i = 0; i < list.length; i++) {
- if (lang.equalsIgnoreCase(list[i])) return true;
- }
- return false;
- }
-
- private boolean hasDictionary(Locale locale) {
- final Resources res = getResources();
- final Configuration conf = res.getConfiguration();
- final Locale saveLocale = conf.locale;
- boolean haveDictionary = false;
- conf.locale = locale;
- res.updateConfiguration(conf, res.getDisplayMetrics());
-
- 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
- // 4000-5000 words, whereas the LARGE_DICTIONARY is about 20000+ words.
- if (bd.getSize() > Suggest.LARGE_DICTIONARY_THRESHOLD / 4) {
- haveDictionary = true;
- }
- bd.close();
- conf.locale = saveLocale;
- res.updateConfiguration(conf, res.getDisplayMetrics());
- return haveDictionary;
- }
-
- private String get5Code(Locale locale) {
- String country = locale.getCountry();
- return locale.getLanguage()
- + (TextUtils.isEmpty(country) ? "" : "_" + country);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- // Save the selected languages
- String checkedLanguages = "";
- PreferenceGroup parent = getPreferenceScreen();
- int count = parent.getPreferenceCount();
- for (int i = 0; i < count; i++) {
- CheckBoxPreference pref = (CheckBoxPreference) parent.getPreference(i);
- if (pref.isChecked()) {
- Locale locale = mAvailableLanguages.get(i).mLocale;
- checkedLanguages += get5Code(locale) + ",";
- }
- }
- if (checkedLanguages.length() < 1) checkedLanguages = null; // Save null
- Editor editor = mPrefs.edit();
- editor.putString(Settings.PREF_SELECTED_LANGUAGES, checkedLanguages);
- SharedPreferencesCompat.apply(editor);
- }
-
- public ArrayList<Loc> getUniqueLocales() {
- String[] locales = getAssets().getLocales();
- Arrays.sort(locales);
- ArrayList<Loc> uniqueLocales = new ArrayList<Loc>();
-
- final int origSize = locales.length;
- Loc[] preprocess = new Loc[origSize];
- int finalSize = 0;
- for (int i = 0 ; i < origSize; i++ ) {
- String s = locales[i];
- int len = s.length();
- if (len == 5) {
- String language = s.substring(0, 2);
- String country = s.substring(3, 5);
- Locale l = new Locale(language, country);
-
- // Exclude languages that are not relevant to LatinIME
- if (arrayContains(BLACKLIST_LANGUAGES, language)) continue;
-
- if (finalSize == 0) {
- preprocess[finalSize++] =
- new Loc(SubtypeSwitcher.getFullDisplayName(l, true), l);
- } else {
- // check previous entry:
- // same lang and a country -> upgrade to full name and
- // insert ours with full name
- // diff lang -> insert ours with lang-only name
- if (preprocess[finalSize-1].mLocale.getLanguage().equals(
- language)) {
- preprocess[finalSize-1].setLabel(SubtypeSwitcher.getFullDisplayName(
- preprocess[finalSize-1].mLocale, false));
- preprocess[finalSize++] =
- new Loc(SubtypeSwitcher.getFullDisplayName(l, false), l);
- } else {
- String displayName;
- if (s.equals("zz_ZZ")) {
- // ignore this locale
- } else {
- displayName = SubtypeSwitcher.getFullDisplayName(l, true);
- preprocess[finalSize++] = new Loc(displayName, l);
- }
- }
- }
- }
- }
- for (int i = 0; i < finalSize ; i++) {
- uniqueLocales.add(preprocess[i]);
- }
- return uniqueLocales;
- }
-
- private boolean arrayContains(String[] array, String value) {
- for (int i = 0; i < array.length; i++) {
- if (array[i].equalsIgnoreCase(value)) return true;
- }
- return false;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/LanguageSwitcher.java b/java/src/com/android/inputmethod/latin/LanguageSwitcher.java
deleted file mode 100644
index 2dd3038f9..000000000
--- a/java/src/com/android/inputmethod/latin/LanguageSwitcher.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-import android.text.TextUtils;
-
-import java.util.ArrayList;
-import java.util.Locale;
-
-/**
- * Keeps track of list of selected input languages and the current
- * input language that the user has selected.
- */
-public class LanguageSwitcher {
-
- private final ArrayList<Locale> mLocales = new ArrayList<Locale>();
- private final LatinIME mIme;
- private String[] mSelectedLanguageArray;
- private String mSelectedLanguages;
- private int mCurrentIndex = 0;
- private String mDefaultInputLanguage;
- private Locale mDefaultInputLocale;
- private Locale mSystemLocale;
-
- public LanguageSwitcher(LatinIME ime) {
- mIme = ime;
- }
-
- public int getLocaleCount() {
- return mLocales.size();
- }
-
- /**
- * Loads the currently selected input languages from shared preferences.
- * @param sp
- * @return whether there was any change
- */
- public boolean loadLocales(SharedPreferences sp) {
- String selectedLanguages = sp.getString(Settings.PREF_SELECTED_LANGUAGES, null);
- String currentLanguage = sp.getString(Settings.PREF_INPUT_LANGUAGE, null);
- if (selectedLanguages == null || selectedLanguages.length() < 1) {
- loadDefaults();
- if (mLocales.size() == 0) {
- return false;
- }
- mLocales.clear();
- return true;
- }
- if (selectedLanguages.equals(mSelectedLanguages)) {
- return false;
- }
- mSelectedLanguageArray = selectedLanguages.split(",");
- mSelectedLanguages = selectedLanguages; // Cache it for comparison later
- constructLocales();
- mCurrentIndex = 0;
- if (currentLanguage != null) {
- // Find the index
- mCurrentIndex = 0;
- for (int i = 0; i < mLocales.size(); i++) {
- if (mSelectedLanguageArray[i].equals(currentLanguage)) {
- mCurrentIndex = i;
- break;
- }
- }
- // If we didn't find the index, use the first one
- }
- return true;
- }
-
- private void loadDefaults() {
- mDefaultInputLocale = mIme.getResources().getConfiguration().locale;
- String country = mDefaultInputLocale.getCountry();
- mDefaultInputLanguage = mDefaultInputLocale.getLanguage() +
- (TextUtils.isEmpty(country) ? "" : "_" + country);
- }
-
- private void constructLocales() {
- mLocales.clear();
- for (final String lang : mSelectedLanguageArray) {
- final Locale locale = new Locale(lang.substring(0, 2),
- lang.length() > 4 ? lang.substring(3, 5) : "");
- mLocales.add(locale);
- }
- }
-
- /**
- * Returns the currently selected input language code, or the display language code if
- * no specific locale was selected for input.
- */
- public String getInputLanguage() {
- if (getLocaleCount() == 0) return mDefaultInputLanguage;
-
- return mSelectedLanguageArray[mCurrentIndex];
- }
-
- /**
- * Returns the list of enabled language codes.
- */
- public String[] getEnabledLanguages() {
- return mSelectedLanguageArray;
- }
-
- /**
- * Returns the currently selected input locale, or the display locale if no specific
- * locale was selected for input.
- * @return
- */
- public Locale getInputLocale() {
- if (getLocaleCount() == 0) return mDefaultInputLocale;
-
- return mLocales.get(mCurrentIndex);
- }
-
- private int nextLocaleIndex() {
- final int size = mLocales.size();
- return (mCurrentIndex + 1) % size;
- }
-
- private int prevLocaleIndex() {
- final int size = mLocales.size();
- return (mCurrentIndex - 1 + size) % size;
- }
-
- /**
- * Returns the next input locale in the list. Wraps around to the beginning of the
- * list if we're at the end of the list.
- * @return
- */
- public Locale getNextInputLocale() {
- if (getLocaleCount() == 0) return mDefaultInputLocale;
- return mLocales.get(nextLocaleIndex());
- }
-
- /**
- * Sets the system locale (display UI) used for comparing with the input language.
- * @param locale the locale of the system
- */
- public void setSystemLocale(Locale locale) {
- mSystemLocale = locale;
- }
-
- /**
- * Returns the system locale.
- * @return the system locale
- */
- public Locale getSystemLocale() {
- return mSystemLocale;
- }
-
- /**
- * Returns the previous input locale in the list. Wraps around to the end of the
- * list if we're at the beginning of the list.
- * @return
- */
- public Locale getPrevInputLocale() {
- if (getLocaleCount() == 0) return mDefaultInputLocale;
- return mLocales.get(prevLocaleIndex());
- }
-
- public void reset() {
- mCurrentIndex = 0;
- }
-
- public void next() {
- mCurrentIndex = nextLocaleIndex();
- }
-
- public void prev() {
- mCurrentIndex = prevLocaleIndex();
- }
-
- public void persist(SharedPreferences prefs) {
- Editor editor = prefs.edit();
- editor.putString(Settings.PREF_INPUT_LANGUAGE, getInputLanguage());
- SharedPreferencesCompat.apply(editor);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index bf831bfbc..5b242b1e0 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -33,7 +33,6 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.SystemClock;
-import android.os.Vibrator;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.text.InputType;
@@ -42,46 +41,44 @@ import android.util.DisplayMetrics;
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Printer;
-import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.CompletionInfo;
-import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
-import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InputMethodSubtype;
-import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
-import android.widget.LinearLayout;
+import com.android.inputmethod.compat.CompatUtils;
+import com.android.inputmethod.compat.EditorInfoCompatUtils;
+import com.android.inputmethod.compat.InputConnectionCompatUtils;
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.compat.InputMethodServiceCompatWrapper;
+import com.android.inputmethod.compat.InputTypeCompatUtils;
+import com.android.inputmethod.compat.SuggestionSpanUtils;
+import com.android.inputmethod.deprecated.LanguageSwitcherProxy;
+import com.android.inputmethod.deprecated.VoiceProxy;
+import com.android.inputmethod.deprecated.recorrection.Recorrection;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardActionListener;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.KeyboardView;
import com.android.inputmethod.keyboard.LatinKeyboard;
import com.android.inputmethod.keyboard.LatinKeyboardView;
-import com.android.inputmethod.latin.Utils.RingCharBuffer;
-import com.android.inputmethod.voice.VoiceIMEConnector;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Locale;
/**
* Input method implementation for Qwerty'ish keyboard.
*/
-public class LatinIME extends InputMethodService implements KeyboardActionListener {
+public class LatinIME extends InputMethodServiceCompatWrapper implements KeyboardActionListener,
+ CandidateView.Listener {
private static final String TAG = LatinIME.class.getSimpleName();
private static final boolean PERF_DEBUG = false;
private static final boolean TRACE = false;
@@ -94,6 +91,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
*
* @deprecated Use {@link LatinIME#IME_OPTION_NO_MICROPHONE} with package name prefixed.
*/
+ @SuppressWarnings("dep-ann")
public static final String IME_OPTION_NO_MICROPHONE_COMPAT = "nm";
/**
@@ -109,9 +107,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
*/
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;
private static final int EXTENDED_TOUCHABLE_REGION_HEIGHT = 100;
// How many continuous deletes at which to start deleting at a higher speed.
@@ -119,6 +114,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Key events coming any faster than this are long-presses.
private static final int QUICK_PRESS = 200;
+ /**
+ * The name of the scheme used by the Package Manager to warn of a new package installation,
+ * replacement or removal.
+ */
+ private static final String SCHEME_PACKAGE = "package";
+
private int mSuggestionVisibility;
private static final int SUGGESTION_VISIBILILTY_SHOW_VALUE
= R.string.prefs_suggestion_visibility_show_value;
@@ -133,55 +134,48 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
SUGGESTION_VISIBILILTY_HIDE_VALUE
};
+ private Settings.Values mSettingsValues;
+
private View mCandidateViewContainer;
+ private int mCandidateStripHeight;
private CandidateView mCandidateView;
private Suggest mSuggest;
private CompletionInfo[] mApplicationSpecifiedCompletions;
private AlertDialog mOptionsDialog;
- private InputMethodManager mImm;
+ private InputMethodManagerCompatWrapper mImm;
private Resources mResources;
private SharedPreferences mPrefs;
private String mInputMethodId;
private KeyboardSwitcher mKeyboardSwitcher;
private SubtypeSwitcher mSubtypeSwitcher;
- private VoiceIMEConnector mVoiceConnector;
+ private VoiceProxy mVoiceProxy;
+ private Recorrection mRecorrection;
private UserDictionary mUserDictionary;
private UserBigramDictionary mUserBigramDictionary;
private ContactsDictionary mContactsDictionary;
private AutoDictionary mAutoDictionary;
+ // TODO: Create an inner class to group options and pseudo-options to improve readability.
// These variables are initialized according to the {@link EditorInfo#inputType}.
- private boolean mAutoSpace;
+ private boolean mShouldInsertMagicSpace;
private boolean mInputTypeNoAutoCorrect;
private boolean mIsSettingsSuggestionStripOn;
private boolean mApplicationSpecifiedCompletionOn;
- private AccessibilityUtils mAccessibilityUtils;
-
private final StringBuilder mComposing = new StringBuilder();
private WordComposer mWord = new WordComposer();
private CharSequence mBestWord;
- private boolean mHasValidSuggestions;
+ private boolean mHasUncommittedTypedChars;
private boolean mHasDictionary;
- private boolean mJustAddedAutoSpace;
- private boolean mAutoCorrectEnabled;
- private boolean mRecorrectionEnabled;
- private boolean mBigramSuggestionEnabled;
- private boolean mAutoCorrectOn;
- private boolean mVibrateOn;
- private boolean mSoundOn;
- private boolean mPopupOn;
- private boolean mAutoCap;
- private boolean mQuickFixes;
- private boolean mConfigEnableShowSubtypeSettings;
- private boolean mConfigSwipeDownDismissKeyboardEnabled;
- private int mConfigDelayBeforeFadeoutLanguageOnSpacebar;
- private int mConfigDurationOfFadeoutLanguageOnSpacebar;
- private float mConfigFinalFadeoutFactorOfLanguageOnSpacebar;
- private long mConfigDoubleSpacesTurnIntoPeriodTimeout;
+ // Magic space: a space that should disappear on space/apostrophe insertion, move after the
+ // punctuation on punctuation insertion, and become a real space on alpha char insertion.
+ private boolean mJustAddedMagicSpace; // This indicates whether the last char is a magic space.
+ // This indicates whether the last keypress resulted in processing of double space replacement
+ // with period-space.
+ private boolean mJustReplacedDoubleSpace;
private int mCorrectionMode;
private int mCommittedLength;
@@ -189,76 +183,28 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Keep track of the last selection range to decide if we need to show word alternatives
private int mLastSelectionStart;
private int mLastSelectionEnd;
- private SuggestedWords mSuggestPuncList;
- // Indicates whether the suggestion strip is to be on in landscape
- private boolean mJustAccepted;
+ // Whether we are expecting an onUpdateSelection event to fire. If it does when we don't
+ // "expect" it, it means the user actually moved the cursor.
+ private boolean mExpectingUpdateSelection;
private int mDeleteCount;
private long mLastKeyTime;
private AudioManager mAudioManager;
// Align sound effect volume on music volume
private static final float FX_VOLUME = -1.0f;
- private boolean mSilentMode;
+ private boolean mSilentModeOn; // System-wide current configuration
- /* package */ String mWordSeparators;
- private String mSentenceSeparators;
- private String mSuggestPuncs;
- // TODO: Move this flag to VoiceIMEConnector
+ // TODO: Move this flag to VoiceProxy
private boolean mConfigurationChanging;
+ // Object for reacting to adding/removing a dictionary pack.
+ private BroadcastReceiver mDictionaryPackInstallReceiver =
+ new DictionaryPackInstallBroadcastReceiver(this);
+
// Keeps track of most recently inserted text (multi-character key) for reverting
private CharSequence mEnteredText;
- private final ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>();
-
- public abstract static class WordAlternatives {
- protected CharSequence mChosenWord;
-
- public WordAlternatives() {
- // Nothing
- }
-
- public WordAlternatives(CharSequence chosenWord) {
- mChosenWord = chosenWord;
- }
-
- @Override
- public int hashCode() {
- return mChosenWord.hashCode();
- }
-
- public abstract CharSequence getOriginalWord();
-
- public CharSequence getChosenWord() {
- return mChosenWord;
- }
-
- public abstract SuggestedWords.Builder getAlternatives();
- }
-
- public class TypedWordAlternatives extends WordAlternatives {
- private WordComposer word;
-
- public TypedWordAlternatives() {
- // Nothing
- }
-
- public TypedWordAlternatives(CharSequence chosenWord, WordComposer wordComposer) {
- super(chosenWord);
- word = wordComposer;
- }
-
- @Override
- public CharSequence getOriginalWord() {
- return word.getTypedWord();
- }
-
- @Override
- public SuggestedWords.Builder getAlternatives() {
- return getTypedSuggestions(word);
- }
- }
public final UIHandler mHandler = new UIHandler();
@@ -270,44 +216,54 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private static final int MSG_FADEOUT_LANGUAGE_ON_SPACEBAR = 4;
private static final int MSG_DISMISS_LANGUAGE_ON_SPACEBAR = 5;
private static final int MSG_SPACE_TYPED = 6;
+ private static final int MSG_SET_BIGRAM_PREDICTIONS = 7;
@Override
public void handleMessage(Message msg) {
final KeyboardSwitcher switcher = mKeyboardSwitcher;
- final LatinKeyboardView inputView = switcher.getInputView();
+ final LatinKeyboardView inputView = switcher.getKeyboardView();
switch (msg.what) {
case MSG_UPDATE_SUGGESTIONS:
updateSuggestions();
break;
case MSG_UPDATE_OLD_SUGGESTIONS:
- setOldSuggestions();
+ mRecorrection.fetchAndDisplayRecorrectionSuggestions(mVoiceProxy, mCandidateView,
+ mSuggest, mKeyboardSwitcher, mWord, mHasUncommittedTypedChars,
+ mLastSelectionStart, mLastSelectionEnd, mSettingsValues.mWordSeparators);
break;
case MSG_UPDATE_SHIFT_STATE:
switcher.updateShiftState();
break;
+ case MSG_SET_BIGRAM_PREDICTIONS:
+ updateBigramPredictions();
+ break;
case MSG_VOICE_RESULTS:
- mVoiceConnector.handleVoiceResults(preferCapitalization()
+ mVoiceProxy.handleVoiceResults(preferCapitalization()
|| (switcher.isAlphabetMode() && switcher.isShiftedOrShiftLocked()));
break;
case MSG_FADEOUT_LANGUAGE_ON_SPACEBAR:
- if (inputView != null)
+ if (inputView != null) {
inputView.setSpacebarTextFadeFactor(
- (1.0f + mConfigFinalFadeoutFactorOfLanguageOnSpacebar) / 2,
+ (1.0f + mSettingsValues.mFinalFadeoutFactorOfLanguageOnSpacebar) / 2,
(LatinKeyboard)msg.obj);
+ }
sendMessageDelayed(obtainMessage(MSG_DISMISS_LANGUAGE_ON_SPACEBAR, msg.obj),
- mConfigDurationOfFadeoutLanguageOnSpacebar);
+ mSettingsValues.mDurationOfFadeoutLanguageOnSpacebar);
break;
case MSG_DISMISS_LANGUAGE_ON_SPACEBAR:
- if (inputView != null)
+ if (inputView != null) {
inputView.setSpacebarTextFadeFactor(
- mConfigFinalFadeoutFactorOfLanguageOnSpacebar, (LatinKeyboard)msg.obj);
+ mSettingsValues.mFinalFadeoutFactorOfLanguageOnSpacebar,
+ (LatinKeyboard)msg.obj);
+ }
break;
}
}
public void postUpdateSuggestions() {
removeMessages(MSG_UPDATE_SUGGESTIONS);
- sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTIONS), DELAY_UPDATE_SUGGESTIONS);
+ sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTIONS),
+ mSettingsValues.mDelayUpdateSuggestions);
}
public void cancelUpdateSuggestions() {
@@ -321,7 +277,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void postUpdateOldSuggestions() {
removeMessages(MSG_UPDATE_OLD_SUGGESTIONS);
sendMessageDelayed(obtainMessage(MSG_UPDATE_OLD_SUGGESTIONS),
- DELAY_UPDATE_OLD_SUGGESTIONS);
+ mSettingsValues.mDelayUpdateOldSuggestions);
}
public void cancelUpdateOldSuggestions() {
@@ -330,13 +286,24 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void postUpdateShiftKeyState() {
removeMessages(MSG_UPDATE_SHIFT_STATE);
- sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), DELAY_UPDATE_SHIFT_STATE);
+ sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE),
+ mSettingsValues.mDelayUpdateShiftState);
}
public void cancelUpdateShiftState() {
removeMessages(MSG_UPDATE_SHIFT_STATE);
}
+ public void postUpdateBigramPredictions() {
+ removeMessages(MSG_SET_BIGRAM_PREDICTIONS);
+ sendMessageDelayed(obtainMessage(MSG_SET_BIGRAM_PREDICTIONS),
+ mSettingsValues.mDelayUpdateSuggestions);
+ }
+
+ public void cancelUpdateBigramPredictions() {
+ removeMessages(MSG_SET_BIGRAM_PREDICTIONS);
+ }
+
public void updateVoiceResults() {
sendMessage(obtainMessage(MSG_VOICE_RESULTS));
}
@@ -344,17 +311,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void startDisplayLanguageOnSpacebar(boolean localeChanged) {
removeMessages(MSG_FADEOUT_LANGUAGE_ON_SPACEBAR);
removeMessages(MSG_DISMISS_LANGUAGE_ON_SPACEBAR);
- final LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
+ final LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
if (inputView != null) {
final LatinKeyboard keyboard = mKeyboardSwitcher.getLatinKeyboard();
- // The language is never displayed when the delay is zero.
- if (mConfigDelayBeforeFadeoutLanguageOnSpacebar != 0)
- inputView.setSpacebarTextFadeFactor(localeChanged ? 1.0f
- : mConfigFinalFadeoutFactorOfLanguageOnSpacebar, keyboard);
// The language is always displayed when the delay is negative.
- if (localeChanged && mConfigDelayBeforeFadeoutLanguageOnSpacebar > 0) {
+ final boolean needsToDisplayLanguage = localeChanged
+ || mSettingsValues.mDelayBeforeFadeoutLanguageOnSpacebar < 0;
+ // The language is never displayed when the delay is zero.
+ if (mSettingsValues.mDelayBeforeFadeoutLanguageOnSpacebar != 0) {
+ inputView.setSpacebarTextFadeFactor(needsToDisplayLanguage ? 1.0f
+ : mSettingsValues.mFinalFadeoutFactorOfLanguageOnSpacebar, keyboard);
+ }
+ // The fadeout animation will start when the delay is positive.
+ if (localeChanged && mSettingsValues.mDelayBeforeFadeoutLanguageOnSpacebar > 0) {
sendMessageDelayed(obtainMessage(MSG_FADEOUT_LANGUAGE_ON_SPACEBAR, keyboard),
- mConfigDelayBeforeFadeoutLanguageOnSpacebar);
+ mSettingsValues.mDelayBeforeFadeoutLanguageOnSpacebar);
}
}
}
@@ -362,7 +333,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void startDoubleSpacesTimer() {
removeMessages(MSG_SPACE_TYPED);
sendMessageDelayed(obtainMessage(MSG_SPACE_TYPED),
- mConfigDoubleSpacesTurnIntoPeriodTimeout);
+ mSettingsValues.mDoubleSpacesTurnIntoPeriodTimeout);
}
public void cancelDoubleSpacesTimer() {
@@ -379,44 +350,25 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
mPrefs = prefs;
LatinImeLogger.init(this, prefs);
+ LanguageSwitcherProxy.init(this, prefs);
SubtypeSwitcher.init(this, prefs);
KeyboardSwitcher.init(this, prefs);
- AccessibilityUtils.init(this, prefs);
+ Recorrection.init(this, prefs);
super.onCreate();
- mImm = ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE));
+ mImm = InputMethodManagerCompatWrapper.getInstance(this);
mInputMethodId = Utils.getInputMethodId(mImm, getPackageName());
mSubtypeSwitcher = SubtypeSwitcher.getInstance();
mKeyboardSwitcher = KeyboardSwitcher.getInstance();
- mAccessibilityUtils = AccessibilityUtils.getInstance();
+ mRecorrection = Recorrection.getInstance();
DEBUG = LatinImeLogger.sDBG;
+ loadSettings();
+
final Resources res = getResources();
mResources = res;
- // 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.config_default_recorrection_enabled));
- } else {
- mRecorrectionEnabled = res.getBoolean(R.bool.config_default_recorrection_enabled);
- }
-
- mConfigEnableShowSubtypeSettings = res.getBoolean(
- R.bool.config_enable_show_subtype_settings);
- mConfigSwipeDownDismissKeyboardEnabled = res.getBoolean(
- R.bool.config_swipe_down_dismiss_keyboard_enabled);
- mConfigDelayBeforeFadeoutLanguageOnSpacebar = res.getInteger(
- R.integer.config_delay_before_fadeout_language_on_spacebar);
- mConfigDurationOfFadeoutLanguageOnSpacebar = res.getInteger(
- 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;
for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
@@ -429,49 +381,73 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
mOrientation = res.getConfiguration().orientation;
- initSuggestPuncList();
- // register to receive ringer mode change and network state change.
+ // Register to receive ringer mode change and network state change.
+ // Also receive installation and removal of a dictionary pack.
final IntentFilter filter = new IntentFilter();
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(mReceiver, filter);
- mVoiceConnector = VoiceIMEConnector.init(this, prefs, mHandler);
+ mVoiceProxy = VoiceProxy.init(this, prefs, mHandler);
+
+ final IntentFilter packageFilter = new IntentFilter();
+ packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ packageFilter.addDataScheme(SCHEME_PACKAGE);
+ registerReceiver(mDictionaryPackInstallReceiver, packageFilter);
+
+ final IntentFilter newDictFilter = new IntentFilter();
+ newDictFilter.addAction(
+ DictionaryPackInstallBroadcastReceiver.NEW_DICTIONARY_INTENT_ACTION);
+ registerReceiver(mDictionaryPackInstallReceiver, newDictFilter);
+ }
+
+ // Has to be package-visible for unit tests
+ /* package */ void loadSettings() {
+ if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+ if (null == mSubtypeSwitcher) mSubtypeSwitcher = SubtypeSwitcher.getInstance();
+ mSettingsValues = new Settings.Values(mPrefs, this, mSubtypeSwitcher.getInputLocaleStr());
}
private void initSuggest() {
- String locale = mSubtypeSwitcher.getInputLocaleStr();
+ final String localeStr = mSubtypeSwitcher.getInputLocaleStr();
+ final Locale keyboardLocale = Utils.constructLocaleFromString(localeStr);
- Locale savedLocale = mSubtypeSwitcher.changeSystemLocale(new Locale(locale));
+ final Resources res = mResources;
+ final Locale savedLocale = Utils.setSystemLocale(res, keyboardLocale);
if (mSuggest != null) {
mSuggest.close();
}
- final SharedPreferences prefs = mPrefs;
- mQuickFixes = isQuickFixesEnabled(prefs);
- final Resources res = mResources;
int mainDicResId = Utils.getMainDictionaryResourceId(res);
- mSuggest = new Suggest(this, mainDicResId);
- loadAndSetAutoCorrectionThreshold(prefs);
+ mSuggest = new Suggest(this, mainDicResId, keyboardLocale);
+ if (mSettingsValues.mAutoCorrectEnabled) {
+ mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold);
+ }
updateAutoTextEnabled();
- mUserDictionary = new UserDictionary(this, locale);
+ mUserDictionary = new UserDictionary(this, localeStr);
mSuggest.setUserDictionary(mUserDictionary);
mContactsDictionary = new ContactsDictionary(this, Suggest.DIC_CONTACTS);
mSuggest.setContactsDictionary(mContactsDictionary);
- mAutoDictionary = new AutoDictionary(this, this, locale, Suggest.DIC_AUTO);
+ mAutoDictionary = new AutoDictionary(this, this, localeStr, Suggest.DIC_AUTO);
mSuggest.setAutoDictionary(mAutoDictionary);
- mUserBigramDictionary = new UserBigramDictionary(this, this, locale, Suggest.DIC_USER);
+ mUserBigramDictionary = new UserBigramDictionary(this, this, localeStr, Suggest.DIC_USER);
mSuggest.setUserBigramDictionary(mUserBigramDictionary);
updateCorrectionMode();
- mWordSeparators = res.getString(R.string.word_separators);
- mSentenceSeparators = res.getString(R.string.sentence_separators);
- mSubtypeSwitcher.changeSystemLocale(savedLocale);
+ Utils.setSystemLocale(res, savedLocale);
+ }
+
+ /* package private */ void resetSuggestMainDict() {
+ final String localeStr = mSubtypeSwitcher.getInputLocaleStr();
+ final Locale keyboardLocale = Utils.constructLocaleFromString(localeStr);
+ int mainDicResId = Utils.getMainDictionaryResourceId(mResources);
+ mSuggest.resetMainDict(this, mainDicResId, keyboardLocale);
}
@Override
@@ -481,7 +457,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mSuggest = null;
}
unregisterReceiver(mReceiver);
- mVoiceConnector.destroy();
+ unregisterReceiver(mDictionaryPackInstallReceiver);
+ mVoiceProxy.destroy();
LatinImeLogger.commit();
LatinImeLogger.onDestroy();
super.onDestroy();
@@ -502,8 +479,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mConfigurationChanging = true;
super.onConfigurationChanged(conf);
- mVoiceConnector.onConfigurationChanged(conf);
+ mVoiceProxy.onConfigurationChanged(conf);
mConfigurationChanging = false;
+
+ // This will work only when the subtype is not supported.
+ LanguageSwitcherProxy.onConfigurationChanged(conf);
}
@Override
@@ -512,26 +492,24 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
@Override
- public View onCreateCandidatesView() {
- LayoutInflater inflater = getLayoutInflater();
- LinearLayout container = (LinearLayout)inflater.inflate(R.layout.candidates, null);
- mCandidateViewContainer = container;
- if (container.getPaddingRight() != 0) {
- HorizontalScrollView scrollView =
- (HorizontalScrollView) container.findViewById(R.id.candidates_scroll_view);
- scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER);
- container.setGravity(Gravity.CENTER_HORIZONTAL);
- }
- mCandidateView = (CandidateView) container.findViewById(R.id.candidates);
- mCandidateView.setService(this);
- setCandidatesViewShown(true);
- return container;
+ public void setInputView(View view) {
+ super.setInputView(view);
+ mCandidateViewContainer = view.findViewById(R.id.candidates_container);
+ mCandidateView = (CandidateView) view.findViewById(R.id.candidates);
+ mCandidateView.setListener(this);
+ mCandidateStripHeight = (int)mResources.getDimension(R.dimen.candidate_strip_height);
+ }
+
+ @Override
+ public void setCandidatesView(View view) {
+ // To ensure that CandidatesView will never be set.
+ return;
}
@Override
public void onStartInputView(EditorInfo attribute, boolean restarting) {
final KeyboardSwitcher switcher = mKeyboardSwitcher;
- LatinKeyboardView inputView = switcher.getInputView();
+ LatinKeyboardView inputView = switcher.getKeyboardView();
if (DEBUG) {
Log.d(TAG, "onStartInputView: inputType=" + ((attribute == null) ? "none"
@@ -549,20 +527,32 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Most such things we decide below in initializeInputAttributesAndGetMode, but we need to
// know now whether this is a password text field, because we need to know now whether we
// want to enable the voice button.
- final VoiceIMEConnector voiceIme = mVoiceConnector;
- voiceIme.resetVoiceStates(Utils.isPasswordInputType(attribute.inputType)
- || Utils.isVisiblePasswordInputType(attribute.inputType));
+ final VoiceProxy voiceIme = mVoiceProxy;
+ voiceIme.resetVoiceStates(InputTypeCompatUtils.isPasswordInputType(attribute.inputType)
+ || InputTypeCompatUtils.isVisiblePasswordInputType(attribute.inputType));
initializeInputAttributes(attribute);
inputView.closing();
mEnteredText = null;
mComposing.setLength(0);
- mHasValidSuggestions = false;
+ mHasUncommittedTypedChars = false;
mDeleteCount = 0;
- mJustAddedAutoSpace = false;
+ mJustAddedMagicSpace = false;
+ mJustReplacedDoubleSpace = false;
+
+ loadSettings();
+ updateCorrectionMode();
+ updateAutoTextEnabled();
+ updateSuggestionVisibility(mPrefs, mResources);
+
+ if (mSuggest != null && mSettingsValues.mAutoCorrectEnabled) {
+ mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold);
+ }
+ mVoiceProxy.loadSettings(attribute, mPrefs);
+ // This will work only when the subtype is not supported.
+ LanguageSwitcherProxy.loadSettings();
- loadSettings(attribute);
if (mSubtypeSwitcher.isKeyboardMode()) {
switcher.loadKeyboard(attribute,
mSubtypeSwitcher.isShortcutImeEnabled() && voiceIme.isVoiceButtonEnabled(),
@@ -570,20 +560,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
switcher.updateShiftState();
}
- setCandidatesViewShownInternal(isCandidateStripVisible(),
- false /* needsInputViewShown */ );
+ setSuggestionStripShownInternal(isCandidateStripVisible(), /* needsInputViewShown */ false);
// Delay updating suggestions because keyboard input view may not be shown at this point.
mHandler.postUpdateSuggestions();
updateCorrectionMode();
- final boolean accessibilityEnabled = mAccessibilityUtils.isAccessibilityEnabled();
-
- inputView.setPreviewEnabled(mPopupOn);
+ inputView.setKeyPreviewPopupEnabled(mSettingsValues.mKeyPreviewPopupOn,
+ mSettingsValues.mKeyPreviewPopupDismissDelay);
inputView.setProximityCorrectionEnabled(true);
- inputView.setAccessibilityEnabled(accessibilityEnabled);
// If we just entered a text field, maybe it has some old text that requires correction
- checkRecorrectionOnStart();
+ mRecorrection.checkRecorrectionOnStart();
inputView.setForeground(true);
voiceIme.onStartInputView(inputView.getWindowToken());
@@ -596,7 +583,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return;
final int inputType = attribute.inputType;
final int variation = inputType & InputType.TYPE_MASK_VARIATION;
- mAutoSpace = false;
+ mShouldInsertMagicSpace = false;
mInputTypeNoAutoCorrect = false;
mIsSettingsSuggestionStripOn = false;
mApplicationSpecifiedCompletionOn = false;
@@ -605,17 +592,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
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)) {
+ if (InputTypeCompatUtils.isPasswordInputType(inputType)
+ || InputTypeCompatUtils.isVisiblePasswordInputType(inputType)) {
mIsSettingsSuggestionStripOn = false;
}
- if (Utils.isEmailVariation(variation)
+ if (InputTypeCompatUtils.isEmailVariation(variation)
|| variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) {
- mAutoSpace = false;
+ mShouldInsertMagicSpace = false;
} else {
- mAutoSpace = true;
+ mShouldInsertMagicSpace = true;
}
- if (Utils.isEmailVariation(variation)) {
+ if (InputTypeCompatUtils.isEmailVariation(variation)) {
mIsSettingsSuggestionStripOn = false;
} else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
mIsSettingsSuggestionStripOn = false;
@@ -646,32 +633,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
- private void checkRecorrectionOnStart() {
- if (!mRecorrectionEnabled) return;
-
- final InputConnection ic = getCurrentInputConnection();
- if (ic == null) return;
- // There could be a pending composing span. Clean it up first.
- ic.finishComposingText();
-
- if (isShowingSuggestionsStrip() && isSuggestionsRequested()) {
- // First get the cursor position. This is required by setOldSuggestions(), so that
- // it can pass the correct range to setComposingRegion(). At this point, we don't
- // have valid values for mLastSelectionStart/End because onUpdateSelection() has
- // not been called yet.
- ExtractedTextRequest etr = new ExtractedTextRequest();
- etr.token = 0; // anything is fine here
- ExtractedText et = ic.getExtractedText(etr, 0);
- if (et == null) return;
-
- mLastSelectionStart = et.startOffset + et.selectionStart;
- mLastSelectionEnd = et.startOffset + et.selectionEnd;
-
- // Then look for possible corrections in a delayed fashion
- if (!TextUtils.isEmpty(et.text) && isCursorTouchingWord()) {
- mHandler.postUpdateOldSuggestions();
- }
- }
+ @Override
+ public void onWindowHidden() {
+ super.onWindowHidden();
+ KeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
+ if (inputView != null) inputView.closing();
}
@Override
@@ -681,9 +647,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
LatinImeLogger.commit();
mKeyboardSwitcher.onAutoCorrectionStateChanged(false);
- mVoiceConnector.flushVoiceInputLogs(mConfigurationChanging);
+ mVoiceProxy.flushVoiceInputLogs(mConfigurationChanging);
- KeyboardView inputView = mKeyboardSwitcher.getInputView();
+ KeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
if (inputView != null) inputView.closing();
if (mAutoDictionary != null) mAutoDictionary.flushPendingWrites();
if (mUserBigramDictionary != null) mUserBigramDictionary.flushPendingWrites();
@@ -692,7 +658,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onFinishInputView(boolean finishingInput) {
super.onFinishInputView(finishingInput);
- KeyboardView inputView = mKeyboardSwitcher.getInputView();
+ KeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
if (inputView != null) inputView.setForeground(false);
// Remove pending messages related to update suggestions
mHandler.cancelUpdateSuggestions();
@@ -702,7 +668,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onUpdateExtractedText(int token, ExtractedText text) {
super.onUpdateExtractedText(token, text);
- mVoiceConnector.showPunctuationHintIfNecessary();
+ mVoiceProxy.showPunctuationHintIfNecessary();
}
@Override
@@ -723,67 +689,62 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
+ ", ce=" + candidatesEnd);
}
- mVoiceConnector.setCursorAndSelection(newSelEnd, newSelStart);
+ mVoiceProxy.setCursorAndSelection(newSelEnd, newSelStart);
// If the current selection in the text view changes, we should
// clear whatever candidate text we have.
final boolean selectionChanged = (newSelStart != candidatesEnd
|| newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart;
final boolean candidatesCleared = candidatesStart == -1 && candidatesEnd == -1;
- if (((mComposing.length() > 0 && mHasValidSuggestions)
- || mVoiceConnector.isVoiceInputHighlighted())
+ if (((mComposing.length() > 0 && mHasUncommittedTypedChars)
+ || mVoiceProxy.isVoiceInputHighlighted())
&& (selectionChanged || candidatesCleared)) {
if (candidatesCleared) {
// If the composing span has been cleared, save the typed word in the history for
// recorrection before we reset the candidate strip. Then, we'll be able to show
// suggestions for recorrection right away.
- saveWordInHistory(mComposing);
+ mRecorrection.saveRecorrectionSuggestion(mWord, mComposing);
}
mComposing.setLength(0);
- mHasValidSuggestions = false;
- mHandler.postUpdateSuggestions();
+ mHasUncommittedTypedChars = false;
+ if (isCursorTouchingWord()) {
+ mHandler.cancelUpdateBigramPredictions();
+ mHandler.postUpdateSuggestions();
+ } else {
+ setPunctuationSuggestions();
+ }
TextEntryState.reset();
InputConnection ic = getCurrentInputConnection();
if (ic != null) {
ic.finishComposingText();
}
- mVoiceConnector.setVoiceInputHighlighted(false);
- } else if (!mHasValidSuggestions && !mJustAccepted) {
+ mVoiceProxy.setVoiceInputHighlighted(false);
+ } else if (!mHasUncommittedTypedChars && !mExpectingUpdateSelection) {
if (TextEntryState.isAcceptedDefault() || TextEntryState.isSpaceAfterPicked()) {
if (TextEntryState.isAcceptedDefault())
TextEntryState.reset();
- mJustAddedAutoSpace = false; // The user moved the cursor.
}
}
- mJustAccepted = false;
+ if (!mExpectingUpdateSelection) {
+ mJustAddedMagicSpace = false; // The user moved the cursor.
+ mJustReplacedDoubleSpace = false;
+ }
+ mExpectingUpdateSelection = false;
mHandler.postUpdateShiftKeyState();
// Make a note of the cursor position
mLastSelectionStart = newSelStart;
mLastSelectionEnd = newSelEnd;
- if (mRecorrectionEnabled && 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()
- && (candidatesStart == candidatesEnd || newSelStart != oldSelStart
- || TextEntryState.isRecorrecting())
- && (newSelStart < newSelEnd - 1 || !mHasValidSuggestions)) {
- if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) {
- mHandler.postUpdateOldSuggestions();
- } else {
- 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()
- && !mCandidateView.isShowingAddToDictionaryHint()) {
- setPunctuationSuggestions();
- }
- }
- }
- }
- }
+ mRecorrection.updateRecorrectionSelection(mKeyboardSwitcher,
+ mCandidateView, candidatesStart, candidatesEnd, newSelStart,
+ newSelEnd, oldSelStart, mLastSelectionStart,
+ mLastSelectionEnd, mHasUncommittedTypedChars);
+ }
+
+ public void setLastSelection(int start, int end) {
+ mLastSelectionStart = start;
+ mLastSelectionEnd = end;
}
/**
@@ -796,7 +757,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
*/
@Override
public void onExtractedTextClicked() {
- if (mRecorrectionEnabled && isSuggestionsRequested()) return;
+ if (mRecorrection.isRecorrectionEnabled() && isSuggestionsRequested()) return;
super.onExtractedTextClicked();
}
@@ -812,7 +773,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
*/
@Override
public void onExtractedCursorMovement(int dx, int dy) {
- if (mRecorrectionEnabled && isSuggestionsRequested()) return;
+ if (mRecorrection.isRecorrectionEnabled() && isSuggestionsRequested()) return;
super.onExtractedCursorMovement(dx, dy);
}
@@ -827,8 +788,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mOptionsDialog.dismiss();
mOptionsDialog = null;
}
- mVoiceConnector.hideVoiceWindow(mConfigurationChanging);
- mWordHistory.clear();
+ mVoiceProxy.hideVoiceWindow(mConfigurationChanging);
+ mRecorrection.clearWordsInHistory();
super.hideWindow();
}
@@ -856,57 +817,58 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// When in fullscreen mode, show completions generated by the application
setSuggestions(builder.build());
mBestWord = null;
- setCandidatesViewShown(true);
+ setSuggestionStripShown(true);
}
}
- private void setCandidatesViewShownInternal(boolean shown, boolean needsInputViewShown) {
- // TODO: Remove this if we support candidates with hard keyboard
+ private void setSuggestionStripShownInternal(boolean shown, boolean needsInputViewShown) {
+ // TODO: Modify this if we support candidates with hard keyboard
if (onEvaluateInputViewShown()) {
- super.setCandidatesViewShown(shown
- && (needsInputViewShown ? mKeyboardSwitcher.isInputViewShown() : true));
+ final boolean shouldShowCandidates = shown
+ && (needsInputViewShown ? mKeyboardSwitcher.isInputViewShown() : true);
+ if (isExtractViewShown()) {
+ // No need to have extra space to show the key preview.
+ mCandidateViewContainer.setMinimumHeight(0);
+ mCandidateViewContainer.setVisibility(
+ shouldShowCandidates ? View.VISIBLE : View.GONE);
+ } else {
+ // We must control the visibility of the suggestion strip in order to avoid clipped
+ // key previews, even when we don't show the suggestion strip.
+ mCandidateViewContainer.setVisibility(
+ shouldShowCandidates ? View.VISIBLE : View.INVISIBLE);
+ }
}
}
- @Override
- public void setCandidatesViewShown(boolean shown) {
- setCandidatesViewShownInternal(shown, true /* needsInputViewShown */ );
+ private void setSuggestionStripShown(boolean shown) {
+ setSuggestionStripShownInternal(shown, /* needsInputViewShown */true);
}
@Override
public void onComputeInsets(InputMethodService.Insets outInsets) {
super.onComputeInsets(outInsets);
- if (!isFullscreenMode()) {
- outInsets.contentTopInsets = outInsets.visibleTopInsets;
- }
- KeyboardView inputView = mKeyboardSwitcher.getInputView();
+ final KeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
+ if (inputView == null)
+ return;
+ final int containerHeight = mCandidateViewContainer.getHeight();
+ int touchY = containerHeight;
// Need to set touchable region only if input view is being shown
- if (inputView != null && mKeyboardSwitcher.isInputViewShown()) {
- final int x = 0;
- int y = 0;
- final int width = inputView.getWidth();
- int height = inputView.getHeight() + EXTENDED_TOUCHABLE_REGION_HEIGHT;
- if (mCandidateViewContainer != null) {
- ViewParent candidateParent = mCandidateViewContainer.getParent();
- if (candidateParent instanceof FrameLayout) {
- FrameLayout fl = (FrameLayout) candidateParent;
- if (fl != null) {
- // Check frame layout's visibility
- if (fl.getVisibility() == View.INVISIBLE) {
- y = fl.getHeight();
- height += y;
- } else if (fl.getVisibility() == View.VISIBLE) {
- height += fl.getHeight();
- }
- }
- }
+ if (mKeyboardSwitcher.isInputViewShown()) {
+ if (mCandidateViewContainer.getVisibility() == View.VISIBLE) {
+ touchY -= mCandidateStripHeight;
}
+ final int touchWidth = inputView.getWidth();
+ final int touchHeight = inputView.getHeight() + containerHeight
+ // Extend touchable region below the keyboard.
+ + EXTENDED_TOUCHABLE_REGION_HEIGHT;
if (DEBUG) {
- Log.d(TAG, "Touchable region " + x + ", " + y + ", " + width + ", " + height);
+ Log.d(TAG, "Touchable region: y=" + touchY + " width=" + touchWidth
+ + " height=" + touchHeight);
}
- outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION;
- outInsets.touchableRegion.set(x, y, width, height);
+ setTouchableRegionCompat(outInsets, 0, touchY, touchWidth, touchHeight);
}
+ outInsets.contentTopInsets = touchY;
+ outInsets.visibleTopInsets = touchY;
}
@Override
@@ -927,8 +889,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
- if (event.getRepeatCount() == 0 && mKeyboardSwitcher.getInputView() != null) {
- if (mKeyboardSwitcher.getInputView().handleBack()) {
+ if (event.getRepeatCount() == 0 && mKeyboardSwitcher.getKeyboardView() != null) {
+ if (mKeyboardSwitcher.getKeyboardView().handleBack()) {
return true;
}
}
@@ -962,8 +924,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
public void commitTyped(InputConnection inputConnection) {
- if (mHasValidSuggestions) {
- mHasValidSuggestions = false;
+ if (mHasUncommittedTypedChars) {
+ mHasUncommittedTypedChars = false;
if (mComposing.length() > 0) {
if (inputConnection != null) {
inputConnection.commitText(mComposing, 1);
@@ -979,45 +941,29 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public boolean getCurrentAutoCapsState() {
InputConnection ic = getCurrentInputConnection();
EditorInfo ei = getCurrentInputEditorInfo();
- if (mAutoCap && ic != null && ei != null && ei.inputType != InputType.TYPE_NULL) {
+ if (mSettingsValues.mAutoCap && ic != null && ei != null
+ && ei.inputType != InputType.TYPE_NULL) {
return ic.getCursorCapsMode(ei.inputType) != 0;
}
return false;
}
- private void swapPunctuationAndSpace() {
+ private void swapSwapperAndSpace() {
final InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
CharSequence lastTwo = ic.getTextBeforeCursor(2, 0);
+ // It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called.
if (lastTwo != null && lastTwo.length() == 2
- && lastTwo.charAt(0) == Keyboard.CODE_SPACE
- && isSentenceSeparator(lastTwo.charAt(1))) {
+ && lastTwo.charAt(0) == Keyboard.CODE_SPACE) {
ic.beginBatchEdit();
ic.deleteSurroundingText(2, 0);
ic.commitText(lastTwo.charAt(1) + " ", 1);
ic.endBatchEdit();
mKeyboardSwitcher.updateShiftState();
- mJustAddedAutoSpace = true;
}
}
- private void reswapPeriodAndSpace() {
- final InputConnection ic = getCurrentInputConnection();
- if (ic == null) return;
- CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
- if (lastThree != null && lastThree.length() == 3
- && lastThree.charAt(0) == Keyboard.CODE_PERIOD
- && lastThree.charAt(1) == Keyboard.CODE_SPACE
- && lastThree.charAt(2) == Keyboard.CODE_PERIOD) {
- ic.beginBatchEdit();
- ic.deleteSurroundingText(3, 0);
- ic.commitText(" ..", 1);
- ic.endBatchEdit();
- mKeyboardSwitcher.updateShiftState();
- }
- }
-
- private void doubleSpace() {
+ private void maybeDoubleSpace() {
if (mCorrectionMode == Suggest.CORRECTION_NONE) return;
final InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
@@ -1033,7 +979,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
ic.commitText(". ", 1);
ic.endBatchEdit();
mKeyboardSwitcher.updateShiftState();
- mJustAddedAutoSpace = true;
+ mJustReplacedDoubleSpace = true;
} else {
mHandler.startDoubleSpacesTimer();
}
@@ -1064,6 +1010,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
+ @Override
public boolean addWordToDictionary(String word) {
mUserDictionary.addWord(word, 128);
// Suggestion strip should be updated after the operation of adding word to the
@@ -1081,14 +1028,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
private void onSettingsKeyPressed() {
- if (!isShowingOptionDialog()) {
- if (!mConfigEnableShowSubtypeSettings) {
- showSubtypeSelectorAndSettings();
- } else if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm)) {
- showOptionsMenu();
- } else {
- launchSettings();
- }
+ if (isShowingOptionDialog())
+ return;
+ if (InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) {
+ showSubtypeSelectorAndSettings();
+ } else if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm)) {
+ showOptionsMenu();
+ } else {
+ launchSettings();
}
}
@@ -1115,22 +1062,24 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
mLastKeyTime = when;
KeyboardSwitcher switcher = mKeyboardSwitcher;
- final boolean accessibilityEnabled = switcher.isAccessibilityEnabled();
final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
+ final boolean lastStateOfJustReplacedDoubleSpace = mJustReplacedDoubleSpace;
+ mJustReplacedDoubleSpace = false;
switch (primaryCode) {
case Keyboard.CODE_DELETE:
- handleBackspace();
+ handleBackspace(lastStateOfJustReplacedDoubleSpace);
mDeleteCount++;
+ mExpectingUpdateSelection = true;
LatinImeLogger.logOnDelete();
break;
case Keyboard.CODE_SHIFT:
// Shift key is handled in onPress() when device has distinct multi-touch panel.
- if (!distinctMultiTouch || accessibilityEnabled)
+ if (!distinctMultiTouch)
switcher.toggleShift();
break;
case Keyboard.CODE_SWITCH_ALPHA_SYMBOL:
// Symbol key is handled in onPress() when device has distinct multi-touch panel.
- if (!distinctMultiTouch || accessibilityEnabled)
+ if (!distinctMultiTouch)
switcher.changeKeyboardMode();
break;
case Keyboard.CODE_CANCEL:
@@ -1144,32 +1093,37 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
case Keyboard.CODE_SETTINGS_LONGPRESS:
onSettingsKeyLongPressed();
break;
- case Keyboard.CODE_NEXT_LANGUAGE:
- toggleLanguage(false, true);
+ case LatinKeyboard.CODE_NEXT_LANGUAGE:
+ toggleLanguage(true);
break;
- case Keyboard.CODE_PREV_LANGUAGE:
- toggleLanguage(false, false);
+ case LatinKeyboard.CODE_PREV_LANGUAGE:
+ toggleLanguage(false);
break;
case Keyboard.CODE_CAPSLOCK:
switcher.toggleCapsLock();
break;
- case Keyboard.CODE_VOICE:
+ case Keyboard.CODE_SHORTCUT:
mSubtypeSwitcher.switchToShortcutIME();
break;
case Keyboard.CODE_TAB:
handleTab();
+ // There are two cases for tab. Either we send a "next" event, that may change the
+ // focus but will never move the cursor. Or, we send a real tab keycode, which some
+ // applications may accept or ignore, and we don't know whether this will move the
+ // cursor or not. So actually, we don't really know.
+ // So to go with the safer option, we'd rather behave as if the user moved the
+ // cursor when they didn't than the opposite. We also expect that most applications
+ // will actually use tab only for focus movement.
+ // To sum it up: do not update mExpectingUpdateSelection here.
break;
default:
- if (primaryCode != Keyboard.CODE_ENTER) {
- mJustAddedAutoSpace = false;
- }
- RingCharBuffer.getInstance().push((char)primaryCode, x, y);
- LatinImeLogger.logOnInputChar();
- if (isWordSeparator(primaryCode)) {
- handleSeparator(primaryCode);
+ if (mSettingsValues.isWordSeparator(primaryCode)) {
+ handleSeparator(primaryCode, x, y);
} else {
handleCharacter(primaryCode, keyCodes, x, y);
}
+ mExpectingUpdateSelection = true;
+ break;
}
switcher.onKey(primaryCode);
// Reset after any single keystroke
@@ -1178,10 +1132,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onTextInput(CharSequence text) {
- mVoiceConnector.commitVoiceInput();
+ mVoiceProxy.commitVoiceInput();
InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
- abortRecorrection(false);
+ mRecorrection.abortRecorrection(false);
ic.beginBatchEdit();
commitTyped(ic);
maybeRemovePreviousPeriod(text);
@@ -1189,7 +1143,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
ic.endBatchEdit();
mKeyboardSwitcher.updateShiftState();
mKeyboardSwitcher.onKey(Keyboard.CODE_DUMMY);
- mJustAddedAutoSpace = false;
+ mJustAddedMagicSpace = false;
mEnteredText = text;
}
@@ -1199,26 +1153,33 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mKeyboardSwitcher.onCancelInput();
}
- private void handleBackspace() {
- if (mVoiceConnector.logAndRevertVoiceInput()) return;
+ private void handleBackspace(boolean justReplacedDoubleSpace) {
+ if (mVoiceProxy.logAndRevertVoiceInput()) return;
final InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
ic.beginBatchEdit();
- mVoiceConnector.handleBackspace();
+ mVoiceProxy.handleBackspace();
boolean deleteChar = false;
- if (mHasValidSuggestions) {
+ if (mHasUncommittedTypedChars) {
final int length = mComposing.length();
if (length > 0) {
mComposing.delete(length - 1, length);
mWord.deleteLast();
ic.setComposingText(mComposing, 1);
if (mComposing.length() == 0) {
- mHasValidSuggestions = false;
+ mHasUncommittedTypedChars = false;
+ }
+ if (1 == length) {
+ // 1 == length means we are about to erase the last character of the word,
+ // so we can show bigrams.
+ mHandler.postUpdateBigramPredictions();
+ } else {
+ // length > 1, so we still have letters to deduce a suggestion from.
+ mHandler.postUpdateSuggestions();
}
- mHandler.postUpdateSuggestions();
} else {
ic.deleteSurroundingText(1, 0);
}
@@ -1233,6 +1194,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
ic.endBatchEdit();
return;
}
+ if (justReplacedDoubleSpace) {
+ if (revertDoubleSpace()) {
+ ic.endBatchEdit();
+ return;
+ }
+ }
if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) {
ic.deleteSurroundingText(mEnteredText.length(), 0);
@@ -1258,9 +1225,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void handleTab() {
final int imeOptions = getCurrentInputEditorInfo().imeOptions;
- final int navigationFlags =
- EditorInfo.IME_FLAG_NAVIGATE_NEXT | EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
- if ((imeOptions & navigationFlags) == 0) {
+ if (!EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions)
+ && !EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions)) {
sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB);
return;
}
@@ -1271,37 +1237,32 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// True if keyboard is in either chording shift or manual temporary upper case mode.
final boolean isManualTemporaryUpperCase = mKeyboardSwitcher.isManualTemporaryUpperCase();
- if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0
+ if (EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions)
&& !isManualTemporaryUpperCase) {
- ic.performEditorAction(EditorInfo.IME_ACTION_NEXT);
- } else if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS) != 0
+ EditorInfoCompatUtils.performEditorActionNext(ic);
+ } else if (EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions)
&& isManualTemporaryUpperCase) {
- ic.performEditorAction(EditorInfo.IME_ACTION_PREVIOUS);
- }
- }
-
- private void abortRecorrection(boolean force) {
- if (force || TextEntryState.isRecorrecting()) {
- TextEntryState.onAbortRecorrection();
- setCandidatesViewShown(isCandidateStripVisible());
- getCurrentInputConnection().finishComposingText();
- clearSuggestions();
+ EditorInfoCompatUtils.performEditorActionPrevious(ic);
}
}
private void handleCharacter(int primaryCode, int[] keyCodes, int x, int y) {
- mVoiceConnector.handleCharacter();
+ mVoiceProxy.handleCharacter();
- if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isRecorrecting()) {
- abortRecorrection(false);
+ if (mJustAddedMagicSpace && mSettingsValues.isMagicSpaceStripper(primaryCode)) {
+ removeTrailingSpace();
+ }
+
+ if (mLastSelectionStart == mLastSelectionEnd) {
+ mRecorrection.abortRecorrection(false);
}
int code = primaryCode;
if (isAlphabet(code) && isSuggestionsRequested() && !isCursorTouchingWord()) {
- if (!mHasValidSuggestions) {
- mHasValidSuggestions = true;
+ if (!mHasUncommittedTypedChars) {
+ mHasUncommittedTypedChars = true;
mComposing.setLength(0);
- saveWordInHistory(mBestWord);
+ mRecorrection.saveRecorrectionSuggestion(mWord, mBestWord);
mWord.reset();
clearSuggestions();
}
@@ -1325,7 +1286,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
}
- if (mHasValidSuggestions) {
+ if (mHasUncommittedTypedChars) {
if (mComposing.length() == 0 && switcher.isAlphabetMode()
&& switcher.isShiftedOrShiftLocked()) {
mWord.setFirstCharCapitalized(true);
@@ -1344,16 +1305,23 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} else {
sendKeyChar((char)code);
}
+ if (mJustAddedMagicSpace && mSettingsValues.isMagicSpaceSwapper(primaryCode)) {
+ swapSwapperAndSpace();
+ } else {
+ mJustAddedMagicSpace = false;
+ }
+
switcher.updateShiftState();
if (LatinIME.PERF_DEBUG) measureCps();
- TextEntryState.typedCharacter((char) code, isWordSeparator(code));
+ TextEntryState.typedCharacter((char) code, mSettingsValues.isWordSeparator(code), x, y);
}
- private void handleSeparator(int primaryCode) {
- mVoiceConnector.handleSeparator();
+ private void handleSeparator(int primaryCode, int x, int y) {
+ mVoiceProxy.handleSeparator();
// Should dismiss the "Touch again to save" message when handling separator
if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) {
+ mHandler.cancelUpdateBigramPredictions();
mHandler.postUpdateSuggestions();
}
@@ -1362,54 +1330,61 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final InputConnection ic = getCurrentInputConnection();
if (ic != null) {
ic.beginBatchEdit();
- abortRecorrection(false);
+ mRecorrection.abortRecorrection(false);
}
- if (mHasValidSuggestions) {
+ if (mHasUncommittedTypedChars) {
// In certain languages where single quote is a separator, it's better
// not to auto correct, but accept the typed word. For instance,
// in Italian dov' should not be expanded to dove' because the elision
// requires the last vowel to be removed.
- if (mAutoCorrectOn && primaryCode != '\'') {
- pickedDefault = pickDefaultSuggestion();
- // Picked the suggestion by the space key. We consider this
- // as "added an auto space".
- if (primaryCode == Keyboard.CODE_SPACE) {
- mJustAddedAutoSpace = true;
- }
+ final boolean shouldAutoCorrect =
+ (mSettingsValues.mAutoCorrectEnabled || mSettingsValues.mQuickFixes)
+ && !mInputTypeNoAutoCorrect && mHasDictionary;
+ if (shouldAutoCorrect && primaryCode != Keyboard.CODE_SINGLE_QUOTE) {
+ pickedDefault = pickDefaultSuggestion(primaryCode);
} else {
commitTyped(ic);
}
}
- if (mJustAddedAutoSpace && primaryCode == Keyboard.CODE_ENTER) {
- removeTrailingSpace();
- mJustAddedAutoSpace = false;
- }
- sendKeyChar((char)primaryCode);
- // Handle the case of ". ." -> " .." with auto-space if necessary
- // before changing the TextEntryState.
- if (TextEntryState.isPunctuationAfterAccepted() && primaryCode == Keyboard.CODE_PERIOD) {
- reswapPeriodAndSpace();
+ if (mJustAddedMagicSpace) {
+ if (mSettingsValues.isMagicSpaceSwapper(primaryCode)) {
+ sendKeyChar((char)primaryCode);
+ swapSwapperAndSpace();
+ } else {
+ if (mSettingsValues.isMagicSpaceStripper(primaryCode)) removeTrailingSpace();
+ sendKeyChar((char)primaryCode);
+ mJustAddedMagicSpace = false;
+ }
+ } else {
+ sendKeyChar((char)primaryCode);
}
- TextEntryState.typedCharacter((char) primaryCode, true);
- if (TextEntryState.isPunctuationAfterAccepted() && primaryCode != Keyboard.CODE_ENTER) {
- swapPunctuationAndSpace();
- } else if (isSuggestionsRequested() && primaryCode == Keyboard.CODE_SPACE) {
- doubleSpace();
+ if (isSuggestionsRequested() && primaryCode == Keyboard.CODE_SPACE) {
+ maybeDoubleSpace();
}
+
+ TextEntryState.typedCharacter((char) primaryCode, true, x, y);
+
if (pickedDefault) {
CharSequence typedWord = mWord.getTypedWord();
TextEntryState.backToAcceptedDefault(typedWord);
if (!TextUtils.isEmpty(typedWord) && !typedWord.equals(mBestWord)) {
- if (ic != null) {
- CorrectionInfo correctionInfo = new CorrectionInfo(
- mLastSelectionEnd - typedWord.length(), typedWord, mBestWord);
- ic.commitCorrection(correctionInfo);
- }
+ InputConnectionCompatUtils.commitCorrection(
+ ic, mLastSelectionEnd - typedWord.length(), typedWord, mBestWord);
if (mCandidateView != null)
mCandidateView.onAutoCorrectionInverted(mBestWord);
}
+ }
+ if (Keyboard.CODE_SPACE == primaryCode) {
+ if (!isCursorTouchingWord()) {
+ mHandler.cancelUpdateSuggestions();
+ mHandler.cancelUpdateOldSuggestions();
+ mHandler.postUpdateBigramPredictions();
+ }
+ } else {
+ // Set punctuation right away. onUpdateSelection will fire but tests whether it is
+ // already displayed or not, so it's okay.
setPunctuationSuggestions();
}
mKeyboardSwitcher.updateShiftState();
@@ -1420,45 +1395,29 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void handleClose() {
commitTyped(getCurrentInputConnection());
- mVoiceConnector.handleClose();
+ mVoiceProxy.handleClose();
requestHideSelf(0);
- LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
+ LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
if (inputView != null)
inputView.closing();
}
- private void saveWordInHistory(CharSequence result) {
- if (mWord.size() <= 1) {
- return;
- }
- // Skip if result is null. It happens in some edge case.
- if (TextUtils.isEmpty(result)) {
- return;
- }
-
- // Make a copy of the CharSequence, since it is/could be a mutable CharSequence
- final String resultCopy = result.toString();
- TypedWordAlternatives entry = new TypedWordAlternatives(resultCopy,
- new WordComposer(mWord));
- mWordHistory.add(entry);
- }
-
- private boolean isSuggestionsRequested() {
+ public boolean isSuggestionsRequested() {
return mIsSettingsSuggestionStripOn
&& (mCorrectionMode > 0 || isShowingSuggestionsStrip());
}
- private boolean isShowingPunctuationList() {
- return mSuggestPuncList == mCandidateView.getSuggestions();
+ public boolean isShowingPunctuationList() {
+ return mSettingsValues.mSuggestPuncList == mCandidateView.getSuggestions();
}
- private boolean isShowingSuggestionsStrip() {
+ public boolean isShowingSuggestionsStrip() {
return (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_VALUE)
|| (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE
&& mOrientation == Configuration.ORIENTATION_PORTRAIT);
}
- private boolean isCandidateStripVisible() {
+ public boolean isCandidateStripVisible() {
if (mCandidateView == null)
return false;
if (mCandidateView.isShowingAddToDictionaryHint() || TextEntryState.isRecorrecting())
@@ -1474,7 +1433,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (DEBUG) {
Log.d(TAG, "Switch to keyboard view.");
}
- View v = mKeyboardSwitcher.getInputView();
+ View v = mKeyboardSwitcher.getKeyboardView();
if (v != null) {
// Confirms that the keyboard view doesn't have parent view.
ViewParent p = v.getParent();
@@ -1483,7 +1442,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
setInputView(v);
}
- setCandidatesViewShown(isCandidateStripVisible());
+ setSuggestionStripShown(isCandidateStripVisible());
updateInputViewShown();
mHandler.postUpdateSuggestions();
}
@@ -1493,9 +1452,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
public void setSuggestions(SuggestedWords words) {
- if (mVoiceConnector.getAndResetIsShowingHint()) {
- setCandidatesView(mCandidateViewContainer);
- }
+// if (mVoiceProxy.getAndResetIsShowingHint()) {
+// setCandidatesView(mCandidateViewContainer);
+// }
if (mCandidateView != null) {
mCandidateView.setSuggestions(words);
@@ -1509,33 +1468,23 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void updateSuggestions() {
// Check if we have a suggestion engine attached.
if ((mSuggest == null || !isSuggestionsRequested())
- && !mVoiceConnector.isVoiceInputHighlighted()) {
+ && !mVoiceProxy.isVoiceInputHighlighted()) {
return;
}
- if (!mHasValidSuggestions) {
+ if (!mHasUncommittedTypedChars) {
setPunctuationSuggestions();
return;
}
showSuggestions(mWord);
}
- private SuggestedWords.Builder getTypedSuggestions(WordComposer word) {
- return mSuggest.getSuggestedWordBuilder(mKeyboardSwitcher.getInputView(), word, null);
- }
-
- private void showCorrections(WordAlternatives alternatives) {
- SuggestedWords.Builder builder = alternatives.getAlternatives();
- builder.setTypedWordValid(false).setHasMinimalSuggestion(false);
- showSuggestions(builder.build(), alternatives.getOriginalWord());
- }
-
private void showSuggestions(WordComposer word) {
// TODO: May need a better way of retrieving previous word
CharSequence prevWord = EditingUtils.getPreviousWord(getCurrentInputConnection(),
- mWordSeparators);
+ mSettingsValues.mWordSeparators);
SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(
- mKeyboardSwitcher.getInputView(), word, prevWord);
+ mKeyboardSwitcher.getKeyboardView(), word, prevWord);
boolean correctionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasAutoCorrection();
final CharSequence typedWord = word.getTypedWord();
@@ -1556,19 +1505,22 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// in most cases, suggestion count is 1 when typed word's length is 1, but we do always
// need to clear the previous state when the user starts typing a word (i.e. typed word's
// length == 1).
- if (builder.size() > 1 || typedWord.length() == 1 || typedWordValid
- || mCandidateView.isShowingAddToDictionaryHint()) {
- builder.setTypedWordValid(typedWordValid).setHasMinimalSuggestion(correctionAvailable);
- } else {
- final SuggestedWords previousSuggestions = mCandidateView.getSuggestions();
- if (previousSuggestions == mSuggestPuncList)
- return;
- builder.addTypedWordAndPreviousSuggestions(typedWord, previousSuggestions);
+ if (typedWord != null) {
+ if (builder.size() > 1 || typedWord.length() == 1 || typedWordValid
+ || mCandidateView.isShowingAddToDictionaryHint()) {
+ builder.setTypedWordValid(typedWordValid).setHasMinimalSuggestion(
+ correctionAvailable);
+ } else {
+ final SuggestedWords previousSuggestions = mCandidateView.getSuggestions();
+ if (previousSuggestions == mSettingsValues.mSuggestPuncList)
+ return;
+ builder.addTypedWordAndPreviousSuggestions(typedWord, previousSuggestions);
+ }
}
showSuggestions(builder.build(), typedWord);
}
- private void showSuggestions(SuggestedWords suggestedWords, CharSequence typedWord) {
+ public void showSuggestions(SuggestedWords suggestedWords, CharSequence typedWord) {
setSuggestions(suggestedWords);
if (suggestedWords.size() > 0) {
if (Utils.shouldBlockedBySafetyNetForAutoCorrection(suggestedWords, mSuggest)) {
@@ -1581,30 +1533,31 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} else {
mBestWord = null;
}
- setCandidatesViewShown(isCandidateStripVisible());
+ setSuggestionStripShown(isCandidateStripVisible());
}
- private boolean pickDefaultSuggestion() {
+ private boolean pickDefaultSuggestion(int separatorCode) {
// Complete any pending candidate query first
if (mHandler.hasPendingUpdateSuggestions()) {
mHandler.cancelUpdateSuggestions();
updateSuggestions();
}
if (mBestWord != null && mBestWord.length() > 0) {
- TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord);
- mJustAccepted = true;
- pickSuggestion(mBestWord);
+ TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord, separatorCode);
+ mExpectingUpdateSelection = true;
+ commitBestWord(mBestWord);
// Add the word to the auto dictionary if it's not a known word
addToAutoAndUserBigramDictionaries(mBestWord, AutoDictionary.FREQUENCY_FOR_TYPED);
return true;
-
}
return false;
}
+ @Override
public void pickSuggestionManually(int index, CharSequence suggestion) {
SuggestedWords suggestions = mCandidateView.getSuggestions();
- mVoiceConnector.flushAndLogAllTextModificationCounters(index, suggestion, mWordSeparators);
+ mVoiceProxy.flushAndLogAllTextModificationCounters(index, suggestion,
+ mSettingsValues.mWordSeparators);
final boolean recorrecting = TextEntryState.isRecorrecting();
InputConnection ic = getCurrentInputConnection();
@@ -1629,23 +1582,38 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
// If this is a punctuation, apply it through the normal key press
- if (suggestion.length() == 1 && (isWordSeparator(suggestion.charAt(0))
- || isSuggestedPunctuation(suggestion.charAt(0)))) {
+ if (suggestion.length() == 1 && (mSettingsValues.isWordSeparator(suggestion.charAt(0))
+ || mSettingsValues.isSuggestedPunctuation(suggestion.charAt(0)))) {
// Word separators are suggested before the user inputs something.
// So, LatinImeLogger logs "" as a user's input.
LatinImeLogger.logOnManualSuggestion(
"", suggestion.toString(), index, suggestions.mWords);
+ // Find out whether the previous character is a space. If it is, as a special case
+ // for punctuation entered through the suggestion strip, it should be considered
+ // a magic space even if it was a normal space. This is meant to help in case the user
+ // pressed space on purpose of displaying the suggestion strip punctuation.
final char primaryCode = suggestion.charAt(0);
+ final CharSequence beforeText = ic != null ? ic.getTextBeforeCursor(1, 0) : "";
+ final int toLeft = (ic == null || TextUtils.isEmpty(beforeText))
+ ? 0 : beforeText.charAt(0);
+ final boolean oldMagicSpace = mJustAddedMagicSpace;
+ if (Keyboard.CODE_SPACE == toLeft) mJustAddedMagicSpace = true;
onCodeInput(primaryCode, new int[] { primaryCode },
KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
+ mJustAddedMagicSpace = oldMagicSpace;
if (ic != null) {
ic.endBatchEdit();
}
return;
}
- mJustAccepted = true;
- pickSuggestion(suggestion);
+ if (!mHasUncommittedTypedChars) {
+ // If we are not composing a word, then it was a suggestion inferred from
+ // context - no user input. We should reset the word composer.
+ mWord.reset();
+ }
+ mExpectingUpdateSelection = true;
+ commitBestWord(suggestion);
// Add the word to the auto dictionary if it's not a known word
if (index == 0) {
addToAutoAndUserBigramDictionaries(suggestion, AutoDictionary.FREQUENCY_FOR_PICKED);
@@ -1656,9 +1624,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
index, suggestions.mWords);
TextEntryState.acceptedSuggestion(mComposing.toString(), suggestion);
// Follow it with a space
- if (mAutoSpace && !recorrecting) {
- sendSpace();
- mJustAddedAutoSpace = true;
+ if (mShouldInsertMagicSpace && !recorrecting) {
+ sendMagicSpace();
}
// We should show the hint if the user pressed the first entry AND either:
@@ -1680,13 +1647,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Fool the state watcher so that a subsequent backspace will not do a revert, unless
// we just did a correction, in which case we need to stay in
// TextEntryState.State.PICKED_SUGGESTION state.
- TextEntryState.typedCharacter((char) Keyboard.CODE_SPACE, true);
- setPunctuationSuggestions();
- } else if (!showingAddToDictionaryHint) {
+ TextEntryState.typedCharacter((char) Keyboard.CODE_SPACE, true,
+ WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
+ }
+ if (!showingAddToDictionaryHint) {
// If we're not showing the "Touch again to save", then show corrections again.
// In case the cursor position doesn't change, make sure we show the suggestions again.
- clearSuggestions();
- mHandler.postUpdateOldSuggestions();
+ updateBigramPredictions();
+ // Updating the predictions right away may be slow and feel unresponsive on slower
+ // terminals. On the other hand if we just postUpdateBigramPredictions() it will
+ // take a noticeable delay to update them which may feel uneasy.
}
if (showingAddToDictionaryHint) {
mCandidateView.showAddToDictionaryHint(suggestion);
@@ -1699,107 +1669,50 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
/**
* Commits the chosen word to the text field and saves it for later
* retrieval.
- * @param suggestion the suggestion picked by the user to be committed to
- * the text field
*/
- private void pickSuggestion(CharSequence suggestion) {
+ private void commitBestWord(CharSequence bestWord) {
KeyboardSwitcher switcher = mKeyboardSwitcher;
if (!switcher.isKeyboardAvailable())
return;
InputConnection ic = getCurrentInputConnection();
if (ic != null) {
- mVoiceConnector.rememberReplacedWord(suggestion, mWordSeparators);
- ic.commitText(suggestion, 1);
+ mVoiceProxy.rememberReplacedWord(bestWord, mSettingsValues.mWordSeparators);
+ SuggestedWords suggestedWords = mCandidateView.getSuggestions();
+ ic.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan(
+ this, bestWord, suggestedWords), 1);
}
- saveWordInHistory(suggestion);
- mHasValidSuggestions = false;
- mCommittedLength = suggestion.length();
+ mRecorrection.saveRecorrectionSuggestion(mWord, bestWord);
+ mHasUncommittedTypedChars = false;
+ mCommittedLength = bestWord.length();
}
- /**
- * Tries to apply any typed alternatives for the word if we have any cached alternatives,
- * otherwise tries to find new corrections and completions for the word.
- * @param touching The word that the cursor is touching, with position information
- * @return true if an alternative was found, false otherwise.
- */
- private boolean applyTypedAlternatives(EditingUtils.SelectedWord touching) {
- // If we didn't find a match, search for result in typed word history
- WordComposer foundWord = null;
- WordAlternatives alternatives = null;
- // Search old suggestions to suggest re-corrected suggestions.
- for (WordAlternatives entry : mWordHistory) {
- if (TextUtils.equals(entry.getChosenWord(), touching.mWord)) {
- if (entry instanceof TypedWordAlternatives) {
- foundWord = ((TypedWordAlternatives) entry).word;
- }
- alternatives = entry;
- break;
- }
- }
- // If we didn't find a match, at least suggest corrections as re-corrected suggestions.
- if (foundWord == null
- && (AutoCorrection.isValidWord(
- mSuggest.getUnigramDictionaries(), touching.mWord, true))) {
- foundWord = new WordComposer();
- for (int i = 0; i < touching.mWord.length(); i++) {
- foundWord.add(touching.mWord.charAt(i), new int[] {
- touching.mWord.charAt(i)
- }, WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
- }
- foundWord.setFirstCharCapitalized(Character.isUpperCase(touching.mWord.charAt(0)));
- }
- // Found a match, show suggestions
- if (foundWord != null || alternatives != null) {
- if (alternatives == null) {
- alternatives = new TypedWordAlternatives(touching.mWord, foundWord);
- }
- showCorrections(alternatives);
- if (foundWord != null) {
- mWord = new WordComposer(foundWord);
- } else {
- mWord.reset();
- }
- return true;
- }
- return false;
- }
+ private static final WordComposer sEmptyWordComposer = new WordComposer();
+ public void updateBigramPredictions() {
+ if (mSuggest == null || !isSuggestionsRequested())
+ return;
- private void setOldSuggestions() {
- mVoiceConnector.setShowingVoiceSuggestions(false);
- if (mCandidateView != null && mCandidateView.isShowingAddToDictionaryHint()) {
+ if (!mSettingsValues.mBigramPredictionEnabled) {
+ setPunctuationSuggestions();
return;
}
- InputConnection ic = getCurrentInputConnection();
- if (ic == null) return;
- if (!mHasValidSuggestions) {
- // Extract the selected or touching text
- EditingUtils.SelectedWord touching = EditingUtils.getWordAtCursorOrSelection(ic,
- mLastSelectionStart, mLastSelectionEnd, mWordSeparators);
- if (touching != null && touching.mWord.length() > 1) {
- ic.beginBatchEdit();
-
- if (!mVoiceConnector.applyVoiceAlternatives(touching)
- && !applyTypedAlternatives(touching)) {
- abortRecorrection(true);
- } else {
- TextEntryState.selectedForRecorrection();
- EditingUtils.underlineWord(ic, touching);
- }
+ final CharSequence prevWord = EditingUtils.getThisWord(getCurrentInputConnection(),
+ mSettingsValues.mWordSeparators);
+ SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(
+ mKeyboardSwitcher.getKeyboardView(), sEmptyWordComposer, prevWord);
- ic.endBatchEdit();
- } else {
- abortRecorrection(true);
- setPunctuationSuggestions(); // Show the punctuation suggestions list
- }
+ if (builder.size() > 0) {
+ // Explicitly supply an empty typed word (the no-second-arg version of
+ // showSuggestions will retrieve the word near the cursor, we don't want that here)
+ showSuggestions(builder.build(), "");
} else {
- abortRecorrection(true);
+ if (!isShowingPunctuationList()) setPunctuationSuggestions();
}
}
- private void setPunctuationSuggestions() {
- setSuggestions(mSuggestPuncList);
- setCandidatesViewShown(isCandidateStripVisible());
+ public void setPunctuationSuggestions() {
+ setSuggestions(mSettingsValues.mSuggestPuncList);
+ setSuggestionStripShown(isCandidateStripVisible());
}
private void addToAutoAndUserBigramDictionaries(CharSequence suggestion, int frequencyDelta) {
@@ -1837,27 +1750,30 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
if (mUserBigramDictionary != null) {
+ // We don't want to register as bigrams words separated by a separator.
+ // For example "I will, and you too" : we don't want the pair ("will" "and") to be
+ // a bigram.
CharSequence prevWord = EditingUtils.getPreviousWord(getCurrentInputConnection(),
- mSentenceSeparators);
+ mSettingsValues.mWordSeparators);
if (!TextUtils.isEmpty(prevWord)) {
mUserBigramDictionary.addBigrams(prevWord.toString(), suggestion.toString());
}
}
}
- private boolean isCursorTouchingWord() {
+ public boolean isCursorTouchingWord() {
InputConnection ic = getCurrentInputConnection();
if (ic == null) return false;
CharSequence toLeft = ic.getTextBeforeCursor(1, 0);
CharSequence toRight = ic.getTextAfterCursor(1, 0);
if (!TextUtils.isEmpty(toLeft)
- && !isWordSeparator(toLeft.charAt(0))
- && !isSuggestedPunctuation(toLeft.charAt(0))) {
+ && !mSettingsValues.isWordSeparator(toLeft.charAt(0))
+ && !mSettingsValues.isSuggestedPunctuation(toLeft.charAt(0))) {
return true;
}
if (!TextUtils.isEmpty(toRight)
- && !isWordSeparator(toRight.charAt(0))
- && !isSuggestedPunctuation(toRight.charAt(0))) {
+ && !mSettingsValues.isWordSeparator(toRight.charAt(0))
+ && !mSettingsValues.isSuggestedPunctuation(toRight.charAt(0))) {
return true;
}
return false;
@@ -1870,53 +1786,64 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void revertLastWord(boolean deleteChar) {
final int length = mComposing.length();
- if (!mHasValidSuggestions && length > 0) {
+ if (!mHasUncommittedTypedChars && length > 0) {
final InputConnection ic = getCurrentInputConnection();
final CharSequence punctuation = ic.getTextBeforeCursor(1, 0);
if (deleteChar) ic.deleteSurroundingText(1, 0);
int toDelete = mCommittedLength;
final CharSequence toTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0);
- if (!TextUtils.isEmpty(toTheLeft) && isWordSeparator(toTheLeft.charAt(0))) {
+ if (!TextUtils.isEmpty(toTheLeft)
+ && mSettingsValues.isWordSeparator(toTheLeft.charAt(0))) {
toDelete--;
}
ic.deleteSurroundingText(toDelete, 0);
// Re-insert punctuation only when the deleted character was word separator and the
// composing text wasn't equal to the auto-corrected text.
if (deleteChar
- && !TextUtils.isEmpty(punctuation) && isWordSeparator(punctuation.charAt(0))
+ && !TextUtils.isEmpty(punctuation)
+ && mSettingsValues.isWordSeparator(punctuation.charAt(0))
&& !TextUtils.equals(mComposing, toTheLeft)) {
ic.commitText(mComposing, 1);
TextEntryState.acceptedTyped(mComposing);
ic.commitText(punctuation, 1);
- TextEntryState.typedCharacter(punctuation.charAt(0), true);
+ TextEntryState.typedCharacter(punctuation.charAt(0), true,
+ WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
// Clear composing text
mComposing.setLength(0);
} else {
- mHasValidSuggestions = true;
+ mHasUncommittedTypedChars = true;
ic.setComposingText(mComposing, 1);
TextEntryState.backspace();
}
+ mHandler.cancelUpdateBigramPredictions();
mHandler.postUpdateSuggestions();
} else {
sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
}
}
- protected String getWordSeparators() {
- return mWordSeparators;
+ public boolean revertDoubleSpace() {
+ mHandler.cancelDoubleSpacesTimer();
+ final InputConnection ic = getCurrentInputConnection();
+ // Here we test whether we indeed have a period and a space before us. This should not
+ // be needed, but it's there just in case something went wrong.
+ final CharSequence textBeforeCursor = ic.getTextBeforeCursor(2, 0);
+ if (!". ".equals(textBeforeCursor))
+ return false;
+ ic.beginBatchEdit();
+ ic.deleteSurroundingText(2, 0);
+ ic.commitText(" ", 1);
+ ic.endBatchEdit();
+ return true;
}
public boolean isWordSeparator(int code) {
- String separators = getWordSeparators();
- return separators.contains(String.valueOf((char)code));
- }
-
- private boolean isSentenceSeparator(int code) {
- return mSentenceSeparators.contains(String.valueOf((char)code));
+ return mSettingsValues.isWordSeparator(code);
}
- private void sendSpace() {
+ private void sendMagicSpace() {
sendKeyChar((char)Keyboard.CODE_SPACE);
+ mJustAddedMagicSpace = true;
mKeyboardSwitcher.updateShiftState();
}
@@ -1924,28 +1851,31 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return mWord.isFirstCharCapitalized();
}
- // Notify that language or mode have been changed and toggleLanguage will update KeyboaredID
+ // Notify that language or mode have been changed and toggleLanguage will update KeyboardID
// according to new language or mode.
public void onRefreshKeyboard() {
- toggleLanguage(true, true);
- }
-
- // "reset" and "next" are used only for USE_SPACEBAR_LANGUAGE_SWITCHER.
- private void toggleLanguage(boolean reset, boolean next) {
- if (mSubtypeSwitcher.useSpacebarLanguageSwitcher()) {
- mSubtypeSwitcher.toggleLanguage(reset, next);
- }
// Reload keyboard because the current language has been changed.
mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(),
- mSubtypeSwitcher.isShortcutImeEnabled() && mVoiceConnector.isVoiceButtonEnabled(),
- mVoiceConnector.isVoiceButtonOnPrimary());
+ mSubtypeSwitcher.isShortcutImeEnabled() && mVoiceProxy.isVoiceButtonEnabled(),
+ mVoiceProxy.isVoiceButtonOnPrimary());
initSuggest();
+ loadSettings();
mKeyboardSwitcher.updateShiftState();
}
+ // "reset" and "next" are used only for USE_SPACEBAR_LANGUAGE_SWITCHER.
+ private void toggleLanguage(boolean next) {
+ if (mSubtypeSwitcher.useSpacebarLanguageSwitcher()) {
+ mSubtypeSwitcher.toggleLanguage(next);
+ }
+ // The following is necessary because on API levels < 10, we don't get notified when
+ // subtype changes.
+ onRefreshKeyboard();
+ }
+
@Override
public void onSwipeDown() {
- if (mConfigSwipeDownDismissKeyboardEnabled)
+ if (mSettingsValues.mSwipeDownDismissKeyboardEnabled)
handleClose();
}
@@ -1964,21 +1894,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} else {
switcher.onOtherKeyPressed();
}
- mAccessibilityUtils.onPress(primaryCode, switcher);
}
@Override
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(withSliding);
} else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
switcher.onReleaseSymbol();
}
- mAccessibilityUtils.onRelease(primaryCode, switcher);
}
@@ -2001,7 +1928,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
}
if (mAudioManager != null) {
- mSilentMode = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL);
+ mSilentModeOn = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL);
}
}
@@ -2009,11 +1936,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// if mAudioManager is null, we don't have the ringer state yet
// mAudioManager will be set by updateRingerMode
if (mAudioManager == null) {
- if (mKeyboardSwitcher.getInputView() != null) {
+ if (mKeyboardSwitcher.getKeyboardView() != null) {
updateRingerMode();
}
}
- if (mSoundOn && !mSilentMode) {
+ if (isSoundOn()) {
// FIXME: Volume and enable should come from UI settings
// FIXME: These should be triggered after auto-repeat logic
int sound = AudioManager.FX_KEYPRESS_STANDARD;
@@ -2033,10 +1960,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
public void vibrate() {
- if (!mVibrateOn) {
+ if (!mSettingsValues.mVibrateOn) {
return;
}
- LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
+ LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
if (inputView != null) {
inputView.performHapticFeedback(
HapticFeedbackConstants.KEYBOARD_TAP,
@@ -2044,28 +1971,24 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
- public void promoteToUserDictionary(String word, int frequency) {
- if (mUserDictionary.isValidWord(word)) return;
- mUserDictionary.addWord(word, frequency);
- }
-
public WordComposer getCurrentWord() {
return mWord;
}
- public boolean getPopupOn() {
- return mPopupOn;
+ boolean isSoundOn() {
+ return mSettingsValues.mSoundOn && !mSilentModeOn;
}
private void updateCorrectionMode() {
// TODO: cleanup messy flags
mHasDictionary = mSuggest != null ? mSuggest.hasMainDictionary() : false;
- mAutoCorrectOn = (mAutoCorrectEnabled || mQuickFixes)
- && !mInputTypeNoAutoCorrect && mHasDictionary;
- mCorrectionMode = (mAutoCorrectOn && mAutoCorrectEnabled)
+ final boolean shouldAutoCorrect = (mSettingsValues.mAutoCorrectEnabled
+ || mSettingsValues.mQuickFixes) && !mInputTypeNoAutoCorrect && mHasDictionary;
+ mCorrectionMode = (shouldAutoCorrect && mSettingsValues.mAutoCorrectEnabled)
? Suggest.CORRECTION_FULL
- : (mAutoCorrectOn ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE);
- mCorrectionMode = (mBigramSuggestionEnabled && mAutoCorrectOn && mAutoCorrectEnabled)
+ : (shouldAutoCorrect ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE);
+ mCorrectionMode = (mSettingsValues.mBigramSuggestionEnabled && shouldAutoCorrect
+ && mSettingsValues.mAutoCorrectEnabled)
? Suggest.CORRECTION_FULL_BIGRAM : mCorrectionMode;
if (mSuggest != null) {
mSuggest.setCorrectionMode(mCorrectionMode);
@@ -2074,12 +1997,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void updateAutoTextEnabled() {
if (mSuggest == null) return;
- mSuggest.setQuickFixesEnabled(mQuickFixes
+ mSuggest.setQuickFixesEnabled(mSettingsValues.mQuickFixes
&& SubtypeSwitcher.getInstance().isSystemLanguageSameAsInputLanguage());
}
- private void updateSuggestionVisibility(SharedPreferences prefs) {
- final Resources res = mResources;
+ private void updateSuggestionVisibility(final SharedPreferences prefs, final Resources res) {
final String suggestionVisiblityStr = prefs.getString(
Settings.PREF_SHOW_SUGGESTIONS_SETTING,
res.getString(R.string.prefs_suggestion_visibility_default_value));
@@ -2107,121 +2029,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
startActivity(intent);
}
- private void loadSettings(EditorInfo attribute) {
- // Get the settings preferences
- final SharedPreferences prefs = mPrefs;
- Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
- mVibrateOn = vibrator != null && vibrator.hasVibrator()
- && prefs.getBoolean(Settings.PREF_VIBRATE_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);
- mQuickFixes = isQuickFixesEnabled(prefs);
-
- mAutoCorrectEnabled = isAutoCorrectEnabled(prefs);
- mBigramSuggestionEnabled = mAutoCorrectEnabled && isBigramSuggestionEnabled(prefs);
- loadAndSetAutoCorrectionThreshold(prefs);
-
- mVoiceConnector.loadSettings(attribute, prefs);
-
- updateCorrectionMode();
- updateAutoTextEnabled();
- updateSuggestionVisibility(prefs);
- SubtypeSwitcher.getInstance().loadSettings();
- }
-
- /**
- * Load Auto correction threshold from SharedPreferences, and modify mSuggest's threshold.
- */
- private void loadAndSetAutoCorrectionThreshold(SharedPreferences sp) {
- // When mSuggest is not initialized, cannnot modify mSuggest's threshold.
- if (mSuggest == null) return;
- // When auto correction setting is turned off, the threshold is ignored.
- if (!isAutoCorrectEnabled(sp)) return;
-
- final String currentAutoCorrectionSetting = sp.getString(
- Settings.PREF_AUTO_CORRECTION_THRESHOLD,
- mResources.getString(R.string.auto_correction_threshold_mode_index_modest));
- final String[] autoCorrectionThresholdValues = mResources.getStringArray(
- R.array.auto_correction_threshold_values);
- // When autoCrrectionThreshold is greater than 1.0, auto correction is virtually turned off.
- double autoCorrectionThreshold = Double.MAX_VALUE;
- try {
- final int arrayIndex = Integer.valueOf(currentAutoCorrectionSetting);
- if (arrayIndex >= 0 && arrayIndex < autoCorrectionThresholdValues.length) {
- autoCorrectionThreshold = Double.parseDouble(
- autoCorrectionThresholdValues[arrayIndex]);
- }
- } catch (NumberFormatException e) {
- // Whenever the threshold settings are correct, never come here.
- autoCorrectionThreshold = Double.MAX_VALUE;
- Log.w(TAG, "Cannot load auto correction threshold setting."
- + " currentAutoCorrectionSetting: " + currentAutoCorrectionSetting
- + ", autoCorrectionThresholdValues: "
- + Arrays.toString(autoCorrectionThresholdValues));
- }
- // TODO: This should be refactored :
- // setAutoCorrectionThreshold should be called outside of this method.
- mSuggest.setAutoCorrectionThreshold(autoCorrectionThreshold);
- }
-
- private boolean isPopupEnabled(SharedPreferences sp) {
- final boolean showPopupOption = getResources().getBoolean(
- R.bool.config_enable_show_popup_on_keypress_option);
- if (!showPopupOption) return mResources.getBoolean(R.bool.config_default_popup_preview);
- return sp.getBoolean(Settings.PREF_POPUP_ON,
- mResources.getBoolean(R.bool.config_default_popup_preview));
- }
-
- private boolean isQuickFixesEnabled(SharedPreferences sp) {
- final boolean showQuickFixesOption = mResources.getBoolean(
- R.bool.config_enable_quick_fixes_option);
- if (!showQuickFixesOption) {
- return isAutoCorrectEnabled(sp);
- }
- return sp.getBoolean(Settings.PREF_QUICK_FIXES, mResources.getBoolean(
- R.bool.config_default_quick_fixes));
- }
-
- private boolean isAutoCorrectEnabled(SharedPreferences sp) {
- final String currentAutoCorrectionSetting = sp.getString(
- Settings.PREF_AUTO_CORRECTION_THRESHOLD,
- mResources.getString(R.string.auto_correction_threshold_mode_index_modest));
- final String autoCorrectionOff = mResources.getString(
- R.string.auto_correction_threshold_mode_index_off);
- return !currentAutoCorrectionSetting.equals(autoCorrectionOff);
- }
-
- private boolean isBigramSuggestionEnabled(SharedPreferences sp) {
- final boolean showBigramSuggestionsOption = mResources.getBoolean(
- R.bool.config_enable_bigram_suggestions_option);
- if (!showBigramSuggestionsOption) {
- return isAutoCorrectEnabled(sp);
- }
- return sp.getBoolean(Settings.PREF_BIGRAM_SUGGESTIONS, mResources.getBoolean(
- R.bool.config_default_bigram_suggestions));
- }
-
- private void initSuggestPuncList() {
- if (mSuggestPuncs != null || mSuggestPuncList != null)
- return;
- SuggestedWords.Builder builder = new SuggestedWords.Builder();
- String puncs = mResources.getString(R.string.suggested_punctuations);
- if (puncs != null) {
- for (int i = 0; i < puncs.length(); i++) {
- builder.addWord(puncs.subSequence(i, i + 1));
- }
- }
- mSuggestPuncList = builder.build();
- mSuggestPuncs = puncs;
- }
-
- private boolean isSuggestedPunctuation(int code) {
- return mSuggestPuncs.contains(String.valueOf((char)code));
- }
-
private void showSubtypeSelectorAndSettings() {
final CharSequence title = getString(R.string.english_ime_input_options);
final CharSequence[] items = new CharSequence[] {
@@ -2235,13 +2042,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
di.dismiss();
switch (position) {
case 0:
- Intent intent = new Intent(
- android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ Intent intent = CompatUtils.getInputLanguageSelectionIntent(
+ mInputMethodId, Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
- intent.putExtra(android.provider.Settings.EXTRA_INPUT_METHOD_ID,
- mInputMethodId);
startActivity(intent);
break;
case 1:
@@ -2278,7 +2082,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void showOptionsMenuInternal(CharSequence title, CharSequence[] items,
DialogInterface.OnClickListener listener) {
- final IBinder windowToken = mKeyboardSwitcher.getInputView().getWindowToken();
+ final IBinder windowToken = mKeyboardSwitcher.getKeyboardView().getWindowToken();
if (windowToken == null) return;
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setCancelable(true);
@@ -2307,14 +2111,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
p.println(" mComposing=" + mComposing.toString());
p.println(" mIsSuggestionsRequested=" + mIsSettingsSuggestionStripOn);
p.println(" mCorrectionMode=" + mCorrectionMode);
- p.println(" mHasValidSuggestions=" + mHasValidSuggestions);
- p.println(" mAutoCorrectOn=" + mAutoCorrectOn);
- p.println(" mAutoSpace=" + mAutoSpace);
+ p.println(" mHasUncommittedTypedChars=" + mHasUncommittedTypedChars);
+ p.println(" mAutoCorrectEnabled=" + mSettingsValues.mAutoCorrectEnabled);
+ p.println(" mShouldInsertMagicSpace=" + mShouldInsertMagicSpace);
p.println(" mApplicationSpecifiedCompletionOn=" + mApplicationSpecifiedCompletionOn);
p.println(" TextEntryState.state=" + TextEntryState.getState());
- p.println(" mSoundOn=" + mSoundOn);
- p.println(" mVibrateOn=" + mVibrateOn);
- p.println(" mPopupOn=" + mPopupOn);
+ p.println(" mSoundOn=" + mSettingsValues.mSoundOn);
+ p.println(" mVibrateOn=" + mSettingsValues.mVibrateOn);
+ p.println(" mKeyPreviewPopupOn=" + mSettingsValues.mKeyPreviewPopupOn);
}
// Characters per second measurement
@@ -2334,9 +2138,4 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
for (int i = 0; i < CPS_BUFFER_SIZE; i++) total += mCpsIntervals[i];
System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total));
}
-
- @Override
- public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) {
- SubtypeSwitcher.getInstance().updateSubtype(subtype);
- }
}
diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
index aaecfffdd..e460471a5 100644
--- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java
+++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
@@ -45,10 +45,10 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang
String before, String after, int position, List<CharSequence> suggestions) {
}
- public static void logOnAutoSuggestion(String before, String after) {
+ public static void logOnAutoCorrection(String before, String after, int separatorCode) {
}
- public static void logOnAutoSuggestionCanceled() {
+ public static void logOnAutoCorrectionCancelled() {
}
public static void logOnDelete() {
@@ -57,6 +57,9 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang
public static void logOnInputChar() {
}
+ public static void logOnInputSeparator() {
+ }
+
public static void logOnException(String metaData, Throwable e) {
}
diff --git a/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java
new file mode 100644
index 000000000..eb740e111
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+
+import java.util.List;
+import java.util.Locale;
+
+class PrivateBinaryDictionaryGetter {
+ private PrivateBinaryDictionaryGetter() {}
+ public static List<AssetFileAddress> getDictionaryFiles(Locale locale, Context context) {
+ return null;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 341d5add0..956c51e06 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -16,17 +16,21 @@
package com.android.inputmethod.latin;
-import com.android.inputmethod.voice.VoiceIMEConnector;
-import com.android.inputmethod.voice.VoiceInputLogger;
+import com.android.inputmethod.compat.CompatUtils;
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.compat.InputMethodServiceCompatWrapper;
+import com.android.inputmethod.deprecated.VoiceProxy;
+import com.android.inputmethod.compat.VibratorCompatWrapper;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.backup.BackupManager;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.res.Resources;
import android.os.Bundle;
-import android.os.Vibrator;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
@@ -41,6 +45,7 @@ import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.widget.TextView;
+import java.util.Arrays;
import java.util.Locale;
public class Settings extends PreferenceActivity
@@ -51,7 +56,7 @@ public class Settings extends PreferenceActivity
public static final String PREF_GENERAL_SETTINGS_KEY = "general_settings";
public static final String PREF_VIBRATE_ON = "vibrate_on";
public static final String PREF_SOUND_ON = "sound_on";
- public static final String PREF_POPUP_ON = "popup_on";
+ public static final String PREF_KEY_PREVIEW_POPUP_ON = "popup_on";
public static final String PREF_RECORRECTION_ENABLED = "recorrection_enabled";
public static final String PREF_AUTO_CAP = "auto_cap";
public static final String PREF_SETTINGS_KEY = "settings_key";
@@ -60,29 +65,243 @@ public class Settings extends PreferenceActivity
public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
public static final String PREF_SUBTYPES = "subtype_settings";
- public static final String PREF_PREDICTION_SETTINGS_KEY = "prediction_settings";
+ public static final String PREF_CORRECTION_SETTINGS_KEY = "correction_settings";
public static final String PREF_QUICK_FIXES = "quick_fixes";
public static final String PREF_SHOW_SUGGESTIONS_SETTING = "show_suggestions_setting";
public static final String PREF_AUTO_CORRECTION_THRESHOLD = "auto_correction_threshold";
+ public static final String PREF_DEBUG_SETTINGS = "debug_settings";
+
+ public static final String PREF_NGRAM_SETTINGS_KEY = "ngram_settings";
public static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion";
+ public static final String PREF_BIGRAM_PREDICTIONS = "bigram_prediction";
+
+ public static final String PREF_MISC_SETTINGS_KEY = "misc_settings";
+
+ public static final String PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY =
+ "pref_key_preview_popup_dismiss_delay";
public static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
// Dialog ids
private static final int VOICE_INPUT_CONFIRM_DIALOG = 0;
+ public static class Values {
+ // From resources:
+ public final boolean mSwipeDownDismissKeyboardEnabled;
+ public final int mDelayBeforeFadeoutLanguageOnSpacebar;
+ public final int mDelayUpdateSuggestions;
+ public final int mDelayUpdateOldSuggestions;
+ public final int mDelayUpdateShiftState;
+ public final int mDurationOfFadeoutLanguageOnSpacebar;
+ public final float mFinalFadeoutFactorOfLanguageOnSpacebar;
+ public final long mDoubleSpacesTurnIntoPeriodTimeout;
+ public final String mWordSeparators;
+ public final String mMagicSpaceStrippers;
+ public final String mMagicSpaceSwappers;
+ public final String mSuggestPuncs;
+ public final SuggestedWords mSuggestPuncList;
+
+ // From preferences:
+ public final boolean mSoundOn; // Sound setting private to Latin IME (see mSilentModeOn)
+ public final boolean mVibrateOn;
+ public final boolean mKeyPreviewPopupOn;
+ public final int mKeyPreviewPopupDismissDelay;
+ public final boolean mAutoCap;
+ public final boolean mQuickFixes;
+ public final boolean mAutoCorrectEnabled;
+ public final double mAutoCorrectionThreshold;
+ // Suggestion: use bigrams to adjust scores of suggestions obtained from unigram dictionary
+ public final boolean mBigramSuggestionEnabled;
+ // Prediction: use bigrams to predict the next word when there is no input for it yet
+ public final boolean mBigramPredictionEnabled;
+
+ public Values(final SharedPreferences prefs, final Context context,
+ final String localeStr) {
+ final Resources res = context.getResources();
+ final Locale savedLocale;
+ if (null != localeStr) {
+ final Locale keyboardLocale = Utils.constructLocaleFromString(localeStr);
+ savedLocale = Utils.setSystemLocale(res, keyboardLocale);
+ } else {
+ savedLocale = null;
+ }
+
+ // Get the resources
+ mSwipeDownDismissKeyboardEnabled = res.getBoolean(
+ R.bool.config_swipe_down_dismiss_keyboard_enabled);
+ mDelayBeforeFadeoutLanguageOnSpacebar = res.getInteger(
+ R.integer.config_delay_before_fadeout_language_on_spacebar);
+ mDelayUpdateSuggestions =
+ res.getInteger(R.integer.config_delay_update_suggestions);
+ mDelayUpdateOldSuggestions = res.getInteger(
+ R.integer.config_delay_update_old_suggestions);
+ mDelayUpdateShiftState =
+ res.getInteger(R.integer.config_delay_update_shift_state);
+ mDurationOfFadeoutLanguageOnSpacebar = res.getInteger(
+ R.integer.config_duration_of_fadeout_language_on_spacebar);
+ mFinalFadeoutFactorOfLanguageOnSpacebar = res.getInteger(
+ R.integer.config_final_fadeout_percentage_of_language_on_spacebar) / 100.0f;
+ mDoubleSpacesTurnIntoPeriodTimeout = res.getInteger(
+ R.integer.config_double_spaces_turn_into_period_timeout);
+ mMagicSpaceStrippers = res.getString(R.string.magic_space_stripping_symbols);
+ mMagicSpaceSwappers = res.getString(R.string.magic_space_swapping_symbols);
+ String wordSeparators = mMagicSpaceStrippers + mMagicSpaceSwappers
+ + res.getString(R.string.magic_space_promoting_symbols);
+ final String notWordSeparators = res.getString(R.string.non_word_separator_symbols);
+ for (int i = notWordSeparators.length() - 1; i >= 0; --i) {
+ wordSeparators = wordSeparators.replace(notWordSeparators.substring(i, i + 1), "");
+ }
+ mWordSeparators = wordSeparators;
+ mSuggestPuncs = res.getString(R.string.suggested_punctuations);
+ // TODO: it would be nice not to recreate this each time we change the configuration
+ mSuggestPuncList = createSuggestPuncList(mSuggestPuncs);
+
+ // Get the settings preferences
+ final boolean hasVibrator = VibratorCompatWrapper.getInstance(context).hasVibrator();
+ mVibrateOn = hasVibrator && prefs.getBoolean(Settings.PREF_VIBRATE_ON, false);
+ mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON,
+ res.getBoolean(R.bool.config_default_sound_enabled));
+
+ mKeyPreviewPopupOn = isKeyPreviewPopupEnabled(prefs, res);
+ mKeyPreviewPopupDismissDelay = getKeyPreviewPopupDismissDelay(prefs, res);
+ mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true);
+ mQuickFixes = isQuickFixesEnabled(prefs, res);
+
+ mAutoCorrectEnabled = isAutoCorrectEnabled(prefs, res);
+ mBigramSuggestionEnabled = mAutoCorrectEnabled
+ && isBigramSuggestionEnabled(prefs, res, mAutoCorrectEnabled);
+ mBigramPredictionEnabled = mBigramSuggestionEnabled
+ && isBigramPredictionEnabled(prefs, res);
+
+ mAutoCorrectionThreshold = getAutoCorrectionThreshold(prefs, res);
+
+ Utils.setSystemLocale(res, savedLocale);
+ }
+
+ public boolean isSuggestedPunctuation(int code) {
+ return mSuggestPuncs.contains(String.valueOf((char)code));
+ }
+
+ public boolean isWordSeparator(int code) {
+ return mWordSeparators.contains(String.valueOf((char)code));
+ }
+
+ public boolean isMagicSpaceStripper(int code) {
+ return mMagicSpaceStrippers.contains(String.valueOf((char)code));
+ }
+
+ public boolean isMagicSpaceSwapper(int code) {
+ return mMagicSpaceSwappers.contains(String.valueOf((char)code));
+ }
+
+ // Helper methods
+ private static boolean isQuickFixesEnabled(SharedPreferences sp, Resources resources) {
+ final boolean showQuickFixesOption = resources.getBoolean(
+ R.bool.config_enable_quick_fixes_option);
+ if (!showQuickFixesOption) {
+ return isAutoCorrectEnabled(sp, resources);
+ }
+ return sp.getBoolean(Settings.PREF_QUICK_FIXES, resources.getBoolean(
+ R.bool.config_default_quick_fixes));
+ }
+
+ private static boolean isAutoCorrectEnabled(SharedPreferences sp, Resources resources) {
+ final String currentAutoCorrectionSetting = sp.getString(
+ Settings.PREF_AUTO_CORRECTION_THRESHOLD,
+ resources.getString(R.string.auto_correction_threshold_mode_index_modest));
+ final String autoCorrectionOff = resources.getString(
+ R.string.auto_correction_threshold_mode_index_off);
+ return !currentAutoCorrectionSetting.equals(autoCorrectionOff);
+ }
+
+ // Public to access from KeyboardSwitcher. Should it have access to some
+ // process-global instance instead?
+ public static boolean isKeyPreviewPopupEnabled(SharedPreferences sp, Resources resources) {
+ final boolean showPopupOption = resources.getBoolean(
+ R.bool.config_enable_show_popup_on_keypress_option);
+ if (!showPopupOption) return resources.getBoolean(R.bool.config_default_popup_preview);
+ return sp.getBoolean(Settings.PREF_KEY_PREVIEW_POPUP_ON,
+ resources.getBoolean(R.bool.config_default_popup_preview));
+ }
+
+ // Likewise
+ public static int getKeyPreviewPopupDismissDelay(SharedPreferences sp,
+ Resources resources) {
+ return Integer.parseInt(sp.getString(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
+ Integer.toString(resources.getInteger(R.integer.config_delay_after_preview))));
+ }
+
+ private static boolean isBigramSuggestionEnabled(SharedPreferences sp, Resources resources,
+ boolean autoCorrectEnabled) {
+ final boolean showBigramSuggestionsOption = resources.getBoolean(
+ R.bool.config_enable_bigram_suggestions_option);
+ if (!showBigramSuggestionsOption) {
+ return autoCorrectEnabled;
+ }
+ return sp.getBoolean(Settings.PREF_BIGRAM_SUGGESTIONS, resources.getBoolean(
+ R.bool.config_default_bigram_suggestions));
+ }
+
+ private static boolean isBigramPredictionEnabled(SharedPreferences sp,
+ Resources resources) {
+ return sp.getBoolean(Settings.PREF_BIGRAM_PREDICTIONS, resources.getBoolean(
+ R.bool.config_default_bigram_prediction));
+ }
+
+ private static double getAutoCorrectionThreshold(SharedPreferences sp,
+ Resources resources) {
+ final String currentAutoCorrectionSetting = sp.getString(
+ Settings.PREF_AUTO_CORRECTION_THRESHOLD,
+ resources.getString(R.string.auto_correction_threshold_mode_index_modest));
+ final String[] autoCorrectionThresholdValues = resources.getStringArray(
+ R.array.auto_correction_threshold_values);
+ // When autoCorrectionThreshold is greater than 1.0, it's like auto correction is off.
+ double autoCorrectionThreshold = Double.MAX_VALUE;
+ try {
+ final int arrayIndex = Integer.valueOf(currentAutoCorrectionSetting);
+ if (arrayIndex >= 0 && arrayIndex < autoCorrectionThresholdValues.length) {
+ autoCorrectionThreshold = Double.parseDouble(
+ autoCorrectionThresholdValues[arrayIndex]);
+ }
+ } catch (NumberFormatException e) {
+ // Whenever the threshold settings are correct, never come here.
+ autoCorrectionThreshold = Double.MAX_VALUE;
+ Log.w(TAG, "Cannot load auto correction threshold setting."
+ + " currentAutoCorrectionSetting: " + currentAutoCorrectionSetting
+ + ", autoCorrectionThresholdValues: "
+ + Arrays.toString(autoCorrectionThresholdValues));
+ }
+ return autoCorrectionThreshold;
+ }
+
+ private static SuggestedWords createSuggestPuncList(final String puncs) {
+ SuggestedWords.Builder builder = new SuggestedWords.Builder();
+ if (puncs != null) {
+ for (int i = 0; i < puncs.length(); i++) {
+ builder.addWord(puncs.subSequence(i, i + 1));
+ }
+ }
+ return builder.build();
+ }
+ }
+
private PreferenceScreen mInputLanguageSelection;
private CheckBoxPreference mQuickFixes;
private ListPreference mVoicePreference;
private ListPreference mSettingsKeyPreference;
private ListPreference mShowCorrectionSuggestionsPreference;
private ListPreference mAutoCorrectionThreshold;
+ private ListPreference mKeyPreviewPopupDismissDelay;
+ // Suggestion: use bigrams to adjust scores of suggestions obtained from unigram dictionary
private CheckBoxPreference mBigramSuggestion;
+ // Prediction: use bigrams to predict the next word when there is no input for it yet
+ private CheckBoxPreference mBigramPrediction;
+ private Preference mDebugSettingsPreference;
private boolean mVoiceOn;
private AlertDialog mDialog;
- private VoiceInputLogger mLogger;
+ private VoiceProxy.VoiceLoggerWrapper mVoiceLogger;
private boolean mOkClicked = false;
private String mVoiceModeOff;
@@ -92,11 +311,14 @@ public class Settings extends PreferenceActivity
R.string.auto_correction_threshold_mode_index_off);
final String currentSetting = mAutoCorrectionThreshold.getValue();
mBigramSuggestion.setEnabled(!currentSetting.equals(autoCorrectionOff));
+ mBigramPrediction.setEnabled(!currentSetting.equals(autoCorrectionOff));
}
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
+ final Resources res = getResources();
+
addPreferencesFromResource(R.xml.prefs);
mInputLanguageSelection = (PreferenceScreen) findPreference(PREF_SUBTYPES);
mInputLanguageSelection.setOnPreferenceClickListener(this);
@@ -111,69 +333,92 @@ public class Settings extends PreferenceActivity
mVoiceModeOff = getString(R.string.voice_mode_off);
mVoiceOn = !(prefs.getString(PREF_VOICE_SETTINGS_KEY, mVoiceModeOff)
.equals(mVoiceModeOff));
- mLogger = VoiceInputLogger.getLogger(this);
+ mVoiceLogger = VoiceProxy.VoiceLoggerWrapper.getInstance(this);
mAutoCorrectionThreshold = (ListPreference) findPreference(PREF_AUTO_CORRECTION_THRESHOLD);
mBigramSuggestion = (CheckBoxPreference) findPreference(PREF_BIGRAM_SUGGESTIONS);
+ mBigramPrediction = (CheckBoxPreference) findPreference(PREF_BIGRAM_PREDICTIONS);
+ mDebugSettingsPreference = findPreference(PREF_DEBUG_SETTINGS);
+ if (mDebugSettingsPreference != null) {
+ final Intent debugSettingsIntent = new Intent(Intent.ACTION_MAIN);
+ debugSettingsIntent.setClassName(getPackageName(), DebugSettings.class.getName());
+ mDebugSettingsPreference.setIntent(debugSettingsIntent);
+ }
+
ensureConsistencyOfAutoCorrectionSettings();
final PreferenceGroup generalSettings =
(PreferenceGroup) findPreference(PREF_GENERAL_SETTINGS_KEY);
final PreferenceGroup textCorrectionGroup =
- (PreferenceGroup) findPreference(PREF_PREDICTION_SETTINGS_KEY);
+ (PreferenceGroup) findPreference(PREF_CORRECTION_SETTINGS_KEY);
- final boolean showSettingsKeyOption = getResources().getBoolean(
+ final boolean showSettingsKeyOption = res.getBoolean(
R.bool.config_enable_show_settings_key_option);
if (!showSettingsKeyOption) {
generalSettings.removePreference(mSettingsKeyPreference);
}
- final boolean showVoiceKeyOption = getResources().getBoolean(
+ final boolean showVoiceKeyOption = res.getBoolean(
R.bool.config_enable_show_voice_key_option);
if (!showVoiceKeyOption) {
generalSettings.removePreference(mVoicePreference);
}
- Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
- if (vibrator == null || !vibrator.hasVibrator()) {
+ if (!VibratorCompatWrapper.getInstance(this).hasVibrator()) {
generalSettings.removePreference(findPreference(PREF_VIBRATE_ON));
}
- final boolean showSubtypeSettings = getResources().getBoolean(
- R.bool.config_enable_show_subtype_settings);
- if (!showSubtypeSettings) {
+ if (InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) {
generalSettings.removePreference(findPreference(PREF_SUBTYPES));
}
- final boolean showPopupOption = getResources().getBoolean(
+ final boolean showPopupOption = res.getBoolean(
R.bool.config_enable_show_popup_on_keypress_option);
if (!showPopupOption) {
- generalSettings.removePreference(findPreference(PREF_POPUP_ON));
+ generalSettings.removePreference(findPreference(PREF_KEY_PREVIEW_POPUP_ON));
}
- final boolean showRecorrectionOption = getResources().getBoolean(
+ final boolean showRecorrectionOption = res.getBoolean(
R.bool.config_enable_show_recorrection_option);
if (!showRecorrectionOption) {
generalSettings.removePreference(findPreference(PREF_RECORRECTION_ENABLED));
}
- final boolean showQuickFixesOption = getResources().getBoolean(
+ final boolean showQuickFixesOption = res.getBoolean(
R.bool.config_enable_quick_fixes_option);
if (!showQuickFixesOption) {
textCorrectionGroup.removePreference(findPreference(PREF_QUICK_FIXES));
}
- final boolean showBigramSuggestionsOption = getResources().getBoolean(
+ final boolean showBigramSuggestionsOption = res.getBoolean(
R.bool.config_enable_bigram_suggestions_option);
if (!showBigramSuggestionsOption) {
textCorrectionGroup.removePreference(findPreference(PREF_BIGRAM_SUGGESTIONS));
+ textCorrectionGroup.removePreference(findPreference(PREF_BIGRAM_PREDICTIONS));
}
- final boolean showUsabilityModeStudyOption = getResources().getBoolean(
+ final boolean showUsabilityModeStudyOption = res.getBoolean(
R.bool.config_enable_usability_study_mode_option);
if (!showUsabilityModeStudyOption) {
getPreferenceScreen().removePreference(findPreference(PREF_USABILITY_STUDY_MODE));
}
+
+ mKeyPreviewPopupDismissDelay =
+ (ListPreference)findPreference(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
+ final String[] entries = new String[] {
+ res.getString(R.string.key_preview_popup_dismiss_no_delay),
+ res.getString(R.string.key_preview_popup_dismiss_default_delay),
+ };
+ final String popupDismissDelayDefaultValue = Integer.toString(res.getInteger(
+ R.integer.config_delay_after_preview));
+ mKeyPreviewPopupDismissDelay.setEntries(entries);
+ mKeyPreviewPopupDismissDelay.setEntryValues(
+ new String[] { "0", popupDismissDelayDefaultValue });
+ if (null == mKeyPreviewPopupDismissDelay.getValue()) {
+ mKeyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue);
+ }
+ mKeyPreviewPopupDismissDelay.setEnabled(
+ Settings.Values.isKeyPreviewPopupEnabled(prefs, res));
}
@Override
@@ -181,10 +426,10 @@ public class Settings extends PreferenceActivity
super.onResume();
int autoTextSize = AutoText.getSize(getListView());
if (autoTextSize < 1) {
- ((PreferenceGroup) findPreference(PREF_PREDICTION_SETTINGS_KEY))
+ ((PreferenceGroup) findPreference(PREF_CORRECTION_SETTINGS_KEY))
.removePreference(mQuickFixes);
}
- if (!VoiceIMEConnector.VOICE_INSTALLED
+ if (!VoiceProxy.VOICE_INSTALLED
|| !SpeechRecognizer.isRecognitionAvailable(this)) {
getPreferenceScreen().removePreference(mVoicePreference);
} else {
@@ -192,6 +437,7 @@ public class Settings extends PreferenceActivity
}
updateSettingsKeySummary();
updateShowCorrectionSuggestionsSummary();
+ updateKeyPreviewPopupDelaySummary();
}
@Override
@@ -210,6 +456,12 @@ public class Settings extends PreferenceActivity
.equals(mVoiceModeOff)) {
showVoiceConfirmation();
}
+ } else if (key.equals(PREF_KEY_PREVIEW_POPUP_ON)) {
+ final ListPreference popupDismissDelay =
+ (ListPreference)findPreference(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
+ if (null != popupDismissDelay) {
+ popupDismissDelay.setEnabled(prefs.getBoolean(PREF_KEY_PREVIEW_POPUP_ON, true));
+ }
}
ensureConsistencyOfAutoCorrectionSettings();
mVoiceOn = !(prefs.getString(PREF_VOICE_SETTINGS_KEY, mVoiceModeOff)
@@ -217,21 +469,15 @@ public class Settings extends PreferenceActivity
updateVoiceModeSummary();
updateSettingsKeySummary();
updateShowCorrectionSuggestionsSummary();
+ updateKeyPreviewPopupDelaySummary();
}
@Override
public boolean onPreferenceClick(Preference pref) {
if (pref == mInputLanguageSelection) {
- final String action;
- if (android.os.Build.VERSION.SDK_INT
- >= /* android.os.Build.VERSION_CODES.HONEYCOMB */ 11) {
- // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS
- // TODO: Can this be a constant instead of literal String constant?
- action = "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
- } else {
- action = "com.android.inputmethod.latin.INPUT_LANGUAGE_SELECTION";
- }
- startActivity(new Intent(action));
+ startActivity(CompatUtils.getInputLanguageSelectionIntent(
+ Utils.getInputMethodId(InputMethodManagerCompatWrapper.getInstance(this),
+ getApplicationInfo().packageName), 0));
return true;
}
return false;
@@ -250,6 +496,11 @@ public class Settings extends PreferenceActivity
[mSettingsKeyPreference.findIndexOfValue(mSettingsKeyPreference.getValue())]);
}
+ private void updateKeyPreviewPopupDelaySummary() {
+ final ListPreference lp = mKeyPreviewPopupDismissDelay;
+ lp.setSummary(lp.getEntries()[lp.findIndexOfValue(lp.getValue())]);
+ }
+
private void showVoiceConfirmation() {
mOkClicked = false;
showDialog(VOICE_INPUT_CONFIRM_DIALOG);
@@ -277,10 +528,10 @@ public class Settings extends PreferenceActivity
public void onClick(DialogInterface dialog, int whichButton) {
if (whichButton == DialogInterface.BUTTON_NEGATIVE) {
mVoicePreference.setValue(mVoiceModeOff);
- mLogger.settingsWarningDialogCancel();
+ mVoiceLogger.settingsWarningDialogCancel();
} else if (whichButton == DialogInterface.BUTTON_POSITIVE) {
mOkClicked = true;
- mLogger.settingsWarningDialogOk();
+ mVoiceLogger.settingsWarningDialogOk();
}
updateVoicePreference();
}
@@ -311,7 +562,7 @@ public class Settings extends PreferenceActivity
AlertDialog dialog = builder.create();
mDialog = dialog;
dialog.setOnDismissListener(this);
- mLogger.settingsWarningDialogShown();
+ mVoiceLogger.settingsWarningDialogShown();
return dialog;
default:
Log.e(TAG, "unknown dialog " + id);
@@ -321,7 +572,7 @@ public class Settings extends PreferenceActivity
@Override
public void onDismiss(DialogInterface dialog) {
- mLogger.settingsWarningDialogDismissed();
+ mVoiceLogger.settingsWarningDialogDismissed();
if (!mOkClicked) {
// This assumes that onPreferenceClick gets called first, and this if the user
// agreed after the warning, we set the mOkClicked value to true.
@@ -331,10 +582,6 @@ public class Settings extends PreferenceActivity
private void updateVoicePreference() {
boolean isChecked = !mVoicePreference.getValue().equals(mVoiceModeOff);
- if (isChecked) {
- mLogger.voiceInputSettingEnabled();
- } else {
- mLogger.voiceInputSettingDisabled();
- }
+ mVoiceLogger.voiceInputSettingEnabled(isChecked);
}
}
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index dc14d770a..8b51af880 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -16,11 +16,12 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.compat.InputMethodInfoCompatWrapper;
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper;
+import com.android.inputmethod.deprecated.VoiceProxy;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.LatinKeyboard;
-import com.android.inputmethod.voice.SettingsUtil;
-import com.android.inputmethod.voice.VoiceIMEConnector;
-import com.android.inputmethod.voice.VoiceInput;
import android.content.Context;
import android.content.Intent;
@@ -31,12 +32,10 @@ import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
+import android.os.AsyncTask;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InputMethodSubtype;
import java.util.ArrayList;
import java.util.Arrays;
@@ -53,32 +52,37 @@ public class SubtypeSwitcher {
private static final String VOICE_MODE = "voice";
private static final String SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY =
"requireNetworkConnectivity";
+ public static final String USE_SPACEBAR_LANGUAGE_SWITCH_KEY = "use_spacebar_language_switch";
+
private final TextUtils.SimpleStringSplitter mLocaleSplitter =
new TextUtils.SimpleStringSplitter(LOCALE_SEPARATER);
private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
private /* final */ LatinIME mService;
- private /* final */ SharedPreferences mPrefs;
- private /* final */ InputMethodManager mImm;
+ private /* final */ InputMethodManagerCompatWrapper mImm;
private /* final */ Resources mResources;
private /* final */ ConnectivityManager mConnectivityManager;
private /* final */ boolean mConfigUseSpacebarLanguageSwitcher;
- private final ArrayList<InputMethodSubtype> mEnabledKeyboardSubtypesOfCurrentInputMethod =
- new ArrayList<InputMethodSubtype>();
+ private /* final */ SharedPreferences mPrefs;
+ private final ArrayList<InputMethodSubtypeCompatWrapper>
+ mEnabledKeyboardSubtypesOfCurrentInputMethod =
+ new ArrayList<InputMethodSubtypeCompatWrapper>();
private final ArrayList<String> mEnabledLanguagesOfCurrentInputMethod = new ArrayList<String>();
+ private final LanguageBarInfo mLanguageBarInfo = new LanguageBarInfo();
/*-----------------------------------------------------------*/
// Variants which should be changed only by reload functions.
private boolean mNeedsToDisplayLanguage;
private boolean mIsSystemLanguageSameAsInputLanguage;
- private InputMethodInfo mShortcutInputMethodInfo;
- private InputMethodSubtype mShortcutSubtype;
- private List<InputMethodSubtype> mAllEnabledSubtypesOfCurrentInputMethod;
- private InputMethodSubtype mCurrentSubtype;
+ private InputMethodInfoCompatWrapper mShortcutInputMethodInfo;
+ private InputMethodSubtypeCompatWrapper mShortcutSubtype;
+ private List<InputMethodSubtypeCompatWrapper> mAllEnabledSubtypesOfCurrentInputMethod;
+ private InputMethodSubtypeCompatWrapper mCurrentSubtype;
private Locale mSystemLocale;
private Locale mInputLocale;
private String mInputLocaleStr;
- private VoiceInput mVoiceInput;
+ private String mInputMethodId;
+ private VoiceProxy.VoiceInputWrapper mVoiceInputWrapper;
/*-----------------------------------------------------------*/
private boolean mIsNetworkConnected;
@@ -88,10 +92,9 @@ public class SubtypeSwitcher {
}
public static void init(LatinIME service, SharedPreferences prefs) {
+ SubtypeLocale.init(service);
sInstance.initialize(service, prefs);
sInstance.updateAllParameters();
-
- SubtypeLocale.init(service);
}
private SubtypeSwitcher() {
@@ -100,9 +103,8 @@ public class SubtypeSwitcher {
private void initialize(LatinIME service, SharedPreferences prefs) {
mService = service;
- mPrefs = prefs;
mResources = service.getResources();
- mImm = (InputMethodManager) service.getSystemService(Context.INPUT_METHOD_SERVICE);
+ mImm = InputMethodManagerCompatWrapper.getInstance(service);
mConnectivityManager = (ConnectivityManager) service.getSystemService(
Context.CONNECTIVITY_SERVICE);
mEnabledKeyboardSubtypesOfCurrentInputMethod.clear();
@@ -112,15 +114,12 @@ public class SubtypeSwitcher {
mInputLocaleStr = null;
mCurrentSubtype = null;
mAllEnabledSubtypesOfCurrentInputMethod = null;
- // TODO: Voice input should be created here
- mVoiceInput = null;
- mConfigUseSpacebarLanguageSwitcher = mResources.getBoolean(
- R.bool.config_use_spacebar_language_switcher);
- if (mConfigUseSpacebarLanguageSwitcher)
- initLanguageSwitcher(service);
+ mVoiceInputWrapper = null;
+ mPrefs = prefs;
final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
mIsNetworkConnected = (info != null && info.isConnected());
+ mInputMethodId = Utils.getInputMethodId(mImm, service.getPackageName());
}
// Update all parameters stored in SubtypeSwitcher.
@@ -134,11 +133,10 @@ public class SubtypeSwitcher {
// Update parameters which are changed outside LatinIME. This parameters affect UI so they
// should be updated every time onStartInputview.
public void updateParametersOnStartInputView() {
- if (mConfigUseSpacebarLanguageSwitcher) {
- updateForSpacebarLanguageSwitch();
- } else {
- updateEnabledSubtypes();
- }
+ mConfigUseSpacebarLanguageSwitcher = mPrefs.getBoolean(USE_SPACEBAR_LANGUAGE_SWITCH_KEY,
+ mService.getResources().getBoolean(
+ R.bool.config_use_spacebar_language_switcher));
+ updateEnabledSubtypes();
updateShortcutIME();
}
@@ -150,7 +148,7 @@ public class SubtypeSwitcher {
null, true);
mEnabledLanguagesOfCurrentInputMethod.clear();
mEnabledKeyboardSubtypesOfCurrentInputMethod.clear();
- for (InputMethodSubtype ims: mAllEnabledSubtypesOfCurrentInputMethod) {
+ for (InputMethodSubtypeCompatWrapper ims : mAllEnabledSubtypesOfCurrentInputMethod) {
final String locale = ims.getLocale();
final String mode = ims.getMode();
mLocaleSplitter.setString(locale);
@@ -172,6 +170,10 @@ public class SubtypeSwitcher {
Log.w(TAG, "Last subtype was disabled. Update to the current one.");
}
updateSubtype(mImm.getCurrentInputMethodSubtype());
+ } else {
+ // mLanguageBarInfo.update() will be called in updateSubtype so there is no need
+ // to call this in the if-clause above.
+ mLanguageBarInfo.update();
}
}
@@ -184,10 +186,10 @@ public class SubtypeSwitcher {
+ ", " + mShortcutSubtype.getMode())));
}
// TODO: Update an icon for shortcut IME
- Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts =
+ final Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>> shortcuts =
mImm.getShortcutInputMethodsAndSubtypes();
- for (InputMethodInfo imi: shortcuts.keySet()) {
- List<InputMethodSubtype> subtypes = shortcuts.get(imi);
+ for (InputMethodInfoCompatWrapper imi : shortcuts.keySet()) {
+ List<InputMethodSubtypeCompatWrapper> subtypes = shortcuts.get(imi);
// TODO: Returns the first found IMI for now. Should handle all shortcuts as
// appropriate.
mShortcutInputMethodInfo = imi;
@@ -206,7 +208,7 @@ public class SubtypeSwitcher {
}
// Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function.
- public void updateSubtype(InputMethodSubtype newSubtype) {
+ public void updateSubtype(InputMethodSubtypeCompatWrapper newSubtype) {
final String newLocale;
final String newMode;
final String oldMode = getCurrentSubtypeMode();
@@ -243,32 +245,33 @@ public class SubtypeSwitcher {
// We cancel its status when we change mode, while we reset otherwise.
if (isKeyboardMode()) {
if (modeChanged) {
- if (VOICE_MODE.equals(oldMode) && mVoiceInput != null) {
- mVoiceInput.cancel();
+ if (VOICE_MODE.equals(oldMode) && mVoiceInputWrapper != null) {
+ mVoiceInputWrapper.cancel();
}
}
if (modeChanged || languageChanged) {
updateShortcutIME();
mService.onRefreshKeyboard();
}
- } else if (isVoiceMode() && mVoiceInput != null) {
+ } else if (isVoiceMode() && mVoiceInputWrapper != null) {
if (VOICE_MODE.equals(oldMode)) {
- mVoiceInput.reset();
+ mVoiceInputWrapper.reset();
}
// If needsToShowWarningDialog is true, voice input need to show warning before
// show recognition view.
if (languageChanged || modeChanged
- || VoiceIMEConnector.getInstance().needsToShowWarningDialog()) {
+ || VoiceProxy.getInstance().needsToShowWarningDialog()) {
triggerVoiceIME();
}
} else {
Log.w(TAG, "Unknown subtype mode: " + newMode);
- if (VOICE_MODE.equals(oldMode) && mVoiceInput != null) {
+ if (VOICE_MODE.equals(oldMode) && mVoiceInputWrapper != null) {
// We need to reset the voice input to release the resources and to reset its status
// as it is not the current input mode.
- mVoiceInput.reset();
+ mVoiceInputWrapper.reset();
}
}
+ mLanguageBarInfo.update();
}
// Update the current input locale from Locale string.
@@ -277,14 +280,8 @@ public class SubtypeSwitcher {
// "en_US" --> language: en & country: US
// "en" --> language: en
// "" --> the system locale
- mLocaleSplitter.setString(inputLocaleStr);
- if (mLocaleSplitter.hasNext()) {
- String language = mLocaleSplitter.next();
- if (mLocaleSplitter.hasNext()) {
- mInputLocale = new Locale(language, mLocaleSplitter.next());
- } else {
- mInputLocale = new Locale(language);
- }
+ if (!TextUtils.isEmpty(inputLocaleStr)) {
+ mInputLocale = Utils.constructLocaleFromString(inputLocaleStr);
mInputLocaleStr = inputLocaleStr;
} else {
mInputLocale = mSystemLocale;
@@ -303,25 +300,47 @@ public class SubtypeSwitcher {
////////////////////////////
public void switchToShortcutIME() {
- final IBinder token = mService.getWindow().getWindow().getAttributes().token;
- if (token == null || mShortcutInputMethodInfo == null) {
+ if (mShortcutInputMethodInfo == null) {
return;
}
+
final String imiId = mShortcutInputMethodInfo.getId();
- final InputMethodSubtype subtype = mShortcutSubtype;
- new Thread("SwitchToShortcutIME") {
+ final InputMethodSubtypeCompatWrapper subtype = mShortcutSubtype;
+ switchToTargetIME(imiId, subtype);
+ }
+
+ private void switchToTargetIME(
+ final String imiId, final InputMethodSubtypeCompatWrapper subtype) {
+ final IBinder token = mService.getWindow().getWindow().getAttributes().token;
+ if (token == null) {
+ return;
+ }
+ new AsyncTask<Void, Void, Void>() {
@Override
- public void run() {
+ protected Void doInBackground(Void... params) {
mImm.setInputMethodAndSubtype(token, imiId, subtype);
+ return null;
}
- }.start();
+
+ @Override
+ protected void onPostExecute(Void result) {
+ // Calls in this method need to be done in the same thread as the thread which
+ // called switchToShortcutIME().
+
+ // Notify an event that the current subtype was changed. This event will be
+ // handled if "onCurrentInputMethodSubtypeChanged" can't be implemented
+ // when the API level is 10 or previous.
+ mService.notifyOnCurrentInputMethodSubtypeChanged(subtype);
+ }
+ }.execute();
}
public Drawable getShortcutIcon() {
return getSubtypeIcon(mShortcutInputMethodInfo, mShortcutSubtype);
}
- private Drawable getSubtypeIcon(InputMethodInfo imi, InputMethodSubtype subtype) {
+ private Drawable getSubtypeIcon(
+ InputMethodInfoCompatWrapper imi, InputMethodSubtypeCompatWrapper subtype) {
final PackageManager pm = mService.getPackageManager();
if (imi != null) {
final String imiPackageName = imi.getPackageName();
@@ -360,11 +379,16 @@ public class SubtypeSwitcher {
return false;
if (mShortcutSubtype == null)
return true;
+ // For compatibility, if the shortcut subtype is dummy, we assume the shortcut IME
+ // (built-in voice dummy subtype) is available.
+ if (!mShortcutSubtype.hasOriginalObject()) return true;
final boolean allowsImplicitlySelectedSubtypes = true;
- for (final InputMethodSubtype enabledSubtype : mImm.getEnabledInputMethodSubtypeList(
- mShortcutInputMethodInfo, allowsImplicitlySelectedSubtypes)) {
- if (enabledSubtype.equals(mShortcutSubtype))
+ for (final InputMethodSubtypeCompatWrapper enabledSubtype :
+ mImm.getEnabledInputMethodSubtypeList(
+ mShortcutInputMethodInfo, allowsImplicitlySelectedSubtypes)) {
+ if (enabledSubtype.equals(mShortcutSubtype)) {
return true;
+ }
}
return false;
}
@@ -389,7 +413,7 @@ public class SubtypeSwitcher {
final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance();
final LatinKeyboard keyboard = switcher.getLatinKeyboard();
if (keyboard != null) {
- keyboard.updateShortcutKey(isShortcutImeReady(), switcher.getInputView());
+ keyboard.updateShortcutKey(isShortcutImeReady(), switcher.getKeyboardView());
}
}
@@ -398,11 +422,7 @@ public class SubtypeSwitcher {
//////////////////////////////////
public int getEnabledKeyboardLocaleCount() {
- if (mConfigUseSpacebarLanguageSwitcher) {
- return mLanguageSwitcher.getLocaleCount();
- } else {
- return mEnabledKeyboardSubtypesOfCurrentInputMethod.size();
- }
+ return mEnabledKeyboardSubtypesOfCurrentInputMethod.size();
}
public boolean useSpacebarLanguageSwitcher() {
@@ -414,90 +434,40 @@ public class SubtypeSwitcher {
}
public Locale getInputLocale() {
- if (mConfigUseSpacebarLanguageSwitcher) {
- return mLanguageSwitcher.getInputLocale();
- } else {
- return mInputLocale;
- }
+ return mInputLocale;
}
public String getInputLocaleStr() {
- if (mConfigUseSpacebarLanguageSwitcher) {
- String inputLanguage = null;
- inputLanguage = mLanguageSwitcher.getInputLanguage();
- // Should return system locale if there is no Language available.
- if (inputLanguage == null) {
- inputLanguage = getSystemLocale().getLanguage();
- }
- return inputLanguage;
- } else {
- return mInputLocaleStr;
- }
+ return mInputLocaleStr;
}
public String[] getEnabledLanguages() {
- 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[enabledLanguageCount]);
+ 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[enabledLanguageCount]);
}
public Locale getSystemLocale() {
- if (mConfigUseSpacebarLanguageSwitcher) {
- return mLanguageSwitcher.getSystemLocale();
- } else {
- return mSystemLocale;
- }
+ return mSystemLocale;
}
public boolean isSystemLanguageSameAsInputLanguage() {
- if (mConfigUseSpacebarLanguageSwitcher) {
- return getSystemLocale().getLanguage().equalsIgnoreCase(
- getInputLocaleStr().substring(0, 2));
- } else {
- return mIsSystemLanguageSameAsInputLanguage;
- }
+ return mIsSystemLanguageSameAsInputLanguage;
}
public void onConfigurationChanged(Configuration conf) {
final Locale systemLocale = conf.locale;
// If system configuration was changed, update all parameters.
if (!TextUtils.equals(systemLocale.toString(), mSystemLocale.toString())) {
- if (mConfigUseSpacebarLanguageSwitcher) {
- // If the system locale changes and is different from the saved
- // locale (mSystemLocale), then reload the input locale list from the
- // latin ime settings (shared prefs) and reset the input locale
- // to the first one.
- mLanguageSwitcher.loadLocales(mPrefs);
- mLanguageSwitcher.setSystemLocale(systemLocale);
- } else {
- updateAllParameters();
- }
+ updateAllParameters();
}
}
- /**
- * Change system locale for this application
- * @param newLocale
- * @return oldLocale
- */
- public Locale changeSystemLocale(Locale newLocale) {
- Configuration conf = mResources.getConfiguration();
- Locale oldLocale = conf.locale;
- conf.locale = newLocale;
- mResources.updateConfiguration(conf, mResources.getDisplayMetrics());
- return oldLocale;
- }
-
public boolean isKeyboardMode() {
return KEYBOARD_MODE.equals(getCurrentSubtypeMode());
}
@@ -507,9 +477,9 @@ public class SubtypeSwitcher {
// Voice Input functions //
///////////////////////////
- public boolean setVoiceInput(VoiceInput vi) {
- if (mVoiceInput == null && vi != null) {
- mVoiceInput = vi;
+ public boolean setVoiceInputWrapper(VoiceProxy.VoiceInputWrapper vi) {
+ if (mVoiceInputWrapper == null && vi != null) {
+ mVoiceInputWrapper = vi;
if (isVoiceMode()) {
if (DBG) {
Log.d(TAG, "Set and call voice input.: " + getInputLocaleStr());
@@ -525,17 +495,85 @@ public class SubtypeSwitcher {
return null == mCurrentSubtype ? false : VOICE_MODE.equals(getCurrentSubtypeMode());
}
+ public boolean isDummyVoiceMode() {
+ return mCurrentSubtype != null && mCurrentSubtype.getOriginalObject() == null
+ && VOICE_MODE.equals(getCurrentSubtypeMode());
+ }
+
private void triggerVoiceIME() {
if (!mService.isInputViewShown()) return;
- VoiceIMEConnector.getInstance().startListening(false,
- KeyboardSwitcher.getInstance().getInputView().getWindowToken());
+ VoiceProxy.getInstance().startListening(false,
+ KeyboardSwitcher.getInstance().getKeyboardView().getWindowToken());
}
//////////////////////////////////////
// Spacebar Language Switch support //
//////////////////////////////////////
- private LanguageSwitcher mLanguageSwitcher;
+ private class LanguageBarInfo {
+ private int mCurrentKeyboardSubtypeIndex;
+ private InputMethodSubtypeCompatWrapper mNextKeyboardSubtype;
+ private InputMethodSubtypeCompatWrapper mPreviousKeyboardSubtype;
+ private String mNextLanguage;
+ private String mPreviousLanguage;
+ public LanguageBarInfo() {
+ update();
+ }
+
+ private String getNextLanguage() {
+ return mNextLanguage;
+ }
+
+ private String getPreviousLanguage() {
+ return mPreviousLanguage;
+ }
+
+ public InputMethodSubtypeCompatWrapper getNextKeyboardSubtype() {
+ return mNextKeyboardSubtype;
+ }
+
+ public InputMethodSubtypeCompatWrapper getPreviousKeyboardSubtype() {
+ return mPreviousKeyboardSubtype;
+ }
+
+ public void update() {
+ if (!mConfigUseSpacebarLanguageSwitcher
+ || mEnabledKeyboardSubtypesOfCurrentInputMethod == null
+ || mEnabledKeyboardSubtypesOfCurrentInputMethod.size() == 0) return;
+ mCurrentKeyboardSubtypeIndex = getCurrentIndex();
+ mNextKeyboardSubtype = getNextKeyboardSubtypeInternal(mCurrentKeyboardSubtypeIndex);
+ Locale locale = Utils.constructLocaleFromString(mNextKeyboardSubtype.getLocale());
+ mNextLanguage = getFullDisplayName(locale, true);
+ mPreviousKeyboardSubtype = getPreviousKeyboardSubtypeInternal(
+ mCurrentKeyboardSubtypeIndex);
+ locale = Utils.constructLocaleFromString(mPreviousKeyboardSubtype.getLocale());
+ mPreviousLanguage = getFullDisplayName(locale, true);
+ }
+
+ private int normalize(int index) {
+ final int N = mEnabledKeyboardSubtypesOfCurrentInputMethod.size();
+ final int ret = index % N;
+ return ret < 0 ? ret + N : ret;
+ }
+
+ private int getCurrentIndex() {
+ final int N = mEnabledKeyboardSubtypesOfCurrentInputMethod.size();
+ for (int i = 0; i < N; ++i) {
+ if (mEnabledKeyboardSubtypesOfCurrentInputMethod.get(i).equals(mCurrentSubtype)) {
+ return i;
+ }
+ }
+ return 0;
+ }
+
+ private InputMethodSubtypeCompatWrapper getNextKeyboardSubtypeInternal(int index) {
+ return mEnabledKeyboardSubtypesOfCurrentInputMethod.get(normalize(index + 1));
+ }
+
+ private InputMethodSubtypeCompatWrapper getPreviousKeyboardSubtypeInternal(int index) {
+ return mEnabledKeyboardSubtypesOfCurrentInputMethod.get(normalize(index - 1));
+ }
+ }
public static String getFullDisplayName(Locale locale, boolean returnsNameInThisLocale) {
if (returnsNameInThisLocale) {
@@ -546,7 +584,12 @@ public class SubtypeSwitcher {
}
public static String getDisplayLanguage(Locale locale) {
- return toTitleCase(locale.getDisplayLanguage(locale));
+ return toTitleCase(SubtypeLocale.getFullDisplayName(locale));
+ }
+
+ public static String getMiddleDisplayLanguage(Locale locale) {
+ return toTitleCase((Utils.constructLocaleFromString(
+ locale.getLanguage()).getDisplayLanguage(locale)));
}
public static String getShortDisplayLanguage(Locale locale) {
@@ -560,32 +603,16 @@ public class SubtypeSwitcher {
return Character.toUpperCase(s.charAt(0)) + s.substring(1);
}
- private void updateForSpacebarLanguageSwitch() {
- // We need to update mNeedsToDisplayLanguage in onStartInputView because
- // getEnabledKeyboardLocaleCount could have been changed.
- mNeedsToDisplayLanguage = !(getEnabledKeyboardLocaleCount() <= 1
- && getSystemLocale().getLanguage().equalsIgnoreCase(
- getInputLocale().getLanguage()));
- }
-
public String getInputLanguageName() {
return getDisplayLanguage(getInputLocale());
}
public String getNextInputLanguageName() {
- if (mConfigUseSpacebarLanguageSwitcher) {
- return getDisplayLanguage(mLanguageSwitcher.getNextInputLocale());
- } else {
- return "";
- }
+ return mLanguageBarInfo.getNextLanguage();
}
public String getPreviousInputLanguageName() {
- if (mConfigUseSpacebarLanguageSwitcher) {
- return getDisplayLanguage(mLanguageSwitcher.getPrevInputLocale());
- } else {
- return "";
- }
+ return mLanguageBarInfo.getPreviousLanguage();
}
/////////////////////////////
@@ -612,60 +639,36 @@ public class SubtypeSwitcher {
}
- // A list of locales which are supported by default for voice input, unless we get a
- // different list from Gservices.
- private static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES =
- "en " +
- "en_US " +
- "en_GB " +
- "en_AU " +
- "en_CA " +
- "en_IE " +
- "en_IN " +
- "en_NZ " +
- "en_SG " +
- "en_ZA ";
-
public boolean isVoiceSupported(String locale) {
// Get the current list of supported locales and check the current locale against that
// list. We cache this value so as not to check it every time the user starts a voice
// input. Because this method is called by onStartInputView, this should mean that as
// long as the locale doesn't change while the user is keeping the IME open, the
// value should never be stale.
- String supportedLocalesString = SettingsUtil.getSettingsString(
- mService.getContentResolver(),
- SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES,
- DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES);
+ String supportedLocalesString = VoiceProxy.getSupportedLocalesString(
+ mService.getContentResolver());
List<String> voiceInputSupportedLocales = Arrays.asList(
supportedLocalesString.split("\\s+"));
return voiceInputSupportedLocales.contains(locale);
}
- public void loadSettings() {
- if (mConfigUseSpacebarLanguageSwitcher) {
- mLanguageSwitcher.loadLocales(mPrefs);
- }
+ private void changeToNextSubtype() {
+ final InputMethodSubtypeCompatWrapper subtype =
+ mLanguageBarInfo.getNextKeyboardSubtype();
+ switchToTargetIME(mInputMethodId, subtype);
}
- public void toggleLanguage(boolean reset, boolean next) {
- if (mConfigUseSpacebarLanguageSwitcher) {
- if (reset) {
- mLanguageSwitcher.reset();
- } else {
- if (next) {
- mLanguageSwitcher.next();
- } else {
- mLanguageSwitcher.prev();
- }
- }
- mLanguageSwitcher.persist(mPrefs);
- }
+ private void changeToPreviousSubtype() {
+ final InputMethodSubtypeCompatWrapper subtype =
+ mLanguageBarInfo.getPreviousKeyboardSubtype();
+ switchToTargetIME(mInputMethodId, subtype);
}
- private void initLanguageSwitcher(LatinIME service) {
- final Configuration conf = service.getResources().getConfiguration();
- mLanguageSwitcher = new LanguageSwitcher(service);
- mLanguageSwitcher.loadLocales(mPrefs);
- mLanguageSwitcher.setSystemLocale(conf.locale);
+ public void toggleLanguage(boolean next) {
+ if (next) {
+ changeToNextSubtype();
+ } else {
+ changeToPreviousSubtype();
+ }
}
}
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 0de474e59..ca75866c0 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -27,6 +27,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -47,7 +48,7 @@ public class Suggest implements Dictionary.WordCallback {
/**
* Words that appear in both bigram and unigram data gets multiplier ranging from
- * BIGRAM_MULTIPLIER_MIN to BIGRAM_MULTIPLIER_MAX depending on the frequency score from
+ * BIGRAM_MULTIPLIER_MIN to BIGRAM_MULTIPLIER_MAX depending on the score from
* bigram data.
*/
public static final double BIGRAM_MULTIPLIER_MIN = 1.2;
@@ -55,7 +56,7 @@ public class Suggest implements Dictionary.WordCallback {
/**
* Maximum possible bigram frequency. Will depend on how many bits are being used in data
- * structure. Maximum bigram freqeuncy will get the BIGRAM_MULTIPLIER_MAX as the multiplier.
+ * structure. Maximum bigram frequency will get the BIGRAM_MULTIPLIER_MAX as the multiplier.
*/
public static final int MAXIMUM_BIGRAM_FREQUENCY = 127;
@@ -74,13 +75,11 @@ public class Suggest implements Dictionary.WordCallback {
public static final String DICT_KEY_USER_BIGRAM = "user_bigram";
public static final String DICT_KEY_WHITELIST ="whitelist";
- static final int LARGE_DICTIONARY_THRESHOLD = 200 * 1000;
-
private static final boolean DBG = LatinImeLogger.sDBG;
private AutoCorrection mAutoCorrection;
- private BinaryDictionary mMainDict;
+ private Dictionary mMainDict;
private WhitelistDictionary mWhiteListDictionary;
private final Map<String, Dictionary> mUnigramDictionaries = new HashMap<String, Dictionary>();
private final Map<String, Dictionary> mBigramDictionaries = new HashMap<String, Dictionary>();
@@ -92,13 +91,13 @@ public class Suggest implements Dictionary.WordCallback {
private boolean mQuickFixesEnabled;
private double mAutoCorrectionThreshold;
- private int[] mPriorities = new int[mPrefMaxSuggestions];
- private int[] mBigramPriorities = new int[PREF_MAX_BIGRAMS];
+ private int[] mScores = new int[mPrefMaxSuggestions];
+ private int[] mBigramScores = new int[PREF_MAX_BIGRAMS];
private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>();
ArrayList<CharSequence> mBigramSuggestions = new ArrayList<CharSequence>();
private ArrayList<CharSequence> mStringPool = new ArrayList<CharSequence>();
- private String mLowerOriginalWord;
+ private CharSequence mTypedWord;
// TODO: Remove these member variables by passing more context to addWord() callback method
private boolean mIsFirstCharCapitalized;
@@ -106,15 +105,18 @@ public class Suggest implements Dictionary.WordCallback {
private int mCorrectionMode = CORRECTION_BASIC;
- public Suggest(Context context, int dictionaryResId) {
- init(context, BinaryDictionary.initDictionary(context, dictionaryResId, DIC_MAIN));
+ public Suggest(Context context, int dictionaryResId, Locale locale) {
+ init(context, DictionaryFactory.createDictionaryFromManager(context, locale,
+ dictionaryResId));
}
- /* package for test */ Suggest(File dictionary, long startOffset, long length) {
- init(null, BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN));
+ /* package for test */ Suggest(Context context, File dictionary, long startOffset, long length,
+ Flag[] flagArray) {
+ init(null, DictionaryFactory.createDictionaryForTest(context, dictionary, startOffset,
+ length, flagArray));
}
- private void init(Context context, BinaryDictionary mainDict) {
+ private void init(Context context, Dictionary mainDict) {
if (mainDict != null) {
mMainDict = mainDict;
mUnigramDictionaries.put(DICT_KEY_MAIN, mainDict);
@@ -128,6 +130,19 @@ public class Suggest implements Dictionary.WordCallback {
initPool();
}
+ public void resetMainDict(Context context, int dictionaryResId, Locale locale) {
+ final Dictionary newMainDict = DictionaryFactory.createDictionaryFromManager(
+ context, locale, dictionaryResId);
+ mMainDict = newMainDict;
+ if (null == newMainDict) {
+ mUnigramDictionaries.remove(DICT_KEY_MAIN);
+ mBigramDictionaries.remove(DICT_KEY_MAIN);
+ } else {
+ mUnigramDictionaries.put(DICT_KEY_MAIN, newMainDict);
+ mBigramDictionaries.put(DICT_KEY_MAIN, newMainDict);
+ }
+ }
+
private void initPool() {
for (int i = 0; i < mPrefMaxSuggestions; i++) {
StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
@@ -148,7 +163,7 @@ public class Suggest implements Dictionary.WordCallback {
}
public boolean hasMainDictionary() {
- return mMainDict != null && mMainDict.getSize() > LARGE_DICTIONARY_THRESHOLD;
+ return mMainDict != null;
}
public Map<String, Dictionary> getUnigramDictionaries() {
@@ -207,8 +222,8 @@ public class Suggest implements Dictionary.WordCallback {
throw new IllegalArgumentException("maxSuggestions must be between 1 and 100");
}
mPrefMaxSuggestions = maxSuggestions;
- mPriorities = new int[mPrefMaxSuggestions];
- mBigramPriorities = new int[PREF_MAX_BIGRAMS];
+ mScores = new int[mPrefMaxSuggestions];
+ mBigramScores = new int[PREF_MAX_BIGRAMS];
collectGarbage(mSuggestions, mPrefMaxSuggestions);
while (mStringPool.size() < mPrefMaxSuggestions) {
StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
@@ -248,6 +263,16 @@ public class Suggest implements Dictionary.WordCallback {
return sb;
}
+ protected void addBigramToSuggestions(CharSequence bigram) {
+ final int poolSize = mStringPool.size();
+ final StringBuilder sb = poolSize > 0 ?
+ (StringBuilder) mStringPool.remove(poolSize - 1)
+ : new StringBuilder(getApproxMaxWordLength());
+ sb.setLength(0);
+ sb.append(bigram);
+ mSuggestions.add(sb);
+ }
+
// TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder
public SuggestedWords.Builder getSuggestedWordBuilder(View view, WordComposer wordComposer,
CharSequence prevWordForBigram) {
@@ -256,25 +281,23 @@ public class Suggest implements Dictionary.WordCallback {
mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
mIsAllUpperCase = wordComposer.isAllUpperCase();
collectGarbage(mSuggestions, mPrefMaxSuggestions);
- Arrays.fill(mPriorities, 0);
+ Arrays.fill(mScores, 0);
// Save a lowercase version of the original word
CharSequence typedWord = wordComposer.getTypedWord();
if (typedWord != null) {
final String typedWordString = typedWord.toString();
typedWord = typedWordString;
- mLowerOriginalWord = typedWordString.toLowerCase();
// Treating USER_TYPED as UNIGRAM suggestion for logging now.
LatinImeLogger.onAddSuggestedWord(typedWordString, Suggest.DIC_USER_TYPED,
Dictionary.DataType.UNIGRAM);
- } else {
- mLowerOriginalWord = "";
}
+ mTypedWord = typedWord;
- if (wordComposer.size() == 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM
+ if (wordComposer.size() <= 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM
|| mCorrectionMode == CORRECTION_BASIC)) {
// At first character typed, search only the bigrams
- Arrays.fill(mBigramPriorities, 0);
+ Arrays.fill(mBigramScores, 0);
collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS);
if (!TextUtils.isEmpty(prevWordForBigram)) {
@@ -285,21 +308,26 @@ public class Suggest implements Dictionary.WordCallback {
for (final Dictionary dictionary : mBigramDictionaries.values()) {
dictionary.getBigrams(wordComposer, prevWordForBigram, this);
}
- char currentChar = wordComposer.getTypedWord().charAt(0);
- char currentCharUpper = Character.toUpperCase(currentChar);
- int count = 0;
- int bigramSuggestionSize = mBigramSuggestions.size();
- for (int i = 0; i < bigramSuggestionSize; i++) {
- if (mBigramSuggestions.get(i).charAt(0) == currentChar
- || mBigramSuggestions.get(i).charAt(0) == currentCharUpper) {
- int poolSize = mStringPool.size();
- StringBuilder sb = poolSize > 0 ?
- (StringBuilder) mStringPool.remove(poolSize - 1)
- : new StringBuilder(getApproxMaxWordLength());
- sb.setLength(0);
- sb.append(mBigramSuggestions.get(i));
- mSuggestions.add(count++, sb);
- if (count > mPrefMaxSuggestions) break;
+ if (TextUtils.isEmpty(typedWord)) {
+ // Nothing entered: return all bigrams for the previous word
+ int insertCount = Math.min(mBigramSuggestions.size(), mPrefMaxSuggestions);
+ for (int i = 0; i < insertCount; ++i) {
+ addBigramToSuggestions(mBigramSuggestions.get(i));
+ }
+ } else {
+ // Word entered: return only bigrams that match the first char of the typed word
+ final char currentChar = typedWord.charAt(0);
+ final char currentCharUpper = Character.toUpperCase(currentChar);
+ int count = 0;
+ final int bigramSuggestionSize = mBigramSuggestions.size();
+ for (int i = 0; i < bigramSuggestionSize; i++) {
+ final CharSequence bigramSuggestion = mBigramSuggestions.get(i);
+ final char bigramSuggestionFirstChar = bigramSuggestion.charAt(0);
+ if (bigramSuggestionFirstChar == currentChar
+ || bigramSuggestionFirstChar == currentCharUpper) {
+ addBigramToSuggestions(bigramSuggestion);
+ if (++count > mPrefMaxSuggestions) break;
+ }
}
}
}
@@ -346,7 +374,7 @@ public class Suggest implements Dictionary.WordCallback {
mWhiteListDictionary.getWhiteListedWord(typedWordString));
mAutoCorrection.updateAutoCorrectionStatus(mUnigramDictionaries, wordComposer,
- mSuggestions, mPriorities, typedWord, mAutoCorrectionThreshold, mCorrectionMode,
+ mSuggestions, mScores, typedWord, mAutoCorrectionThreshold, mCorrectionMode,
autoText, whitelistedWord);
if (autoText != null) {
@@ -364,26 +392,25 @@ public class Suggest implements Dictionary.WordCallback {
if (DBG) {
double normalizedScore = mAutoCorrection.getNormalizedScore();
- ArrayList<SuggestedWords.SuggestedWordInfo> frequencyInfoList =
+ ArrayList<SuggestedWords.SuggestedWordInfo> scoreInfoList =
new ArrayList<SuggestedWords.SuggestedWordInfo>();
- frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false));
- final int priorityLength = mPriorities.length;
- for (int i = 0; i < priorityLength; ++i) {
+ scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false));
+ for (int i = 0; i < mScores.length; ++i) {
if (normalizedScore > 0) {
- final String priorityThreshold = Integer.toString(mPriorities[i]) + " (" +
- normalizedScore + ")";
- frequencyInfoList.add(
- new SuggestedWords.SuggestedWordInfo(priorityThreshold, false));
+ final String scoreThreshold = String.format("%d (%4.2f)", mScores[i],
+ normalizedScore);
+ scoreInfoList.add(
+ new SuggestedWords.SuggestedWordInfo(scoreThreshold, false));
normalizedScore = 0.0;
} else {
- final String priority = Integer.toString(mPriorities[i]);
- frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo(priority, false));
+ final String score = Integer.toString(mScores[i]);
+ scoreInfoList.add(new SuggestedWords.SuggestedWordInfo(score, false));
}
}
- for (int i = priorityLength; i < mSuggestions.size(); ++i) {
- frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false));
+ for (int i = mScores.length; i < mSuggestions.size(); ++i) {
+ scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false));
}
- return new SuggestedWords.Builder().addWords(mSuggestions, frequencyInfoList);
+ return new SuggestedWords.Builder().addWords(mSuggestions, scoreInfoList);
}
return new SuggestedWords.Builder().addWords(mSuggestions, null);
}
@@ -419,52 +446,37 @@ public class Suggest implements Dictionary.WordCallback {
return mAutoCorrection.hasAutoCorrection();
}
- private static boolean compareCaseInsensitive(final String lowerOriginalWord,
- final char[] word, final int offset, final int length) {
- final int originalLength = lowerOriginalWord.length();
- if (originalLength == length && Character.isUpperCase(word[offset])) {
- for (int i = 0; i < originalLength; i++) {
- if (lowerOriginalWord.charAt(i) != Character.toLowerCase(word[offset+i])) {
- return false;
- }
- }
- return true;
- }
- return false;
- }
-
@Override
- public boolean addWord(final char[] word, final int offset, final int length, int freq,
+ public boolean addWord(final char[] word, final int offset, final int length, int score,
final int dicTypeId, final Dictionary.DataType dataType) {
Dictionary.DataType dataTypeForLog = dataType;
- ArrayList<CharSequence> suggestions;
- int[] priorities;
- int prefMaxSuggestions;
+ final ArrayList<CharSequence> suggestions;
+ final int[] sortedScores;
+ final int prefMaxSuggestions;
if(dataType == Dictionary.DataType.BIGRAM) {
suggestions = mBigramSuggestions;
- priorities = mBigramPriorities;
+ sortedScores = mBigramScores;
prefMaxSuggestions = PREF_MAX_BIGRAMS;
} else {
suggestions = mSuggestions;
- priorities = mPriorities;
+ sortedScores = mScores;
prefMaxSuggestions = mPrefMaxSuggestions;
}
int pos = 0;
// Check if it's the same word, only caps are different
- if (compareCaseInsensitive(mLowerOriginalWord, word, offset, length)) {
+ if (Utils.equalsIgnoreCase(mTypedWord, word, offset, length)) {
// TODO: remove this surrounding if clause and move this logic to
// getSuggestedWordBuilder.
if (suggestions.size() > 0) {
- final String currentHighestWordLowerCase =
- suggestions.get(0).toString().toLowerCase();
+ final String currentHighestWord = suggestions.get(0).toString();
// If the current highest word is also equal to typed word, we need to compare
// frequency to determine the insertion position. This does not ensure strictly
// correct ordering, but ensures the top score is on top which is enough for
// removing duplicates correctly.
- if (compareCaseInsensitive(currentHighestWordLowerCase, word, offset, length)
- && freq <= priorities[0]) {
+ if (Utils.equalsIgnoreCase(currentHighestWord, word, offset, length)
+ && score <= sortedScores[0]) {
pos = 1;
}
}
@@ -475,24 +487,24 @@ public class Suggest implements Dictionary.WordCallback {
if(bigramSuggestion >= 0) {
dataTypeForLog = Dictionary.DataType.BIGRAM;
// turn freq from bigram into multiplier specified above
- double multiplier = (((double) mBigramPriorities[bigramSuggestion])
+ double multiplier = (((double) mBigramScores[bigramSuggestion])
/ MAXIMUM_BIGRAM_FREQUENCY)
* (BIGRAM_MULTIPLIER_MAX - BIGRAM_MULTIPLIER_MIN)
+ BIGRAM_MULTIPLIER_MIN;
/* Log.d(TAG,"bigram num: " + bigramSuggestion
+ " wordB: " + mBigramSuggestions.get(bigramSuggestion).toString()
- + " currentPriority: " + freq + " bigramPriority: "
- + mBigramPriorities[bigramSuggestion]
+ + " currentScore: " + score + " bigramScore: "
+ + mBigramScores[bigramSuggestion]
+ " multiplier: " + multiplier); */
- freq = (int)Math.round((freq * multiplier));
+ score = (int)Math.round((score * multiplier));
}
}
- // Check the last one's priority and bail
- if (priorities[prefMaxSuggestions - 1] >= freq) return true;
+ // Check the last one's score and bail
+ if (sortedScores[prefMaxSuggestions - 1] >= score) return true;
while (pos < prefMaxSuggestions) {
- if (priorities[pos] < freq
- || (priorities[pos] == freq && length < suggestions.get(pos).length())) {
+ if (sortedScores[pos] < score
+ || (sortedScores[pos] == score && length < suggestions.get(pos).length())) {
break;
}
pos++;
@@ -502,8 +514,8 @@ public class Suggest implements Dictionary.WordCallback {
return true;
}
- System.arraycopy(priorities, pos, priorities, pos + 1, prefMaxSuggestions - pos - 1);
- priorities[pos] = freq;
+ System.arraycopy(sortedScores, pos, sortedScores, pos + 1, prefMaxSuggestions - pos - 1);
+ sortedScores[pos] = score;
int poolSize = mStringPool.size();
StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1)
: new StringBuilder(getApproxMaxWordLength());
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index fe7aac7c2..a8cdfc02e 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -32,14 +32,14 @@ public class SuggestedWords {
public final List<SuggestedWordInfo> mSuggestedWordInfoList;
private SuggestedWords(List<CharSequence> words, boolean typedWordValid,
- boolean hasMinamlSuggestion, List<SuggestedWordInfo> suggestedWordInfoList) {
+ boolean hasMinimalSuggestion, List<SuggestedWordInfo> suggestedWordInfoList) {
if (words != null) {
mWords = words;
} else {
mWords = Collections.emptyList();
}
mTypedWordValid = typedWordValid;
- mHasMinimalSuggestion = hasMinamlSuggestion;
+ mHasMinimalSuggestion = hasMinimalSuggestion;
mSuggestedWordInfoList = suggestedWordInfoList;
}
@@ -113,8 +113,8 @@ public class SuggestedWords {
return this;
}
- public Builder setHasMinimalSuggestion(boolean hasMinamlSuggestion) {
- mHasMinimalSuggestion = hasMinamlSuggestion;
+ public Builder setHasMinimalSuggestion(boolean hasMinimalSuggestion) {
+ mHasMinimalSuggestion = hasMinimalSuggestion;
return this;
}
diff --git a/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java b/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java
new file mode 100644
index 000000000..4a3f42d5d
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java
@@ -0,0 +1,43 @@
+/*
+ * 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 com.android.inputmethod.compat.SuggestionSpanUtils;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class SuggestionSpanPickedNotificationReceiver extends BroadcastReceiver {
+ private static final boolean DBG = LatinImeLogger.sDBG;
+ private static final String TAG =
+ SuggestionSpanPickedNotificationReceiver.class.getSimpleName();
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (SuggestionSpanUtils.ACTION_SUGGESTION_PICKED.equals(intent.getAction())) {
+ if (DBG) {
+ final String before = intent.getStringExtra(
+ SuggestionSpanUtils.SUGGESTION_SPAN_PICKED_BEFORE);
+ final String after = intent.getStringExtra(
+ SuggestionSpanUtils.SUGGESTION_SPAN_PICKED_AFTER);
+ Log.d(TAG, "Received notification picked: " + before + "," + after);
+ }
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/TextEntryState.java b/java/src/com/android/inputmethod/latin/TextEntryState.java
index 63196430b..de13f3ae4 100644
--- a/java/src/com/android/inputmethod/latin/TextEntryState.java
+++ b/java/src/com/android/inputmethod/latin/TextEntryState.java
@@ -16,6 +16,8 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.latin.Utils.RingCharBuffer;
+
import android.util.Log;
public class TextEntryState {
@@ -43,10 +45,12 @@ public class TextEntryState {
sState = newState;
}
- public static void acceptedDefault(CharSequence typedWord, CharSequence actualWord) {
+ public static void acceptedDefault(CharSequence typedWord, CharSequence actualWord,
+ int separatorCode) {
if (typedWord == null) return;
setState(ACCEPTED_DEFAULT);
- LatinImeLogger.logOnAutoSuggestion(typedWord.toString(), actualWord.toString());
+ LatinImeLogger.logOnAutoCorrection(
+ typedWord.toString(), actualWord.toString(), separatorCode);
if (DEBUG)
displayState("acceptedDefault", "typedWord", typedWord, "actualWord", actualWord);
}
@@ -95,7 +99,7 @@ public class TextEntryState {
if (DEBUG) displayState("onAbortRecorrection");
}
- public static void typedCharacter(char c, boolean isSeparator) {
+ public static void typedCharacter(char c, boolean isSeparator, int x, int y) {
final boolean isSpace = (c == ' ');
switch (sState) {
case IN_WORD:
@@ -149,13 +153,19 @@ public class TextEntryState {
setState(START);
break;
}
+ RingCharBuffer.getInstance().push(c, x, y);
+ if (isSeparator) {
+ LatinImeLogger.logOnInputSeparator();
+ } else {
+ LatinImeLogger.logOnInputChar();
+ }
if (DEBUG) displayState("typedCharacter", "char", c, "isSeparator", isSeparator);
}
public static void backspace() {
if (sState == ACCEPTED_DEFAULT) {
setState(UNDO_COMMIT);
- LatinImeLogger.logOnAutoSuggestionCanceled();
+ LatinImeLogger.logOnAutoCorrectionCancelled();
} else if (sState == UNDO_COMMIT) {
setState(IN_WORD);
}
diff --git a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
index 656e6f8e0..5b615ca29 100644
--- a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
@@ -44,12 +44,6 @@ public class UserBigramDictionary extends ExpandableDictionary {
/** Maximum frequency for all pairs */
private static final int FREQUENCY_MAX = 127;
- /**
- * If this pair is typed 6 times, it would be suggested.
- * Should be smaller than ContactsDictionary.FREQUENCY_FOR_CONTACTS_BIGRAM
- */
- protected static final int SUGGEST_THRESHOLD = 6 * FREQUENCY_FOR_TYPED;
-
/** Maximum number of pairs. Pruning will start when databases goes above this number. */
private static int sMaxUserBigrams = 10000;
@@ -164,10 +158,14 @@ public class UserBigramDictionary extends ExpandableDictionary {
* Pair will be added to the userbigram database.
*/
public int addBigrams(String word1, String word2) {
- // remove caps
+ // remove caps if second word is autocapitalized
if (mIme != null && mIme.getCurrentWord().isAutoCapitalized()) {
word2 = Character.toLowerCase(word2.charAt(0)) + word2.substring(1);
}
+ // Do not insert a word as a bigram of itself
+ if (word1.equals(word2)) {
+ return 0;
+ }
int freq = super.addBigram(word1, word2, FREQUENCY_FOR_TYPED);
if (freq > FREQUENCY_MAX) freq = FREQUENCY_MAX;
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index 1cbc434b6..6bdc0a857 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -16,6 +16,15 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.compat.InputMethodInfoCompatWrapper;
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper;
+import com.android.inputmethod.compat.InputTypeCompatUtils;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardId;
+
+import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.inputmethodservice.InputMethodService;
import android.os.AsyncTask;
@@ -26,10 +35,6 @@ 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;
-
-import com.android.inputmethod.keyboard.KeyboardId;
import java.io.BufferedReader;
import java.io.File;
@@ -39,12 +44,17 @@ import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
public class Utils {
private static final String TAG = Utils.class.getSimpleName();
private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4;
private static boolean DBG = LatinImeLogger.sDBG;
+ private static boolean DBG_EDIT_DISTANCE = false;
private Utils() {
// Intentional empty constructor for utility class.
@@ -101,17 +111,49 @@ public class Utils {
}
}
- public static boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm) {
- return imm.getEnabledInputMethodList().size() > 1
+ public static boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManagerCompatWrapper imm) {
+ final List<InputMethodInfoCompatWrapper> enabledImis = imm.getEnabledInputMethodList();
+
+ // Filters out IMEs that have auxiliary subtypes only (including either implicitly or
+ // explicitly enabled ones).
+ final ArrayList<InputMethodInfoCompatWrapper> filteredImis =
+ new ArrayList<InputMethodInfoCompatWrapper>();
+
+ outerloop:
+ for (InputMethodInfoCompatWrapper imi : enabledImis) {
+ // We can return true immediately after we find two or more filtered IMEs.
+ if (filteredImis.size() > 1) return true;
+ final List<InputMethodSubtypeCompatWrapper> subtypes =
+ imm.getEnabledInputMethodSubtypeList(imi, true);
+ // IMEs that have no subtypes should be included.
+ if (subtypes.isEmpty()) {
+ filteredImis.add(imi);
+ continue;
+ }
+ // IMEs that have one or more non-auxiliary subtypes should be included.
+ for (InputMethodSubtypeCompatWrapper subtype : subtypes) {
+ if (!subtype.isAuxiliary()) {
+ filteredImis.add(imi);
+ continue outerloop;
+ }
+ }
+ }
+
+ return filteredImis.size() > 1
// imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled
// input method subtype (The current IME should be LatinIME.)
|| imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
}
- public static String getInputMethodId(InputMethodManager imm, String packageName) {
- for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) {
+ public static String getInputMethodId(InputMethodManagerCompatWrapper imm, String packageName) {
+ return getInputMethodInfo(imm, packageName).getId();
+ }
+
+ public static InputMethodInfoCompatWrapper getInputMethodInfo(
+ InputMethodManagerCompatWrapper imm, String packageName) {
+ for (final InputMethodInfoCompatWrapper imi : imm.getEnabledInputMethodList()) {
if (imi.getPackageName().equals(packageName))
- return imi.getId();
+ return imi;
}
throw new RuntimeException("Can not find input method id for " + packageName);
}
@@ -202,11 +244,11 @@ public class Utils {
return mCharBuf[mEnd];
}
}
- public char getLastChar() {
- if (mLength < 1) {
+ public char getBackwardNthChar(int n) {
+ if (mLength <= n || n < 0) {
return PLACEHOLDER_DELIMITER_CHAR;
} else {
- return mCharBuf[normalize(mEnd - 1)];
+ return mCharBuf[normalize(mEnd - n - 1)];
}
}
public int getPreviousX(char c, int back) {
@@ -227,9 +269,16 @@ public class Utils {
return mYBuf[index];
}
}
- public String getLastString() {
+ public String getLastWord(int ignoreCharCount) {
StringBuilder sb = new StringBuilder();
- for (int i = 0; i < mLength; ++i) {
+ int i = ignoreCharCount;
+ for (; i < mLength; ++i) {
+ char c = mCharBuf[normalize(mEnd - 1 - i)];
+ if (!((LatinIME)mContext).isWordSeparator(c)) {
+ break;
+ }
+ }
+ for (; i < mLength; ++i) {
char c = mCharBuf[normalize(mEnd - 1 - i)];
if (!((LatinIME)mContext).isWordSeparator(c)) {
sb.append(c);
@@ -244,6 +293,8 @@ public class Utils {
}
}
+
+ /* Damerau-Levenshtein distance */
public static int editDistance(CharSequence s, CharSequence t) {
if (s == null || t == null) {
throw new IllegalArgumentException("editDistance: Arguments should not be null.");
@@ -259,12 +310,27 @@ public class Utils {
}
for (int i = 0; i < sl; ++i) {
for (int j = 0; j < tl; ++j) {
- if (Character.toLowerCase(s.charAt(i)) == Character.toLowerCase(t.charAt(j))) {
- dp[i + 1][j + 1] = dp[i][j];
- } else {
- dp[i + 1][j + 1] = 1 + Math.min(dp[i][j],
- Math.min(dp[i + 1][j], dp[i][j + 1]));
+ final char sc = Character.toLowerCase(s.charAt(i));
+ final char tc = Character.toLowerCase(t.charAt(j));
+ final int cost = sc == tc ? 0 : 1;
+ dp[i + 1][j + 1] = Math.min(
+ dp[i][j + 1] + 1, Math.min(dp[i + 1][j] + 1, dp[i][j] + cost));
+ // Overwrite for transposition cases
+ if (i > 0 && j > 0
+ && sc == Character.toLowerCase(t.charAt(j - 1))
+ && tc == Character.toLowerCase(s.charAt(i - 1))) {
+ dp[i + 1][j + 1] = Math.min(dp[i + 1][j + 1], dp[i - 1][j - 1] + cost);
+ }
+ }
+ }
+ if (DBG_EDIT_DISTANCE) {
+ Log.d(TAG, "editDistance:" + s + "," + t);
+ for (int i = 0; i < dp.length; ++i) {
+ StringBuffer sb = new StringBuffer();
+ for (int j = 0; j < dp[i].length; ++j) {
+ sb.append(dp[i][j]).append(',');
}
+ Log.d(TAG, i + ":" + sb.toString());
}
}
return dp[sl][tl];
@@ -285,7 +351,7 @@ public class Utils {
// In dictionary.cpp, getSuggestion() method,
// suggestion scores are computed using the below formula.
- // original score (called 'frequency')
+ // original score
// := pow(mTypedLetterMultiplier (this is defined 2),
// (the number of matched characters between typed word and suggested word))
// * (individual word's score which defined in the unigram dictionary,
@@ -295,7 +361,7 @@ public class Utils {
// (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.
+ // capitalization, then treat it as if the score was 255.
// - If before.length() == after.length()
// => multiply by mFullWordMultiplier (this is defined 2))
// So, maximum original score is pow(2, min(before.length(), after.length())) * 255 * 2 * 1.2
@@ -306,6 +372,7 @@ public class Utils {
private static final int MAX_INITIAL_SCORE = 255;
private static final int TYPED_LETTER_MULTIPLIER = 2;
private static final int FULL_WORD_MULTIPLIER = 2;
+ private static final int S_INT_MAX = 2147483647;
public static double calcNormalizedScore(CharSequence before, CharSequence after, int score) {
final int beforeLength = before.length();
final int afterLength = after.length();
@@ -313,8 +380,16 @@ public class Utils {
final int distance = editDistance(before, after);
// If afterLength < beforeLength, the algorithm is suggesting a word by excessive character
// correction.
- final double maximumScore = MAX_INITIAL_SCORE
- * Math.pow(TYPED_LETTER_MULTIPLIER, Math.min(beforeLength, afterLength))
+ int spaceCount = 0;
+ for (int i = 0; i < afterLength; ++i) {
+ if (after.charAt(i) == Keyboard.CODE_SPACE) {
+ ++spaceCount;
+ }
+ }
+ if (spaceCount == afterLength) return 0;
+ final double maximumScore = score == S_INT_MAX ? S_INT_MAX : MAX_INITIAL_SCORE
+ * Math.pow(
+ TYPED_LETTER_MULTIPLIER, Math.min(beforeLength, afterLength - spaceCount))
* FULL_WORD_MULTIPLIER;
// add a weight based on edit distance.
// distance <= max(afterLength, beforeLength) == afterLength,
@@ -485,7 +560,7 @@ public class Utils {
case InputType.TYPE_CLASS_PHONE:
return KeyboardId.MODE_PHONE;
case InputType.TYPE_CLASS_TEXT:
- if (Utils.isEmailVariation(variation)) {
+ if (InputTypeCompatUtils.isEmailVariation(variation)) {
return KeyboardId.MODE_EMAIL;
} else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
return KeyboardId.MODE_URL;
@@ -501,42 +576,6 @@ public class Utils {
}
}
- public static boolean isEmailVariation(int variation) {
- return variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
- || variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
- }
-
- public static boolean isWebInputType(int inputType) {
- final int variation =
- inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION);
- return (variation
- == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT))
- || (variation
- == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD))
- || (variation
- == (InputType.TYPE_CLASS_TEXT | 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;
@@ -560,7 +599,9 @@ public class Utils {
* @return main dictionary resource id
*/
public static int getMainDictionaryResourceId(Resources res) {
- return res.getIdentifier("main", "raw", LatinIME.class.getPackage().getName());
+ final String MAIN_DIC_NAME = "main";
+ String packageName = LatinIME.class.getPackage().getName();
+ return res.getIdentifier(MAIN_DIC_NAME, "raw", packageName);
}
public static void loadNativeLibrary() {
@@ -570,4 +611,108 @@ public class Utils {
Log.e(TAG, "Could not load native library jni_latinime");
}
}
+
+ /**
+ * Returns true if a and b are equal ignoring the case of the character.
+ * @param a first character to check
+ * @param b second character to check
+ * @return {@code true} if a and b are equal, {@code false} otherwise.
+ */
+ public static boolean equalsIgnoreCase(char a, char b) {
+ // Some language, such as Turkish, need testing both cases.
+ return a == b
+ || Character.toLowerCase(a) == Character.toLowerCase(b)
+ || Character.toUpperCase(a) == Character.toUpperCase(b);
+ }
+
+ /**
+ * Returns true if a and b are equal ignoring the case of the characters, including if they are
+ * both null.
+ * @param a first CharSequence to check
+ * @param b second CharSequence to check
+ * @return {@code true} if a and b are equal, {@code false} otherwise.
+ */
+ public static boolean equalsIgnoreCase(CharSequence a, CharSequence b) {
+ if (a == b)
+ return true; // including both a and b are null.
+ if (a == null || b == null)
+ return false;
+ final int length = a.length();
+ if (length != b.length())
+ return false;
+ for (int i = 0; i < length; i++) {
+ if (!equalsIgnoreCase(a.charAt(i), b.charAt(i)))
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns true if a and b are equal ignoring the case of the characters, including if a is null
+ * and b is zero length.
+ * @param a CharSequence to check
+ * @param b character array to check
+ * @param offset start offset of array b
+ * @param length length of characters in array b
+ * @return {@code true} if a and b are equal, {@code false} otherwise.
+ * @throws IndexOutOfBoundsException
+ * if {@code offset < 0 || length < 0 || offset + length > data.length}.
+ * @throws NullPointerException if {@code b == null}.
+ */
+ public static boolean equalsIgnoreCase(CharSequence a, char[] b, int offset, int length) {
+ if (offset < 0 || length < 0 || length > b.length - offset)
+ throw new IndexOutOfBoundsException("array.length=" + b.length + " offset=" + offset
+ + " length=" + length);
+ if (a == null)
+ return length == 0; // including a is null and b is zero length.
+ if (a.length() != length)
+ return false;
+ for (int i = 0; i < length; i++) {
+ if (!equalsIgnoreCase(a.charAt(i), b[offset + i]))
+ return false;
+ }
+ return true;
+ }
+
+ public static float getDipScale(Context context) {
+ final float scale = context.getResources().getDisplayMetrics().density;
+ return scale;
+ }
+
+ /** Convert pixel to DIP */
+ public static int dipToPixel(float scale, int dip) {
+ return (int) (dip * scale + 0.5);
+ }
+
+ public static Locale setSystemLocale(Resources res, Locale newLocale) {
+ final Configuration conf = res.getConfiguration();
+ final Locale saveLocale = conf.locale;
+ conf.locale = newLocale;
+ res.updateConfiguration(conf, res.getDisplayMetrics());
+ return saveLocale;
+ }
+
+ private static final HashMap<String, Locale> sLocaleCache = new HashMap<String, Locale>();
+
+ public static Locale constructLocaleFromString(String localeStr) {
+ if (localeStr == null)
+ return null;
+ synchronized (sLocaleCache) {
+ if (sLocaleCache.containsKey(localeStr))
+ return sLocaleCache.get(localeStr);
+ Locale retval = null;
+ String[] localeParams = localeStr.split("_", 3);
+ if (localeParams.length == 1) {
+ retval = new Locale(localeParams[0]);
+ } else if (localeParams.length == 2) {
+ retval = new Locale(localeParams[0], localeParams[1]);
+ } else if (localeParams.length == 3) {
+ retval = new Locale(localeParams[0], localeParams[1], localeParams[2]);
+ }
+ if (retval != null) {
+ sLocaleCache.put(localeStr, retval);
+ }
+ return retval;
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
index 2389d4e3c..4377373d2 100644
--- a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
+++ b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
@@ -39,6 +39,7 @@ public class WhitelistDictionary extends Dictionary {
public static WhitelistDictionary init(Context context) {
synchronized (sInstance) {
if (context != null) {
+ // Wordlist is initialized by the proper language in Suggestion.java#init
sInstance.initWordlist(
context.getResources().getStringArray(R.array.wordlist_whitelist));
} else {
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 02583895b..af5e4b179 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -31,18 +31,18 @@ public class WordComposer {
/**
* The list of unicode values for each keystroke (including surrounding keys)
*/
- private final ArrayList<int[]> mCodes;
+ private ArrayList<int[]> mCodes;
private int mTypedLength;
- private final int[] mXCoordinates;
- private final int[] mYCoordinates;
+ private int[] mXCoordinates;
+ private int[] mYCoordinates;
/**
* The word chosen from the candidate list, until it is committed.
*/
private String mPreferredWord;
- private final StringBuilder mTypedWord;
+ private StringBuilder mTypedWord;
private int mCapsCount;
@@ -62,7 +62,11 @@ public class WordComposer {
mYCoordinates = new int[N];
}
- WordComposer(WordComposer source) {
+ public WordComposer(WordComposer source) {
+ init(source);
+ }
+
+ public void init(WordComposer source) {
mCodes = new ArrayList<int[]>(source.mCodes);
mPreferredWord = source.mPreferredWord;
mTypedWord = new StringBuilder(source.mTypedWord);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellChecker.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellChecker.java
new file mode 100644
index 000000000..e3407a269
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellChecker.java
@@ -0,0 +1,114 @@
+/*
+ * 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.spellcheck;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.Dictionary.DataType;
+import com.android.inputmethod.latin.Dictionary.WordCallback;
+import com.android.inputmethod.latin.DictionaryFactory;
+import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.WordComposer;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Implements spell checking methods.
+ */
+public class SpellChecker {
+
+ public final Dictionary mDictionary;
+
+ public SpellChecker(final Context context, final Locale locale) {
+ final Resources resources = context.getResources();
+ final int fallbackResourceId = Utils.getMainDictionaryResourceId(resources);
+ mDictionary = DictionaryFactory.createDictionaryFromManager(context, locale,
+ fallbackResourceId);
+ }
+
+ // Note : this must be reentrant
+ /**
+ * Finds out whether a word is in the dictionary or not.
+ *
+ * @param text the sequence containing the word to check for.
+ * @param start the index of the first character of the word in text.
+ * @param end the index of the next-to-last character in text.
+ * @return true if the word is in the dictionary, false otherwise.
+ */
+ public boolean isCorrect(final CharSequence text, final int start, final int end) {
+ return mDictionary.isValidWord(text.subSequence(start, end));
+ }
+
+ private static class SuggestionsGatherer implements WordCallback {
+ private final int DEFAULT_SUGGESTION_LENGTH = 16;
+ private final List<String> mSuggestions = new LinkedList<String>();
+ private int[] mScores = new int[DEFAULT_SUGGESTION_LENGTH];
+ private int mLength = 0;
+
+ synchronized public boolean addWord(char[] word, int wordOffset, int wordLength, int score,
+ int dicTypeId, DataType dataType) {
+ if (mLength >= mScores.length) {
+ final int newLength = mScores.length * 2;
+ mScores = new int[newLength];
+ }
+ final int positionIndex = Arrays.binarySearch(mScores, 0, mLength, score);
+ // binarySearch returns the index if the element exists, and -<insertion index> - 1
+ // if it doesn't. See documentation for binarySearch.
+ final int insertionIndex = positionIndex >= 0 ? positionIndex : -positionIndex - 1;
+ System.arraycopy(mScores, insertionIndex, mScores, insertionIndex + 1,
+ mLength - insertionIndex);
+ mLength += 1;
+ mScores[insertionIndex] = score;
+ mSuggestions.add(insertionIndex, new String(word, wordOffset, wordLength));
+ return true;
+ }
+
+ public List<String> getGatheredSuggestions() {
+ return mSuggestions;
+ }
+ }
+
+ // Note : this must be reentrant
+ /**
+ * Gets a list of suggestions for a specific string.
+ *
+ * This returns a list of possible corrections for the text passed as an
+ * arguments. It may split or group words, and even perform grammatical
+ * analysis.
+ *
+ * @param text the sequence containing the word to check for.
+ * @param start the index of the first character of the word in text.
+ * @param end the index of the next-to-last character in text.
+ * @return a list of possible suggestions to replace the text.
+ */
+ public List<String> getSuggestions(final CharSequence text, final int start, final int end) {
+ final SuggestionsGatherer suggestionsGatherer = new SuggestionsGatherer();
+ final WordComposer composer = new WordComposer();
+ for (int i = start; i < end; ++i) {
+ int character = text.charAt(i);
+ composer.add(character, new int[] { character },
+ WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
+ }
+ mDictionary.getWords(composer, suggestionsGatherer);
+ return suggestionsGatherer.getGatheredSuggestions();
+ }
+}