Create a method to add multiple dictionary entries.

Bug: 11740462
Change-Id: I7903cb02fd08d649a05b8799fb3cd00c3da26e00
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index d059cc8..a0e4e54 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -288,7 +288,7 @@
     private void runGCIfRequiredInternalLocked(final boolean mindsBlockByGC) {
         // Calls to needsToRunGC() need to be serialized.
         if (mBinaryDictionary.needsToRunGC(mindsBlockByGC)) {
-            if (setIsRegeneratingIfNotRegenerating()) {
+            if (setProcessingLargeTaskIfNot()) {
                 // Run GC after currently existing time sensitive operations.
                 getExecutor(mFilename).executePrioritized(new Runnable() {
                     @Override
@@ -296,7 +296,7 @@
                         try {
                             mBinaryDictionary.flushWithGC();
                         } finally {
-                            mFilenameDictionaryUpdateController.mIsRegenerating.set(false);
+                            mFilenameDictionaryUpdateController.mProcessingLargeTask.set(false);
                         }
                     }
                 });
@@ -359,13 +359,76 @@
         });
     }
 
+    public interface AddMultipleDictionaryEntriesCallback {
+        public void onFinished();
+    }
+
+    public static class LanguageModelParam {
+        public final String mWord0;
+        public final String mWord1;
+        public final boolean mIsValid;
+        public final int mFrequency;
+        public final int mBigramFrequency;
+        public LanguageModelParam(final String word0, final String word1, final boolean isValid,
+                final int frequency, final int bigramFrequency) {
+            mWord0 = word0;
+            mWord1 = word1;
+            mIsValid = isValid;
+            mFrequency = frequency;
+            mBigramFrequency = bigramFrequency;
+        }
+    }
+
+    /**
+     * Dynamically add multiple entries to the dictionary.
+     */
+    protected void addMultipleDictionaryEntriesDynamically(
+            final ArrayList<LanguageModelParam> languageModelParams,
+            final AddMultipleDictionaryEntriesCallback callback) {
+        if (!mIsUpdatable) {
+            Log.w(TAG, "addMultipleDictionaryEntriesDynamically is called for non-updatable " +
+                    "dictionary: " + mFilename);
+            return;
+        }
+        getExecutor(mFilename).execute(new Runnable() {
+            @Override
+            public void run() {
+                final boolean locked = setProcessingLargeTaskIfNot();
+                try {
+                    for (final LanguageModelParam languageModelParam : languageModelParams) {
+                        if (languageModelParam.mWord1 == null) {
+                            continue;
+                        }
+                        if (mBinaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) {
+                            mBinaryDictionary.flushWithGC();
+                        }
+                        mBinaryDictionary.addUnigramWord(languageModelParam.mWord1,
+                                languageModelParam.mFrequency);
+                        if (languageModelParam.mWord0 != null
+                                && !languageModelParam.mWord0.equals(languageModelParam.mWord1)) {
+                            mBinaryDictionary.addBigramWords(languageModelParam.mWord0,
+                                    languageModelParam.mWord1, languageModelParam.mBigramFrequency);
+                        }
+                    }
+                } finally {
+                    if (callback != null) {
+                        callback.onFinished();
+                    }
+                    if (locked) {
+                        mFilenameDictionaryUpdateController.mProcessingLargeTask.set(false);
+                    }
+                }
+            }
+        });
+    }
+
     @Override
     public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
             final String prevWord, final ProximityInfo proximityInfo,
             final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
             final int sessionId) {
         reloadDictionaryIfRequired();
-        if (isRegenerating()) {
+        if (processingLargeTask()) {
             return null;
         }
         final AsyncResultHolder<ArrayList<SuggestedWordInfo>> holder =
@@ -402,7 +465,7 @@
     }
 
     protected boolean isValidWordInner(final String word) {
-        if (isRegenerating()) {
+        if (processingLargeTask()) {
             return false;
         }
         final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>();
@@ -524,7 +587,7 @@
      */
     public final void reloadDictionaryIfRequired() {
         if (!isReloadRequired()) return;
-        if (setIsRegeneratingIfNotRegenerating()) {
+        if (setProcessingLargeTaskIfNot()) {
             reloadDictionary();
         }
     }
@@ -536,13 +599,14 @@
         return mBinaryDictionary == null || mPerInstanceDictionaryUpdateController.isOutOfDate();
     }
 
-    private boolean isRegenerating() {
-        return mFilenameDictionaryUpdateController.mIsRegenerating.get();
+    private boolean processingLargeTask() {
+        return mFilenameDictionaryUpdateController.mProcessingLargeTask.get();
     }
 
-    // Returns whether the dictionary can be regenerated.
-    private boolean setIsRegeneratingIfNotRegenerating() {
-        return mFilenameDictionaryUpdateController.mIsRegenerating.compareAndSet(
+    // Returns whether the dictionary is being used for a large task. If true, we should not use
+    // this dictionary for latency sensitive operations.
+    private boolean setProcessingLargeTaskIfNot() {
+        return mFilenameDictionaryUpdateController.mProcessingLargeTask.compareAndSet(
                 false /* expect */ , true /* update */);
     }
 
@@ -592,7 +656,7 @@
                     }
                     mPerInstanceDictionaryUpdateController.mLastUpdateTime = time;
                 } finally {
-                    mFilenameDictionaryUpdateController.mIsRegenerating.set(false);
+                    mFilenameDictionaryUpdateController.mProcessingLargeTask.set(false);
                 }
             }
         });
@@ -619,13 +683,13 @@
     }
 
     /**
-     * For tracking whether the dictionary is out of date and the dictionary is regenerating.
-     * Can be shared across multiple dictionary instances that access the same filename.
+     * For tracking whether the dictionary is out of date and the dictionary is used in a large
+     * task. Can be shared across multiple dictionary instances that access the same filename.
      */
     private static class DictionaryUpdateController {
         public volatile long mLastUpdateTime = 0;
         public volatile long mLastUpdateRequestTime = 0;
-        public volatile AtomicBoolean mIsRegenerating = new AtomicBoolean();
+        public volatile AtomicBoolean mProcessingLargeTask = new AtomicBoolean();
 
         public boolean isOutOfDate() {
             return (mLastUpdateRequestTime > mLastUpdateTime);
@@ -666,7 +730,7 @@
                     mBinaryDictionary.flushWithGC();
                     r.run();
                 } finally {
-                    mFilenameDictionaryUpdateController.mIsRegenerating.set(false);
+                    mFilenameDictionaryUpdateController.mProcessingLargeTask.set(false);
                 }
             }
         });
diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
index e7a25d2..42bd760 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
@@ -113,6 +113,18 @@
         return false;
     }
 
+    public void addMultipleDictionaryEntriesToDictionary(
+            final ArrayList<LanguageModelParam> languageModelParams,
+            final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) {
+        if (languageModelParams == null || languageModelParams.isEmpty()) {
+            if (callback != null) {
+                callback.onFinished();
+            }
+            return;
+        }
+        addMultipleDictionaryEntriesDynamically(languageModelParams, callback);
+    }
+
     /**
      * Pair will be added to the decaying dictionary.
      *
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
index a86f6e5..1f46f5b 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
@@ -18,6 +18,8 @@
 
 import android.content.Context;
 
+import com.android.inputmethod.latin.ExpandableBinaryDictionary;
+
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 
@@ -113,6 +115,20 @@
         dictionary.addToDictionary(word0, word1, isValid);
     }
 
+    // TODO: Support multi locale.
+    public void addMultipleDictionaryEntriesToPersonalizationDictionary(
+            final ArrayList<ExpandableBinaryDictionary.LanguageModelParam> languageModelParams,
+            final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) {
+        final DecayingExpandableBinaryDictionaryBase dictionary = getPredictionDictionary();
+        if (dictionary == null) {
+            if (callback != null) {
+                callback.onFinished();
+            }
+            return;
+        }
+        dictionary.addMultipleDictionaryEntriesToDictionary(languageModelParams, callback);
+    }
+
     // Bulk import
     // TODO: Support multi locale to add bigram
     public void addBigramsToPersonalizationDictionary(