Move dict operations to Suggest.

Bug: 8187060

Change-Id: Iabe13abcd43b381360f7080b3f4ee0533f246160
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 54c6543..0494dc1 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -128,8 +128,6 @@
     private final SubtypeSwitcher mSubtypeSwitcher;
     private final SubtypeState mSubtypeState = new SubtypeState();
 
-    private UserBinaryDictionary mUserDictionary;
-
     // Object for reacting to adding/removing a dictionary pack.
     private BroadcastReceiver mDictionaryPackInstallReceiver =
             new DictionaryPackInstallBroadcastReceiver(this);
@@ -518,6 +516,7 @@
         final SettingsValues currentSettingsValues = mSettings.getCurrent();
         if (!mHandler.hasPendingReopenDictionaries() && mInputLogic.mSuggest != null) {
             // May need to reset dictionaries depending on the user settings.
+            // TODO: Quit setting dictionaries from LatinIME.
             mInputLogic.mSuggest.setAdditionalDictionaries(mInputLogic.mSuggest /* oldSuggest */,
                    currentSettingsValues);
         }
@@ -565,9 +564,7 @@
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.getInstance().initSuggest(newSuggest);
         }
-
-        mUserDictionary = new UserBinaryDictionary(this, subtypeLocale);
-        newSuggest.setUserDictionary(mUserDictionary);
+        // TODO: Quit setting dictionaries from LatinIME.
         newSuggest.setAdditionalDictionaries(mInputLogic.mSuggest /* oldSuggest */,
                 mSettings.getCurrent());
         final Suggest oldSuggest = mInputLogic.mSuggest;
@@ -1208,7 +1205,7 @@
         } else {
             wordToEdit = word;
         }
-        mUserDictionary.addWordToUserDictionary(wordToEdit);
+        mInputLogic.mSuggest.addWordToUserDictionary(wordToEdit);
     }
 
     public void displaySettingsDialog() {
@@ -1739,13 +1736,13 @@
                         || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind)
                         && suggest != null
                         // If the suggestion is not in the dictionary, the hint should be shown.
-                        && !AutoCorrectionUtils.isValidWord(suggest, suggestion, true);
+                        && !suggest.isValidWord(suggestion, true);
 
         if (currentSettings.mIsInternal) {
             LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE,
                     Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
         }
-        if (showingAddToDictionaryHint && mUserDictionary.mEnabled) {
+        if (showingAddToDictionaryHint && suggest.isUserDictionaryEnabled()) {
             mSuggestionStripView.showAddToDictionaryHint(
                     suggestion, currentSettings.mHintToSaveText);
         } else {
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 32ab1f3..118242a 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -39,11 +39,13 @@
 import java.util.HashSet;
 import java.util.Locale;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
 
 /**
  * This class loads a dictionary and provides a list of suggestions for a given sequence of
  * characters. This includes corrections and completions.
  */
+// TODO: Separate dictionary operations from suggestions handling logic.
 public final class Suggest {
     public static final String TAG = Suggest.class.getSimpleName();
 
@@ -74,6 +76,7 @@
     private HashSet<String> mOnlyDictionarySetForDebug = null;
     private Dictionary mMainDictionary;
     private ContactsBinaryDictionary mContactsDictionary;
+    private UserBinaryDictionary mUserDictionary;
     private UserHistoryDictionary mUserHistoryDictionary;
     private PersonalizationDictionary mPersonalizationDictionary;
     @UsedForTesting
@@ -98,6 +101,7 @@
             mOnlyDictionarySetForDebug = new HashSet<String>();
             mOnlyDictionarySetForDebug.add(Dictionary.TYPE_PERSONALIZATION);
         }
+        setUserDictionary(new UserBinaryDictionary(context, locale));
     }
 
     @UsedForTesting
@@ -171,27 +175,13 @@
         return mMainDictionary;
     }
 
-    public ContactsBinaryDictionary getContactsDictionary() {
-        return mContactsDictionary;
-    }
-
-    public UserHistoryDictionary getUserHistoryDictionary() {
-        return mUserHistoryDictionary;
-    }
-
-    public PersonalizationDictionary getPersonalizationDictionary() {
-        return mPersonalizationDictionary;
-    }
-
-    public ConcurrentHashMap<String, Dictionary> getUnigramDictionaries() {
-        return mDictionaries;
-    }
-
     /**
      * Sets an optional user dictionary resource to be loaded. The user dictionary is consulted
      * before the main dictionary, if set. This refers to the system-managed user dictionary.
      */
+    @UsedForTesting
     public void setUserDictionary(final UserBinaryDictionary userDictionary) {
+        mUserDictionary = userDictionary;
         addOrReplaceDictionaryInternal(Dictionary.TYPE_USER, userDictionary);
     }
 
@@ -200,17 +190,18 @@
      * the contacts dictionary by passing null to this method. In this case no contacts dictionary
      * won't be used.
      */
+    @UsedForTesting
     public void setContactsDictionary(final ContactsBinaryDictionary contactsDictionary) {
         mContactsDictionary = contactsDictionary;
         addOrReplaceDictionaryInternal(Dictionary.TYPE_CONTACTS, contactsDictionary);
     }
 
