diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
27 files changed, 662 insertions, 468 deletions
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index 693e1cdcc..2e108756e 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. diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java index 43af66eb7..02d5eddaa 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,6 +224,9 @@ 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 = " "; diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java index 560ced9c4..2f79c7662 100644 --- a/java/src/com/android/inputmethod/latin/Dictionary.java +++ b/java/src/com/android/inputmethod/latin/Dictionary.java @@ -16,12 +16,12 @@ 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; /** * Abstract base class for a dictionary that can do a fuzzy search for words based on a set of key @@ -62,9 +62,12 @@ 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) { + public Dictionary(final String dictType, final Locale locale) { mDictType = dictType; + mLocale = locale; } /** @@ -162,7 +165,7 @@ public abstract class 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..480bd1f85 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java @@ -19,6 +19,7 @@ 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; @@ -32,6 +33,7 @@ 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; @@ -58,6 +60,7 @@ public class DictionaryFacilitator { // HACK: This threshold is being used when adding a capitalized entry in the User History // dictionary. private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140; + private static final int MAX_DICTIONARY_FACILITATOR_CACHE_SIZE = 3; private Dictionaries mDictionaries = new Dictionaries(); private boolean mIsUserDictEnabled = false; @@ -65,6 +68,7 @@ public class DictionaryFacilitator { // To synchronize assigning mDictionaries to ensure closing dictionaries. private final Object mLock = new Object(); private final DistracterFilter mDistracterFilter; + private final DictionaryFacilitatorLruCache mFacilitatorCacheForPersonalization; private static final String[] DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS = new String[] { @@ -172,10 +176,14 @@ public class DictionaryFacilitator { public DictionaryFacilitator() { mDistracterFilter = DistracterFilter.EMPTY_DISTRACTER_FILTER; + mFacilitatorCacheForPersonalization = null; } - public DictionaryFacilitator(final DistracterFilter distracterFilter) { - mDistracterFilter = distracterFilter; + public DictionaryFacilitator(final Context context) { + mFacilitatorCacheForPersonalization = new DictionaryFacilitatorLruCache(context, + MAX_DICTIONARY_FACILITATOR_CACHE_SIZE, "" /* dictionaryNamePrefix */); + mDistracterFilter = new DistracterFilterCheckingExactMatchesAndSuggestions(context, + mFacilitatorCacheForPersonalization); } public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) { @@ -350,6 +358,9 @@ public class DictionaryFacilitator { for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) { dictionaries.closeDict(dictType); } + if (mFacilitatorCacheForPersonalization != null) { + mFacilitatorCacheForPersonalization.evictAll(); + } mDistracterFilter.close(); } @@ -492,7 +503,7 @@ public class DictionaryFacilitator { final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId) { final Dictionaries dictionaries = mDictionaries; 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); @@ -596,11 +607,15 @@ public class DictionaryFacilitator { } return; } + // TODO: Get locale from personalizationDataChunk.mDetectedLanguage. + final Locale dataChunkLocale = getLocale(); + final DictionaryFacilitator dictionaryFacilitatorForLocale = + mFacilitatorCacheForPersonalization.get(dataChunkLocale); final ArrayList<LanguageModelParam> languageModelParams = LanguageModelParam.createLanguageModelParamsFrom( personalizationDataChunk.mTokens, personalizationDataChunk.mTimestampInSeconds, - this /* dictionaryFacilitator */, spacingAndPunctuations, + dictionaryFacilitatorForLocale, spacingAndPunctuations, new DistracterFilterCheckingIsInDictionary( mDistracterFilter, personalizationDict)); if (languageModelParams == null || languageModelParams.isEmpty()) { @@ -656,4 +671,15 @@ public class DictionaryFacilitator { } dictToDump.dumpAllWordsForDebug(); } + + public ArrayList<Pair<String, DictionaryStats>> getStatsOfEnabledSubDicts() { + final ArrayList<Pair<String, DictionaryStats>> statsOfEnabledSubDicts = new ArrayList<>(); + final Dictionaries dictionaries = mDictionaries; + for (final String dictType : SUB_DICT_TYPES) { + final ExpandableBinaryDictionary dictionary = dictionaries.getSubDict(dictType); + if (dictionary == null) continue; + statsOfEnabledSubDicts.add(new Pair<>(dictType, dictionary.getDictionaryStats())); + } + 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..fa0265d86 --- /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.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); + } + } + } + } + + 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..a1dd67f27 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(); @@ -644,10 +640,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(); diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java index ebe436128..782e18255 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 private int mInputType; final private EditorInfo mEditorInfo; final private String mPackageNameForPrivateImeOptions; @@ -76,6 +83,7 @@ public final class InputAttributes { mApplicationSpecifiedCompletionOn = false; mShouldInsertSpacesAutomatically = false; mShouldShowVoiceInputKey = false; + mDisableGestureFloatingPreviewText = false; return; } // inputClass == InputType.TYPE_CLASS_TEXT @@ -107,6 +115,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 c348e6cfe..c55acd462 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -93,6 +93,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; @@ -130,8 +131,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); @@ -158,6 +158,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; // Object for reacting to adding/removing a dictionary pack. private final BroadcastReceiver mDictionaryPackInstallReceiver = @@ -538,6 +539,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); @@ -553,8 +555,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(); @@ -586,8 +587,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 @@ -609,7 +609,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mDictionaryFacilitator.updateEnabledSubtypes(mRichImm.getMyEnabledInputMethodSubtypeList( true /* allowsImplicitlySelectedSubtypes */)); refreshPersonalizationDictionarySession(currentSettingsValues); - StatsUtils.onLoadSettings(currentSettingsValues); + mStatsUtilsManager.onLoadSettings(currentSettingsValues); } private void refreshPersonalizationDictionarySession( @@ -699,7 +699,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen unregisterReceiver(mConnectivityAndRingerModeChangeReceiver); unregisterReceiver(mDictionaryPackInstallReceiver); unregisterReceiver(mDictionaryDumpBroadcastReceiver); - StatsUtils.onDestroy(); + mStatsUtilsManager.onDestroy(); super.onDestroy(); } @@ -771,7 +771,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(); @@ -1178,10 +1179,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. @@ -1204,13 +1201,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(); } @@ -1268,48 +1259,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; @@ -1327,13 +1327,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 @@ -1491,7 +1490,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/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/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..9e4aa40a2 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); @@ -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); } } diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java index 89aea4310..233a22512 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java @@ -726,7 +726,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 @@ -754,11 +754,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 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..6f4b09741 100644 --- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java +++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java @@ -18,20 +18,20 @@ package com.android.inputmethod.latin.personalization; import java.util.Collections; import java.util.List; -import java.util.Locale; public class PersonalizationDataChunk { 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/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java index 1da112bf6..11291959c 100644 --- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java @@ -40,7 +40,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. @@ -164,8 +165,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; @@ -217,6 +218,10 @@ public final class SettingsValues { } } + public boolean isMetricsLoggingEnabled() { + return mEnableMetricsLogging; + } + public boolean isApplicationSpecifiedCompletionsOn() { return mInputAttributes.mApplicationSpecifiedCompletionOn; } diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index 90398deb2..49b34d391 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); + mDictionaryFacilitatorCache.get(locale); return dictionaryFacilitator.hasInitializedMainDictionary(); } 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/DistracterFilterCheckingExactMatchesAndSuggestions.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java index 27973287d..e10571e4a 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,15 +51,15 @@ 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_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., @@ -70,19 +73,23 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr * Create a DistracterFilter instance. * * @param context the context. + * @param dictionaryFacilitatorLruCache the cache of dictionaryFacilitators that are used for + * checking distracters. */ - public DistracterFilterCheckingExactMatchesAndSuggestions(final Context context) { + public DistracterFilterCheckingExactMatchesAndSuggestions(final Context context, + final DictionaryFacilitatorLruCache dictionaryFacilitatorLruCache) { mContext = context; - mLocaleToSubtypeMap = new HashMap<>(); - mLocaleToKeyboardMap = new HashMap<>(); - mDictionaryFacilitator = new DictionaryFacilitator(); + mLocaleToSubtypeCache = new ConcurrentHashMap<>(); + mLocaleToKeyboardCache = new ConcurrentHashMap<>(); + mDictionaryFacilitatorLruCache = dictionaryFacilitatorLruCache; mDistractersCache = new LruCache<>(MAX_DISTRACTERS_CACHE_SIZE); - mKeyboard = null; } @Override public void close() { - mDictionaryFacilitator.closeDictionaries(); + mLocaleToSubtypeCache.clear(); + mLocaleToKeyboardCache.clear(); + mDistractersCache.evictAll(); } @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,13 +188,13 @@ 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, + final boolean isValidWord = dictionaryFacilitator.isValidWord(testedWord, false /* ignoreCase */); if (isValidWord) { // Valid word is not a distractor. @@ -208,21 +204,23 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr 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 +230,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 +246,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 isDistractor = suggestionExceedsDistracterThreshold( + firstSuggestion, consideredWord, DISTRACTER_WORD_SCORE_THRESHOLD); + if (DEBUG) { + Log.d(TAG, "isDistracter: " + isDistractor); + } + return isDistractor; } private static boolean suggestionExceedsDistracterThreshold(final SuggestedWordInfo suggestion, 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 38f0b3fee..55557de9d 100644 --- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java @@ -41,9 +41,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<>(); |