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/BinaryDictionary.java22
-rw-r--r--java/src/com/android/inputmethod/latin/Constants.java11
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java27
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryCollection.java15
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitator.java444
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java156
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFactory.java6
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryStats.java35
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java37
-rw-r--r--java/src/com/android/inputmethod/latin/InputAttributes.java11
-rw-r--r--java/src/com/android/inputmethod/latin/InputPointers.java6
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java111
-rw-r--r--java/src/com/android/inputmethod/latin/PersonalizationHelperForDictionaryFacilitator.java185
-rw-r--r--java/src/com/android/inputmethod/latin/PrevWordsInfo.java23
-rw-r--r--java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java2
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java10
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputMethodManager.java13
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java125
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java50
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java20
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestedWords.java9
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java11
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java43
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FormatSpec.java93
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java8
-rw-r--r--java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java7
-rw-r--r--java/src/com/android/inputmethod/latin/settings/Settings.java1
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsValues.java23
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java15
-rw-r--r--java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java30
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java127
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java20
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilter.java34
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java214
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java6
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java53
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java58
-rw-r--r--java/src/com/android/inputmethod/latin/utils/StringUtils.java4
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java42
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SuggestionResults.java8
40 files changed, 1373 insertions, 742 deletions
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 693e1cdcc..8b13bdb02 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -83,7 +83,6 @@ public final class BinaryDictionary extends Dictionary {
public static final String DIR_NAME_SUFFIX_FOR_RECORD_MIGRATION = ".migrating";
private long mNativeDict;
- private final Locale mLocale;
private final long mDictSize;
private final String mDictFilePath;
private final boolean mUseFullEditDistance;
@@ -117,8 +116,7 @@ public final class BinaryDictionary extends Dictionary {
public BinaryDictionary(final String filename, final long offset, final long length,
final boolean useFullEditDistance, final Locale locale, final String dictType,
final boolean isUpdatable) {
- super(dictType);
- mLocale = locale;
+ super(dictType, locale);
mDictSize = length;
mDictFilePath = filename;
mIsUpdatable = isUpdatable;
@@ -138,8 +136,7 @@ public final class BinaryDictionary extends Dictionary {
public BinaryDictionary(final String filename, final boolean useFullEditDistance,
final Locale locale, final String dictType, final long formatVersion,
final Map<String, String> attributeMap) {
- super(dictType);
- mLocale = locale;
+ super(dictType, locale);
mDictSize = 0;
mDictFilePath = filename;
// On memory dictionary is always updatable.
@@ -361,9 +358,8 @@ public final class BinaryDictionary extends Dictionary {
if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
return NOT_A_PROBABILITY;
}
- final int[][] prevWordCodePointArrays = new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][];
- final boolean[] isBeginningOfSentenceArray =
- new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
+ final int[][] prevWordCodePointArrays = new int[prevWordsInfo.getPrevWordCount()][];
+ final boolean[] isBeginningOfSentenceArray = new boolean[prevWordsInfo.getPrevWordCount()];
prevWordsInfo.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray);
final int[] wordCodePoints = StringUtils.toCodePointArray(word);
return getNgramProbabilityNative(mNativeDict, prevWordCodePointArrays,
@@ -458,9 +454,8 @@ public final class BinaryDictionary extends Dictionary {
if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
return false;
}
- final int[][] prevWordCodePointArrays = new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][];
- final boolean[] isBeginningOfSentenceArray =
- new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
+ final int[][] prevWordCodePointArrays = new int[prevWordsInfo.getPrevWordCount()][];
+ final boolean[] isBeginningOfSentenceArray = new boolean[prevWordsInfo.getPrevWordCount()];
prevWordsInfo.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray);
final int[] wordCodePoints = StringUtils.toCodePointArray(word);
if (!addNgramEntryNative(mNativeDict, prevWordCodePointArrays,
@@ -476,9 +471,8 @@ public final class BinaryDictionary extends Dictionary {
if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
return false;
}
- final int[][] prevWordCodePointArrays = new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][];
- final boolean[] isBeginningOfSentenceArray =
- new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
+ final int[][] prevWordCodePointArrays = new int[prevWordsInfo.getPrevWordCount()][];
+ final boolean[] isBeginningOfSentenceArray = new boolean[prevWordsInfo.getPrevWordCount()];
prevWordsInfo.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray);
final int[] wordCodePoints = StringUtils.toCodePointArray(word);
if (!removeNgramEntryNative(mNativeDict, prevWordCodePointArrays,
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 43af66eb7..fc7f95c7b 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -57,6 +57,13 @@ public final class Constants {
@SuppressWarnings("dep-ann")
public static final String FORCE_ASCII = "forceAscii";
+ /**
+ * The private IME option used to suppress the floating gesture preview for a given text
+ * field. This overrides the corresponding keyboard settings preference.
+ * {@link com.android.inputmethod.latin.settings.SettingsValues#mGestureFloatingPreviewTextEnabled}
+ */
+ public static final String NO_FLOATING_GESTURE_PREVIEW = "noGestureFloatingPreview";
+
private ImeOption() {
// This utility class is not publicly instantiable.
}
@@ -217,10 +224,12 @@ public final class Constants {
public static final int CODE_CLOSING_ANGLE_BRACKET = '>';
public static final int CODE_INVERTED_QUESTION_MARK = 0xBF; // ¿
public static final int CODE_INVERTED_EXCLAMATION_MARK = 0xA1; // ¡
+ public static final int CODE_GRAVE_ACCENT = '`';
+ public static final int CODE_CIRCUMFLEX_ACCENT = '^';
+ public static final int CODE_TILDE = '~';
public static final String REGEXP_PERIOD = "\\.";
public static final String STRING_SPACE = " ";
- public static final String STRING_PERIOD_AND_SPACE = ". ";
/**
* Special keys code. Must be negative.
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 560ced9c4..cad9ee7d8 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -16,12 +16,14 @@
package com.android.inputmethod.latin;
-import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
import java.util.ArrayList;
+import java.util.Locale;
+import java.util.Arrays;
+import java.util.HashSet;
/**
* Abstract base class for a dictionary that can do a fuzzy search for words based on a set of key
@@ -62,9 +64,20 @@ public abstract class Dictionary {
// Contextual dictionary.
public static final String TYPE_CONTEXTUAL = "contextual";
public final String mDictType;
+ // The locale for this dictionary. May be null if unknown (phony dictionary for example).
+ public final Locale mLocale;
- public Dictionary(final String dictType) {
+ /**
+ * Set out of the dictionary types listed above that are based on data specific to the user,
+ * e.g., the user's contacts.
+ */
+ private static final HashSet<String> sUserSpecificDictionaryTypes =
+ new HashSet(Arrays.asList(new String[] { TYPE_USER_TYPED, TYPE_USER, TYPE_CONTACTS,
+ TYPE_USER_HISTORY, TYPE_PERSONALIZATION, TYPE_CONTEXTUAL }));
+
+ public Dictionary(final String dictType, final Locale locale) {
mDictType = dictType;
+ mLocale = locale;
}
/**
@@ -156,13 +169,21 @@ public abstract class Dictionary {
}
/**
+ * Whether this dictionary is based on data specific to the user, e.g., the user's contacts.
+ * @return Whether this dictionary is specific to the user.
+ */
+ public boolean isUserSpecific() {
+ return sUserSpecificDictionaryTypes.contains(mDictType);
+ }
+
+ /**
* Not a true dictionary. A placeholder used to indicate suggestions that don't come from any
* real dictionary.
*/
private static class PhonyDictionary extends Dictionary {
// This class is not publicly instantiable.
private PhonyDictionary(final String type) {
- super(type);
+ super(type, null);
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index 2b4c54d48..ca5e93714 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -25,6 +25,7 @@ import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Locale;
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -34,13 +35,14 @@ public final class DictionaryCollection extends Dictionary {
private final String TAG = DictionaryCollection.class.getSimpleName();
protected final CopyOnWriteArrayList<Dictionary> mDictionaries;
- public DictionaryCollection(final String dictType) {
- super(dictType);
+ public DictionaryCollection(final String dictType, final Locale locale) {
+ super(dictType, locale);
mDictionaries = new CopyOnWriteArrayList<>();
}
- public DictionaryCollection(final String dictType, final Dictionary... dictionaries) {
- super(dictType);
+ public DictionaryCollection(final String dictType, final Locale locale,
+ final Dictionary... dictionaries) {
+ super(dictType, locale);
if (null == dictionaries) {
mDictionaries = new CopyOnWriteArrayList<>();
} else {
@@ -49,8 +51,9 @@ public final class DictionaryCollection extends Dictionary {
}
}
- public DictionaryCollection(final String dictType, final Collection<Dictionary> dictionaries) {
- super(dictType);
+ public DictionaryCollection(final String dictType, final Locale locale,
+ final Collection<Dictionary> dictionaries) {
+ super(dictType, locale);
mDictionaries = new CopyOnWriteArrayList<>(dictionaries);
mDictionaries.removeAll(Collections.singleton(null));
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
index 36a02669d..eced45ea5 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
@@ -19,10 +19,12 @@ package com.android.inputmethod.latin;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback;
import com.android.inputmethod.latin.PrevWordsInfo.WordInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.personalization.ContextualDictionary;
@@ -32,9 +34,9 @@ import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
import com.android.inputmethod.latin.utils.DistracterFilter;
+import com.android.inputmethod.latin.utils.DistracterFilterCheckingExactMatchesAndSuggestions;
import com.android.inputmethod.latin.utils.DistracterFilterCheckingIsInDictionary;
import com.android.inputmethod.latin.utils.ExecutorUtils;
-import com.android.inputmethod.latin.utils.LanguageModelParam;
import com.android.inputmethod.latin.utils.SuggestionResults;
import java.io.File;
@@ -59,12 +61,13 @@ public class DictionaryFacilitator {
// dictionary.
private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140;
- private Dictionaries mDictionaries = new Dictionaries();
+ private DictionaryGroup[] mDictionaryGroups = new DictionaryGroup[] { new DictionaryGroup() };
private boolean mIsUserDictEnabled = false;
- private volatile CountDownLatch mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0);
- // To synchronize assigning mDictionaries to ensure closing dictionaries.
+ private volatile CountDownLatch mLatchForWaitingLoadingMainDictionaries = new CountDownLatch(0);
+ // To synchronize assigning mDictionaryGroup to ensure closing dictionaries.
private final Object mLock = new Object();
private final DistracterFilter mDistracterFilter;
+ private final PersonalizationHelperForDictionaryFacilitator mPersonalizationHelper;
private static final String[] DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS =
new String[] {
@@ -96,22 +99,22 @@ public class DictionaryFacilitator {
DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS.length);
/**
- * Class contains dictionaries for a locale.
+ * A group of dictionaries that work together for a single language.
*/
- private static class Dictionaries {
+ private static class DictionaryGroup {
public final Locale mLocale;
private Dictionary mMainDict;
public final ConcurrentHashMap<String, ExpandableBinaryDictionary> mSubDictMap =
new ConcurrentHashMap<>();
- public Dictionaries() {
+ public DictionaryGroup() {
mLocale = null;
}
- public Dictionaries(final Locale locale, final Dictionary mainDict,
+ public DictionaryGroup(final Locale locale, final Dictionary mainDict,
final Map<String, ExpandableBinaryDictionary> subDicts) {
mLocale = locale;
- // Main dictionary can be asynchronously loaded.
+ // The main dictionary can be asynchronously loaded.
setMainDict(mainDict);
for (final Map.Entry<String, ExpandableBinaryDictionary> entry : subDicts.entrySet()) {
setSubDict(entry.getKey(), entry.getValue());
@@ -172,18 +175,27 @@ public class DictionaryFacilitator {
public DictionaryFacilitator() {
mDistracterFilter = DistracterFilter.EMPTY_DISTRACTER_FILTER;
+ mPersonalizationHelper = null;
}
- public DictionaryFacilitator(final DistracterFilter distracterFilter) {
- mDistracterFilter = distracterFilter;
+ public DictionaryFacilitator(final Context context) {
+ mDistracterFilter = new DistracterFilterCheckingExactMatchesAndSuggestions(context);
+ mPersonalizationHelper =
+ new PersonalizationHelperForDictionaryFacilitator(context, mDistracterFilter);
}
public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) {
mDistracterFilter.updateEnabledSubtypes(enabledSubtypes);
+ mPersonalizationHelper.updateEnabledSubtypes(enabledSubtypes);
}
+ public void setIsMonolingualUser(final boolean isMonolingualUser) {
+ mPersonalizationHelper.setIsMonolingualUser(isMonolingualUser);
+ }
+
+ // TODO: remove this, replace with version returning multiple locales
public Locale getLocale() {
- return mDictionaries.mLocale;
+ return mDictionaryGroups[0].mLocale;
}
private static ExpandableBinaryDictionary getSubDict(final String dictType,
@@ -215,97 +227,151 @@ public class DictionaryFacilitator {
usePersonalizedDicts, forceReloadMainDictionary, listener, "" /* dictNamePrefix */);
}
- public void resetDictionariesWithDictNamePrefix(final Context context, final Locale newLocale,
+ private DictionaryGroup findDictionaryGroupWithLocale(final DictionaryGroup[] dictionaryGroups,
+ final Locale locale) {
+ for (int i = 0; i < dictionaryGroups.length; ++i) {
+ if (locale.equals(dictionaryGroups[i].mLocale)) {
+ return dictionaryGroups[i];
+ }
+ }
+ return null;
+ }
+
+ private DictionaryGroup getDictionaryGroupForActiveLanguage() {
+ // TODO: implement this
+ return mDictionaryGroups[0];
+ }
+
+ public void resetDictionariesWithDictNamePrefix(final Context context,
+ final Locale newLocaleToUse,
final boolean useContactsDict, final boolean usePersonalizedDicts,
final boolean forceReloadMainDictionary,
final DictionaryInitializationListener listener,
final String dictNamePrefix) {
- final boolean localeHasBeenChanged = !newLocale.equals(mDictionaries.mLocale);
- // We always try to have the main dictionary. Other dictionaries can be unused.
- final boolean reloadMainDictionary = localeHasBeenChanged || forceReloadMainDictionary;
+ final HashMap<Locale, ArrayList<String>> existingDictsToCleanup = new HashMap<>();
+ // TODO: use several locales
+ final Locale[] newLocales = new Locale[] { newLocaleToUse };
// TODO: Make subDictTypesToUse configurable by resource or a static final list.
final HashSet<String> subDictTypesToUse = new HashSet<>();
+ subDictTypesToUse.add(Dictionary.TYPE_USER);
if (useContactsDict) {
subDictTypesToUse.add(Dictionary.TYPE_CONTACTS);
}
- subDictTypesToUse.add(Dictionary.TYPE_USER);
if (usePersonalizedDicts) {
subDictTypesToUse.add(Dictionary.TYPE_USER_HISTORY);
subDictTypesToUse.add(Dictionary.TYPE_PERSONALIZATION);
subDictTypesToUse.add(Dictionary.TYPE_CONTEXTUAL);
}
- final Dictionary newMainDict;
- if (reloadMainDictionary) {
- // The main dictionary will be asynchronously loaded.
- newMainDict = null;
- } else {
- newMainDict = mDictionaries.getDict(Dictionary.TYPE_MAIN);
- }
-
- final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
- for (final String dictType : SUB_DICT_TYPES) {
- if (!subDictTypesToUse.contains(dictType)) {
- // This dictionary will not be used.
+ // Gather all dictionaries. We'll remove them from the list to clean up later.
+ for (final Locale newLocale : newLocales) {
+ final ArrayList<String> dictsForLocale = new ArrayList<>();
+ existingDictsToCleanup.put(newLocale, dictsForLocale);
+ final DictionaryGroup currentDictionaryGroupForLocale =
+ findDictionaryGroupWithLocale(mDictionaryGroups, newLocale);
+ if (null == currentDictionaryGroupForLocale) {
continue;
}
- final ExpandableBinaryDictionary dict;
- if (!localeHasBeenChanged && mDictionaries.hasDict(dictType)) {
- // Continue to use current dictionary.
- dict = mDictionaries.getSubDict(dictType);
+ for (final String dictType : SUB_DICT_TYPES) {
+ if (currentDictionaryGroupForLocale.hasDict(dictType)) {
+ dictsForLocale.add(dictType);
+ }
+ }
+ if (currentDictionaryGroupForLocale.hasDict(Dictionary.TYPE_MAIN)) {
+ dictsForLocale.add(Dictionary.TYPE_MAIN);
+ }
+ }
+
+ final DictionaryGroup[] newDictionaryGroups = new DictionaryGroup[newLocales.length];
+ for (int i = 0; i < newLocales.length; ++i) {
+ final Locale newLocale = newLocales[i];
+ final DictionaryGroup dictionaryGroupForLocale =
+ findDictionaryGroupWithLocale(mDictionaryGroups, newLocale);
+ final ArrayList<String> dictsToCleanupForLocale = existingDictsToCleanup.get(newLocale);
+ final boolean noExistingDictsForThisLocale = (null == dictionaryGroupForLocale);
+
+ final Dictionary mainDict;
+ if (forceReloadMainDictionary || noExistingDictsForThisLocale
+ || !dictionaryGroupForLocale.hasDict(Dictionary.TYPE_MAIN)) {
+ mainDict = null;
} else {
- // Start to use new dictionary.
- dict = getSubDict(dictType, context, newLocale, null /* dictFile */,
- dictNamePrefix);
+ mainDict = dictionaryGroupForLocale.getDict(Dictionary.TYPE_MAIN);
+ dictsToCleanupForLocale.remove(Dictionary.TYPE_MAIN);
+ }
+
+ final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
+ for (final String subDictType : subDictTypesToUse) {
+ final ExpandableBinaryDictionary subDict;
+ if (noExistingDictsForThisLocale
+ || !dictionaryGroupForLocale.hasDict(subDictType)) {
+ // Create a new dictionary.
+ subDict = getSubDict(subDictType, context, newLocale, null /* dictFile */,
+ dictNamePrefix);
+ } else {
+ // Reuse the existing dictionary, and don't close it at the end
+ subDict = dictionaryGroupForLocale.getSubDict(subDictType);
+ dictsToCleanupForLocale.remove(subDictType);
+ }
+ subDicts.put(subDictType, subDict);
}
- subDicts.put(dictType, dict);
+ newDictionaryGroups[i] = new DictionaryGroup(newLocale, mainDict, subDicts);
}
// Replace Dictionaries.
- final Dictionaries newDictionaries = new Dictionaries(newLocale, newMainDict, subDicts);
- final Dictionaries oldDictionaries;
+ final DictionaryGroup[] oldDictionaryGroups;
synchronized (mLock) {
- oldDictionaries = mDictionaries;
- mDictionaries = newDictionaries;
+ oldDictionaryGroups = mDictionaryGroups;
+ mDictionaryGroups = newDictionaryGroups;
mIsUserDictEnabled = UserBinaryDictionary.isEnabled(context);
- if (reloadMainDictionary) {
- asyncReloadMainDictionary(context, newLocale, listener);
+ if (hasAtLeastOneUninitializedMainDictionary()) {
+ asyncReloadUninitializedMainDictionaries(context, newLocales, listener);
}
}
if (listener != null) {
- listener.onUpdateMainDictionaryAvailability(hasInitializedMainDictionary());
+ listener.onUpdateMainDictionaryAvailability(hasAtLeastOneInitializedMainDictionary());
}
+
// Clean up old dictionaries.
- if (reloadMainDictionary) {
- oldDictionaries.closeDict(Dictionary.TYPE_MAIN);
- }
- for (final String dictType : SUB_DICT_TYPES) {
- if (localeHasBeenChanged || !subDictTypesToUse.contains(dictType)) {
- oldDictionaries.closeDict(dictType);
+ for (final Locale localeToCleanUp : existingDictsToCleanup.keySet()) {
+ final ArrayList<String> dictTypesToCleanUp =
+ existingDictsToCleanup.get(localeToCleanUp);
+ final DictionaryGroup dictionarySetToCleanup =
+ findDictionaryGroupWithLocale(oldDictionaryGroups, localeToCleanUp);
+ for (final String dictType : dictTypesToCleanUp) {
+ dictionarySetToCleanup.closeDict(dictType);
}
}
- oldDictionaries.mSubDictMap.clear();
}
- private void asyncReloadMainDictionary(final Context context, final Locale locale,
- final DictionaryInitializationListener listener) {
+ private void asyncReloadUninitializedMainDictionaries(final Context context,
+ final Locale[] locales, final DictionaryInitializationListener listener) {
final CountDownLatch latchForWaitingLoadingMainDictionary = new CountDownLatch(1);
- mLatchForWaitingLoadingMainDictionary = latchForWaitingLoadingMainDictionary;
+ mLatchForWaitingLoadingMainDictionaries = latchForWaitingLoadingMainDictionary;
ExecutorUtils.getExecutor("InitializeBinaryDictionary").execute(new Runnable() {
@Override
public void run() {
- final Dictionary mainDict =
- DictionaryFactory.createMainDictionaryFromManager(context, locale);
- synchronized (mLock) {
- if (locale.equals(mDictionaries.mLocale)) {
- mDictionaries.setMainDict(mainDict);
- } else {
- // Dictionary facilitator has been reset for another locale.
- mainDict.close();
+ for (final Locale locale : locales) {
+ final DictionaryGroup dictionaryGroup =
+ findDictionaryGroupWithLocale(mDictionaryGroups, locale);
+ if (null == dictionaryGroup) {
+ // This should never happen, but better safe than crashy
+ Log.w(TAG, "Expected a dictionary group for " + locale + " but none found");
+ continue;
+ }
+ final Dictionary mainDict =
+ DictionaryFactory.createMainDictionaryFromManager(context, locale);
+ synchronized (mLock) {
+ if (locale.equals(dictionaryGroup.mLocale)) {
+ dictionaryGroup.setMainDict(mainDict);
+ } else {
+ // Dictionary facilitator has been reset for another locale.
+ mainDict.close();
+ }
}
}
if (listener != null) {
- listener.onUpdateMainDictionaryAvailability(hasInitializedMainDictionary());
+ listener.onUpdateMainDictionaryAvailability(
+ hasAtLeastOneInitializedMainDictionary());
}
latchForWaitingLoadingMainDictionary.countDown();
}
@@ -338,57 +404,94 @@ public class DictionaryFacilitator {
subDicts.put(dictType, dict);
}
}
- mDictionaries = new Dictionaries(locale, mainDictionary, subDicts);
+ mDictionaryGroups = new DictionaryGroup[] {
+ new DictionaryGroup(locale, mainDictionary, subDicts) };
}
public void closeDictionaries() {
- final Dictionaries dictionaries;
+ final DictionaryGroup[] dictionaryGroups;
synchronized (mLock) {
- dictionaries = mDictionaries;
- mDictionaries = new Dictionaries();
+ dictionaryGroups = mDictionaryGroups;
+ mDictionaryGroups = new DictionaryGroup[] { new DictionaryGroup() };
}
- for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
- dictionaries.closeDict(dictType);
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+ dictionaryGroup.closeDict(dictType);
+ }
}
mDistracterFilter.close();
+ if (mPersonalizationHelper != null) {
+ mPersonalizationHelper.close();
+ }
}
@UsedForTesting
public ExpandableBinaryDictionary getSubDictForTesting(final String dictName) {
- return mDictionaries.getSubDict(dictName);
+ return mDictionaryGroups[0].getSubDict(dictName);
}
- // The main dictionary could have been loaded asynchronously. Don't cache the return value
- // of this method.
- public boolean hasInitializedMainDictionary() {
- final Dictionary mainDict = mDictionaries.getDict(Dictionary.TYPE_MAIN);
- return mainDict != null && mainDict.isInitialized();
+ // The main dictionaries are loaded asynchronously. Don't cache the return value
+ // of these methods.
+ public boolean hasAtLeastOneInitializedMainDictionary() {
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ final Dictionary mainDict = dictionaryGroup.getDict(Dictionary.TYPE_MAIN);
+ if (mainDict != null && mainDict.isInitialized()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean hasAtLeastOneUninitializedMainDictionary() {
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ final Dictionary mainDict = dictionaryGroup.getDict(Dictionary.TYPE_MAIN);
+ if (mainDict == null || !mainDict.isInitialized()) {
+ return true;
+ }
+ }
+ return false;
}
public boolean hasPersonalizationDictionary() {
- return mDictionaries.hasDict(Dictionary.TYPE_PERSONALIZATION);
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ if (dictionaryGroup.hasDict(Dictionary.TYPE_PERSONALIZATION)) {
+ return true;
+ }
+ }
+ return false;
}
public void flushPersonalizationDictionary() {
- final ExpandableBinaryDictionary personalizationDict =
- mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION);
- if (personalizationDict != null) {
- personalizationDict.asyncFlushBinaryDictionary();
- }
+ final HashSet<ExpandableBinaryDictionary> personalizationDictsUsedForSuggestion =
+ new HashSet<>();
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ final ExpandableBinaryDictionary personalizationDictUsedForSuggestion =
+ dictionaryGroup.getSubDict(Dictionary.TYPE_PERSONALIZATION);
+ personalizationDictsUsedForSuggestion.add(personalizationDictUsedForSuggestion);
+ }
+ mPersonalizationHelper.flushPersonalizationDictionariesToUpdate(
+ personalizationDictsUsedForSuggestion);
+ mDistracterFilter.close();
}
- public void waitForLoadingMainDictionary(final long timeout, final TimeUnit unit)
+ public void waitForLoadingMainDictionaries(final long timeout, final TimeUnit unit)
throws InterruptedException {
- mLatchForWaitingLoadingMainDictionary.await(timeout, unit);
+ mLatchForWaitingLoadingMainDictionaries.await(timeout, unit);
}
@UsedForTesting
public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit)
throws InterruptedException {
- waitForLoadingMainDictionary(timeout, unit);
- final Map<String, ExpandableBinaryDictionary> dictMap = mDictionaries.mSubDictMap;
- for (final ExpandableBinaryDictionary dict : dictMap.values()) {
- dict.waitAllTasksForTests();
+ waitForLoadingMainDictionaries(timeout, unit);
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ for (final ExpandableBinaryDictionary dict : dictionaryGroup.mSubDictMap.values()) {
+ dict.waitAllTasksForTests();
+ }
}
}
@@ -407,24 +510,24 @@ public class DictionaryFacilitator {
public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
final PrevWordsInfo prevWordsInfo, final int timeStampInSeconds,
final boolean blockPotentiallyOffensive) {
- final Dictionaries dictionaries = mDictionaries;
+ final DictionaryGroup dictionaryGroup = getDictionaryGroupForActiveLanguage();
final String[] words = suggestion.split(Constants.WORD_SEPARATOR);
PrevWordsInfo prevWordsInfoForCurrentWord = prevWordsInfo;
for (int i = 0; i < words.length; i++) {
final String currentWord = words[i];
final boolean wasCurrentWordAutoCapitalized = (i == 0) ? wasAutoCapitalized : false;
- addWordToUserHistory(dictionaries, prevWordsInfoForCurrentWord, currentWord,
+ addWordToUserHistory(dictionaryGroup, prevWordsInfoForCurrentWord, currentWord,
wasCurrentWordAutoCapitalized, timeStampInSeconds, blockPotentiallyOffensive);
prevWordsInfoForCurrentWord =
prevWordsInfoForCurrentWord.getNextPrevWordsInfo(new WordInfo(currentWord));
}
}
- private void addWordToUserHistory(final Dictionaries dictionaries,
+ private void addWordToUserHistory(final DictionaryGroup dictionaryGroup,
final PrevWordsInfo prevWordsInfo, final String word, final boolean wasAutoCapitalized,
final int timeStampInSeconds, final boolean blockPotentiallyOffensive) {
final ExpandableBinaryDictionary userHistoryDictionary =
- dictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
+ dictionaryGroup.getSubDict(Dictionary.TYPE_USER_HISTORY);
if (userHistoryDictionary == null) {
return;
}
@@ -432,7 +535,7 @@ public class DictionaryFacilitator {
if (maxFreq == 0 && blockPotentiallyOffensive) {
return;
}
- final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale);
+ final String lowerCasedWord = word.toLowerCase(dictionaryGroup.mLocale);
final String secondWord;
if (wasAutoCapitalized) {
if (isValidWord(word, false /* ignoreCase */)
@@ -453,8 +556,8 @@ public class DictionaryFacilitator {
// History dictionary in order to avoid suggesting them until the dictionary
// consolidation is done.
// TODO: Remove this hack when ready.
- final int lowerCaseFreqInMainDict = dictionaries.hasDict(Dictionary.TYPE_MAIN) ?
- dictionaries.getDict(Dictionary.TYPE_MAIN).getFrequency(lowerCasedWord) :
+ final int lowerCaseFreqInMainDict = dictionaryGroup.hasDict(Dictionary.TYPE_MAIN) ?
+ dictionaryGroup.getDict(Dictionary.TYPE_MAIN).getFrequency(lowerCasedWord) :
Dictionary.NOT_A_PROBABILITY;
if (maxFreq < lowerCaseFreqInMainDict
&& lowerCaseFreqInMainDict >= CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT) {
@@ -474,7 +577,8 @@ public class DictionaryFacilitator {
}
private void removeWord(final String dictName, final String word) {
- final ExpandableBinaryDictionary dictionary = mDictionaries.getSubDict(dictName);
+ final ExpandableBinaryDictionary dictionary =
+ getDictionaryGroupForActiveLanguage().getSubDict(dictName);
if (dictionary != null) {
dictionary.removeUnigramEntryDynamically(word);
}
@@ -490,20 +594,22 @@ public class DictionaryFacilitator {
public SuggestionResults getSuggestionResults(final WordComposer composer,
final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId) {
- final Dictionaries dictionaries = mDictionaries;
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
final SuggestionResults suggestionResults =
- new SuggestionResults(dictionaries.mLocale, SuggestedWords.MAX_SUGGESTIONS);
+ new SuggestionResults(SuggestedWords.MAX_SUGGESTIONS);
final float[] languageWeight = new float[] { Dictionary.NOT_A_LANGUAGE_WEIGHT };
- for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
- final Dictionary dictionary = dictionaries.getDict(dictType);
- if (null == dictionary) continue;
- final ArrayList<SuggestedWordInfo> dictionarySuggestions =
- dictionary.getSuggestions(composer, prevWordsInfo, proximityInfo,
- settingsValuesForSuggestion, sessionId, languageWeight);
- if (null == dictionarySuggestions) continue;
- suggestionResults.addAll(dictionarySuggestions);
- if (null != suggestionResults.mRawSuggestions) {
- suggestionResults.mRawSuggestions.addAll(dictionarySuggestions);
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+ final Dictionary dictionary = dictionaryGroup.getDict(dictType);
+ if (null == dictionary) continue;
+ final ArrayList<SuggestedWordInfo> dictionarySuggestions =
+ dictionary.getSuggestions(composer, prevWordsInfo, proximityInfo,
+ settingsValuesForSuggestion, sessionId, languageWeight);
+ if (null == dictionarySuggestions) continue;
+ suggestionResults.addAll(dictionarySuggestions);
+ if (null != suggestionResults.mRawSuggestions) {
+ suggestionResults.mRawSuggestions.addAll(dictionarySuggestions);
+ }
}
}
return suggestionResults;
@@ -513,20 +619,22 @@ public class DictionaryFacilitator {
if (TextUtils.isEmpty(word)) {
return false;
}
- final Dictionaries dictionaries = mDictionaries;
- if (dictionaries.mLocale == null) {
- return false;
- }
- final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale);
- for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
- final Dictionary dictionary = dictionaries.getDict(dictType);
- // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
- // would be immutable once it's finished initializing, but concretely a null test is
- // probably good enough for the time being.
- if (null == dictionary) continue;
- if (dictionary.isValidWord(word)
- || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
- return true;
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ if (dictionaryGroup.mLocale == null) {
+ continue;
+ }
+ final String lowerCasedWord = word.toLowerCase(dictionaryGroup.mLocale);
+ for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+ final Dictionary dictionary = dictionaryGroup.getDict(dictType);
+ // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
+ // would be immutable once it's finished initializing, but concretely a null test is
+ // probably good enough for the time being.
+ if (null == dictionary) continue;
+ if (dictionary.isValidWord(word)
+ || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
+ return true;
+ }
}
}
return false;
@@ -538,18 +646,20 @@ public class DictionaryFacilitator {
return Dictionary.NOT_A_PROBABILITY;
}
int maxFreq = Dictionary.NOT_A_PROBABILITY;
- final Dictionaries dictionaries = mDictionaries;
- for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
- final Dictionary dictionary = dictionaries.getDict(dictType);
- if (dictionary == null) continue;
- final int tempFreq;
- if (isGettingMaxFrequencyOfExactMatches) {
- tempFreq = dictionary.getMaxFrequencyOfExactMatches(word);
- } else {
- tempFreq = dictionary.getFrequency(word);
- }
- if (tempFreq >= maxFreq) {
- maxFreq = tempFreq;
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+ final Dictionary dictionary = dictionaryGroup.getDict(dictType);
+ if (dictionary == null) continue;
+ final int tempFreq;
+ if (isGettingMaxFrequencyOfExactMatches) {
+ tempFreq = dictionary.getMaxFrequencyOfExactMatches(word);
+ } else {
+ tempFreq = dictionary.getFrequency(word);
+ }
+ if (tempFreq >= maxFreq) {
+ maxFreq = tempFreq;
+ }
}
}
return maxFreq;
@@ -564,9 +674,12 @@ public class DictionaryFacilitator {
}
private void clearSubDictionary(final String dictName) {
- final ExpandableBinaryDictionary dictionary = mDictionaries.getSubDict(dictName);
- if (dictionary != null) {
- dictionary.clear();
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ final ExpandableBinaryDictionary dictionary = dictionaryGroup.getSubDict(dictName);
+ if (dictionary != null) {
+ dictionary.clear();
+ }
}
}
@@ -578,6 +691,7 @@ public class DictionaryFacilitator {
// personalization dictionary.
public void clearPersonalizationDictionary() {
clearSubDictionary(Dictionary.TYPE_PERSONALIZATION);
+ mPersonalizationHelper.clearDictionariesToUpdate();
}
public void clearContextualDictionary() {
@@ -587,35 +701,17 @@ public class DictionaryFacilitator {
public void addEntriesToPersonalizationDictionary(
final PersonalizationDataChunk personalizationDataChunk,
final SpacingAndPunctuations spacingAndPunctuations,
- final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) {
- final ExpandableBinaryDictionary personalizationDict =
- mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION);
- if (personalizationDict == null) {
- if (callback != null) {
- callback.onFinished();
- }
- return;
- }
- final ArrayList<LanguageModelParam> languageModelParams =
- LanguageModelParam.createLanguageModelParamsFrom(
- personalizationDataChunk.mTokens,
- personalizationDataChunk.mTimestampInSeconds,
- this /* dictionaryFacilitator */, spacingAndPunctuations,
- new DistracterFilterCheckingIsInDictionary(
- mDistracterFilter, personalizationDict));
- if (languageModelParams == null || languageModelParams.isEmpty()) {
- if (callback != null) {
- callback.onFinished();
- }
- return;
- }
- personalizationDict.addMultipleDictionaryEntriesDynamically(languageModelParams, callback);
+ final AddMultipleDictionaryEntriesCallback callback) {
+ mPersonalizationHelper.addEntriesToPersonalizationDictionariesToUpdate(
+ getLocale(), personalizationDataChunk, spacingAndPunctuations, callback);
}
public void addPhraseToContextualDictionary(final String[] phrase, final int probability,
final int bigramProbabilityForWords, final int bigramProbabilityForPhrases) {
+ // TODO: we're inserting the phrase into the dictionary for the active language. Rethink
+ // this a bit from a theoretical point of view.
final ExpandableBinaryDictionary contextualDict =
- mDictionaries.getSubDict(Dictionary.TYPE_CONTEXTUAL);
+ getDictionaryGroupForActiveLanguage().getSubDict(Dictionary.TYPE_CONTEXTUAL);
if (contextualDict == null) {
return;
}
@@ -648,12 +744,28 @@ public class DictionaryFacilitator {
}
public void dumpDictionaryForDebug(final String dictName) {
- final ExpandableBinaryDictionary dictToDump = mDictionaries.getSubDict(dictName);
- if (dictToDump == null) {
- Log.e(TAG, "Cannot dump " + dictName + ". "
- + "The dictionary is not being used for suggestion or cannot be dumped.");
- return;
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ final ExpandableBinaryDictionary dictToDump = dictionaryGroup.getSubDict(dictName);
+ if (dictToDump == null) {
+ Log.e(TAG, "Cannot dump " + dictName + ". "
+ + "The dictionary is not being used for suggestion or cannot be dumped.");
+ return;
+ }
+ dictToDump.dumpAllWordsForDebug();
+ }
+ }
+
+ public ArrayList<Pair<String, DictionaryStats>> getStatsOfEnabledSubDicts() {
+ final ArrayList<Pair<String, DictionaryStats>> statsOfEnabledSubDicts = new ArrayList<>();
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ for (final String dictType : SUB_DICT_TYPES) {
+ final ExpandableBinaryDictionary dictionary = dictionaryGroup.getSubDict(dictType);
+ if (dictionary == null) continue;
+ statsOfEnabledSubDicts.add(new Pair<>(dictType, dictionary.getDictionaryStats()));
+ }
}
- dictToDump.dumpAllWordsForDebug();
+ return statsOfEnabledSubDicts;
}
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
new file mode 100644
index 000000000..ff4a6bde1
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2014 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.HashSet;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import android.content.Context;
+import android.util.Log;
+import android.util.LruCache;
+
+/**
+ * Cache for dictionary facilitators of multiple locales.
+ * This class automatically creates and releases facilitator instances using LRU policy.
+ */
+public class DictionaryFacilitatorLruCache {
+ private static final String TAG = DictionaryFacilitatorLruCache.class.getSimpleName();
+ private static final int WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS = 1000;
+ private static final int MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT = 5;
+
+ /**
+ * Class extends LruCache. This class tracks cached locales and closes evicted dictionaries by
+ * overriding entryRemoved.
+ */
+ private static class DictionaryFacilitatorLruCacheInner extends
+ LruCache<Locale, DictionaryFacilitator> {
+ private final HashSet<Locale> mCachedLocales;
+ public DictionaryFacilitatorLruCacheInner(final HashSet<Locale> cachedLocales,
+ final int maxSize) {
+ super(maxSize);
+ mCachedLocales = cachedLocales;
+ }
+
+ @Override
+ protected void entryRemoved(boolean evicted, Locale key,
+ DictionaryFacilitator oldValue, DictionaryFacilitator newValue) {
+ if (oldValue != null && oldValue != newValue) {
+ oldValue.closeDictionaries();
+ }
+ if (key != null && newValue == null) {
+ // Remove locale from the cache when the dictionary facilitator for the locale is
+ // evicted and new facilitator is not set for the locale.
+ mCachedLocales.remove(key);
+ if (size() >= maxSize()) {
+ Log.w(TAG, "DictionaryFacilitator for " + key.toString()
+ + " has been evicted due to cache size limit."
+ + " size: " + size() + ", maxSize: " + maxSize());
+ }
+ }
+ }
+ }
+
+ private final Context mContext;
+ private final HashSet<Locale> mCachedLocales = new HashSet<>();
+ private final String mDictionaryNamePrefix;
+ private final DictionaryFacilitatorLruCacheInner mLruCache;
+ private final Object mLock = new Object();
+ private boolean mUseContactsDictionary = false;
+
+ public DictionaryFacilitatorLruCache(final Context context, final int maxSize,
+ final String dictionaryNamePrefix) {
+ mContext = context;
+ mLruCache = new DictionaryFacilitatorLruCacheInner(mCachedLocales, maxSize);
+ mDictionaryNamePrefix = dictionaryNamePrefix;
+ }
+
+ private void waitForLoadingMainDictionary(final DictionaryFacilitator dictionaryFacilitator) {
+ for (int i = 0; i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT; i++) {
+ try {
+ dictionaryFacilitator.waitForLoadingMainDictionaries(
+ WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
+ return;
+ } catch (final InterruptedException e) {
+ Log.i(TAG, "Interrupted during waiting for loading main dictionary.", e);
+ if (i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT - 1) {
+ Log.i(TAG, "Retry", e);
+ } else {
+ Log.w(TAG, "Give up retrying. Retried "
+ + MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT + " times.", e);
+ }
+ }
+ }
+ }
+
+ private void resetDictionariesForLocaleLocked(final DictionaryFacilitator dictionaryFacilitator,
+ final Locale locale) {
+ dictionaryFacilitator.resetDictionariesWithDictNamePrefix(mContext, locale,
+ mUseContactsDictionary, false /* usePersonalizedDicts */,
+ false /* forceReloadMainDictionary */, null /* listener */,
+ mDictionaryNamePrefix);
+ }
+
+ public void setUseContactsDictionary(final boolean useContectsDictionary) {
+ if (mUseContactsDictionary == useContectsDictionary) {
+ // The value has not been changed.
+ return;
+ }
+ synchronized (mLock) {
+ mUseContactsDictionary = useContectsDictionary;
+ for (final Locale locale : mCachedLocales) {
+ final DictionaryFacilitator dictionaryFacilitator = mLruCache.get(locale);
+ resetDictionariesForLocaleLocked(dictionaryFacilitator, locale);
+ waitForLoadingMainDictionary(dictionaryFacilitator);
+ }
+ }
+ }
+
+ public DictionaryFacilitator get(final Locale locale) {
+ DictionaryFacilitator dictionaryFacilitator = mLruCache.get(locale);
+ if (dictionaryFacilitator != null) {
+ // dictionary falicitator for the locale is in the cache.
+ return dictionaryFacilitator;
+ }
+ synchronized (mLock) {
+ dictionaryFacilitator = mLruCache.get(locale);
+ if (dictionaryFacilitator != null) {
+ return dictionaryFacilitator;
+ }
+ dictionaryFacilitator = new DictionaryFacilitator();
+ resetDictionariesForLocaleLocked(dictionaryFacilitator, locale);
+ waitForLoadingMainDictionary(dictionaryFacilitator);
+ mLruCache.put(locale, dictionaryFacilitator);
+ mCachedLocales.add(locale);
+ return dictionaryFacilitator;
+ }
+ }
+
+ public void evictAll() {
+ synchronized (mLock) {
+ mLruCache.evictAll();
+ mCachedLocales.clear();
+ }
+ }
+
+ @UsedForTesting
+ HashSet<Locale> getCachedLocalesForTesting() {
+ return mCachedLocales;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index 59de4f82a..3459b426d 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -50,7 +50,7 @@ public final class DictionaryFactory {
final Locale locale, final boolean useFullEditDistance) {
if (null == locale) {
Log.e(TAG, "No locale defined for dictionary");
- return new DictionaryCollection(Dictionary.TYPE_MAIN,
+ return new DictionaryCollection(Dictionary.TYPE_MAIN, locale,
createReadOnlyBinaryDictionary(context, locale));
}
@@ -75,7 +75,7 @@ public final class DictionaryFactory {
// If the list is empty, that means we should not use any dictionary (for example, the user
// explicitly disabled the main dictionary), so the following is okay. dictList is never
// null, but if for some reason it is, DictionaryCollection handles it gracefully.
- return new DictionaryCollection(Dictionary.TYPE_MAIN, dictList);
+ return new DictionaryCollection(Dictionary.TYPE_MAIN, locale, dictList);
}
/**
@@ -188,7 +188,7 @@ public final class DictionaryFactory {
public static Dictionary createDictionaryForTest(final AssetFileAddress[] dictionaryList,
final boolean useFullEditDistance, Locale locale) {
final DictionaryCollection dictionaryCollection =
- new DictionaryCollection(Dictionary.TYPE_MAIN);
+ new DictionaryCollection(Dictionary.TYPE_MAIN, locale);
for (final AssetFileAddress address : dictionaryList) {
final ReadOnlyBinaryDictionary readOnlyBinaryDictionary = new ReadOnlyBinaryDictionary(
address.mFilename, address.mOffset, address.mLength, useFullEditDistance,
diff --git a/java/src/com/android/inputmethod/latin/DictionaryStats.java b/java/src/com/android/inputmethod/latin/DictionaryStats.java
new file mode 100644
index 000000000..75aa2411d
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryStats.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014 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;
+import java.util.Locale;
+
+public class DictionaryStats {
+ public final Locale mLocale;
+ public final String mDictName;
+ public final String mDictFilePath;
+ public final long mDictFileSize;
+ // TODO: Add more members.
+
+ public DictionaryStats(final Locale locale, final String dictName, final File dictFile) {
+ mLocale = locale;
+ mDictName = dictName;
+ mDictFilePath = dictFile.getAbsolutePath();
+ mDictFileSize = dictFile.length();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index c11a220a4..671ba6714 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -86,9 +86,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
private final String mDictName;
- /** Dictionary locale */
- private final Locale mLocale;
-
/** Dictionary file */
private final File mDictFile;
@@ -137,10 +134,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
public ExpandableBinaryDictionary(final Context context, final String dictName,
final Locale locale, final String dictType, final File dictFile) {
- super(dictType);
+ super(dictType, locale);
mDictName = dictName;
mContext = context;
- mLocale = locale;
mDictFile = getDictFile(context, dictName, dictFile);
mBinaryDictionary = null;
mIsReloading = new AtomicBoolean();
@@ -160,23 +156,25 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
private void asyncExecuteTaskWithWriteLock(final Runnable task) {
- asyncExecuteTaskWithLock(mLock.writeLock(), task);
+ asyncExecuteTaskWithLock(mLock.writeLock(), mDictName /* executorName */, task);
}
- private void asyncExecuteTaskWithLock(final Lock lock, final Runnable task) {
- asyncPreCheckAndExecuteTaskWithLock(lock, null /* preCheckTask */, task);
+ private void asyncExecuteTaskWithLock(final Lock lock, final String executorName,
+ final Runnable task) {
+ asyncPreCheckAndExecuteTaskWithLock(lock, null /* preCheckTask */, executorName, task);
}
private void asyncPreCheckAndExecuteTaskWithWriteLock(
final Callable<Boolean> preCheckTask, final Runnable task) {
- asyncPreCheckAndExecuteTaskWithLock(mLock.writeLock(), preCheckTask, task);
+ asyncPreCheckAndExecuteTaskWithLock(mLock.writeLock(), preCheckTask,
+ mDictName /* executorName */, task);
}
// Execute task with lock when the result of preCheckTask is true or preCheckTask is null.
private void asyncPreCheckAndExecuteTaskWithLock(final Lock lock,
- final Callable<Boolean> preCheckTask, final Runnable task) {
- ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
+ final Callable<Boolean> preCheckTask, final String executorName, final Runnable task) {
+ ExecutorUtils.getExecutor(executorName).execute(new Runnable() {
@Override
public void run() {
if (preCheckTask != null) {
@@ -644,10 +642,21 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
});
}
+ public DictionaryStats getDictionaryStats() {
+ reloadDictionaryIfRequired();
+ mLock.readLock().lock();
+ try {
+ // TODO: Get stats form the dictionary.
+ return new DictionaryStats(mLocale, mDictName, mDictFile);
+ } finally {
+ mLock.readLock().unlock();
+ }
+ }
+
@UsedForTesting
public void waitAllTasksForTests() {
final CountDownLatch countDownLatch = new CountDownLatch(1);
- ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
+ asyncExecuteTaskWithWriteLock(new Runnable() {
@Override
public void run() {
countDownLatch.countDown();
@@ -669,10 +678,10 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
public void dumpAllWordsForDebug() {
reloadDictionaryIfRequired();
- asyncExecuteTaskWithLock(mLock.readLock(), new Runnable() {
+ asyncExecuteTaskWithLock(mLock.readLock(), "dumpAllWordsForDebug", new Runnable() {
@Override
public void run() {
- Log.d(TAG, "Dump dictionary: " + mDictName);
+ Log.d(TAG, "Dump dictionary: " + mDictName + " for " + mLocale);
try {
final DictionaryHeader header = mBinaryDictionary.getHeader();
Log.d(TAG, "Format version: " + mBinaryDictionary.getFormatVersion());
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index fecb0ef94..ffd363b5d 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.latin;
+import static com.android.inputmethod.latin.Constants.ImeOption.NO_FLOATING_GESTURE_PREVIEW;
import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE;
import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE_COMPAT;
@@ -42,6 +43,12 @@ public final class InputAttributes {
final public boolean mApplicationSpecifiedCompletionOn;
final public boolean mShouldInsertSpacesAutomatically;
final public boolean mShouldShowVoiceInputKey;
+ /**
+ * Whether the floating gesture preview should be disabled. If true, this should override the
+ * corresponding keyboard settings preference, always suppressing the floating preview text.
+ * {@link com.android.inputmethod.latin.settings.SettingsValues#mGestureFloatingPreviewTextEnabled}
+ */
+ final public boolean mDisableGestureFloatingPreviewText;
final public boolean mIsGeneralTextInput;
final private int mInputType;
final private EditorInfo mEditorInfo;
@@ -77,6 +84,7 @@ public final class InputAttributes {
mApplicationSpecifiedCompletionOn = false;
mShouldInsertSpacesAutomatically = false;
mShouldShowVoiceInputKey = false;
+ mDisableGestureFloatingPreviewText = false;
mIsGeneralTextInput = false;
return;
}
@@ -109,6 +117,9 @@ public final class InputAttributes {
|| hasNoMicrophoneKeyOption();
mShouldShowVoiceInputKey = !noMicrophone;
+ mDisableGestureFloatingPreviewText = InputAttributes.inPrivateImeOptions(
+ mPackageNameForPrivateImeOptions, NO_FLOATING_GESTURE_PREVIEW, editorInfo);
+
// If it's a browser edit field and auto correct is not ON explicitly, then
// disable auto correction, but keep suggestions on.
// If NO_SUGGESTIONS is set, don't do prediction.
diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/java/src/com/android/inputmethod/latin/InputPointers.java
index 790e0d830..d57a881c0 100644
--- a/java/src/com/android/inputmethod/latin/InputPointers.java
+++ b/java/src/com/android/inputmethod/latin/InputPointers.java
@@ -145,6 +145,12 @@ public final class InputPointers {
return mPointerIds.getPrimitiveArray();
}
+ /**
+ * Gets the time each point was registered, in milliseconds, relative to the first event in the
+ * sequence.
+ * @return The time each point was registered, in milliseconds, relative to the first event in
+ * the sequence.
+ */
public int[] getTimes() {
if (DebugFlags.DEBUG_ENABLED || DEBUG_TIME) {
if (!isValidTimeStamps()) {
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 81953221e..e2abb84c7 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -95,6 +95,7 @@ import com.android.inputmethod.latin.utils.IntentUtils;
import com.android.inputmethod.latin.utils.JniUtils;
import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
import com.android.inputmethod.latin.utils.StatsUtils;
+import com.android.inputmethod.latin.utils.StatsUtilsManager;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import com.android.inputmethod.latin.utils.ViewLayoutUtils;
@@ -132,8 +133,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private final Settings mSettings;
private final DictionaryFacilitator mDictionaryFacilitator =
- new DictionaryFacilitator(
- new DistracterFilterCheckingExactMatchesAndSuggestions(this /* context */));
+ new DictionaryFacilitator(this /* context */);
// TODO: Move from LatinIME.
private final PersonalizationDictionaryUpdater mPersonalizationDictionaryUpdater =
new PersonalizationDictionaryUpdater(this /* context */, mDictionaryFacilitator);
@@ -161,6 +161,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private final SubtypeSwitcher mSubtypeSwitcher;
private final SubtypeState mSubtypeState = new SubtypeState();
private final SpecialKeyDetector mSpecialKeyDetector;
+ private StatsUtilsManager mStatsUtilsManager;
// Working variable for {@link #startShowingInputView()} and
// {@link #onEvaluateInputViewShown()}.
private boolean mIsExecutingStartShowingInputView;
@@ -519,6 +520,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mSubtypeSwitcher = SubtypeSwitcher.getInstance();
mKeyboardSwitcher = KeyboardSwitcher.getInstance();
mSpecialKeyDetector = new SpecialKeyDetector(this);
+ mStatsUtilsManager = StatsUtilsManager.getInstance();
mIsHardwareAcceleratedDrawingEnabled =
InputMethodServiceCompatUtils.enableHardwareAcceleration(this);
Log.i(TAG, "Hardware accelerated drawing: " + mIsHardwareAcceleratedDrawingEnabled);
@@ -534,8 +536,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
KeyboardSwitcher.init(this);
AudioAndHapticFeedbackManager.init(this);
AccessibilityUtils.init(this);
- StatsUtils.init(this);
-
+ mStatsUtilsManager.onCreate(this /* context */);
super.onCreate();
mHandler.onCreate();
@@ -567,8 +568,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
registerReceiver(mDictionaryDumpBroadcastReceiver, dictDumpFilter);
DictionaryDecayBroadcastReciever.setUpIntervalAlarmForDictionaryDecaying(this);
-
- StatsUtils.onCreate(mSettings.getCurrent());
+ StatsUtils.onCreate(mSettings.getCurrent(), mRichImm);
}
// Has to be package-visible for unit tests
@@ -590,14 +590,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mDictionaryFacilitator.updateEnabledSubtypes(mRichImm.getMyEnabledInputMethodSubtypeList(
true /* allowsImplicitlySelectedSubtypes */));
refreshPersonalizationDictionarySession(currentSettingsValues);
- StatsUtils.onLoadSettings(currentSettingsValues);
+ mStatsUtilsManager.onLoadSettings(currentSettingsValues);
}
private void refreshPersonalizationDictionarySession(
final SettingsValues currentSettingsValues) {
- mPersonalizationDictionaryUpdater.onLoadSettings(
- currentSettingsValues.mUsePersonalizedDicts,
+ mDictionaryFacilitator.setIsMonolingualUser(
mSubtypeSwitcher.isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes());
+ mPersonalizationDictionaryUpdater.onLoadSettings(
+ currentSettingsValues.mUsePersonalizedDicts);
mContextualDictionaryUpdater.onLoadSettings(currentSettingsValues.mUsePersonalizedDicts);
final boolean shouldKeepUserHistoryDictionaries;
if (currentSettingsValues.mUsePersonalizedDicts) {
@@ -680,7 +681,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
unregisterReceiver(mConnectivityAndRingerModeChangeReceiver);
unregisterReceiver(mDictionaryPackInstallReceiver);
unregisterReceiver(mDictionaryDumpBroadcastReceiver);
- StatsUtils.onDestroy();
+ mStatsUtilsManager.onDestroy();
super.onDestroy();
}
@@ -715,15 +716,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
cleanupInternalStateForFinishInput();
}
}
- // TODO: Remove this test.
- if (!conf.locale.equals(mPersonalizationDictionaryUpdater.getLocale())) {
- refreshPersonalizationDictionarySession(settingsValues);
- }
super.onConfigurationChanged(conf);
}
@Override
public View onCreateInputView() {
+ StatsUtils.onCreateInputView();
return mKeyboardSwitcher.onCreateInputView(mIsHardwareAcceleratedDrawingEnabled);
}
@@ -799,6 +797,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onFinishInputView(final boolean finishingInput) {
+ StatsUtils.onFinishInputView();
mHandler.onFinishInputView(finishingInput);
}
@@ -811,7 +810,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void onCurrentInputMethodSubtypeChanged(final InputMethodSubtype subtype) {
// Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
// is not guaranteed. It may even be called at the same time on a different thread.
- mSubtypeSwitcher.onSubtypeChanged(subtype);
+ final RichInputMethodSubtype richSubtype = new RichInputMethodSubtype(subtype);
+ mSubtypeSwitcher.onSubtypeChanged(richSubtype);
mInputLogic.onSubtypeChanged(SubtypeLocaleUtils.getCombiningRulesExtraValue(subtype),
mSettings.getCurrent());
loadKeyboard();
@@ -875,6 +875,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final boolean inputTypeChanged = !currentSettingsValues.isSameInputType(editorInfo);
final boolean isDifferentTextField = !restarting || inputTypeChanged;
+
+ StatsUtils.onStartInputView(editorInfo.inputType,
+ Settings.getInstance().getCurrent().mDisplayOrientation,
+ !isDifferentTextField);
+
if (isDifferentTextField) {
mSubtypeSwitcher.updateParametersOnStartInputView();
}
@@ -970,7 +975,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.cancelUpdateSuggestionStrip();
mainKeyboardView.setMainDictionaryAvailability(
- mDictionaryFacilitator.hasInitializedMainDictionary());
+ mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary());
mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn,
currentSettingsValues.mKeyPreviewPopupDismissDelay);
mainKeyboardView.setSlidingKeyInputPreviewEnabled(
@@ -1238,10 +1243,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return mInputLogic.getCurrentRecapitalizeState();
}
- public Locale getCurrentSubtypeLocale() {
- return mSubtypeSwitcher.getCurrentSubtypeLocale();
- }
-
/**
* @param codePoints code points to get coordinates for.
* @return x,y coordinates for this keyboard, as a flattened array.
@@ -1263,13 +1264,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Probably never supposed to happen, but just in case.
return;
}
- final String wordToEdit;
- if (CapsModeUtils.isAutoCapsMode(mInputLogic.mLastComposedWord.mCapitalizedMode)) {
- wordToEdit = word.toLowerCase(getCurrentSubtypeLocale());
- } else {
- wordToEdit = word;
- }
- mDictionaryFacilitator.addWordToUserDictionary(this /* context */, wordToEdit);
+ mDictionaryFacilitator.addWordToUserDictionary(this /* context */, word);
mInputLogic.onAddWordToUserDictionary();
}
@@ -1327,48 +1322,57 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mSubtypeState.switchSubtype(token, mRichImm);
}
+ // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for
+ // alphabetic shift and shift while in symbol layout and get rid of this method.
+ private int getCodePointForKeyboard(final int codePoint) {
+ if (Constants.CODE_SHIFT == codePoint) {
+ final Keyboard currentKeyboard = mKeyboardSwitcher.getKeyboard();
+ if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) {
+ return codePoint;
+ } else {
+ return Constants.CODE_SYMBOL_SHIFT;
+ }
+ } else {
+ return codePoint;
+ }
+ }
+
// Implementation of {@link KeyboardActionListener}.
@Override
public void onCodeInput(final int codePoint, final int x, final int y,
final boolean isKeyRepeat) {
+ // TODO: this processing does not belong inside LatinIME, the caller should be doing this.
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
// x and y include some padding, but everything down the line (especially native
// code) needs the coordinates in the keyboard frame.
// TODO: We should reconsider which coordinate system should be used to represent
// keyboard event. Also we should pull this up -- LatinIME has no business doing
- // this transformation, it should be done already before calling onCodeInput.
+ // this transformation, it should be done already before calling onEvent.
final int keyX = mainKeyboardView.getKeyX(x);
final int keyY = mainKeyboardView.getKeyY(y);
- final int codeToSend;
- if (Constants.CODE_SHIFT == codePoint) {
- // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for
- // alphabetic shift and shift while in symbol layout.
- final Keyboard currentKeyboard = mKeyboardSwitcher.getKeyboard();
- if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) {
- codeToSend = codePoint;
- } else {
- codeToSend = Constants.CODE_SYMBOL_SHIFT;
- }
- } else {
- codeToSend = codePoint;
- }
- if (Constants.CODE_SHORTCUT == codePoint) {
+ final Event event = createSoftwareKeypressEvent(getCodePointForKeyboard(codePoint),
+ keyX, keyY, isKeyRepeat);
+ onEvent(event);
+ }
+
+ // This method is public for testability of LatinIME, but also in the future it should
+ // completely replace #onCodeInput.
+ public void onEvent(final Event event) {
+ if (Constants.CODE_SHORTCUT == event.mKeyCode) {
mSubtypeSwitcher.switchToShortcutIME(this);
- // Still call the *#onCodeInput methods for readability.
}
- final Event event = createSoftwareKeypressEvent(codeToSend, keyX, keyY, isKeyRepeat);
final InputTransaction completeInputTransaction =
mInputLogic.onCodeInput(mSettings.getCurrent(), event,
mKeyboardSwitcher.getKeyboardShiftMode(),
mKeyboardSwitcher.getCurrentKeyboardScriptId(), mHandler);
updateStateAfterInputTransaction(completeInputTransaction);
- mKeyboardSwitcher.onCodeInput(codePoint, getCurrentAutoCapsState(),
- getCurrentRecapitalizeState());
+ mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState());
}
// A helper method to split the code point and the key code. Ultimately, they should not be
// squashed into the same variable, and this method should be removed.
- private static Event createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int keyX,
+ // public for testing, as we don't want to copy the same logic into test code
+ public static Event createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int keyX,
final int keyY, final boolean isKeyRepeat) {
final int keyCode;
final int codePoint;
@@ -1386,13 +1390,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onTextInput(final String rawText) {
// TODO: have the keyboard pass the correct key code when we need it.
- final Event event = Event.createSoftwareTextEvent(rawText, Event.NOT_A_KEY_CODE);
+ final Event event = Event.createSoftwareTextEvent(rawText, Constants.CODE_OUTPUT_TEXT);
final InputTransaction completeInputTransaction =
mInputLogic.onTextInput(mSettings.getCurrent(), event,
mKeyboardSwitcher.getKeyboardShiftMode(), mHandler);
updateStateAfterInputTransaction(completeInputTransaction);
- mKeyboardSwitcher.onCodeInput(Constants.CODE_OUTPUT_TEXT, getCurrentAutoCapsState(),
- getCurrentRecapitalizeState());
+ mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState());
}
@Override
@@ -1550,7 +1553,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (!hasSuggestionStripView()) {
return;
}
- mSuggestionStripView.showAddToDictionaryHint(word);
+ final String wordToShow;
+ if (CapsModeUtils.isAutoCapsMode(mInputLogic.mLastComposedWord.mCapitalizedMode)) {
+ wordToShow = word.toLowerCase(mSubtypeSwitcher.getCurrentSubtypeLocale());
+ } else {
+ wordToShow = word;
+ }
+ mSuggestionStripView.showAddToDictionaryHint(wordToShow);
}
// This will show either an empty suggestion strip (if prediction is enabled) or
diff --git a/java/src/com/android/inputmethod/latin/PersonalizationHelperForDictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/PersonalizationHelperForDictionaryFacilitator.java
new file mode 100644
index 000000000..396d062f8
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/PersonalizationHelperForDictionaryFacilitator.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2014 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.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import android.content.Context;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.latin.ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback;
+import com.android.inputmethod.latin.personalization.PersonalizationDataChunk;
+import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
+import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+import com.android.inputmethod.latin.utils.DistracterFilter;
+import com.android.inputmethod.latin.utils.DistracterFilterCheckingIsInDictionary;
+import com.android.inputmethod.latin.utils.LanguageModelParam;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+
+/**
+ * Class for managing and updating personalization dictionaries.
+ */
+public class PersonalizationHelperForDictionaryFacilitator {
+ private final Context mContext;
+ private final DistracterFilter mDistracterFilter;
+ private final HashMap<String, HashSet<Locale>> mLangToLocalesMap = new HashMap<>();
+ private final HashMap<Locale, ExpandableBinaryDictionary> mPersonalizationDictsToUpdate =
+ new HashMap<>();
+ private boolean mIsMonolingualUser = false;
+
+ PersonalizationHelperForDictionaryFacilitator(final Context context,
+ final DistracterFilter distracterFilter) {
+ mContext = context;
+ mDistracterFilter = distracterFilter;
+ }
+
+ public void close() {
+ mLangToLocalesMap.clear();
+ for (final ExpandableBinaryDictionary dict : mPersonalizationDictsToUpdate.values()) {
+ dict.close();
+ }
+ mPersonalizationDictsToUpdate.clear();
+ }
+
+ public void clearDictionariesToUpdate() {
+ for (final ExpandableBinaryDictionary dict : mPersonalizationDictsToUpdate.values()) {
+ dict.clear();
+ }
+ mPersonalizationDictsToUpdate.clear();
+ }
+
+ public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) {
+ for (final InputMethodSubtype subtype : enabledSubtypes) {
+ final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype);
+ final String language = locale.getLanguage();
+ final HashSet<Locale> locales = mLangToLocalesMap.get(language);
+ if (locales != null) {
+ locales.add(locale);
+ } else {
+ final HashSet<Locale> localeSet = new HashSet<>();
+ localeSet.add(locale);
+ mLangToLocalesMap.put(language, localeSet);
+ }
+ }
+ }
+
+ public void setIsMonolingualUser(final boolean isMonolingualUser) {
+ mIsMonolingualUser = isMonolingualUser;
+ }
+
+ /**
+ * Flush personalization dictionaries to dictionary files. Close dictionaries after writing
+ * files except the dictionaries that is used for generating suggestions.
+ *
+ * @param personalizationDictsUsedForSuggestion the personalization dictionaries used for
+ * generating suggestions that won't be closed.
+ */
+ public void flushPersonalizationDictionariesToUpdate(
+ final HashSet<ExpandableBinaryDictionary> personalizationDictsUsedForSuggestion) {
+ for (final ExpandableBinaryDictionary personalizationDict :
+ mPersonalizationDictsToUpdate.values()) {
+ personalizationDict.asyncFlushBinaryDictionary();
+ if (!personalizationDictsUsedForSuggestion.contains(personalizationDict)) {
+ // Close if the dictionary is not being used for suggestion.
+ personalizationDict.close();
+ }
+ }
+ mDistracterFilter.close();
+ mPersonalizationDictsToUpdate.clear();
+ }
+
+ private ExpandableBinaryDictionary getPersonalizationDictToUpdate(final Context context,
+ final Locale locale) {
+ ExpandableBinaryDictionary personalizationDict = mPersonalizationDictsToUpdate.get(locale);
+ if (personalizationDict != null) {
+ return personalizationDict;
+ }
+ personalizationDict = PersonalizationDictionary.getDictionary(context, locale,
+ null /* dictFile */, "" /* dictNamePrefix */);
+ mPersonalizationDictsToUpdate.put(locale, personalizationDict);
+ return personalizationDict;
+ }
+
+ private void addEntriesToPersonalizationDictionariesForLocale(final Locale locale,
+ final PersonalizationDataChunk personalizationDataChunk,
+ final SpacingAndPunctuations spacingAndPunctuations,
+ final AddMultipleDictionaryEntriesCallback callback) {
+ final ExpandableBinaryDictionary personalizationDict =
+ getPersonalizationDictToUpdate(mContext, locale);
+ if (personalizationDict == null) {
+ if (callback != null) {
+ callback.onFinished();
+ }
+ return;
+ }
+ final ArrayList<LanguageModelParam> languageModelParams =
+ LanguageModelParam.createLanguageModelParamsFrom(
+ personalizationDataChunk.mTokens,
+ personalizationDataChunk.mTimestampInSeconds, spacingAndPunctuations,
+ locale, new DistracterFilterCheckingIsInDictionary(
+ mDistracterFilter, personalizationDict));
+ if (languageModelParams == null || languageModelParams.isEmpty()) {
+ if (callback != null) {
+ callback.onFinished();
+ }
+ return;
+ }
+ personalizationDict.addMultipleDictionaryEntriesDynamically(languageModelParams, callback);
+ }
+
+ public void addEntriesToPersonalizationDictionariesToUpdate(final Locale defaultLocale,
+ final PersonalizationDataChunk personalizationDataChunk,
+ final SpacingAndPunctuations spacingAndPunctuations,
+ final AddMultipleDictionaryEntriesCallback callback) {
+ final String language = personalizationDataChunk.mDetectedLanguage;
+ final HashSet<Locale> locales;
+ if (mIsMonolingualUser && PersonalizationDataChunk.LANGUAGE_UNKNOWN.equals(language)
+ && mLangToLocalesMap.size() == 1) {
+ locales = mLangToLocalesMap.get(defaultLocale.getLanguage());
+ } else {
+ locales = mLangToLocalesMap.get(language);
+ }
+ if (locales == null || locales.isEmpty()) {
+ if (callback != null) {
+ callback.onFinished();
+ }
+ return;
+ }
+ final AtomicInteger remainingTaskCount = new AtomicInteger(locales.size());
+ final AddMultipleDictionaryEntriesCallback callbackForLocales =
+ new AddMultipleDictionaryEntriesCallback() {
+ @Override
+ public void onFinished() {
+ if (remainingTaskCount.decrementAndGet() == 0) {
+ // Update tasks for all locales have been finished.
+ if (callback != null) {
+ callback.onFinished();
+ }
+ }
+ }
+ };
+ for (final Locale locale : locales) {
+ addEntriesToPersonalizationDictionariesForLocale(locale, personalizationDataChunk,
+ spacingAndPunctuations, callbackForLocales);
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/PrevWordsInfo.java b/java/src/com/android/inputmethod/latin/PrevWordsInfo.java
index db877ab7a..d662051d9 100644
--- a/java/src/com/android/inputmethod/latin/PrevWordsInfo.java
+++ b/java/src/com/android/inputmethod/latin/PrevWordsInfo.java
@@ -86,33 +86,30 @@ public class PrevWordsInfo {
// For simplicity of implementation, elements may also be EMPTY_WORD_INFO transiently after the
// WordComposer was reset and before starting a new composing word, but we should never be
// calling getSuggetions* in this situation.
- public WordInfo[] mPrevWordsInfo = new WordInfo[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
+ public final WordInfo[] mPrevWordsInfo;
// Construct from the previous word information.
public PrevWordsInfo(final WordInfo prevWordInfo) {
- mPrevWordsInfo[0] = prevWordInfo;
+ mPrevWordsInfo = new WordInfo[] { prevWordInfo };
}
// Construct from WordInfo array. n-th element represents (n+1)-th previous word's information.
public PrevWordsInfo(final WordInfo[] prevWordsInfo) {
- for (int i = 0; i < Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM; i++) {
- mPrevWordsInfo[i] =
- (prevWordsInfo.length > i) ? prevWordsInfo[i] : WordInfo.EMPTY_WORD_INFO;
- }
+ mPrevWordsInfo = prevWordsInfo;
}
// Create next prevWordsInfo using current prevWordsInfo.
public PrevWordsInfo getNextPrevWordsInfo(final WordInfo wordInfo) {
- final WordInfo[] prevWordsInfo = new WordInfo[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
+ final int nextPrevWordCount = Math.min(Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM,
+ mPrevWordsInfo.length + 1);
+ final WordInfo[] prevWordsInfo = new WordInfo[nextPrevWordCount];
prevWordsInfo[0] = wordInfo;
- for (int i = 1; i < prevWordsInfo.length; i++) {
- prevWordsInfo[i] = mPrevWordsInfo[i - 1];
- }
+ System.arraycopy(mPrevWordsInfo, 0, prevWordsInfo, 1, prevWordsInfo.length - 1);
return new PrevWordsInfo(prevWordsInfo);
}
public boolean isValid() {
- return mPrevWordsInfo[0].isValid();
+ return mPrevWordsInfo.length > 0 && mPrevWordsInfo[0].isValid();
}
public void outputToArray(final int[][] codePointArrays,
@@ -129,6 +126,10 @@ public class PrevWordsInfo {
}
}
+ public int getPrevWordCount() {
+ return mPrevWordsInfo.length;
+ }
+
@Override
public int hashCode() {
return Arrays.hashCode(mPrevWordsInfo);
diff --git a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
index 5d4fc5861..ecf25c28b 100644
--- a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
@@ -40,7 +40,7 @@ public final class ReadOnlyBinaryDictionary extends Dictionary {
public ReadOnlyBinaryDictionary(final String filename, final long offset, final long length,
final boolean useFullEditDistance, final Locale locale, final String dictType) {
- super(dictType);
+ super(dictType, locale);
mBinaryDictionary = new BinaryDictionary(filename, offset, length, useFullEditDistance,
locale, dictType, false /* isUpdatable */);
}
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 744b0321a..d672430a1 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -740,17 +740,19 @@ public final class RichInputConnection {
return TextUtils.equals(text, beforeText);
}
- public boolean revertDoubleSpacePeriod() {
+ public boolean revertDoubleSpacePeriod(final SpacingAndPunctuations spacingAndPunctuations) {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
// 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 = getTextBeforeCursor(2, 0);
- if (!TextUtils.equals(Constants.STRING_PERIOD_AND_SPACE, textBeforeCursor)) {
+ if (!TextUtils.equals(spacingAndPunctuations.mSentenceSeparatorAndSpace,
+ textBeforeCursor)) {
// Theoretically we should not be coming here if there isn't ". " before the
// cursor, but the application may be changing the text while we are typing, so
// anything goes. We should not crash.
- Log.d(TAG, "Tried to revert double-space combo but we didn't find "
- + "\"" + Constants.STRING_PERIOD_AND_SPACE + "\" just before the cursor.");
+ Log.d(TAG, "Tried to revert double-space combo but we didn't find \""
+ + spacingAndPunctuations.mSentenceSeparatorAndSpace
+ + "\" just before the cursor.");
return false;
}
// Double-space results in ". ". A backspace to cancel this should result in a single
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
index 7cf4eff92..0d5ce7d6d 100644
--- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
@@ -40,7 +40,8 @@ import java.util.List;
/**
* Enrichment class for InputMethodManager to simplify interaction and add functionality.
*/
-public final class RichInputMethodManager {
+// non final for easy mocking.
+public class RichInputMethodManager {
private static final String TAG = RichInputMethodManager.class.getSimpleName();
private RichInputMethodManager() {
@@ -297,10 +298,14 @@ public final class RichInputMethodManager {
return INDEX_NOT_FOUND;
}
- public InputMethodSubtype getCurrentInputMethodSubtype(
- final InputMethodSubtype defaultSubtype) {
+ public RichInputMethodSubtype getCurrentInputMethodSubtype(
+ final RichInputMethodSubtype defaultSubtype) {
final InputMethodSubtype currentSubtype = mImmWrapper.mImm.getCurrentInputMethodSubtype();
- return (currentSubtype != null) ? currentSubtype : defaultSubtype;
+ if (currentSubtype == null) {
+ return defaultSubtype;
+ }
+ // TODO: Determine locales to use for multi-lingual use.
+ return new RichInputMethodSubtype(currentSubtype);
}
public boolean hasMultipleEnabledIMEsOrSubtypes(final boolean shouldIncludeAuxiliarySubtypes) {
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java b/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java
new file mode 100644
index 000000000..0b08c48e5
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2014 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.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.latin.utils.LocaleUtils;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * Enrichment class for InputMethodSubtype to enable concurrent multi-lingual input.
+ *
+ * Right now, this returns the extra value of its primary subtype.
+ */
+public final class RichInputMethodSubtype {
+ private final InputMethodSubtype mSubtype;
+ private final Locale[] mLocales;
+
+ public RichInputMethodSubtype(final InputMethodSubtype subtype, final Locale... locales) {
+ mSubtype = subtype;
+ mLocales = new Locale[locales.length+1];
+ mLocales[0] = LocaleUtils.constructLocaleFromString(mSubtype.getLocale());
+ System.arraycopy(locales, 0, mLocales, 1, locales.length);
+ }
+
+ // Extra values are determined by the primary subtype. This is probably right, but
+ // we may have to revisit this later.
+ public String getExtraValueOf(final String key) {
+ return mSubtype.getExtraValueOf(key);
+ }
+
+ // The mode is also determined by the primary subtype.
+ public String getMode() {
+ return mSubtype.getMode();
+ }
+
+ public boolean isNoLanguage() {
+ if (mLocales.length > 1) {
+ return false;
+ }
+ return SubtypeLocaleUtils.NO_LANGUAGE.equals(mSubtype.getLocale());
+ }
+
+ public String getNameForLogging() {
+ return toString();
+ }
+
+ // InputMethodSubtype's display name for spacebar text in its locale.
+ // isAdditionalSubtype (T=true, F=false)
+ // locale layout | Middle Full
+ // ------ ------- - --------- ----------------------
+ // en_US qwerty F English English (US) exception
+ // en_GB qwerty F English English (UK) exception
+ // es_US spanish F Español Español (EE.UU.) exception
+ // fr azerty F Français Français
+ // fr_CA qwerty F Français Français (Canada)
+ // fr_CH swiss F Français Français (Suisse)
+ // de qwertz F Deutsch Deutsch
+ // de_CH swiss T Deutsch Deutsch (Schweiz)
+ // zz qwerty F QWERTY QWERTY
+ // fr qwertz T Français Français
+ // de qwerty T Deutsch Deutsch
+ // en_US azerty T English English (US)
+ // zz azerty T AZERTY AZERTY
+ // Get the RichInputMethodSubtype's full display name in its locale.
+ public String getFullDisplayName() {
+ if (isNoLanguage()) {
+ return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype);
+ }
+ return SubtypeLocaleUtils.getSubtypeLocaleDisplayName(mSubtype.getLocale());
+ }
+
+ // Get the RichInputMethodSubtype's middle display name in its locale.
+ public String getMiddleDisplayName() {
+ if (isNoLanguage()) {
+ return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype);
+ }
+ return SubtypeLocaleUtils.getSubtypeLanguageDisplayName(mSubtype.getLocale());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof RichInputMethodSubtype)) {
+ return false;
+ }
+ final RichInputMethodSubtype other = (RichInputMethodSubtype)o;
+ return mSubtype.equals(other.mSubtype) && Arrays.equals(mLocales, other.mLocales);
+ }
+
+ @Override
+ public int hashCode() {
+ return mSubtype.hashCode() + Arrays.hashCode(mLocales);
+ }
+
+ @Override
+ public String toString() {
+ return "Multi-lingual subtype: " + mSubtype.toString() + ", " + Arrays.toString(mLocales);
+ }
+
+ // TODO: remove this method! We can always have several locales. Multi-lingual input will only
+ // be done when this method is gone.
+ public String getLocale() {
+ return mSubtype.getLocale();
+ }
+
+ // TODO: remove this method
+ public InputMethodSubtype getRawSubtype() { return mSubtype; }
+}
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index a725e1611..45d67ff88 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -58,8 +58,8 @@ public final class SubtypeSwitcher {
new LanguageOnSpacebarHelper();
private InputMethodInfo mShortcutInputMethodInfo;
private InputMethodSubtype mShortcutSubtype;
- private InputMethodSubtype mNoLanguageSubtype;
- private InputMethodSubtype mEmojiSubtype;
+ private RichInputMethodSubtype mNoLanguageSubtype;
+ private RichInputMethodSubtype mEmojiSubtype;
private boolean mIsNetworkConnected;
private static final String KEYBOARD_MODE = "keyboard";
@@ -70,26 +70,26 @@ public final class SubtypeSwitcher {
+ "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
+ "," + Constants.Subtype.ExtraValue.ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE
+ "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
- private static final InputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE =
- InputMethodSubtypeCompatUtils.newInputMethodSubtype(
+ private static final RichInputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE =
+ new RichInputMethodSubtype(InputMethodSubtypeCompatUtils.newInputMethodSubtype(
R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark,
SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE,
EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE,
false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */,
- SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE);
+ SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE));
// Caveat: We probably should remove this when we add an Emoji subtype in {@link R.xml.method}.
// Dummy Emoji subtype. See {@link R.xml.method}.
private static final int SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE = 0xd78b2ed0;
private static final String EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE =
"KeyboardLayoutSet=" + SubtypeLocaleUtils.EMOJI
+ "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
- private static final InputMethodSubtype DUMMY_EMOJI_SUBTYPE =
+ private static final RichInputMethodSubtype DUMMY_EMOJI_SUBTYPE = new RichInputMethodSubtype(
InputMethodSubtypeCompatUtils.newInputMethodSubtype(
R.string.subtype_emoji, R.drawable.ic_ime_switcher_dark,
SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE,
EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE,
false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */,
- SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE);
+ SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE));
public static SubtypeSwitcher getInstance() {
return sInstance;
@@ -165,18 +165,17 @@ public final class SubtypeSwitcher {
}
// Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function.
- public void onSubtypeChanged(final InputMethodSubtype newSubtype) {
+ public void onSubtypeChanged(final RichInputMethodSubtype newSubtype) {
if (DBG) {
- Log.w(TAG, "onSubtypeChanged: "
- + SubtypeLocaleUtils.getSubtypeNameForLogging(newSubtype));
+ Log.w(TAG, "onSubtypeChanged: " + newSubtype.getNameForLogging());
}
final Locale newLocale = SubtypeLocaleUtils.getSubtypeLocale(newSubtype);
final Locale systemLocale = mResources.getConfiguration().locale;
final boolean sameLocale = systemLocale.equals(newLocale);
final boolean sameLanguage = systemLocale.getLanguage().equals(newLocale.getLanguage());
- final boolean implicitlyEnabled =
- mRichImm.checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(newSubtype);
+ final boolean implicitlyEnabled = mRichImm
+ .checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(newSubtype.getRawSubtype());
mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(
sameLocale || (sameLanguage && implicitlyEnabled));
@@ -250,7 +249,7 @@ public final class SubtypeSwitcher {
// Subtype Switching functions //
//////////////////////////////////
- public int getLanguageOnSpacebarFormatType(final InputMethodSubtype subtype) {
+ public int getLanguageOnSpacebarFormatType(final RichInputMethodSubtype subtype) {
return mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(subtype);
}
@@ -279,10 +278,10 @@ public final class SubtypeSwitcher {
return true;
}
- private static InputMethodSubtype sForcedSubtypeForTesting = null;
+ private static RichInputMethodSubtype sForcedSubtypeForTesting = null;
@UsedForTesting
void forceSubtype(final InputMethodSubtype subtype) {
- sForcedSubtypeForTesting = subtype;
+ sForcedSubtypeForTesting = new RichInputMethodSubtype(subtype);
}
public Locale getCurrentSubtypeLocale() {
@@ -292,17 +291,18 @@ public final class SubtypeSwitcher {
return SubtypeLocaleUtils.getSubtypeLocale(getCurrentSubtype());
}
- public InputMethodSubtype getCurrentSubtype() {
+ public RichInputMethodSubtype getCurrentSubtype() {
if (null != sForcedSubtypeForTesting) {
return sForcedSubtypeForTesting;
}
return mRichImm.getCurrentInputMethodSubtype(getNoLanguageSubtype());
}
- public InputMethodSubtype getNoLanguageSubtype() {
+ public RichInputMethodSubtype getNoLanguageSubtype() {
if (mNoLanguageSubtype == null) {
- mNoLanguageSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
- SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY);
+ mNoLanguageSubtype = new RichInputMethodSubtype(
+ mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+ SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY));
}
if (mNoLanguageSubtype != null) {
return mNoLanguageSubtype;
@@ -313,10 +313,14 @@ public final class SubtypeSwitcher {
return DUMMY_NO_LANGUAGE_SUBTYPE;
}
- public InputMethodSubtype getEmojiSubtype() {
+ public RichInputMethodSubtype getEmojiSubtype() {
if (mEmojiSubtype == null) {
- mEmojiSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
- SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.EMOJI);
+ final InputMethodSubtype rawEmojiSubtype =
+ mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+ SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.EMOJI);
+ if (null != rawEmojiSubtype) {
+ mEmojiSubtype = new RichInputMethodSubtype(rawEmojiSubtype);
+ }
}
if (mEmojiSubtype != null) {
return mEmojiSubtype;
@@ -328,6 +332,6 @@ public final class SubtypeSwitcher {
}
public String getCombiningRulesExtraValueOfCurrentSubtype() {
- return SubtypeLocaleUtils.getCombiningRulesExtraValue(getCurrentSubtype());
+ return SubtypeLocaleUtils.getCombiningRulesExtraValue(getCurrentSubtype().getRawSubtype());
}
}
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 6779351fd..4ad5ba65e 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -84,7 +84,7 @@ public final class Suggest {
private static ArrayList<SuggestedWordInfo> getTransformedSuggestedWordInfoList(
final WordComposer wordComposer, final SuggestionResults results,
- final int trailingSingleQuotesCount) {
+ final int trailingSingleQuotesCount, final Locale defaultLocale) {
final boolean shouldMakeSuggestionsAllUpperCase = wordComposer.isAllUpperCase()
&& !wordComposer.isResumed();
final boolean isOnlyFirstCharCapitalized =
@@ -96,9 +96,11 @@ public final class Suggest {
|| 0 != trailingSingleQuotesCount) {
for (int i = 0; i < suggestionsCount; ++i) {
final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
+ final Locale wordLocale = wordInfo.mSourceDict.mLocale;
final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
- wordInfo, results.mLocale, shouldMakeSuggestionsAllUpperCase,
- isOnlyFirstCharCapitalized, trailingSingleQuotesCount);
+ wordInfo, null == wordLocale ? defaultLocale : wordLocale,
+ shouldMakeSuggestionsAllUpperCase, isOnlyFirstCharCapitalized,
+ trailingSingleQuotesCount);
suggestionsContainer.set(i, transformedWordInfo);
}
}
@@ -134,7 +136,7 @@ public final class Suggest {
SESSION_ID_TYPING);
final ArrayList<SuggestedWordInfo> suggestionsContainer =
getTransformedSuggestedWordInfoList(wordComposer, suggestionResults,
- trailingSingleQuotesCount);
+ trailingSingleQuotesCount, mDictionaryFacilitator.getLocale());
final boolean didRemoveTypedWord =
SuggestedWordInfo.removeDups(wordComposer.getTypedWord(), suggestionsContainer);
@@ -155,7 +157,7 @@ public final class Suggest {
if (!isCorrectionEnabled || !allowsToBeAutoCorrected || resultsArePredictions
|| suggestionResults.isEmpty() || wordComposer.hasDigits()
|| wordComposer.isMostlyCaps() || wordComposer.isResumed()
- || !mDictionaryFacilitator.hasInitializedMainDictionary()
+ || !mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary()
|| suggestionResults.first().isKindOf(SuggestedWordInfo.KIND_SHORTCUT)) {
// If we don't have a main dictionary, we never want to auto-correct. The reason for
// this is, the user may have a contact whose name happens to match a valid word in
@@ -208,6 +210,7 @@ public final class Suggest {
final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
wordComposer, prevWordsInfo, proximityInfo, settingsValuesForSuggestion,
SESSION_ID_GESTURE);
+ final Locale defaultLocale = mDictionaryFacilitator.getLocale();
final ArrayList<SuggestedWordInfo> suggestionsContainer =
new ArrayList<>(suggestionResults);
final int suggestionsCount = suggestionsContainer.size();
@@ -216,9 +219,10 @@ public final class Suggest {
if (isFirstCharCapitalized || isAllUpperCase) {
for (int i = 0; i < suggestionsCount; ++i) {
final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
+ final Locale wordlocale = wordInfo.mSourceDict.mLocale;
final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
- wordInfo, suggestionResults.mLocale, isAllUpperCase, isFirstCharCapitalized,
- 0 /* trailingSingleQuotesCount */);
+ wordInfo, null == wordlocale ? defaultLocale : wordlocale, isAllUpperCase,
+ isFirstCharCapitalized, 0 /* trailingSingleQuotesCount */);
suggestionsContainer.set(i, transformedWordInfo);
}
}
@@ -231,7 +235,7 @@ public final class Suggest {
SuggestedWordInfo.removeDups(null /* typedWord */, suggestionsContainer);
// For some reason some suggestions with MIN_VALUE are making their way here.
- // TODO: Find a more robust way to detect distractors.
+ // TODO: Find a more robust way to detect distracters.
for (int i = suggestionsContainer.size() - 1; i >= 0; --i) {
if (suggestionsContainer.get(i).mScore < SUPPRESS_SUGGEST_THRESHOLD) {
suggestionsContainer.remove(i);
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 6bc3da885..dcfaa3f6d 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -142,6 +142,15 @@ public class SuggestedWords {
return mSuggestedWordInfoList.get(index);
}
+ /**
+ * Gets the suggestion index from the suggestions list.
+ * @param suggestedWordInfo The {@link SuggestedWordInfo} to find the index.
+ * @return The position of the suggestion in the suggestion list.
+ */
+ public int indexOf(SuggestedWordInfo suggestedWordInfo) {
+ return mSuggestedWordInfoList.indexOf(suggestedWordInfo);
+ }
+
public String getDebugString(final int pos) {
if (!DebugFlags.DEBUG_ENABLED) {
return null;
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 32d1fe372..567aa07f1 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -49,6 +49,7 @@ public final class WordComposer {
private final ArrayList<Event> mEvents;
private final InputPointers mInputPointers = new InputPointers(MAX_WORD_LENGTH);
private String mAutoCorrection;
+ private String mAutoCorrectionDictionaryType;
private boolean mIsResumed;
private boolean mIsBatchMode;
// A memory of the last rejected batch mode suggestion, if any. This goes like this: the user
@@ -418,8 +419,9 @@ public final class WordComposer {
/**
* Sets the auto-correction for this word.
*/
- public void setAutoCorrection(final String correction) {
+ public void setAutoCorrection(final String correction, String dictType) {
mAutoCorrection = correction;
+ mAutoCorrectionDictionaryType = dictType;
}
/**
@@ -430,6 +432,13 @@ public final class WordComposer {
}
/**
+ * @return the auto-correction dictionary type or null if none.
+ */
+ public String getAutoCorrectionDictionaryTypeOrNull() {
+ return mAutoCorrectionDictionaryType;
+ }
+
+ /**
* @return whether we started composing this word by resuming suggestion on an existing string
*/
public boolean isResumed() {
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 21e2a1c10..1f0339c48 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -61,6 +61,7 @@ import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor;
import com.android.inputmethod.latin.utils.AsyncResultHolder;
import com.android.inputmethod.latin.utils.InputTypeUtils;
import com.android.inputmethod.latin.utils.RecapitalizeStatus;
+import com.android.inputmethod.latin.utils.StatsUtils;
import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.TextRange;
@@ -347,6 +348,8 @@ public final class InputLogic {
// That's going to be predictions (or punctuation suggestions), so INPUT_STYLE_NONE.
handler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_NONE);
}
+
+ StatsUtils.onPickSuggestionManually(mSuggestedWords, suggestionInfo);
return inputTransaction;
}
@@ -600,14 +603,21 @@ public final class InputLogic {
final SettingsValues settingsValues, final LatinIME.UIHandler handler) {
if (SuggestedWords.EMPTY != suggestedWords) {
final String autoCorrection;
+ final String dictType;
if (suggestedWords.mWillAutoCorrect) {
- autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION);
+ SuggestedWordInfo info = suggestedWords.getInfo(
+ SuggestedWords.INDEX_OF_AUTO_CORRECTION);
+ autoCorrection = info.mWord;
+ dictType = info.mSourceDict.mDictType;
} else {
// We can't use suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD)
// because it may differ from mWordComposer.mTypedWord.
autoCorrection = suggestedWords.mTypedWord;
+ dictType = Dictionary.TYPE_USER_TYPED;
}
- mWordComposer.setAutoCorrection(autoCorrection);
+ // TODO: Use the SuggestedWordInfo to set the auto correction when
+ // user typed word is available via SuggestedWordInfo.
+ mWordComposer.setAutoCorrection(autoCorrection, dictType);
}
mSuggestedWords = suggestedWords;
final boolean newAutoCorrectionIndicator = suggestedWords.mWillAutoCorrect;
@@ -681,7 +691,7 @@ public final class InputLogic {
break;
case Constants.CODE_CAPSLOCK:
// Note: Changing keyboard to shift lock state is handled in
- // {@link KeyboardSwitcher#onCodeInput(int)}.
+ // {@link KeyboardSwitcher#onEvent(Event)}.
break;
case Constants.CODE_SYMBOL_SHIFT:
// Note: Calling back to the keyboard on the symbol Shift key is handled in
@@ -709,11 +719,11 @@ public final class InputLogic {
break;
case Constants.CODE_EMOJI:
// Note: Switching emoji keyboard is being handled in
- // {@link KeyboardState#onCodeInput(int,int)}.
+ // {@link KeyboardState#onEvent(Event,int)}.
break;
case Constants.CODE_ALPHA_FROM_EMOJI:
// Note: Switching back from Emoji keyboard to the main keyboard is being
- // handled in {@link KeyboardState#onCodeInput(int,int)}.
+ // handled in {@link KeyboardState#onEvent(Event,int)}.
break;
case Constants.CODE_SHIFT_ENTER:
// TODO: remove this object
@@ -1041,8 +1051,10 @@ public final class InputLogic {
if (!TextUtils.isEmpty(rejectedSuggestion)) {
mDictionaryFacilitator.removeWordFromPersonalizedDicts(rejectedSuggestion);
}
+ StatsUtils.onBackspaceWordDelete(rejectedSuggestion.length());
} else {
mWordComposer.applyProcessedEvent(event);
+ StatsUtils.onBackspacePressed(1);
}
if (mWordComposer.isComposingWord()) {
setComposingTextInternal(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
@@ -1053,6 +1065,7 @@ public final class InputLogic {
} else {
if (mLastComposedWord.canRevertCommit()) {
revertCommit(inputTransaction, inputTransaction.mSettingsValues);
+ StatsUtils.onRevertAutoCorrect();
return;
}
if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) {
@@ -1060,6 +1073,7 @@ public final class InputLogic {
// This is triggered on backspace after a key that inputs multiple characters,
// like the smiley key or the .com key.
mConnection.deleteSurroundingText(mEnteredText.length(), 0);
+ StatsUtils.onDeleteMultiCharInput(mEnteredText.length());
mEnteredText = null;
// If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
// In addition we know that spaceState is false, and that we should not be
@@ -1068,16 +1082,19 @@ public final class InputLogic {
}
if (SpaceState.DOUBLE == inputTransaction.mSpaceState) {
cancelDoubleSpacePeriodCountdown();
- if (mConnection.revertDoubleSpacePeriod()) {
+ if (mConnection.revertDoubleSpacePeriod(
+ inputTransaction.mSettingsValues.mSpacingAndPunctuations)) {
// No need to reset mSpaceState, it has already be done (that's why we
// receive it as a parameter)
inputTransaction.setRequiresUpdateSuggestions();
mWordComposer.setCapitalizedModeAtStartComposingTime(
WordComposer.CAPS_MODE_OFF);
+ StatsUtils.onRevertDoubleSpacePeriod();
return;
}
} else if (SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) {
if (mConnection.revertSwapPunctuation()) {
+ StatsUtils.onRevertSwapPunctuation();
// Likewise
return;
}
@@ -1092,6 +1109,7 @@ public final class InputLogic {
mConnection.setSelection(mConnection.getExpectedSelectionEnd(),
mConnection.getExpectedSelectionEnd());
mConnection.deleteSurroundingText(numCharsDeleted, 0);
+ StatsUtils.onBackspaceSelectedText(numCharsDeleted);
} else {
// There is no selection, just delete one character.
if (Constants.NOT_A_CURSOR_POSITION == mConnection.getExpectedSelectionEnd()) {
@@ -1108,9 +1126,12 @@ public final class InputLogic {
// applications are relying on this behavior so we continue to support it for
// older apps, so we retain this behavior if the app has target SDK < JellyBean.
sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
+ int totalDeletedLength = 1;
if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
+ totalDeletedLength++;
}
+ StatsUtils.onBackspacePressed(totalDeletedLength);
} else {
final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
if (codePointBeforeCursor == Constants.NOT_A_CODE) {
@@ -1121,11 +1142,13 @@ public final class InputLogic {
// catch it and have their broken interface react. If you need the keyboard
// to do this, you're doing it wrong -- please fix your app.
mConnection.deleteSurroundingText(1, 0);
+ // TODO: Add a new StatsUtils method onBackspaceWhenNoText()
return;
}
final int lengthToDelete =
Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1;
mConnection.deleteSurroundingText(lengthToDelete, 0);
+ int totalDeletedLength = lengthToDelete;
if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
final int codePointBeforeCursorToDeleteAgain =
mConnection.getCodePointBeforeCursor();
@@ -1133,8 +1156,10 @@ public final class InputLogic {
final int lengthToDeleteAgain = Character.isSupplementaryCodePoint(
codePointBeforeCursorToDeleteAgain) ? 2 : 1;
mConnection.deleteSurroundingText(lengthToDeleteAgain, 0);
+ totalDeletedLength += lengthToDeleteAgain;
}
}
+ StatsUtils.onBackspacePressed(totalDeletedLength);
}
}
if (inputTransaction.mSettingsValues
@@ -1251,7 +1276,9 @@ public final class InputLogic {
if (null == lastTwo) return false;
final int length = lastTwo.length();
if (length < 2) return false;
- if (lastTwo.charAt(length - 1) != Constants.CODE_SPACE) return false;
+ if (lastTwo.charAt(length - 1) != Constants.CODE_SPACE) {
+ return false;
+ }
// We know there is a space in pos -1, and we have at least two chars. If we have only two
// chars, isSurrogatePairs can't return true as charAt(1) is a space, so this is fine.
final int firstCodePoint =
@@ -2071,6 +2098,8 @@ public final class InputLogic {
mConnection.commitCorrection(new CorrectionInfo(
mConnection.getExpectedSelectionEnd() - autoCorrection.length(),
typedWord, autoCorrection));
+ StatsUtils.onAutoCorrection(typedWord, autoCorrection, mWordComposer.isBatchMode(),
+ mWordComposer.getAutoCorrectionDictionaryTypeOrNull());
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index a2ae74b20..ec3c6e291 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -36,9 +36,7 @@ public final class FormatSpec {
* sion
*
* o |
- * p | not used 3 bits
- * t | each unigram and bigram entry has a time stamp?
- * i | 1 bit, 1 = yes, 0 = no : CONTAINS_TIMESTAMP_FLAG
+ * p | not used, 2 bytes.
* o |
* nflags
*
@@ -48,7 +46,7 @@ public final class FormatSpec {
* d |
* ersize
*
- * | attributes list
+ * attributes list
*
* attributes list is:
* <key> = | string of characters at the char format described below, with the terminator used
@@ -86,11 +84,10 @@ public final class FormatSpec {
*/
/* Node (FusionDictionary.PtNode) layout is as follows:
- * | is moved ? 2 bits, 11 = no : FLAG_IS_NOT_MOVED
- * | This must be the same as FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES
- * | 01 = yes : FLAG_IS_MOVED
- * f | the new address is stored in the same place as the parent address
- * l | is deleted? 10 = yes : FLAG_IS_DELETED
+ * | CHILDREN_ADDRESS_TYPE 2 bits, 11 : FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES
+ * | 10 : FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES
+ * f | 01 : FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE
+ * l | 00 : FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS
* a | has several chars ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_MULTIPLE_CHARS
* g | has a terminal ? 1 bit, 1 = yes, 0 = no : FLAG_IS_TERMINAL
* s | has shortcut targets ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_SHORTCUT_TARGETS
@@ -98,16 +95,6 @@ public final class FormatSpec {
* | is not a word ? 1 bit, 1 = yes, 0 = no : FLAG_IS_NOT_A_WORD
* | is blacklisted ? 1 bit, 1 = yes, 0 = no : FLAG_IS_BLACKLISTED
*
- * p |
- * a | parent address, 3byte
- * r | 1 byte = bbbbbbbb match
- * e | case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte)
- * n | otherwise => (bbbbbbbb << 16) + (next byte << 8) + next byte
- * t | This address is relative to the head of the PtNode.
- * a | If the node doesn't have a parent, this field is set to 0.
- * d |
- * dress
- *
* c | IF FLAG_HAS_MULTIPLE_CHARS
* h | char, char, char, char n * (1 or 3 bytes) : use PtNodeInfo for i/o helpers
* a | end 1 byte, = 0
@@ -121,15 +108,10 @@ public final class FormatSpec {
* q |
*
* c |
- * h | children address, 3 bytes
- * i | 1 byte = bbbbbbbb match
- * l | case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte)
- * d | otherwise => (bbbbbbbb<<16) + (next byte << 8) + next byte
- * r | if this node doesn't have children, this field is set to 0.
- * e | (see BinaryDictEncoderUtils#writeVariableSignedAddress)
- * n | This address is relative to the position of this field.
- * a |
- * ddress
+ * h | children address, CHILDREN_ADDRESS_TYPE bytes
+ * i | This address is relative to the position of this field.
+ * l |
+ * drenaddress
*
* | IF FLAG_IS_TERMINAL && FLAG_HAS_SHORTCUT_TARGETS
* | shortcut string list
@@ -179,8 +161,6 @@ public final class FormatSpec {
public static final int MAGIC_NUMBER = 0x9BC13AFE;
static final int NOT_A_VERSION_NUMBER = -1;
- static final int FIRST_VERSION_WITH_DYNAMIC_UPDATE = 3;
- static final int FIRST_VERSION_WITH_TERMINAL_ID = 4;
// These MUST have the same values as the relevant constants in format_utils.h.
// From version 4 on, we use version * 100 + revision as a version number. That allows
@@ -202,9 +182,6 @@ public final class FormatSpec {
// use it in the reading code.
static final int MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH;
- static final int PARENT_ADDRESS_SIZE = 3;
- static final int FORWARD_LINK_ADDRESS_SIZE = 3;
-
// These flags are used only in the static dictionary.
static final int MASK_CHILDREN_ADDRESS_TYPE = 0xC0;
static final int FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS = 0x00;
@@ -220,13 +197,6 @@ public final class FormatSpec {
static final int FLAG_IS_NOT_A_WORD = 0x02;
static final int FLAG_IS_BLACKLISTED = 0x01;
- // These flags are used only in the dynamic dictionary.
- static final int MASK_MOVE_AND_DELETE_FLAG = 0xC0;
- static final int FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE = 0x40;
- static final int FLAG_IS_MOVED = 0x00 | FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE;
- static final int FLAG_IS_NOT_MOVED = 0x80 | FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE;
- static final int FLAG_IS_DELETED = 0x80;
-
static final int FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT = 0x80;
static final int FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE = 0x40;
static final int MASK_BIGRAM_ATTR_ADDRESS_TYPE = 0x30;
@@ -240,52 +210,12 @@ public final class FormatSpec {
static final int PTNODE_TERMINATOR_SIZE = 1;
static final int PTNODE_FLAGS_SIZE = 1;
static final int PTNODE_FREQUENCY_SIZE = 1;
- static final int PTNODE_TERMINAL_ID_SIZE = 4;
static final int PTNODE_MAX_ADDRESS_SIZE = 3;
static final int PTNODE_ATTRIBUTE_FLAGS_SIZE = 1;
static final int PTNODE_ATTRIBUTE_MAX_ADDRESS_SIZE = 3;
static final int PTNODE_SHORTCUT_LIST_SIZE_SIZE = 2;
- // These values are used only by version 4 or later. They MUST match the definitions in
- // ver4_dict_constants.cpp.
- static final String TRIE_FILE_EXTENSION = ".trie";
- public static final String HEADER_FILE_EXTENSION = ".header";
- static final String FREQ_FILE_EXTENSION = ".freq";
- // tat = Terminal Address Table
- static final String TERMINAL_ADDRESS_TABLE_FILE_EXTENSION = ".tat";
- static final String BIGRAM_FILE_EXTENSION = ".bigram";
- static final String SHORTCUT_FILE_EXTENSION = ".shortcut";
- static final String LOOKUP_TABLE_FILE_SUFFIX = "_lookup";
- static final String CONTENT_TABLE_FILE_SUFFIX = "_index";
- static final int FLAGS_IN_FREQ_FILE_SIZE = 1;
- static final int FREQUENCY_AND_FLAGS_SIZE = 2;
- static final int TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE = 3;
- static final int UNIGRAM_TIMESTAMP_SIZE = 4;
- static final int UNIGRAM_COUNTER_SIZE = 1;
- static final int UNIGRAM_LEVEL_SIZE = 1;
-
- // With the English main dictionary as of October 2013, the size of bigram address table is
- // is 345KB with the block size being 16.
- // This is 54% of that of full address table.
- static final int BIGRAM_ADDRESS_TABLE_BLOCK_SIZE = 16;
- static final int BIGRAM_CONTENT_COUNT = 1;
- static final int BIGRAM_FREQ_CONTENT_INDEX = 0;
- static final String BIGRAM_FREQ_CONTENT_ID = "_freq";
- static final int BIGRAM_TIMESTAMP_SIZE = 4;
- static final int BIGRAM_COUNTER_SIZE = 1;
- static final int BIGRAM_LEVEL_SIZE = 1;
-
- static final int SHORTCUT_CONTENT_COUNT = 1;
- static final int SHORTCUT_CONTENT_INDEX = 0;
- // With the English main dictionary as of October 2013, the size of shortcut address table is
- // 26KB with the block size being 64.
- // This is only 4.4% of that of full address table.
- static final int SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE = 64;
- static final String SHORTCUT_CONTENT_ID = "_shortcut";
-
static final int NO_CHILDREN_ADDRESS = Integer.MIN_VALUE;
- static final int NO_PARENT_ADDRESS = 0;
- static final int NO_FORWARD_LINK_ADDRESS = 0;
static final int INVALID_CHARACTER = -1;
static final int MAX_PTNODES_FOR_ONE_BYTE_PTNODE_COUNT = 0x7F; // 127
@@ -302,14 +232,11 @@ public final class FormatSpec {
// This option needs to be the same numeric value as the one in binary_format.h.
static final int NOT_VALID_WORD = -99;
- static final int SIGNED_CHILDREN_ADDRESS_SIZE = 3;
static final int UINT8_MAX = 0xFF;
static final int UINT16_MAX = 0xFFFF;
static final int UINT24_MAX = 0xFFFFFF;
- static final int SINT24_MAX = 0x7FFFFF;
static final int MSB8 = 0x80;
- static final int MSB24 = 0x800000;
/**
* Options about file format.
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java
index 9d72de8c5..734ed5583 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java
@@ -18,20 +18,22 @@ package com.android.inputmethod.latin.personalization;
import java.util.Collections;
import java.util.List;
-import java.util.Locale;
public class PersonalizationDataChunk {
+ public static final String LANGUAGE_UNKNOWN = "";
+
public final boolean mInputByUser;
public final List<String> mTokens;
public final int mTimestampInSeconds;
public final String mPackageName;
- public final Locale mlocale = null;
+ public final String mDetectedLanguage;
public PersonalizationDataChunk(boolean inputByUser, final List<String> tokens,
- final int timestampInSeconds, final String packageName) {
+ final int timestampInSeconds, final String packageName, final String detectedLanguage) {
mInputByUser = inputByUser;
mTokens = Collections.unmodifiableList(tokens);
mTimestampInSeconds = timestampInSeconds;
mPackageName = packageName;
+ mDetectedLanguage = detectedLanguage;
}
}
diff --git a/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java
index f5e4d33a2..a9884ba13 100644
--- a/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java
@@ -19,6 +19,7 @@ package com.android.inputmethod.latin.settings;
import android.os.Bundle;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.define.ProductionFlags;
/**
@@ -29,6 +30,10 @@ public final class AppearanceSettingsFragment extends SubScreenFragment {
public void onCreate(final Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(R.xml.prefs_screen_appearance);
+ if (!ProductionFlags.IS_SPLIT_KEYBOARD_SUPPORTED
+ || !Settings.getInstance().getCurrent().isTablet()) {
+ removePreference(Settings.PREF_ENABLE_SPLIT_KEYBOARD);
+ }
}
@Override
@@ -38,4 +43,4 @@ public final class AppearanceSettingsFragment extends SubScreenFragment {
findPreference(Settings.PREF_CUSTOM_INPUT_STYLES));
ThemeSettingsFragment.updateKeyboardThemeSummary(findPreference(Settings.SCREEN_THEME));
}
-}
+} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index 0de2d8831..529f8a093 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -83,6 +83,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
"pref_include_other_imes_in_language_switch_list";
public static final String PREF_KEYBOARD_THEME = "pref_keyboard_theme";
public static final String PREF_CUSTOM_INPUT_STYLES = "custom_input_styles";
+ public static final String PREF_ENABLE_SPLIT_KEYBOARD = "pref_split_keyboard";
// TODO: consolidate key preview dismiss delay with the key preview animation parameters.
public static final String PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY =
"pref_key_preview_popup_dismiss_delay";
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index d8c548d8b..3339ab57f 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -25,6 +25,7 @@ import android.util.Log;
import android.view.inputmethod.EditorInfo;
import com.android.inputmethod.compat.AppWorkaroundsUtils;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.RichInputMethodManager;
@@ -40,7 +41,8 @@ import java.util.Locale;
* When you call the constructor of this class, you may want to change the current system locale by
* using {@link com.android.inputmethod.latin.utils.RunInLocale}.
*/
-public final class SettingsValues {
+// Non-final for testing via mock library.
+public class SettingsValues {
private static final String TAG = SettingsValues.class.getSimpleName();
// "floatMaxValue" and "floatNegativeInfinity" are special marker strings for
// Float.NEGATIVE_INFINITE and Float.MAX_VALUE. Currently used for auto-correction settings.
@@ -78,6 +80,9 @@ public final class SettingsValues {
public final int mKeyLongpressTimeout;
public final boolean mEnableMetricsLogging;
public final boolean mShouldShowUiToAcceptTypedWord;
+ // Use split layout for keyboard.
+ public final boolean mIsSplitKeyboardEnabled;
+ public final int mScreenMetrics;
// From the input box
public final InputAttributes mInputAttributes;
@@ -153,6 +158,9 @@ public final class SettingsValues {
mDoubleSpacePeriodTimeout = res.getInteger(R.integer.config_double_space_period_timeout);
mHasHardwareKeyboard = Settings.readHasHardwareKeyboard(res.getConfiguration());
mEnableMetricsLogging = prefs.getBoolean(Settings.PREF_ENABLE_METRICS_LOGGING, true);
+ mIsSplitKeyboardEnabled = prefs.getBoolean(Settings.PREF_ENABLE_SPLIT_KEYBOARD, false);
+ mScreenMetrics = res.getInteger(R.integer.config_screen_metrics);
+
mShouldShowUiToAcceptTypedWord = Settings.HAS_UI_TO_ACCEPT_TYPED_WORD
&& prefs.getBoolean(DebugSettings.PREF_SHOW_UI_TO_ACCEPT_TYPED_WORD, true);
// Compute other readable settings
@@ -164,8 +172,8 @@ public final class SettingsValues {
autoCorrectionThresholdRawValue);
mGestureInputEnabled = Settings.readGestureInputEnabled(prefs, res);
mGestureTrailEnabled = prefs.getBoolean(Settings.PREF_GESTURE_PREVIEW_TRAIL, true);
- mGestureFloatingPreviewTextEnabled = prefs.getBoolean(
- Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true);
+ mGestureFloatingPreviewTextEnabled = !mInputAttributes.mDisableGestureFloatingPreviewText
+ && prefs.getBoolean(Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true);
mPhraseGestureEnabled = Settings.readPhraseGestureEnabled(prefs, res);
mAutoCorrectionEnabledPerUserSettings = mAutoCorrectEnabled
&& !mInputAttributes.mInputTypeNoAutoCorrect;
@@ -211,6 +219,15 @@ public final class SettingsValues {
}
}
+ public boolean isMetricsLoggingEnabled() {
+ return mEnableMetricsLogging;
+ }
+
+ public boolean isTablet() {
+ return mScreenMetrics == Constants.SCREEN_METRICS_SMALL_TABLET
+ || mScreenMetrics == Constants.SCREEN_METRICS_LARGE_TABLET;
+ }
+
public boolean isApplicationSpecifiedCompletionsOn() {
return mInputAttributes.mApplicationSpecifiedCompletionOn;
}
diff --git a/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java b/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
index 49d81104d..97aad3b6d 100644
--- a/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
+++ b/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
@@ -36,6 +36,8 @@ public final class SpacingAndPunctuations {
public final int[] mSortedWordSeparators;
public final PunctuationSuggestions mSuggestPuncList;
private final int mSentenceSeparator;
+ private final int mAbbreviationMarker;
+ private final int[] mSortedSentenceTerminators;
public final String mSentenceSeparatorAndSpace;
public final boolean mCurrentLanguageHasSpaces;
public final boolean mUsesAmericanTypography;
@@ -55,7 +57,10 @@ public final class SpacingAndPunctuations {
res.getString(R.string.symbols_word_connectors));
mSortedWordSeparators = StringUtils.toSortedCodePointArray(
res.getString(R.string.symbols_word_separators));
+ mSortedSentenceTerminators = StringUtils.toSortedCodePointArray(
+ res.getString(R.string.symbols_sentence_terminators));
mSentenceSeparator = res.getInteger(R.integer.sentence_separator);
+ mAbbreviationMarker = res.getInteger(R.integer.abbreviation_marker);
mSentenceSeparatorAndSpace = new String(new int[] {
mSentenceSeparator, Constants.CODE_SPACE }, 0, 2);
mCurrentLanguageHasSpaces = res.getBoolean(R.bool.current_language_has_spaces);
@@ -77,8 +82,10 @@ public final class SpacingAndPunctuations {
mSortedSymbolsClusteringTogether = model.mSortedSymbolsClusteringTogether;
mSortedWordConnectors = model.mSortedWordConnectors;
mSortedWordSeparators = overrideSortedWordSeparators;
+ mSortedSentenceTerminators = model.mSortedSentenceTerminators;
mSuggestPuncList = model.mSuggestPuncList;
mSentenceSeparator = model.mSentenceSeparator;
+ mAbbreviationMarker = model.mAbbreviationMarker;
mSentenceSeparatorAndSpace = model.mSentenceSeparatorAndSpace;
mCurrentLanguageHasSpaces = model.mCurrentLanguageHasSpaces;
mUsesAmericanTypography = model.mUsesAmericanTypography;
@@ -109,6 +116,14 @@ public final class SpacingAndPunctuations {
return Arrays.binarySearch(mSortedSymbolsClusteringTogether, code) >= 0;
}
+ public boolean isSentenceTerminator(final int code) {
+ return Arrays.binarySearch(mSortedSentenceTerminators, code) >= 0;
+ }
+
+ public boolean isAbbreviationMarker(final int code) {
+ return code == mAbbreviationMarker;
+ }
+
public boolean isSentenceSeparator(final int code) {
return code == mSentenceSeparator;
}
diff --git a/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java
index 5a3fc3600..29289aed2 100644
--- a/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java
@@ -17,7 +17,6 @@
package com.android.inputmethod.latin.settings;
import android.content.Context;
-import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.Preference;
@@ -32,12 +31,12 @@ import com.android.inputmethod.latin.settings.RadioButtonPreference.OnRadioButto
*/
public final class ThemeSettingsFragment extends SubScreenFragment
implements OnRadioButtonClickedListener {
- private String mSelectedThemeId;
+ private int mSelectedThemeId;
static class KeyboardThemePreference extends RadioButtonPreference {
- final String mThemeId;
+ final int mThemeId;
- KeyboardThemePreference(final Context context, final String name, final String id) {
+ KeyboardThemePreference(final Context context, final String name, final int id) {
super(context);
setTitle(name);
mThemeId = id;
@@ -45,14 +44,13 @@ public final class ThemeSettingsFragment extends SubScreenFragment
}
static void updateKeyboardThemeSummary(final Preference pref) {
- final Resources res = pref.getContext().getResources();
- final SharedPreferences prefs = pref.getSharedPreferences();
- final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(prefs);
- final String keyboardThemeId = String.valueOf(keyboardTheme.mThemeId);
+ final Context context = pref.getContext();
+ final Resources res = context.getResources();
+ final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(context);
final String[] keyboardThemeNames = res.getStringArray(R.array.keyboard_theme_names);
- final String[] keyboardThemeIds = res.getStringArray(R.array.keyboard_theme_ids);
+ final int[] keyboardThemeIds = res.getIntArray(R.array.keyboard_theme_ids);
for (int index = 0; index < keyboardThemeNames.length; index++) {
- if (keyboardThemeId.equals(keyboardThemeIds[index])) {
+ if (keyboardTheme.mThemeId == keyboardThemeIds[index]) {
pref.setSummary(keyboardThemeNames[index]);
return;
}
@@ -64,18 +62,18 @@ public final class ThemeSettingsFragment extends SubScreenFragment
super.onCreate(icicle);
addPreferencesFromResource(R.xml.prefs_screen_theme);
final PreferenceScreen screen = getPreferenceScreen();
+ final Context context = getActivity();
final Resources res = getResources();
final String[] keyboardThemeNames = res.getStringArray(R.array.keyboard_theme_names);
- final String[] keyboardThemeIds = res.getStringArray(R.array.keyboard_theme_ids);
+ final int[] keyboardThemeIds = res.getIntArray(R.array.keyboard_theme_ids);
for (int index = 0; index < keyboardThemeNames.length; index++) {
final KeyboardThemePreference pref = new KeyboardThemePreference(
- getActivity(), keyboardThemeNames[index], keyboardThemeIds[index]);
+ context, keyboardThemeNames[index], keyboardThemeIds[index]);
screen.addPreference(pref);
pref.setOnRadioButtonClickedListener(this);
}
- final SharedPreferences prefs = getSharedPreferences();
- final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(prefs);
- mSelectedThemeId = String.valueOf(keyboardTheme.mThemeId);
+ final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(context);
+ mSelectedThemeId = keyboardTheme.mThemeId;
}
@Override
@@ -106,7 +104,7 @@ public final class ThemeSettingsFragment extends SubScreenFragment
final Preference preference = screen.getPreference(index);
if (preference instanceof KeyboardThemePreference) {
final KeyboardThemePreference pref = (KeyboardThemePreference)preference;
- final boolean selected = mSelectedThemeId.equals(pref.mThemeId);
+ final boolean selected = (mSelectedThemeId == pref.mThemeId);
pref.setSelected(selected);
}
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 90398deb2..352391611 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -16,14 +16,11 @@
package com.android.inputmethod.latin.spellcheck;
-import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.service.textservice.SpellCheckerService;
import android.text.InputType;
-import android.util.Log;
-import android.util.LruCache;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.textservice.SuggestionsInfo;
@@ -32,39 +29,21 @@ import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.KeyboardLayoutSet;
import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.ContactsBinaryDictionary;
-import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.DictionaryCollection;
import com.android.inputmethod.latin.DictionaryFacilitator;
-import com.android.inputmethod.latin.DictionaryFactory;
+import com.android.inputmethod.latin.DictionaryFacilitatorLruCache;
import com.android.inputmethod.latin.PrevWordsInfo;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.RichInputMethodSubtype;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
-import com.android.inputmethod.latin.UserBinaryDictionary;
import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
-import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.LocaleUtils;
import com.android.inputmethod.latin.utils.ScriptUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.SuggestionResults;
import com.android.inputmethod.latin.WordComposer;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
import java.util.Locale;
-import java.util.Map;
-import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
/**
* Service for spell checking, using LatinIME's dictionaries and mechanisms.
@@ -80,61 +59,28 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
private static final int SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT = 368;
private static final String DICTIONARY_NAME_PREFIX = "spellcheck_";
- private static final int WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS = 1000;
- private static final int MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT = 5;
private static final String[] EMPTY_STRING_ARRAY = new String[0];
- private final HashSet<Locale> mCachedLocales = new HashSet<>();
-
private final int MAX_NUM_OF_THREADS_READ_DICTIONARY = 2;
private final Semaphore mSemaphore = new Semaphore(MAX_NUM_OF_THREADS_READ_DICTIONARY,
true /* fair */);
// TODO: Make each spell checker session has its own session id.
private final ConcurrentLinkedQueue<Integer> mSessionIdPool = new ConcurrentLinkedQueue<>();
- private static class DictionaryFacilitatorLruCache extends
- LruCache<Locale, DictionaryFacilitator> {
- private final HashSet<Locale> mCachedLocales;
- public DictionaryFacilitatorLruCache(final HashSet<Locale> cachedLocales, int maxSize) {
- super(maxSize);
- mCachedLocales = cachedLocales;
- }
-
- @Override
- protected void entryRemoved(boolean evicted, Locale key,
- DictionaryFacilitator oldValue, DictionaryFacilitator newValue) {
- if (oldValue != null && oldValue != newValue) {
- oldValue.closeDictionaries();
- }
- if (key != null && newValue == null) {
- // Remove locale from the cache when the dictionary facilitator for the locale is
- // evicted and new facilitator is not set for the locale.
- mCachedLocales.remove(key);
- if (size() >= maxSize()) {
- Log.w(TAG, "DictionaryFacilitator for " + key.toString()
- + " has been evicted due to cache size limit."
- + " size: " + size() + ", maxSize: " + maxSize());
- }
- }
- }
- }
-
private static final int MAX_DICTIONARY_FACILITATOR_COUNT = 3;
- private final LruCache<Locale, DictionaryFacilitator> mDictionaryFacilitatorCache =
- new DictionaryFacilitatorLruCache(mCachedLocales, MAX_DICTIONARY_FACILITATOR_COUNT);
+ private final DictionaryFacilitatorLruCache mDictionaryFacilitatorCache =
+ new DictionaryFacilitatorLruCache(this /* context */, MAX_DICTIONARY_FACILITATOR_COUNT,
+ DICTIONARY_NAME_PREFIX);
private final ConcurrentHashMap<Locale, Keyboard> mKeyboardCache = new ConcurrentHashMap<>();
// The threshold for a suggestion to be considered "recommended".
private float mRecommendedThreshold;
- // Whether to use the contacts dictionary
- private boolean mUseContactsDictionary;
// TODO: make a spell checker option to block offensive words or not
private final SettingsValuesForSuggestion mSettingsValuesForSuggestion =
new SettingsValuesForSuggestion(true /* blockPotentiallyOffensive */,
true /* spaceAwareGestureEnabled */,
null /* additionalFeaturesSettingValues */);
- private final Object mDictionaryLock = new Object();
public static final String SINGLE_QUOTE = "\u0027";
public static final String APOSTROPHE = "\u2019";
@@ -176,20 +122,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
if (!PREF_USE_CONTACTS_KEY.equals(key)) return;
final boolean useContactsDictionary = prefs.getBoolean(PREF_USE_CONTACTS_KEY, true);
- if (useContactsDictionary != mUseContactsDictionary) {
- mSemaphore.acquireUninterruptibly(MAX_NUM_OF_THREADS_READ_DICTIONARY);
- try {
- mUseContactsDictionary = useContactsDictionary;
- for (final Locale locale : mCachedLocales) {
- final DictionaryFacilitator dictionaryFacilitator =
- mDictionaryFacilitatorCache.get(locale);
- resetDictionariesForLocale(this /* context */,
- dictionaryFacilitator, locale, mUseContactsDictionary);
- }
- } finally {
- mSemaphore.release(MAX_NUM_OF_THREADS_READ_DICTIONARY);
- }
- }
+ mDictionaryFacilitatorCache.setUseContactsDictionary(useContactsDictionary);
}
@Override
@@ -222,7 +155,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
mSemaphore.acquireUninterruptibly();
try {
DictionaryFacilitator dictionaryFacilitatorForLocale =
- getDictionaryFacilitatorForLocaleLocked(locale);
+ mDictionaryFacilitatorCache.get(locale);
return dictionaryFacilitatorForLocale.isValidWord(word, false /* igroreCase */);
} finally {
mSemaphore.release();
@@ -236,7 +169,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
try {
sessionId = mSessionIdPool.poll();
DictionaryFacilitator dictionaryFacilitatorForLocale =
- getDictionaryFacilitatorForLocaleLocked(locale);
+ mDictionaryFacilitatorCache.get(locale);
return dictionaryFacilitatorForLocale.getSuggestionResults(composer, prevWordsInfo,
proximityInfo, mSettingsValuesForSuggestion, sessionId);
} finally {
@@ -251,56 +184,18 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
mSemaphore.acquireUninterruptibly();
try {
final DictionaryFacilitator dictionaryFacilitator =
- getDictionaryFacilitatorForLocaleLocked(locale);
- return dictionaryFacilitator.hasInitializedMainDictionary();
+ mDictionaryFacilitatorCache.get(locale);
+ return dictionaryFacilitator.hasAtLeastOneInitializedMainDictionary();
} finally {
mSemaphore.release();
}
}
- private DictionaryFacilitator getDictionaryFacilitatorForLocaleLocked(final Locale locale) {
- DictionaryFacilitator dictionaryFacilitatorForLocale =
- mDictionaryFacilitatorCache.get(locale);
- if (dictionaryFacilitatorForLocale == null) {
- dictionaryFacilitatorForLocale = new DictionaryFacilitator();
- mDictionaryFacilitatorCache.put(locale, dictionaryFacilitatorForLocale);
- mCachedLocales.add(locale);
- resetDictionariesForLocale(this /* context */, dictionaryFacilitatorForLocale,
- locale, mUseContactsDictionary);
- }
- return dictionaryFacilitatorForLocale;
- }
-
- private static void resetDictionariesForLocale(final Context context,
- final DictionaryFacilitator dictionaryFacilitator, final Locale locale,
- final boolean useContactsDictionary) {
- dictionaryFacilitator.resetDictionariesWithDictNamePrefix(context, locale,
- useContactsDictionary, false /* usePersonalizedDicts */,
- false /* forceReloadMainDictionary */, null /* listener */,
- DICTIONARY_NAME_PREFIX);
- for (int i = 0; i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT; i++) {
- try {
- dictionaryFacilitator.waitForLoadingMainDictionary(
- WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
- return;
- } catch (final InterruptedException e) {
- Log.i(TAG, "Interrupted during waiting for loading main dictionary.", e);
- if (i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT - 1) {
- Log.i(TAG, "Retry", e);
- } else {
- Log.w(TAG, "Give up retrying. Retried "
- + MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT + " times.", e);
- }
- }
- }
- }
-
@Override
public boolean onUnbind(final Intent intent) {
mSemaphore.acquireUninterruptibly(MAX_NUM_OF_THREADS_READ_DICTIONARY);
try {
mDictionaryFacilitatorCache.evictAll();
- mCachedLocales.clear();
} finally {
mSemaphore.release(MAX_NUM_OF_THREADS_READ_DICTIONARY);
}
@@ -334,7 +229,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(this, editorInfo);
builder.setKeyboardGeometry(
SPELLCHECKER_DUMMY_KEYBOARD_WIDTH, SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT);
- builder.setSubtype(subtype);
+ builder.setSubtype(new RichInputMethodSubtype(subtype));
builder.setIsSpellChecker(true /* isSpellChecker */);
builder.disableTouchPositionCorrectionData();
return builder.build();
diff --git a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
index 936219332..02f1c5f00 100644
--- a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
@@ -213,12 +213,22 @@ public final class CapsModeUtils {
char c = cs.charAt(--j);
// We found the next interesting chunk of text ; next we need to determine if it's the
- // end of a sentence. If we have a question mark or an exclamation mark, it's the end of
- // a sentence. If it's neither, the only remaining case is the period so we get the opposite
- // case out of the way.
- if (c == Constants.CODE_QUESTION_MARK || c == Constants.CODE_EXCLAMATION_MARK) {
- return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_SENTENCES) & reqModes;
+ // end of a sentence. If we have a sentence terminator (typically a question mark or an
+ // exclamation mark), then it's the end of a sentence; however, we treat the abbreviation
+ // marker specially because usually is the same char as the sentence separator (the
+ // period in most languages) and in this case we need to apply a heuristic to determine
+ // in which of these senses it's used.
+ if (spacingAndPunctuations.isSentenceTerminator(c)
+ && !spacingAndPunctuations.isAbbreviationMarker(c)) {
+ return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS
+ | TextUtils.CAP_MODE_SENTENCES) & reqModes;
}
+ // If we reach here, we know we have whitespace before the cursor and before that there
+ // is something that either does not terminate the sentence, or a symbol preceded by the
+ // start of the text, or it's the sentence separator AND it happens to be the same code
+ // point as the abbreviation marker.
+ // If it's a symbol or something that does not terminate the sentence, then we need to
+ // return caps for MODE_CHARACTERS and MODE_WORDS, but not for MODE_SENTENCES.
if (!spacingAndPunctuations.isSentenceSeparator(c) || j <= 0) {
return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
}
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
index 787e4a59d..94c62429e 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
@@ -36,10 +36,38 @@ public interface DistracterFilter {
public boolean isDistracterToWordsInDictionaries(final PrevWordsInfo prevWordsInfo,
final String testedWord, final Locale locale);
+ public int getWordHandlingType(final PrevWordsInfo prevWordsInfo, final String testedWord,
+ final Locale locale);
+
public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes);
public void close();
+ public static final class HandlingType {
+ private final static int REQUIRE_NO_SPECIAL_HANDLINGS = 0x0;
+ private final static int SHOULD_BE_LOWER_CASED = 0x1;
+ private final static int SHOULD_BE_HANDLED_AS_OOV = 0x2;
+
+ public static int getHandlingType(final boolean shouldBeLowerCased, final boolean isOov) {
+ int wordHandlingType = HandlingType.REQUIRE_NO_SPECIAL_HANDLINGS;
+ if (shouldBeLowerCased) {
+ wordHandlingType |= HandlingType.SHOULD_BE_LOWER_CASED;
+ }
+ if (isOov) {
+ wordHandlingType |= HandlingType.SHOULD_BE_HANDLED_AS_OOV;
+ }
+ return wordHandlingType;
+ }
+
+ public static boolean shouldBeLowerCased(final int handlingType) {
+ return (handlingType & SHOULD_BE_LOWER_CASED) != 0;
+ }
+
+ public static boolean shouldBeHandledAsOov(final int handlingType) {
+ return (handlingType & SHOULD_BE_HANDLED_AS_OOV) != 0;
+ }
+ };
+
public static final DistracterFilter EMPTY_DISTRACTER_FILTER = new DistracterFilter() {
@Override
public boolean isDistracterToWordsInDictionaries(PrevWordsInfo prevWordsInfo,
@@ -48,6 +76,12 @@ public interface DistracterFilter {
}
@Override
+ public int getWordHandlingType(final PrevWordsInfo prevWordsInfo,
+ final String testedWord, final Locale locale) {
+ return HandlingType.REQUIRE_NO_SPECIAL_HANDLINGS;
+ }
+
+ @Override
public void close() {
}
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
index 27973287d..f8a845304 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
@@ -20,13 +20,14 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ConcurrentHashMap;
import android.content.Context;
import android.content.res.Resources;
import android.text.InputType;
import android.util.Log;
import android.util.LruCache;
+import android.util.Pair;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodSubtype;
@@ -34,7 +35,9 @@ import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.KeyboardLayoutSet;
import com.android.inputmethod.latin.DictionaryFacilitator;
+import com.android.inputmethod.latin.DictionaryFacilitatorLruCache;
import com.android.inputmethod.latin.PrevWordsInfo;
+import com.android.inputmethod.latin.RichInputMethodSubtype;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.WordComposer;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
@@ -48,21 +51,22 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
DistracterFilterCheckingExactMatchesAndSuggestions.class.getSimpleName();
private static final boolean DEBUG = false;
- private static final long TIMEOUT_TO_WAIT_LOADING_DICTIONARIES_IN_SECONDS = 120;
- private static final int MAX_DISTRACTERS_CACHE_SIZE = 512;
+ private static final int MAX_DICTIONARY_FACILITATOR_CACHE_SIZE = 3;
+ private static final int MAX_DISTRACTERS_CACHE_SIZE = 1024;
private final Context mContext;
- private final Map<Locale, InputMethodSubtype> mLocaleToSubtypeMap;
- private final Map<Locale, Keyboard> mLocaleToKeyboardMap;
- private final DictionaryFacilitator mDictionaryFacilitator;
- private final LruCache<String, Boolean> mDistractersCache;
- private Keyboard mKeyboard;
+ private final ConcurrentHashMap<Locale, InputMethodSubtype> mLocaleToSubtypeCache;
+ private final ConcurrentHashMap<Locale, Keyboard> mLocaleToKeyboardCache;
+ private final DictionaryFacilitatorLruCache mDictionaryFacilitatorLruCache;
+ // The key is a pair of a locale and a word. The value indicates the word is a distracter to
+ // words of the locale.
+ private final LruCache<Pair<Locale, String>, Boolean> mDistractersCache;
private final Object mLock = new Object();
// If the score of the top suggestion exceeds this value, the tested word (e.g.,
- // an OOV, a misspelling, or an in-vocabulary word) would be considered as a distractor to
+ // an OOV, a misspelling, or an in-vocabulary word) would be considered as a distracter to
// words in dictionary. The greater the threshold is, the less likely the tested word would
- // become a distractor, which means the tested word will be more likely to be added to
+ // become a distracter, which means the tested word will be more likely to be added to
// the dictionary.
private static final float DISTRACTER_WORD_SCORE_THRESHOLD = 0.4f;
@@ -73,16 +77,19 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
*/
public DistracterFilterCheckingExactMatchesAndSuggestions(final Context context) {
mContext = context;
- mLocaleToSubtypeMap = new HashMap<>();
- mLocaleToKeyboardMap = new HashMap<>();
- mDictionaryFacilitator = new DictionaryFacilitator();
+ mLocaleToSubtypeCache = new ConcurrentHashMap<>();
+ mLocaleToKeyboardCache = new ConcurrentHashMap<>();
+ mDictionaryFacilitatorLruCache = new DictionaryFacilitatorLruCache(context,
+ MAX_DICTIONARY_FACILITATOR_CACHE_SIZE, "" /* dictionaryNamePrefix */);
mDistractersCache = new LruCache<>(MAX_DISTRACTERS_CACHE_SIZE);
- mKeyboard = null;
}
@Override
public void close() {
- mDictionaryFacilitator.closeDictionaries();
+ mLocaleToSubtypeCache.clear();
+ mLocaleToKeyboardCache.clear();
+ mDictionaryFacilitatorLruCache.evictAll();
+ // Don't clear mDistractersCache.
}
@Override
@@ -99,29 +106,36 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
newLocaleToSubtypeMap.put(locale, subtype);
}
}
- if (mLocaleToSubtypeMap.equals(newLocaleToSubtypeMap)) {
+ if (mLocaleToSubtypeCache.equals(newLocaleToSubtypeMap)) {
// Enabled subtypes have not been changed.
return;
}
- synchronized (mLock) {
- mLocaleToSubtypeMap.clear();
- mLocaleToSubtypeMap.putAll(newLocaleToSubtypeMap);
- mLocaleToKeyboardMap.clear();
+ // Update subtype and keyboard map for locales that are in the current mapping.
+ for (final Locale locale: mLocaleToSubtypeCache.keySet()) {
+ if (newLocaleToSubtypeMap.containsKey(locale)) {
+ final InputMethodSubtype newSubtype = newLocaleToSubtypeMap.remove(locale);
+ if (newSubtype.equals(newLocaleToSubtypeMap.get(locale))) {
+ // Mapping has not been changed.
+ continue;
+ }
+ mLocaleToSubtypeCache.replace(locale, newSubtype);
+ } else {
+ mLocaleToSubtypeCache.remove(locale);
+ }
+ mLocaleToKeyboardCache.remove(locale);
}
+ // Add locales that are not in the current mapping.
+ mLocaleToSubtypeCache.putAll(newLocaleToSubtypeMap);
}
- private void loadKeyboardForLocale(final Locale newLocale) {
- final Keyboard cachedKeyboard = mLocaleToKeyboardMap.get(newLocale);
+ private Keyboard getKeyboardForLocale(final Locale locale) {
+ final Keyboard cachedKeyboard = mLocaleToKeyboardCache.get(locale);
if (cachedKeyboard != null) {
- mKeyboard = cachedKeyboard;
- return;
- }
- final InputMethodSubtype subtype;
- synchronized (mLock) {
- subtype = mLocaleToSubtypeMap.get(newLocale);
+ return cachedKeyboard;
}
+ final InputMethodSubtype subtype = mLocaleToSubtypeCache.get(locale);
if (subtype == null) {
- return;
+ return null;
}
final EditorInfo editorInfo = new EditorInfo();
editorInfo.inputType = InputType.TYPE_CLASS_TEXT;
@@ -131,18 +145,12 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
final int keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res);
final int keyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res);
builder.setKeyboardGeometry(keyboardWidth, keyboardHeight);
- builder.setSubtype(subtype);
+ builder.setSubtype(new RichInputMethodSubtype(subtype));
builder.setIsSpellChecker(false /* isSpellChecker */);
final KeyboardLayoutSet layoutSet = builder.build();
- mKeyboard = layoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
- }
-
- private void loadDictionariesForLocale(final Locale newlocale) throws InterruptedException {
- mDictionaryFacilitator.resetDictionaries(mContext, newlocale,
- false /* useContactsDict */, false /* usePersonalizedDicts */,
- false /* forceReloadMainDictionary */, null /* listener */);
- mDictionaryFacilitator.waitForLoadingMainDictionary(
- TIMEOUT_TO_WAIT_LOADING_DICTIONARIES_IN_SECONDS, TimeUnit.SECONDS);
+ final Keyboard newKeyboard = layoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
+ mLocaleToKeyboardCache.put(locale, newKeyboard);
+ return newKeyboard;
}
/**
@@ -160,30 +168,18 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
if (locale == null) {
return false;
}
- if (!locale.equals(mDictionaryFacilitator.getLocale())) {
- synchronized (mLock) {
- if (!mLocaleToSubtypeMap.containsKey(locale)) {
- Log.e(TAG, "Locale " + locale + " is not enabled.");
- // TODO: Investigate what we should do for disabled locales.
- return false;
- }
- loadKeyboardForLocale(locale);
- // Reset dictionaries for the locale.
- try {
- mDistractersCache.evictAll();
- loadDictionariesForLocale(locale);
- } catch (final InterruptedException e) {
- Log.e(TAG, "Interrupted while waiting for loading dicts in DistracterFilter",
- e);
- return false;
- }
- }
+ if (!mLocaleToSubtypeCache.containsKey(locale)) {
+ Log.e(TAG, "Locale " + locale + " is not enabled.");
+ // TODO: Investigate what we should do for disabled locales.
+ return false;
}
-
+ final DictionaryFacilitator dictionaryFacilitator =
+ mDictionaryFacilitatorLruCache.get(locale);
if (DEBUG) {
Log.d(TAG, "testedWord: " + testedWord);
}
- final Boolean isCachedDistracter = mDistractersCache.get(testedWord);
+ final Pair<Locale, String> cacheKey = new Pair<>(locale, testedWord);
+ final Boolean isCachedDistracter = mDistractersCache.get(cacheKey);
if (isCachedDistracter != null && isCachedDistracter) {
if (DEBUG) {
Log.d(TAG, "isDistracter: true (cache hit)");
@@ -192,37 +188,38 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
}
final boolean isDistracterCheckedByGetMaxFreqencyOfExactMatches =
- checkDistracterUsingMaxFreqencyOfExactMatches(testedWord);
+ checkDistracterUsingMaxFreqencyOfExactMatches(dictionaryFacilitator, testedWord);
if (isDistracterCheckedByGetMaxFreqencyOfExactMatches) {
- // Add the word to the cache.
- mDistractersCache.put(testedWord, Boolean.TRUE);
+ // Add the pair of locale and word to the cache.
+ mDistractersCache.put(cacheKey, Boolean.TRUE);
return true;
}
- final boolean isValidWord = mDictionaryFacilitator.isValidWord(testedWord,
- false /* ignoreCase */);
- if (isValidWord) {
- // Valid word is not a distractor.
+ final boolean Word = dictionaryFacilitator.isValidWord(testedWord, false /* ignoreCase */);
+ if (Word) {
+ // Valid word is not a distracter.
if (DEBUG) {
Log.d(TAG, "isDistracter: false (valid word)");
}
return false;
}
+ final Keyboard keyboard = getKeyboardForLocale(locale);
final boolean isDistracterCheckedByGetSuggestion =
- checkDistracterUsingGetSuggestions(testedWord);
+ checkDistracterUsingGetSuggestions(dictionaryFacilitator, keyboard, testedWord);
if (isDistracterCheckedByGetSuggestion) {
- // Add the word to the cache.
- mDistractersCache.put(testedWord, Boolean.TRUE);
+ // Add the pair of locale and word to the cache.
+ mDistractersCache.put(cacheKey, Boolean.TRUE);
return true;
}
return false;
}
- private boolean checkDistracterUsingMaxFreqencyOfExactMatches(final String testedWord) {
+ private static boolean checkDistracterUsingMaxFreqencyOfExactMatches(
+ final DictionaryFacilitator dictionaryFacilitator, final String testedWord) {
// The tested word is a distracter when there is a word that is exact matched to the tested
// word and its probability is higher than the tested word's probability.
- final int perfectMatchFreq = mDictionaryFacilitator.getFrequency(testedWord);
- final int exactMatchFreq = mDictionaryFacilitator.getMaxFrequencyOfExactMatches(testedWord);
+ final int perfectMatchFreq = dictionaryFacilitator.getFrequency(testedWord);
+ final int exactMatchFreq = dictionaryFacilitator.getMaxFrequencyOfExactMatches(testedWord);
final boolean isDistracter = perfectMatchFreq < exactMatchFreq;
if (DEBUG) {
Log.d(TAG, "perfectMatchFreq: " + perfectMatchFreq);
@@ -232,8 +229,10 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
return isDistracter;
}
- private boolean checkDistracterUsingGetSuggestions(final String testedWord) {
- if (mKeyboard == null) {
+ private boolean checkDistracterUsingGetSuggestions(
+ final DictionaryFacilitator dictionaryFacilitator, final Keyboard keyboard,
+ final String testedWord) {
+ if (keyboard == null) {
return false;
}
final SettingsValuesForSuggestion settingsValuesForSuggestion =
@@ -246,24 +245,24 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
testedWord;
final WordComposer composer = new WordComposer();
final int[] codePoints = StringUtils.toCodePointArray(testedWord);
-
+ final int[] coordinates = keyboard.getCoordinates(codePoints);
+ composer.setComposingWord(codePoints, coordinates);
+ final SuggestionResults suggestionResults;
synchronized (mLock) {
- final int[] coordinates = mKeyboard.getCoordinates(codePoints);
- composer.setComposingWord(codePoints, coordinates);
- final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
- composer, PrevWordsInfo.EMPTY_PREV_WORDS_INFO, mKeyboard.getProximityInfo(),
+ suggestionResults = dictionaryFacilitator.getSuggestionResults(
+ composer, PrevWordsInfo.EMPTY_PREV_WORDS_INFO, keyboard.getProximityInfo(),
settingsValuesForSuggestion, 0 /* sessionId */);
- if (suggestionResults.isEmpty()) {
- return false;
- }
- final SuggestedWordInfo firstSuggestion = suggestionResults.first();
- final boolean isDistractor = suggestionExceedsDistracterThreshold(
- firstSuggestion, consideredWord, DISTRACTER_WORD_SCORE_THRESHOLD);
- if (DEBUG) {
- Log.d(TAG, "isDistracter: " + isDistractor);
- }
- return isDistractor;
}
+ if (suggestionResults.isEmpty()) {
+ return false;
+ }
+ final SuggestedWordInfo firstSuggestion = suggestionResults.first();
+ final boolean isDistracter = suggestionExceedsDistracterThreshold(
+ firstSuggestion, consideredWord, DISTRACTER_WORD_SCORE_THRESHOLD);
+ if (DEBUG) {
+ Log.d(TAG, "isDistracter: " + isDistracter);
+ }
+ return isDistracter;
}
private static boolean suggestionExceedsDistracterThreshold(final SuggestedWordInfo suggestion,
@@ -283,4 +282,41 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
}
return false;
}
+
+ private boolean shouldBeLowerCased(final PrevWordsInfo prevWordsInfo, final String testedWord,
+ final Locale locale) {
+ final DictionaryFacilitator dictionaryFacilitator =
+ mDictionaryFacilitatorLruCache.get(locale);
+ if (dictionaryFacilitator.isValidWord(testedWord, false /* ignoreCase */)) {
+ return false;
+ }
+ final String lowerCaseTargetWord = testedWord.toLowerCase(locale);
+ if (testedWord.equals(lowerCaseTargetWord)) {
+ return false;
+ }
+ if (dictionaryFacilitator.isValidWord(lowerCaseTargetWord, false /* ignoreCase */)) {
+ return true;
+ }
+ if (StringUtils.getCapitalizationType(testedWord) == StringUtils.CAPITALIZE_FIRST
+ && !prevWordsInfo.isValid()) {
+ // TODO: Check beginning-of-sentence.
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public int getWordHandlingType(final PrevWordsInfo prevWordsInfo, final String testedWord,
+ final Locale locale) {
+ // TODO: Use this method for user history dictionary.
+ if (testedWord == null|| locale == null) {
+ return HandlingType.getHandlingType(false /* shouldBeLowerCased */, false /* isOov */);
+ }
+ final boolean shouldBeLowerCased = shouldBeLowerCased(prevWordsInfo, testedWord, locale);
+ final String caseModifiedWord =
+ shouldBeLowerCased ? testedWord.toLowerCase(locale) : testedWord;
+ final boolean isOov = !mDictionaryFacilitatorLruCache.get(locale).isValidWord(
+ caseModifiedWord, false /* ignoreCase */);
+ return HandlingType.getHandlingType(shouldBeLowerCased, isOov);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
index 4ad4ba784..349236f18 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
@@ -48,6 +48,12 @@ public class DistracterFilterCheckingIsInDictionary implements DistracterFilter
}
@Override
+ public int getWordHandlingType(final PrevWordsInfo prevWordsInfo, final String testedWord,
+ final Locale locale) {
+ return mDistracterFilter.getWordHandlingType(prevWordsInfo, testedWord, locale);
+ }
+
+ @Override
public void updateEnabledSubtypes(List<InputMethodSubtype> enabledSubtypes) {
// Do nothing.
}
diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
index fbce3f2fd..05d124764 100644
--- a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
+++ b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
@@ -22,6 +22,7 @@ import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.DictionaryFacilitator;
import com.android.inputmethod.latin.PrevWordsInfo;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+import com.android.inputmethod.latin.utils.DistracterFilter.HandlingType;
import java.util.ArrayList;
import java.util.List;
@@ -81,8 +82,7 @@ public final class LanguageModelParam {
// Process a list of words and return a list of {@link LanguageModelParam} objects.
public static ArrayList<LanguageModelParam> createLanguageModelParamsFrom(
final List<String> tokens, final int timestamp,
- final DictionaryFacilitator dictionaryFacilitator,
- final SpacingAndPunctuations spacingAndPunctuations,
+ final SpacingAndPunctuations spacingAndPunctuations, final Locale locale,
final DistracterFilter distracterFilter) {
final ArrayList<LanguageModelParam> languageModelParams = new ArrayList<>();
final int N = tokens.size();
@@ -111,8 +111,7 @@ public final class LanguageModelParam {
}
final LanguageModelParam languageModelParam =
detectWhetherVaildWordOrNotAndGetLanguageModelParam(
- prevWordsInfo, tempWord, timestamp, dictionaryFacilitator,
- distracterFilter);
+ prevWordsInfo, tempWord, timestamp, locale, distracterFilter);
if (languageModelParam == null) {
continue;
}
@@ -125,47 +124,25 @@ public final class LanguageModelParam {
private static LanguageModelParam detectWhetherVaildWordOrNotAndGetLanguageModelParam(
final PrevWordsInfo prevWordsInfo, final String targetWord, final int timestamp,
- final DictionaryFacilitator dictionaryFacilitator,
- final DistracterFilter distracterFilter) {
- final Locale locale = dictionaryFacilitator.getLocale();
+ final Locale locale, final DistracterFilter distracterFilter) {
if (locale == null) {
return null;
}
- if (dictionaryFacilitator.isValidWord(targetWord, false /* ignoreCase */)) {
- return createAndGetLanguageModelParamOfWord(prevWordsInfo, targetWord, timestamp,
- true /* isValidWord */, locale, distracterFilter);
- }
-
- final String lowerCaseTargetWord = targetWord.toLowerCase(locale);
- if (dictionaryFacilitator.isValidWord(lowerCaseTargetWord, false /* ignoreCase */)) {
- // Add the lower-cased word.
- return createAndGetLanguageModelParamOfWord(prevWordsInfo, lowerCaseTargetWord,
- timestamp, true /* isValidWord */, locale, distracterFilter);
+ final int wordHandlingType = distracterFilter.getWordHandlingType(prevWordsInfo,
+ targetWord, locale);
+ final String word = HandlingType.shouldBeLowerCased(wordHandlingType) ?
+ targetWord.toLowerCase(locale) : targetWord;
+ if (distracterFilter.isDistracterToWordsInDictionaries(prevWordsInfo, targetWord, locale)) {
+ // The word is a distracter.
+ return null;
}
-
- // Treat the word as an OOV word.
- return createAndGetLanguageModelParamOfWord(prevWordsInfo, targetWord, timestamp,
- false /* isValidWord */, locale, distracterFilter);
+ return createAndGetLanguageModelParamOfWord(prevWordsInfo, word, timestamp,
+ !HandlingType.shouldBeHandledAsOov(wordHandlingType));
}
private static LanguageModelParam createAndGetLanguageModelParamOfWord(
- final PrevWordsInfo prevWordsInfo, final String targetWord, final int timestamp,
- final boolean isValidWord, final Locale locale,
- final DistracterFilter distracterFilter) {
- final String word;
- if (StringUtils.getCapitalizationType(targetWord) == StringUtils.CAPITALIZE_FIRST
- && !prevWordsInfo.isValid() && !isValidWord) {
- word = targetWord.toLowerCase(locale);
- } else {
- word = targetWord;
- }
- // Check whether the word is a distracter to words in the dictionaries.
- if (distracterFilter.isDistracterToWordsInDictionaries(prevWordsInfo, word, locale)) {
- if (DEBUG) {
- Log.d(TAG, "The word (" + word + ") is a distracter. Skip this word.");
- }
- return null;
- }
+ final PrevWordsInfo prevWordsInfo, final String word, final int timestamp,
+ final boolean isValidWord) {
final int unigramProbability = isValidWord ?
UNIGRAM_PROBABILITY_FOR_VALID_WORD : UNIGRAM_PROBABILITY_FOR_OOV_WORD;
if (!prevWordsInfo.isValid()) {
diff --git a/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java b/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java
deleted file mode 100644
index 1ca895fdb..000000000
--- a/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2014 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.utils;
-
-import android.view.inputmethod.InputMethodSubtype;
-
-public final class SpacebarLanguageUtils {
- private SpacebarLanguageUtils() {
- // Intentional empty constructor for utility class.
- }
-
- // InputMethodSubtype's display name for spacebar text in its locale.
- // isAdditionalSubtype (T=true, F=false)
- // locale layout | Middle Full
- // ------ ------- - --------- ----------------------
- // en_US qwerty F English English (US) exception
- // en_GB qwerty F English English (UK) exception
- // es_US spanish F Español Español (EE.UU.) exception
- // fr azerty F Français Français
- // fr_CA qwerty F Français Français (Canada)
- // fr_CH swiss F Français Français (Suisse)
- // de qwertz F Deutsch Deutsch
- // de_CH swiss T Deutsch Deutsch (Schweiz)
- // zz qwerty F QWERTY QWERTY
- // fr qwertz T Français Français
- // de qwerty T Deutsch Deutsch
- // en_US azerty T English English (US)
- // zz azerty T AZERTY AZERTY
- // Get InputMethodSubtype's full display name in its locale.
- public static String getFullDisplayName(final InputMethodSubtype subtype) {
- if (SubtypeLocaleUtils.isNoLanguage(subtype)) {
- return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype);
- }
- return SubtypeLocaleUtils.getSubtypeLocaleDisplayName(subtype.getLocale());
- }
-
- // Get InputMethodSubtype's middle display name in its locale.
- public static String getMiddleDisplayName(final InputMethodSubtype subtype) {
- if (SubtypeLocaleUtils.isNoLanguage(subtype)) {
- return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype);
- }
- return SubtypeLocaleUtils.getSubtypeLanguageDisplayName(subtype.getLocale());
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
index 1781924ac..bbcef990d 100644
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
@@ -49,9 +49,9 @@ public final class StringUtils {
// This utility class is not publicly instantiable.
}
- public static int codePointCount(final String text) {
+ public static int codePointCount(final CharSequence text) {
if (TextUtils.isEmpty(text)) return 0;
- return text.codePointCount(0, text.length());
+ return Character.codePointCount(text, 0, text.length());
}
public static String newSingleCodePointString(int codePoint) {
diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index 351d01400..96a6510fc 100644
--- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -27,11 +27,17 @@ import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.RichInputMethodSubtype;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Locale;
+/**
+ * A helper class to deal with subtype locales.
+ */
+// TODO: consolidate this into RichInputMethodSubtype
public final class SubtypeLocaleUtils {
private static final String TAG = SubtypeLocaleUtils.class.getSimpleName();
@@ -52,6 +58,8 @@ public final class SubtypeLocaleUtils {
private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap = new HashMap<>();
// Keyboard layout to subtype name resource id map.
private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap = new HashMap<>();
+ // Exceptional locale whose name should be displayed in Locale.ROOT.
+ static final HashSet<String> sExceptionalLocaleDisplayedInRootLocale = new HashSet<>();
// Exceptional locale to subtype name resource id map.
private static final HashMap<String, Integer> sExceptionalLocaleToNameIdsMap = new HashMap<>();
// Exceptional locale to subtype name with layout resource id map.
@@ -106,6 +114,12 @@ public final class SubtypeLocaleUtils {
sKeyboardLayoutToNameIdsMap.put(key, noLanguageResId);
}
+ final String[] exceptionalLocaleInRootLocale = res.getStringArray(
+ R.array.subtype_locale_displayed_in_root_locale);
+ for (int i = 0; i < exceptionalLocaleInRootLocale.length; i++) {
+ sExceptionalLocaleDisplayedInRootLocale.add(exceptionalLocaleInRootLocale[i]);
+ }
+
final String[] exceptionalLocales = res.getStringArray(
R.array.subtype_locale_exception_keys);
for (int i = 0; i < exceptionalLocales.length; i++) {
@@ -157,6 +171,9 @@ public final class SubtypeLocaleUtils {
if (NO_LANGUAGE.equals(localeString)) {
return sResources.getConfiguration().locale;
}
+ if (sExceptionalLocaleDisplayedInRootLocale.contains(localeString)) {
+ return Locale.ROOT;
+ }
return LocaleUtils.constructLocaleFromString(localeString);
}
@@ -171,9 +188,15 @@ public final class SubtypeLocaleUtils {
}
public static String getSubtypeLanguageDisplayName(final String localeString) {
- final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString);
- return getSubtypeLocaleDisplayNameInternal(locale.getLanguage(), displayLocale);
+ final String languageString;
+ if (sExceptionalLocaleDisplayedInRootLocale.contains(localeString)) {
+ languageString = localeString;
+ } else {
+ final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
+ languageString = locale.getLanguage();
+ }
+ return getSubtypeLocaleDisplayNameInternal(languageString, displayLocale);
}
private static String getSubtypeLocaleDisplayNameInternal(final String localeString,
@@ -242,6 +265,7 @@ public final class SubtypeLocaleUtils {
private static String getSubtypeDisplayNameInternal(final InputMethodSubtype subtype,
final Locale displayLocale) {
final String replacementString = getReplacementString(subtype, displayLocale);
+ // TODO: rework this for multi-lingual subtypes
final int nameResId = subtype.getNameResId();
final RunInLocale<String> getSubtypeName = new RunInLocale<String>() {
@Override
@@ -264,12 +288,14 @@ public final class SubtypeLocaleUtils {
getSubtypeName.runInLocale(sResources, displayLocale), displayLocale);
}
- public static boolean isNoLanguage(final InputMethodSubtype subtype) {
+ public static Locale getSubtypeLocale(final InputMethodSubtype subtype) {
final String localeString = subtype.getLocale();
- return NO_LANGUAGE.equals(localeString);
+ return LocaleUtils.constructLocaleFromString(localeString);
}
- public static Locale getSubtypeLocale(final InputMethodSubtype subtype) {
+ // TODO: remove this. When RichInputMethodSubtype#getLocale is removed we can do away with this
+ // method at the same time.
+ public static Locale getSubtypeLocale(final RichInputMethodSubtype subtype) {
final String localeString = subtype.getLocale();
return LocaleUtils.constructLocaleFromString(localeString);
}
@@ -283,6 +309,10 @@ public final class SubtypeLocaleUtils {
return sKeyboardLayoutToDisplayNameMap.get(layoutName);
}
+ public static String getKeyboardLayoutSetName(final RichInputMethodSubtype subtype) {
+ return getKeyboardLayoutSetName(subtype.getRawSubtype());
+ }
+
public static String getKeyboardLayoutSetName(final InputMethodSubtype subtype) {
String keyboardLayoutSet = subtype.getExtraValueOf(KEYBOARD_LAYOUT_SET);
if (keyboardLayoutSet == null) {
@@ -318,7 +348,7 @@ public final class SubtypeLocaleUtils {
return Arrays.binarySearch(SORTED_RTL_LANGUAGES, language) >= 0;
}
- public static boolean isRtlLanguage(final InputMethodSubtype subtype) {
+ public static boolean isRtlLanguage(final RichInputMethodSubtype subtype) {
return isRtlLanguage(getSubtypeLocale(subtype));
}
diff --git a/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java
index 7170bd789..eaa5743d4 100644
--- a/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java
+++ b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java
@@ -30,18 +30,16 @@ import java.util.TreeSet;
* than its limit
*/
public final class SuggestionResults extends TreeSet<SuggestedWordInfo> {
- public final Locale mLocale;
public final ArrayList<SuggestedWordInfo> mRawSuggestions;
private final int mCapacity;
- public SuggestionResults(final Locale locale, final int capacity) {
- this(locale, sSuggestedWordInfoComparator, capacity);
+ public SuggestionResults(final int capacity) {
+ this(sSuggestedWordInfoComparator, capacity);
}
- public SuggestionResults(final Locale locale, final Comparator<SuggestedWordInfo> comparator,
+ public SuggestionResults(final Comparator<SuggestedWordInfo> comparator,
final int capacity) {
super(comparator);
- mLocale = locale;
mCapacity = capacity;
if (ProductionFlags.INCLUDE_RAW_SUGGESTIONS) {
mRawSuggestions = new ArrayList<>();