diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java index a97916733..0f410d574 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java @@ -19,14 +19,18 @@ package com.android.inputmethod.latin; import android.content.Context; import android.text.TextUtils; import android.util.Log; +import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.personalization.ContextualDictionary; +import com.android.inputmethod.latin.personalization.PersonalizationDataChunk; import com.android.inputmethod.latin.personalization.PersonalizationDictionary; import com.android.inputmethod.latin.personalization.UserHistoryDictionary; +import com.android.inputmethod.latin.settings.SpacingAndPunctuations; import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.DistracterFilter; import com.android.inputmethod.latin.utils.ExecutorUtils; import com.android.inputmethod.latin.utils.LanguageModelParam; import com.android.inputmethod.latin.utils.SuggestionResults; @@ -37,6 +41,7 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -57,6 +62,7 @@ public class DictionaryFacilitator { private volatile CountDownLatch mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0); // To synchronize assigning mDictionaries to ensure closing dictionaries. private final Object mLock = new Object(); + private final DistracterFilter mDistracterFilter; private static final String[] DICT_TYPES_ORDERED_TO_GET_SUGGESTION = new String[] { @@ -162,7 +168,17 @@ public class DictionaryFacilitator { public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable); } - public DictionaryFacilitator() {} + public DictionaryFacilitator() { + mDistracterFilter = new DistracterFilter(); + } + + public DictionaryFacilitator(final DistracterFilter distracterFilter) { + mDistracterFilter = distracterFilter; + } + + public void updateEnabledSubtypes(final List enabledSubtypes) { + mDistracterFilter.updateEnabledSubtypes(enabledSubtypes); + } public Locale getLocale() { return mDictionaries.mLocale; @@ -321,6 +337,7 @@ public class DictionaryFacilitator { for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTION) { dictionaries.closeDict(dictType); } + mDistracterFilter.close(); } // The main dictionary could have been loaded asynchronously. Don't cache the return value @@ -537,9 +554,16 @@ public class DictionaryFacilitator { personalizationDict.clear(); } - public void addMultipleDictionaryEntriesToPersonalizationDictionary( - final ArrayList languageModelParams, + public void addEntriesToPersonalizationDictionary( + final PersonalizationDataChunk personalizationDataChunk, + final SpacingAndPunctuations spacingAndPunctuations, final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) { + final ArrayList languageModelParams = + LanguageModelParam.createLanguageModelParamsFrom( + personalizationDataChunk.mTokens, + personalizationDataChunk.mTimestampInSeconds, + this /* dictionaryFacilitator */, spacingAndPunctuations, + mDistracterFilter); final ExpandableBinaryDictionary personalizationDict = mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION); if (personalizationDict == null || languageModelParams == null diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 4863326ab..5fc40585a 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -81,6 +81,7 @@ import com.android.inputmethod.latin.suggestions.SuggestionStripView; import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor; import com.android.inputmethod.latin.utils.ApplicationUtils; import com.android.inputmethod.latin.utils.CapsModeUtils; +import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.CoordinateUtils; import com.android.inputmethod.latin.utils.DialogUtils; import com.android.inputmethod.latin.utils.DistracterFilter; @@ -95,6 +96,7 @@ import com.android.inputmethod.research.ResearchLogger; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.List; import java.util.Locale; import java.util.concurrent.TimeUnit; @@ -122,7 +124,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private static final String SCHEME_PACKAGE = "package"; private final Settings mSettings; - private final DictionaryFacilitator mDictionaryFacilitator = new DictionaryFacilitator(); + private final DictionaryFacilitator mDictionaryFacilitator = + new DictionaryFacilitator(new DistracterFilter(this /* context */)); private final InputLogic mInputLogic = new InputLogic(this /* LatinIME */, this /* SuggestionStripViewAccessor */, mDictionaryFacilitator); // We expect to have only one decoder in almost all cases, hence the default capacity of 1. @@ -538,6 +541,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (!mHandler.hasPendingReopenDictionaries()) { resetSuggestForLocale(locale); } + mDictionaryFacilitator.updateEnabledSubtypes(mRichImm.getMyEnabledInputMethodSubtypeList( + true /* allowsImplicitlySelectedSubtypes */)); refreshPersonalizationDictionarySession(); StatsUtils.onLoadSettings(currentSettingsValues); } @@ -564,9 +569,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen PersonalizationHelper.removeAllPersonalizationDictionaries(this); PersonalizationDictionarySessionRegistrar.resetAll(this); } else { - final DistracterFilter distracterFilter = createDistracterFilter(); - PersonalizationDictionarySessionRegistrar.init( - this, mDictionaryFacilitator, distracterFilter); + PersonalizationDictionarySessionRegistrar.init(this, mDictionaryFacilitator); } } @@ -660,9 +663,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mInputLogic.mConnection.finishComposingText(); mInputLogic.mConnection.endBatchEdit(); } - final DistracterFilter distracterFilter = createDistracterFilter(); PersonalizationDictionarySessionRegistrar.onConfigurationChanged(this, conf, - mDictionaryFacilitator, distracterFilter); + mDictionaryFacilitator); super.onConfigurationChanged(conf); } @@ -1739,11 +1741,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } @UsedForTesting - /* package for test */ DistracterFilter createDistracterFilter() { - // Return an empty distracter filter when this method is called before onCreate(). - return (mRichImm != null) ? new DistracterFilter(this /* Context */, - mRichImm.getMyEnabledInputMethodSubtypeList( - true /* allowsImplicitlySelectedSubtypes */)) : new DistracterFilter(); + /* package for test */ List getEnabledSubtypesForTest() { + return (mRichImm != null) ? mRichImm.getMyEnabledInputMethodSubtypeList( + true /* allowsImplicitlySelectedSubtypes */) : new ArrayList(); } public void dumpDictionaryForDebug(final String dictName) { diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java new file mode 100644 index 000000000..9d72de8c5 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java @@ -0,0 +1,37 @@ +/* + * 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.personalization; + +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +public class PersonalizationDataChunk { + public final boolean mInputByUser; + public final List mTokens; + public final int mTimestampInSeconds; + public final String mPackageName; + public final Locale mlocale = null; + + public PersonalizationDataChunk(boolean inputByUser, final List tokens, + final int timestampInSeconds, final String packageName) { + mInputByUser = inputByUser; + mTokens = Collections.unmodifiableList(tokens); + mTimestampInSeconds = timestampInSeconds; + mPackageName = packageName; + } +} diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java index 805f4220f..450644032 100644 --- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java +++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java @@ -20,17 +20,14 @@ import android.content.Context; import android.content.res.Configuration; import com.android.inputmethod.latin.DictionaryFacilitator; -import com.android.inputmethod.latin.utils.DistracterFilter; public class PersonalizationDictionarySessionRegistrar { public static void init(final Context context, - final DictionaryFacilitator dictionaryFacilitator, - final DistracterFilter distracterFilter) { + final DictionaryFacilitator dictionaryFacilitator) { } public static void onConfigurationChanged(final Context context, final Configuration conf, - final DictionaryFacilitator dictionaryFacilitator, - final DistracterFilter distracterFilter) { + final DictionaryFacilitator dictionaryFacilitator) { } public static void onUpdateData(final Context context, final String type) { diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java index 19a01eea8..331bc505c 100644 --- a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java +++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java @@ -16,7 +16,6 @@ package com.android.inputmethod.latin.utils; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -57,6 +56,7 @@ public class DistracterFilter { private final DictionaryFacilitator mDictionaryFacilitator; private final Suggest mSuggest; private Keyboard mKeyboard; + private final Object mLock = new Object(); // If the score of the top suggestion exceeds this value, the tested word (e.g., // an OOV, a misspelling, or an in-vocabulary word) would be considered as a distracter to @@ -67,35 +67,59 @@ public class DistracterFilter { // Create empty distracter filter. public DistracterFilter() { - this(null, new ArrayList()); + mContext = null; + mLocaleToSubtypeMap = new HashMap<>(); + mLocaleToKeyboardMap = new HashMap<>(); + // TODO: Quit assigning null. + mDictionaryFacilitator = null; + mSuggest = null; + mKeyboard = null; } /** * Create a DistracterFilter instance. * * @param context the context. - * @param enabledSubtypes the enabled subtypes. */ - public DistracterFilter(final Context context, final List enabledSubtypes) { + public DistracterFilter(final Context context) { mContext = context; mLocaleToSubtypeMap = new HashMap<>(); - if (enabledSubtypes != null) { - for (final InputMethodSubtype subtype : enabledSubtypes) { - final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype); - if (mLocaleToSubtypeMap.containsKey(locale)) { - // Multiple subtypes are enabled for one locale. - // TODO: Investigate what we should do for this case. - continue; - } - mLocaleToSubtypeMap.put(locale, subtype); - } - } mLocaleToKeyboardMap = new HashMap<>(); mDictionaryFacilitator = new DictionaryFacilitator(); mSuggest = new Suggest(mDictionaryFacilitator); mKeyboard = null; } + public void close() { + if (mDictionaryFacilitator != null) { + mDictionaryFacilitator.closeDictionaries(); + } + } + + public void updateEnabledSubtypes(final List enabledSubtypes) { + final Map newLocaleToSubtypeMap = new HashMap<>(); + if (enabledSubtypes != null) { + for (final InputMethodSubtype subtype : enabledSubtypes) { + final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype); + if (newLocaleToSubtypeMap.containsKey(locale)) { + // Multiple subtypes are enabled for one locale. + // TODO: Investigate what we should do for this case. + continue; + } + newLocaleToSubtypeMap.put(locale, subtype); + } + } + if (mLocaleToSubtypeMap.equals(newLocaleToSubtypeMap)) { + // Enabled subtypes have not been changed. + return; + } + synchronized (mLock) { + mLocaleToSubtypeMap.clear(); + mLocaleToSubtypeMap.putAll(newLocaleToSubtypeMap); + mLocaleToKeyboardMap.clear(); + } + } + private static boolean suggestionExceedsDistracterThreshold( final SuggestedWordInfo suggestion, final String consideredWord, final float distracterThreshold) { @@ -116,7 +140,10 @@ public class DistracterFilter { mKeyboard = cachedKeyboard; return; } - final InputMethodSubtype subtype = mLocaleToSubtypeMap.get(newLocale); + final InputMethodSubtype subtype; + synchronized (mLock) { + subtype = mLocaleToSubtypeMap.get(newLocale); + } if (subtype == null) { return; } @@ -153,22 +180,25 @@ public class DistracterFilter { */ public boolean isDistracterToWordsInDictionaries(final PrevWordsInfo prevWordsInfo, final String testedWord, final Locale locale) { - if (locale == null) { + if (mSuggest == null || locale == null) { return false; } if (!locale.equals(mDictionaryFacilitator.getLocale())) { - 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 { - loadDictionariesForLocale(locale); - } catch (final InterruptedException e) { - Log.e(TAG, "Interrupted while waiting for loading dicts in DistracterFilter", e); - return false; + 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 { + loadDictionariesForLocale(locale); + } catch (final InterruptedException e) { + Log.e(TAG, "Interrupted while waiting for loading dicts in DistracterFilter", + e); + return false; + } } } if (mKeyboard == null) { diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java index 36543cca6..430efdd19 100644 --- a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java +++ b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java @@ -24,6 +24,7 @@ import com.android.inputmethod.latin.PrevWordsInfo; import com.android.inputmethod.latin.settings.SpacingAndPunctuations; import java.util.ArrayList; +import java.util.List; import java.util.Locale; // Note: this class is used as a parameter type of a native method. You should be careful when you @@ -79,7 +80,7 @@ public final class LanguageModelParam { // Process a list of words and return a list of {@link LanguageModelParam} objects. public static ArrayList createLanguageModelParamsFrom( - final ArrayList tokens, final int timestamp, + final List tokens, final int timestamp, final DictionaryFacilitator dictionaryFacilitator, final SpacingAndPunctuations spacingAndPunctuations, final DistracterFilter distracterFilter) { diff --git a/tests/src/com/android/inputmethod/latin/DistracterFilterTest.java b/tests/src/com/android/inputmethod/latin/DistracterFilterTest.java index e98f9eacc..5631f97eb 100644 --- a/tests/src/com/android/inputmethod/latin/DistracterFilterTest.java +++ b/tests/src/com/android/inputmethod/latin/DistracterFilterTest.java @@ -32,7 +32,8 @@ public class DistracterFilterTest extends InputTestsBase { @Override protected void setUp() throws Exception { super.setUp(); - mDistracterFilter = mLatinIME.createDistracterFilter(); + mDistracterFilter = new DistracterFilter(getContext()); + mDistracterFilter.updateEnabledSubtypes(mLatinIME.getEnabledSubtypesForTest()); } public void testIsDistractorToWordsInDictionaries() {