-    public void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) {
+    private void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) {
         mUserHistoryDictionary = userHistoryDictionary;
         addOrReplaceDictionaryInternal(Dictionary.TYPE_USER_HISTORY, userHistoryDictionary);
     }
 
-    public void setPersonalizationDictionary(
+    private void setPersonalizationDictionary(
             final PersonalizationDictionary personalizationDictionary) {
         mPersonalizationDictionary = personalizationDictionary;
         addOrReplaceDictionaryInternal(Dictionary.TYPE_PERSONALIZATION, personalizationDictionary);
@@ -225,7 +216,7 @@
     public void setAdditionalDictionaries(final Suggest oldSuggest,
             final SettingsValues settingsValues) {
         // Contacts dictionary
-        resetContactsDictionary(null != oldSuggest ? oldSuggest.getContactsDictionary() : null,
+        resetContactsDictionary(null != oldSuggest ? oldSuggest.mContactsDictionary : null,
                 settingsValues);
         // User history dictionary & Personalization dictionary
         resetPersonalizedDictionaries(oldSuggest, settingsValues);
@@ -245,9 +236,9 @@
         final boolean shouldSetDictionaries = settingsValues.mUsePersonalizedDicts;
 
         final UserHistoryDictionary oldUserHistoryDictionary = (null == oldSuggest) ? null :
-                oldSuggest.getUserHistoryDictionary();
+                oldSuggest.mUserHistoryDictionary;
         final PersonalizationDictionary oldPersonalizationDictionary = (null == oldSuggest) ? null :
-                oldSuggest.getPersonalizationDictionary();
+                oldSuggest.mPersonalizationDictionary;
         final UserHistoryDictionary userHistoryDictionaryToUse;
         final PersonalizationDictionary personalizationDictionaryToUse;
         if (!shouldSetDictionaries) {
@@ -311,6 +302,43 @@
         setContactsDictionary(dictionaryToUse);
     }
 
+    public boolean isUserDictionaryEnabled() {
+        if (mUserDictionary == null) {
+            return false;
+        }
+        return mUserDictionary.mEnabled;
+    }
+
+    public void addWordToUserDictionary(String word) {
+        if (mUserDictionary == null) {
+            return;
+        }
+        mUserDictionary.addWordToUserDictionary(word);
+    }
+
+    public String addToUserHistory(final WordComposer wordComposer, final String previousWord,
+            final String suggestion) {
+        if (mUserHistoryDictionary == null) {
+            return null;
+        }
+        final String secondWord;
+        if (wordComposer.wasAutoCapitalized() && !wordComposer.isMostlyCaps()) {
+            secondWord = suggestion.toLowerCase(mLocale);
+        } else {
+            secondWord = suggestion;
+        }
+        // We demote unrecognized words (frequency < 0, below) by specifying them as "invalid".
+        // We don't add words with 0-frequency (assuming they would be profanity etc.).
+        final int maxFreq = getMaxFrequency(suggestion);
+        if (maxFreq == 0) {
+            return null;
+        }
+        final boolean isValid = maxFreq > 0;
+        final int timeStamp = (int)TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis()));
+        mUserHistoryDictionary.addToDictionary(previousWord, secondWord, isValid, timeStamp);
+        return previousWord;
+    }
+
     public void cancelAddingUserHistory(final String previousWord, final String committedWord) {
         if (mUserHistoryDictionary != null) {
             mUserHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord);
@@ -389,8 +417,8 @@
         // or if it's a 2+ characters non-word (i.e. it's not in the dictionary).
         final boolean allowsToBeAutoCorrected = (null != whitelistedWord
                 && !whitelistedWord.equals(consideredWord))
-                || (consideredWord.length() > 1 && !AutoCorrectionUtils.isValidWord(this,
-                        consideredWord, wordComposer.isFirstCharCapitalized()));
+                || (consideredWord.length() > 1
+                        && !isValidWord(consideredWord, wordComposer.isFirstCharCapitalized()));
 
         final boolean hasAutoCorrection;
         // TODO: using isCorrectionEnabled here is not very good. It's probably useless, because
@@ -594,6 +622,45 @@
                 wordInfo.mAutoCommitFirstWordConfidence);
     }
 
+    public boolean isValidWord(final String word, final boolean ignoreCase) {
+        if (TextUtils.isEmpty(word)) {
+            return false;
+        }
+        final String lowerCasedWord = word.toLowerCase(mLocale);
+        for (final String key : mDictionaries.keySet()) {
+            final Dictionary dictionary = mDictionaries.get(key);
+            // It's unclear how realistically 'dictionary' can be null, but the monkey is somehow
+            // managing to get null in here. Presumably the language is changing to a language with
+            // no main dictionary and the monkey manages to type a whole word before the thread
+            // that reads the dictionary is started or something?
+            // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
+            // would be immutable once it's finished initializing, but concretely a null test is
+            // probably good enough for the time being.
+            if (null == dictionary) continue;
+            if (dictionary.isValidWord(word)
+                    || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private int getMaxFrequency(final String word) {
+        if (TextUtils.isEmpty(word)) {
+            return Dictionary.NOT_A_PROBABILITY;
+        }
+        int maxFreq = -1;
+        for (final String key : mDictionaries.keySet()) {
+            final Dictionary dictionary = mDictionaries.get(key);
+            if (null == dictionary) continue;
+            final int tempFreq = dictionary.getFrequency(word);
+            if (tempFreq >= maxFreq) {
+                maxFreq = tempFreq;
+            }
+        }
+        return maxFreq;
+    }
+
     public void close() {
         final HashSet<Dictionary> dictionaries = CollectionUtils.newHashSet();
         dictionaries.addAll(mDictionaries.values());
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index df34687..0686ff6 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -44,12 +44,10 @@
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.WordComposer;
 import com.android.inputmethod.latin.define.ProductionFlag;
-import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
 import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.settings.SettingsValues;
 import com.android.inputmethod.latin.suggestions.SuggestionStripView;
 import com.android.inputmethod.latin.utils.AsyncResultHolder;
-import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.InputTypeUtils;
 import com.android.inputmethod.latin.utils.LatinImeLoggerUtils;
@@ -60,7 +58,6 @@
 
 import java.util.ArrayList;
 import java.util.TreeSet;
-import java.util.concurrent.TimeUnit;
 
 /**
  * This class manages the input logic.
@@ -977,24 +974,8 @@
         final Suggest suggest = mSuggest;
         if (suggest == null) return null;
 
-        final UserHistoryDictionary userHistoryDictionary = suggest.getUserHistoryDictionary();
-        if (userHistoryDictionary == null) return null;
-
         final String prevWord = mConnection.getNthPreviousWord(settingsValues, 2);
-        final String secondWord;
-        if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
-            secondWord = suggestion.toLowerCase(settingsValues.mLocale);
-        } else {
-            secondWord = suggestion;
-        }
-        // We demote unrecognized words (frequency < 0, below) by specifying them as "invalid".
-        // We don't add words with 0-frequency (assuming they would be profanity etc.).
-        final int maxFreq = AutoCorrectionUtils.getMaxFrequency(
-                suggest.getUnigramDictionaries(), suggestion);
-        if (maxFreq == 0) return null;
-        userHistoryDictionary.addToDictionary(prevWord, secondWord, maxFreq > 0,
-                (int)TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis())));
-        return prevWord;
+        return suggest.addToUserHistory(mWordComposer, prevWord, suggestion);
     }
 
     public void performUpdateSuggestionStripSync(final SettingsValues settingsValues,
diff --git a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
index 066c5fd..37c173f 100644
--- a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
@@ -17,16 +17,11 @@
 package com.android.inputmethod.latin.utils;
 
 import com.android.inputmethod.latin.BinaryDictionary;
-import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.Suggest;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 
-import android.text.TextUtils;
 import android.util.Log;
 
-import java.util.concurrent.ConcurrentHashMap;
-
 public final class AutoCorrectionUtils {
     private static final boolean DBG = LatinImeLogger.sDBG;
     private static final String TAG = AutoCorrectionUtils.class.getSimpleName();
@@ -36,48 +31,6 @@
         // Purely static class: can't instantiate.
     }
 
-    public static boolean isValidWord(final Suggest suggest, final String word,
-            final boolean ignoreCase) {
-        if (TextUtils.isEmpty(word)) {
-            return false;
-        }
-        final ConcurrentHashMap<String, Dictionary> dictionaries = suggest.getUnigramDictionaries();
-        final String lowerCasedWord = word.toLowerCase(suggest.mLocale);
-        for (final String key : dictionaries.keySet()) {
-            final Dictionary dictionary = dictionaries.get(key);
-            // It's unclear how realistically 'dictionary' can be null, but the monkey is somehow
-            // managing to get null in here. Presumably the language is changing to a language with
-            // no main dictionary and the monkey manages to type a whole word before the thread
-            // that reads the dictionary is started or something?
-            // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
-            // would be immutable once it's finished initializing, but concretely a null test is
-            // probably good enough for the time being.
-            if (null == dictionary) continue;
-            if (dictionary.isValidWord(word)
-                    || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public static int getMaxFrequency(final ConcurrentHashMap<String, Dictionary> dictionaries,
-            final String word) {
-        if (TextUtils.isEmpty(word)) {
-            return Dictionary.NOT_A_PROBABILITY;
-        }
-        int maxFreq = -1;
-        for (final String key : dictionaries.keySet()) {
-            final Dictionary dictionary = dictionaries.get(key);
-            if (null == dictionary) continue;
-            final int tempFreq = dictionary.getFrequency(word);
-            if (tempFreq >= maxFreq) {
-                maxFreq = tempFreq;
-            }
-        }
-        return maxFreq;
-    }
-
     public static boolean suggestionExceedsAutoCorrectionThreshold(
             final SuggestedWordInfo suggestion, final String consideredWord,
             final float autoCorrectionThreshold) {