From 1fa3e9044f4bb7d1eb62118c94a2223af69c14b0 Mon Sep 17 00:00:00 2001
From: Keisuke Kuroyanagi <ksk@google.com>
Date: Fri, 23 May 2014 16:10:36 +0900
Subject: [PATCH] Move distracter filter to dictionary facilitator.

Bug: 13142176
Bug: 15094186
Change-Id: Ib9e42f8e25538009f3ba62cf1e034cae3f6823c7
---
 .../latin/DictionaryFacilitator.java          | 30 ++++++-
 .../android/inputmethod/latin/LatinIME.java   | 22 +++---
 .../PersonalizationDataChunk.java             | 37 +++++++++
 ...onalizationDictionarySessionRegistrar.java |  7 +-
 .../latin/utils/DistracterFilter.java         | 78 +++++++++++++------
 .../latin/utils/LanguageModelParam.java       |  3 +-
 .../latin/DistracterFilterTest.java           |  3 +-
 7 files changed, 135 insertions(+), 45 deletions(-)
 create mode 100644 java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java

diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
index a979167338..0f410d5742 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<InputMethodSubtype> 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<LanguageModelParam> languageModelParams,
+    public void addEntriesToPersonalizationDictionary(
+            final PersonalizationDataChunk personalizationDataChunk,
+            final SpacingAndPunctuations spacingAndPunctuations,
             final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) {
+        final ArrayList<LanguageModelParam> 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 4863326ab2..5fc40585a3 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<InputMethodSubtype> getEnabledSubtypesForTest() {
+        return (mRichImm != null) ? mRichImm.getMyEnabledInputMethodSubtypeList(
+                true /* allowsImplicitlySelectedSubtypes */) : new ArrayList<InputMethodSubtype>();
     }
 
     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 0000000000..9d72de8c53
--- /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<String> mTokens;
+    public final int mTimestampInSeconds;
+    public final String mPackageName;
+    public final Locale mlocale = null;
+
+    public PersonalizationDataChunk(boolean inputByUser, final List<String> 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 805f4220f3..4506440329 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 19a01eea83..331bc505c4 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,33 +67,57 @@ public class DistracterFilter {
 
     // Create empty distracter filter.
     public DistracterFilter() {
-        this(null, new ArrayList<InputMethodSubtype>());
+        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<InputMethodSubtype> enabledSubtypes) {
+    public DistracterFilter(final Context context) {
         mContext = context;
         mLocaleToSubtypeMap = new HashMap<>();
+        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<InputMethodSubtype> enabledSubtypes) {
+        final Map<Locale, InputMethodSubtype> newLocaleToSubtypeMap = new HashMap<>();
         if (enabledSubtypes != null) {
             for (final InputMethodSubtype subtype : enabledSubtypes) {
                 final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype);
-                if (mLocaleToSubtypeMap.containsKey(locale)) {
+                if (newLocaleToSubtypeMap.containsKey(locale)) {
                     // Multiple subtypes are enabled for one locale.
                     // TODO: Investigate what we should do for this case.
                     continue;
                 }
-                mLocaleToSubtypeMap.put(locale, subtype);
+                newLocaleToSubtypeMap.put(locale, subtype);
             }
         }
-        mLocaleToKeyboardMap = new HashMap<>();
-        mDictionaryFacilitator = new DictionaryFacilitator();
-        mSuggest = new Suggest(mDictionaryFacilitator);
-        mKeyboard = null;
+        if (mLocaleToSubtypeMap.equals(newLocaleToSubtypeMap)) {
+            // Enabled subtypes have not been changed.
+            return;
+        }
+        synchronized (mLock) {
+            mLocaleToSubtypeMap.clear();
+            mLocaleToSubtypeMap.putAll(newLocaleToSubtypeMap);
+            mLocaleToKeyboardMap.clear();
+        }
     }
 
     private static boolean suggestionExceedsDistracterThreshold(
@@ -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 36543cca68..430efdd19c 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<LanguageModelParam> createLanguageModelParamsFrom(
-            final ArrayList<String> tokens, final int timestamp,
+            final List<String> 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 e98f9eacc9..5631f97eb1 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() {
-- 
GitLab