aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
authorKeisuke Kuroyanagi <ksk@google.com>2013-12-26 20:28:37 +0900
committerKeisuke Kuroyanagi <ksk@google.com>2013-12-26 20:48:19 +0900
commita374482719ef9f38395e7e39884433cc72e9cdb7 (patch)
treec4c2a9a6e6e83fc2cc52e3e3c751b3b456655933 /java/src
parent5c45ff12140c801448d0bef1991d803edc674fb8 (diff)
downloadlatinime-a374482719ef9f38395e7e39884433cc72e9cdb7.tar.gz
latinime-a374482719ef9f38395e7e39884433cc72e9cdb7.tar.xz
latinime-a374482719ef9f38395e7e39884433cc72e9cdb7.zip
Extract dict operations from Suggest to a new class.
Bug: 8187060 Change-Id: I77775aa50763158d99753c2312fa11fe14267aef
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java376
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java29
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java343
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java5
-rw-r--r--java/src/com/android/inputmethod/research/MainLogBuffer.java16
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLogger.java17
6 files changed, 426 insertions, 360 deletions
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
new file mode 100644
index 000000000..b46489185
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2013 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.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.Suggest.SuggestInitializationListener;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
+import com.android.inputmethod.latin.personalization.PersonalizationHelper;
+import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
+import com.android.inputmethod.latin.settings.SettingsValues;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+// TODO: Consolidate dictionaries in native code.
+public class DictionaryFacilitatorForSuggest {
+ public static final String TAG = DictionaryFacilitatorForSuggest.class.getSimpleName();
+
+ private final Context mContext;
+ private final Locale mLocale;
+
+ private final ConcurrentHashMap<String, Dictionary> mDictionaries =
+ CollectionUtils.newConcurrentHashMap();
+ private HashSet<String> mOnlyDictionarySetForDebug = null;
+
+ private Dictionary mMainDictionary;
+ private ContactsBinaryDictionary mContactsDictionary;
+ private UserBinaryDictionary mUserDictionary;
+ private UserHistoryDictionary mUserHistoryDictionary;
+ private PersonalizationDictionary mPersonalizationDictionary;
+
+ @UsedForTesting
+ private boolean mIsCurrentlyWaitingForMainDictionary = false;
+
+ public DictionaryFacilitatorForSuggest(final Context context, final Locale locale,
+ final SettingsValues settingsValues, final SuggestInitializationListener listener) {
+ resetMainDict(context, locale, listener);
+ mContext = context;
+ mLocale = locale;
+ // initialize a debug flag for the personalization
+ if (settingsValues.mUseOnlyPersonalizationDictionaryForDebug) {
+ mOnlyDictionarySetForDebug = new HashSet<String>();
+ mOnlyDictionarySetForDebug.add(Dictionary.TYPE_PERSONALIZATION);
+ }
+ setUserDictionary(new UserBinaryDictionary(context, locale));
+ }
+
+ @UsedForTesting
+ DictionaryFacilitatorForSuggest(final Context context, final AssetFileAddress[] dictionaryList,
+ final Locale locale) {
+ final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(dictionaryList,
+ false /* useFullEditDistance */, locale);
+ mContext = context;
+ mLocale = locale;
+ mMainDictionary = mainDict;
+ addOrReplaceDictionary(Dictionary.TYPE_MAIN, mainDict);
+ }
+
+ public void close() {
+ final HashSet<Dictionary> dictionaries = CollectionUtils.newHashSet();
+ dictionaries.addAll(mDictionaries.values());
+ for (final Dictionary dictionary : dictionaries) {
+ dictionary.close();
+ }
+ mMainDictionary = null;
+ mContactsDictionary = null;
+ mUserDictionary = null;
+ mUserHistoryDictionary = null;
+ mPersonalizationDictionary = null;
+ }
+
+ public void resetMainDict(final Context context, final Locale locale,
+ final SuggestInitializationListener listener) {
+ mIsCurrentlyWaitingForMainDictionary = true;
+ mMainDictionary = null;
+ if (listener != null) {
+ listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
+ }
+ new Thread("InitializeBinaryDictionary") {
+ @Override
+ public void run() {
+ final DictionaryCollection newMainDict =
+ DictionaryFactory.createMainDictionaryFromManager(context, locale);
+ setMainDictionary(newMainDict);
+ if (listener != null) {
+ listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
+ }
+ mIsCurrentlyWaitingForMainDictionary = false;
+ }
+ }.start();
+ }
+
+ // The main dictionary could have been loaded asynchronously. Don't cache the return value
+ // of this method.
+ public boolean hasMainDictionary() {
+ return null != mMainDictionary && mMainDictionary.isInitialized();
+ }
+
+ @UsedForTesting
+ public boolean isCurrentlyWaitingForMainDictionary() {
+ return mIsCurrentlyWaitingForMainDictionary;
+ }
+
+ public Dictionary getMainDictionary() {
+ return mMainDictionary;
+ }
+
+ private void setMainDictionary(final Dictionary mainDictionary) {
+ mMainDictionary = mainDictionary;
+ addOrReplaceDictionary(Dictionary.TYPE_MAIN, mainDictionary);
+ }
+
+ /**
+ * 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;
+ addOrReplaceDictionary(Dictionary.TYPE_USER, userDictionary);
+ }
+
+ /**
+ * Sets an optional contacts dictionary resource to be loaded. It is also possible to remove
+ * 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;
+ addOrReplaceDictionary(Dictionary.TYPE_CONTACTS, contactsDictionary);
+ }
+
+ private void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) {
+ mUserHistoryDictionary = userHistoryDictionary;
+ addOrReplaceDictionary(Dictionary.TYPE_USER_HISTORY, userHistoryDictionary);
+ }
+
+ private void setPersonalizationDictionary(
+ final PersonalizationDictionary personalizationDictionary) {
+ mPersonalizationDictionary = personalizationDictionary;
+ addOrReplaceDictionary(Dictionary.TYPE_PERSONALIZATION, personalizationDictionary);
+ }
+
+ /**
+ * Set dictionaries that can be turned off according to the user settings.
+ *
+ * @param oldDictionaryFacilitator the instance having old dictionaries
+ * @param settingsValues current SettingsValues
+ */
+ public void setAdditionalDictionaries(
+ final DictionaryFacilitatorForSuggest oldDictionaryFacilitator,
+ final SettingsValues settingsValues) {
+ // Contacts dictionary
+ resetContactsDictionary(null != oldDictionaryFacilitator ?
+ oldDictionaryFacilitator.mContactsDictionary : null, settingsValues);
+ // User history dictionary & Personalization dictionary
+ resetPersonalizedDictionaries(oldDictionaryFacilitator, settingsValues);
+ }
+
+ /**
+ * Set the user history dictionary and personalization dictionary according to the user
+ * settings.
+ *
+ * @param oldDictionaryFacilitator the instance that has been used
+ * @param settingsValues current settingsValues
+ */
+ // TODO: Consolidate resetPersonalizedDictionaries() and resetContactsDictionary(). Call up the
+ // new method for each dictionary.
+ private void resetPersonalizedDictionaries(
+ final DictionaryFacilitatorForSuggest oldDictionaryFacilitator,
+ final SettingsValues settingsValues) {
+ final boolean shouldSetDictionaries = settingsValues.mUsePersonalizedDicts;
+
+ final UserHistoryDictionary oldUserHistoryDictionary = (null == oldDictionaryFacilitator) ?
+ null : oldDictionaryFacilitator.mUserHistoryDictionary;
+ final PersonalizationDictionary oldPersonalizationDictionary =
+ (null == oldDictionaryFacilitator) ? null :
+ oldDictionaryFacilitator.mPersonalizationDictionary;
+ final UserHistoryDictionary userHistoryDictionaryToUse;
+ final PersonalizationDictionary personalizationDictionaryToUse;
+ if (!shouldSetDictionaries) {
+ userHistoryDictionaryToUse = null;
+ personalizationDictionaryToUse = null;
+ } else {
+ if (null != oldUserHistoryDictionary
+ && oldUserHistoryDictionary.mLocale.equals(mLocale)) {
+ userHistoryDictionaryToUse = oldUserHistoryDictionary;
+ } else {
+ userHistoryDictionaryToUse =
+ PersonalizationHelper.getUserHistoryDictionary(mContext, mLocale);
+ }
+ if (null != oldPersonalizationDictionary
+ && oldPersonalizationDictionary.mLocale.equals(mLocale)) {
+ personalizationDictionaryToUse = oldPersonalizationDictionary;
+ } else {
+ personalizationDictionaryToUse =
+ PersonalizationHelper.getPersonalizationDictionary(mContext, mLocale);
+ }
+ }
+ setUserHistoryDictionary(userHistoryDictionaryToUse);
+ setPersonalizationDictionary(personalizationDictionaryToUse);
+ }
+
+ /**
+ * Set the contacts dictionary according to the user settings.
+ *
+ * This method takes an optional contacts dictionary to use when the locale hasn't changed
+ * since the contacts dictionary can be opened or closed as necessary depending on the settings.
+ *
+ * @param oldContactsDictionary an optional dictionary to use, or null
+ * @param settingsValues current settingsValues
+ */
+ private void resetContactsDictionary(final ContactsBinaryDictionary oldContactsDictionary,
+ final SettingsValues settingsValues) {
+ final boolean shouldSetDictionary = settingsValues.mUseContactsDict;
+ final ContactsBinaryDictionary dictionaryToUse;
+ if (!shouldSetDictionary) {
+ // Make sure the dictionary is closed. If it is already closed, this is a no-op,
+ // so it's safe to call it anyways.
+ if (null != oldContactsDictionary) oldContactsDictionary.close();
+ dictionaryToUse = null;
+ } else {
+ if (null != oldContactsDictionary) {
+ if (!oldContactsDictionary.mLocale.equals(mLocale)) {
+ // If the locale has changed then recreate the contacts dictionary. This
+ // allows locale dependent rules for handling bigram name predictions.
+ oldContactsDictionary.close();
+ dictionaryToUse = new ContactsBinaryDictionary(mContext, mLocale);
+ } else {
+ // Make sure the old contacts dictionary is opened. If it is already open,
+ // this is a no-op, so it's safe to call it anyways.
+ oldContactsDictionary.reopen(mContext);
+ dictionaryToUse = oldContactsDictionary;
+ }
+ } else {
+ dictionaryToUse = new ContactsBinaryDictionary(mContext, mLocale);
+ }
+ }
+ 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);
+ }
+ }
+
+ // TODO: Revise the way to fusion suggestion results.
+ public void getSuggestions(final WordComposer composer,
+ final String prevWord, final ProximityInfo proximityInfo,
+ final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
+ final int sessionId, final Set<SuggestedWordInfo> suggestionSet) {
+ for (final String key : mDictionaries.keySet()) {
+ final Dictionary dictionary = mDictionaries.get(key);
+ suggestionSet.addAll(dictionary.getSuggestionsWithSessionId(composer, prevWord,
+ proximityInfo, blockOffensiveWords, additionalFeaturesOptions, sessionId));
+ }
+ }
+
+ 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;
+ }
+
+ private void addOrReplaceDictionary(final String key, final Dictionary dict) {
+ if (mOnlyDictionarySetForDebug != null && !mOnlyDictionarySetForDebug.contains(key)) {
+ Log.w(TAG, "Ignore add " + key + " dictionary for debug.");
+ return;
+ }
+ final Dictionary oldDict;
+ if (dict == null) {
+ oldDict = mDictionaries.remove(key);
+ } else {
+ oldDict = mDictionaries.put(key, dict);
+ }
+ if (oldDict != null && dict != oldDict) {
+ oldDict.close();
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 182356c80..77def63f1 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -472,7 +472,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
initSuggest();
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().init(this, mKeyboardSwitcher, mInputLogic.mSuggest);
+ ResearchLogger.getInstance().init(this, mKeyboardSwitcher);
}
// Register to receive ringer mode change and network state change.
@@ -516,8 +516,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
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);
+ mInputLogic.mSuggest.mDictionaryFacilitator.setAdditionalDictionaries(
+ mInputLogic.mSuggest.mDictionaryFacilitator /* oldDictionaryFacilitator */,
+ currentSettingsValues);
}
if (currentSettingsValues.mUsePersonalizedDicts) {
PersonalizationDictionarySessionRegistrar.init(this);
@@ -561,10 +562,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().initSuggest(newSuggest);
+ ResearchLogger.getInstance().initDictionary(newSuggest.mDictionaryFacilitator);
}
// TODO: Quit setting dictionaries from LatinIME.
- newSuggest.setAdditionalDictionaries(mInputLogic.mSuggest /* oldSuggest */, settingsValues);
+ newSuggest.mDictionaryFacilitator.setAdditionalDictionaries(
+ (mInputLogic.mSuggest == null) ? null : mInputLogic.mSuggest.mDictionaryFacilitator
+ /* oldDictionaryFacilitator */, settingsValues);
final Suggest oldSuggest = mInputLogic.mSuggest;
mInputLogic.mSuggest = newSuggest;
if (oldSuggest != null) oldSuggest.close();
@@ -572,7 +575,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
/* package private */ void resetSuggestMainDict() {
final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
- mInputLogic.mSuggest.resetMainDict(this, subtypeLocale,
+ mInputLogic.mSuggest.mDictionaryFacilitator.resetMainDict(this, subtypeLocale,
this /* SuggestInitializationListener */);
}
@@ -822,7 +825,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.cancelDoubleSpacePeriodTimer();
mainKeyboardView.setMainDictionaryAvailability(null != suggest
- ? suggest.hasMainDictionary() : false);
+ ? suggest.mDictionaryFacilitator.hasMainDictionary() : false);
mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn,
currentSettingsValues.mKeyPreviewPopupDismissDelay);
mainKeyboardView.setSlidingKeyInputPreviewEnabled(
@@ -1203,7 +1206,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} else {
wordToEdit = word;
}
- mInputLogic.mSuggest.addWordToUserDictionary(wordToEdit);
+ mInputLogic.mSuggest.mDictionaryFacilitator.addWordToUserDictionary(wordToEdit);
}
public void displaySettingsDialog() {
@@ -1724,13 +1727,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|| SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind)
&& suggest != null
// If the suggestion is not in the dictionary, the hint should be shown.
- && !suggest.isValidWord(suggestion, true);
+ && !suggest.mDictionaryFacilitator.isValidWord(suggestion,
+ true /* ignoreCase */);
if (currentSettings.mIsInternal) {
LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE,
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
- if (showingAddToDictionaryHint && suggest.isUserDictionaryEnabled()) {
+ if (showingAddToDictionaryHint
+ && suggest.mDictionaryFacilitator.isUserDictionaryEnabled()) {
mSuggestionStripView.showAddToDictionaryHint(
suggestion, currentSettings.mHintToSaveText);
} else {
@@ -1963,13 +1968,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// DO NOT USE THIS for any other purpose than testing. This is information private to LatinIME.
@UsedForTesting
/* package for test */ boolean isCurrentlyWaitingForMainDictionary() {
- return mInputLogic.mSuggest.isCurrentlyWaitingForMainDictionary();
+ return mInputLogic.mSuggest.mDictionaryFacilitator.isCurrentlyWaitingForMainDictionary();
}
// DO NOT USE THIS for any other purpose than testing. This can break the keyboard badly.
@UsedForTesting
/* package for test */ void replaceMainDictionaryForTest(final Locale locale) {
- mInputLogic.mSuggest.resetMainDict(this, locale, null);
+ mInputLogic.mSuggest.mDictionaryFacilitator.resetMainDict(this, locale, null);
}
public void debugDumpStateAndCrashWithException(final String context) {
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 16841224f..3f38323f9 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -18,14 +18,10 @@ package com.android.inputmethod.latin;
import android.content.Context;
import android.text.TextUtils;
-import android.util.Log;
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.PersonalizationDictionary;
-import com.android.inputmethod.latin.personalization.PersonalizationHelper;
-import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
import com.android.inputmethod.latin.settings.SettingsValues;
import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
import com.android.inputmethod.latin.utils.BoundedTreeSet;
@@ -34,16 +30,12 @@ import com.android.inputmethod.latin.utils.StringUtils;
import java.util.ArrayList;
import java.util.Comparator;
-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();
@@ -69,276 +61,25 @@ public final class Suggest {
private static final boolean DBG = LatinImeLogger.sDBG;
- private final ConcurrentHashMap<String, Dictionary> mDictionaries =
- CollectionUtils.newConcurrentHashMap();
- private HashSet<String> mOnlyDictionarySetForDebug = null;
- private Dictionary mMainDictionary;
- private ContactsBinaryDictionary mContactsDictionary;
- private UserBinaryDictionary mUserDictionary;
- private UserHistoryDictionary mUserHistoryDictionary;
- private PersonalizationDictionary mPersonalizationDictionary;
- @UsedForTesting
- private boolean mIsCurrentlyWaitingForMainDictionary = false;
+ public final DictionaryFacilitatorForSuggest mDictionaryFacilitator;
private float mAutoCorrectionThreshold;
// Locale used for upper- and title-casing words
public final Locale mLocale;
- private final Context mContext;
-
public Suggest(final Context context, final Locale locale, final SettingsValues settingsValues,
final SuggestInitializationListener listener) {
- initAsynchronously(context, locale, listener);
mLocale = locale;
- mContext = context;
- // initialize a debug flag for the personalization
- if (settingsValues.mUseOnlyPersonalizationDictionaryForDebug) {
- mOnlyDictionarySetForDebug = new HashSet<String>();
- mOnlyDictionarySetForDebug.add(Dictionary.TYPE_PERSONALIZATION);
- }
- setUserDictionary(new UserBinaryDictionary(context, locale));
+ mDictionaryFacilitator = new DictionaryFacilitatorForSuggest(context, locale,
+ settingsValues, listener);
}
@UsedForTesting
Suggest(final Context context, final AssetFileAddress[] dictionaryList, final Locale locale) {
- final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(dictionaryList,
- false /* useFullEditDistance */, locale);
mLocale = locale;
- mContext = context;
- mMainDictionary = mainDict;
- addOrReplaceDictionaryInternal(Dictionary.TYPE_MAIN, mainDict);
- }
-
- private void initAsynchronously(final Context context, final Locale locale,
- final SuggestInitializationListener listener) {
- resetMainDict(context, locale, listener);
- }
-
- private void addOrReplaceDictionaryInternal(final String key, final Dictionary dict) {
- if (mOnlyDictionarySetForDebug != null && !mOnlyDictionarySetForDebug.contains(key)) {
- Log.w(TAG, "Ignore add " + key + " dictionary for debug.");
- return;
- }
- addOrReplaceDictionary(mDictionaries, key, dict);
- }
-
- private static void addOrReplaceDictionary(
- final ConcurrentHashMap<String, Dictionary> dictionaries,
- final String key, final Dictionary dict) {
- final Dictionary oldDict = (dict == null)
- ? dictionaries.remove(key)
- : dictionaries.put(key, dict);
- if (oldDict != null && dict != oldDict) {
- oldDict.close();
- }
- }
-
- public void resetMainDict(final Context context, final Locale locale,
- final SuggestInitializationListener listener) {
- mIsCurrentlyWaitingForMainDictionary = true;
- mMainDictionary = null;
- if (listener != null) {
- listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
- }
- new Thread("InitializeBinaryDictionary") {
- @Override
- public void run() {
- final DictionaryCollection newMainDict =
- DictionaryFactory.createMainDictionaryFromManager(context, locale);
- addOrReplaceDictionaryInternal(Dictionary.TYPE_MAIN, newMainDict);
- mMainDictionary = newMainDict;
- if (listener != null) {
- listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
- }
- mIsCurrentlyWaitingForMainDictionary = false;
- }
- }.start();
- }
-
- // The main dictionary could have been loaded asynchronously. Don't cache the return value
- // of this method.
- public boolean hasMainDictionary() {
- return null != mMainDictionary && mMainDictionary.isInitialized();
- }
-
- @UsedForTesting
- public boolean isCurrentlyWaitingForMainDictionary() {
- return mIsCurrentlyWaitingForMainDictionary;
- }
-
- public Dictionary getMainDictionary() {
- return mMainDictionary;
- }
-
- /**
- * 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);
- }
-
- /**
- * Sets an optional contacts dictionary resource to be loaded. It is also possible to remove
- * 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);
- }
-
- private void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) {
- mUserHistoryDictionary = userHistoryDictionary;
- addOrReplaceDictionaryInternal(Dictionary.TYPE_USER_HISTORY, userHistoryDictionary);
- }
-
- private void setPersonalizationDictionary(
- final PersonalizationDictionary personalizationDictionary) {
- mPersonalizationDictionary = personalizationDictionary;
- addOrReplaceDictionaryInternal(Dictionary.TYPE_PERSONALIZATION, personalizationDictionary);
- }
-
- /**
- * Set dictionaries that can be turned off according to the user settings.
- *
- * @param oldSuggest the instance having old dictionaries
- * @param settingsValues current SettingsValues
- */
- public void setAdditionalDictionaries(final Suggest oldSuggest,
- final SettingsValues settingsValues) {
- // Contacts dictionary
- resetContactsDictionary(null != oldSuggest ? oldSuggest.mContactsDictionary : null,
- settingsValues);
- // User history dictionary & Personalization dictionary
- resetPersonalizedDictionaries(oldSuggest, settingsValues);
- }
-
- /**
- * Set the user history dictionary and personalization dictionary according to the user
- * settings.
- *
- * @param oldSuggest the instance that has been used
- * @param settingsValues current settingsValues
- */
- // TODO: Consolidate resetPersonalizedDictionaries() and resetContactsDictionary(). Call up the
- // new method for each dictionary.
- private void resetPersonalizedDictionaries(final Suggest oldSuggest,
- final SettingsValues settingsValues) {
- final boolean shouldSetDictionaries = settingsValues.mUsePersonalizedDicts;
-
- final UserHistoryDictionary oldUserHistoryDictionary = (null == oldSuggest) ? null :
- oldSuggest.mUserHistoryDictionary;
- final PersonalizationDictionary oldPersonalizationDictionary = (null == oldSuggest) ? null :
- oldSuggest.mPersonalizationDictionary;
- final UserHistoryDictionary userHistoryDictionaryToUse;
- final PersonalizationDictionary personalizationDictionaryToUse;
- if (!shouldSetDictionaries) {
- userHistoryDictionaryToUse = null;
- personalizationDictionaryToUse = null;
- } else {
- if (null != oldUserHistoryDictionary
- && oldUserHistoryDictionary.mLocale.equals(mLocale)) {
- userHistoryDictionaryToUse = oldUserHistoryDictionary;
- } else {
- userHistoryDictionaryToUse =
- PersonalizationHelper.getUserHistoryDictionary(mContext, mLocale);
- }
- if (null != oldPersonalizationDictionary
- && oldPersonalizationDictionary.mLocale.equals(mLocale)) {
- personalizationDictionaryToUse = oldPersonalizationDictionary;
- } else {
- personalizationDictionaryToUse =
- PersonalizationHelper.getPersonalizationDictionary(mContext, mLocale);
- }
- }
- setUserHistoryDictionary(userHistoryDictionaryToUse);
- setPersonalizationDictionary(personalizationDictionaryToUse);
- }
-
- /**
- * Set the contacts dictionary according to the user settings.
- *
- * This method takes an optional contacts dictionary to use when the locale hasn't changed
- * since the contacts dictionary can be opened or closed as necessary depending on the settings.
- *
- * @param oldContactsDictionary an optional dictionary to use, or null
- * @param settingsValues current settingsValues
- */
- private void resetContactsDictionary(final ContactsBinaryDictionary oldContactsDictionary,
- final SettingsValues settingsValues) {
- final boolean shouldSetDictionary = settingsValues.mUseContactsDict;
- final ContactsBinaryDictionary dictionaryToUse;
- if (!shouldSetDictionary) {
- // Make sure the dictionary is closed. If it is already closed, this is a no-op,
- // so it's safe to call it anyways.
- if (null != oldContactsDictionary) oldContactsDictionary.close();
- dictionaryToUse = null;
- } else {
- if (null != oldContactsDictionary) {
- if (!oldContactsDictionary.mLocale.equals(mLocale)) {
- // If the locale has changed then recreate the contacts dictionary. This
- // allows locale dependent rules for handling bigram name predictions.
- oldContactsDictionary.close();
- dictionaryToUse = new ContactsBinaryDictionary(mContext, mLocale);
- } else {
- // Make sure the old contacts dictionary is opened. If it is already open,
- // this is a no-op, so it's safe to call it anyways.
- oldContactsDictionary.reopen(mContext);
- dictionaryToUse = oldContactsDictionary;
- }
- } else {
- dictionaryToUse = new ContactsBinaryDictionary(mContext, mLocale);
- }
- }
- 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);
- }
+ mDictionaryFacilitator = new DictionaryFacilitatorForSuggest(context, dictionaryList,
+ locale);
}
public void setAutoCorrectionThreshold(float threshold) {
@@ -392,14 +133,8 @@ public final class Suggest {
} else {
wordComposerForLookup = wordComposer;
}
-
- for (final String key : mDictionaries.keySet()) {
- final Dictionary dictionary = mDictionaries.get(key);
- suggestionsSet.addAll(dictionary.getSuggestions(wordComposerForLookup,
- prevWordForBigram, proximityInfo, blockOffensiveWords,
- additionalFeaturesOptions));
- }
-
+ mDictionaryFacilitator.getSuggestions(wordComposer, prevWordForBigram, proximityInfo,
+ blockOffensiveWords, additionalFeaturesOptions, SESSION_TYPING, suggestionsSet);
final String whitelistedWord;
if (suggestionsSet.isEmpty()) {
whitelistedWord = null;
@@ -413,8 +148,8 @@ public final class Suggest {
// 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
- && !isValidWord(consideredWord, wordComposer.isFirstCharCapitalized()));
+ || (consideredWord.length() > 1 && !mDictionaryFacilitator.isValidWord(
+ consideredWord, wordComposer.isFirstCharCapitalized()));
final boolean hasAutoCorrection;
// TODO: using isCorrectionEnabled here is not very good. It's probably useless, because
@@ -424,7 +159,8 @@ public final class Suggest {
// the word *would* have been auto-corrected.
if (!isCorrectionEnabled || !allowsToBeAutoCorrected || !wordComposer.isComposingWord()
|| suggestionsSet.isEmpty() || wordComposer.hasDigits()
- || wordComposer.isMostlyCaps() || wordComposer.isResumed() || !hasMainDictionary()
+ || wordComposer.isMostlyCaps() || wordComposer.isResumed()
+ || !mDictionaryFacilitator.hasMainDictionary()
|| SuggestedWordInfo.KIND_SHORTCUT == suggestionsSet.first().mKind) {
// If we don't have a main dictionary, we never want to auto-correct. The reason for
// this is, the user may have a contact whose name happens to match a valid word in
@@ -497,15 +233,8 @@ public final class Suggest {
final OnGetSuggestedWordsCallback callback) {
final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
MAX_SUGGESTIONS);
-
- // At second character typed, search the unigrams (scores being affected by bigrams)
- for (final String key : mDictionaries.keySet()) {
- final Dictionary dictionary = mDictionaries.get(key);
- suggestionsSet.addAll(dictionary.getSuggestionsWithSessionId(wordComposer,
- prevWordForBigram, proximityInfo, blockOffensiveWords,
- additionalFeaturesOptions, sessionId));
- }
-
+ mDictionaryFacilitator.getSuggestions(wordComposer, prevWordForBigram, proximityInfo,
+ blockOffensiveWords, additionalFeaturesOptions, sessionId, suggestionsSet);
for (SuggestedWordInfo wordInfo : suggestionsSet) {
LatinImeLogger.onAddSuggestedWord(wordInfo.mWord, wordInfo.mSourceDict.mDictType);
}
@@ -618,51 +347,7 @@ public final class Suggest {
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());
- for (final Dictionary dictionary : dictionaries) {
- dictionary.close();
- }
- mMainDictionary = null;
+ mDictionaryFacilitator.close();
}
}
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 4a4abd7d2..b417a3e37 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -986,7 +986,7 @@ public final class InputLogic {
if (suggest == null) return null;
final String prevWord = mConnection.getNthPreviousWord(settingsValues, 2);
- return suggest.addToUserHistory(mWordComposer, prevWord, suggestion);
+ return suggest.mDictionaryFacilitator.addToUserHistory(mWordComposer, prevWord, suggestion);
}
public void performUpdateSuggestionStripSync(final SettingsValues settingsValues,
@@ -1190,7 +1190,8 @@ public final class InputLogic {
mConnection.deleteSurroundingText(deleteLength, 0);
if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
if (mSuggest != null) {
- mSuggest.cancelAddingUserHistory(previousWord, committedWord);
+ mSuggest.mDictionaryFacilitator.cancelAddingUserHistory(
+ previousWord, committedWord);
}
}
final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString;
diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java
index 6df7c1708..a75663fb3 100644
--- a/java/src/com/android/inputmethod/research/MainLogBuffer.java
+++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java
@@ -20,7 +20,7 @@ import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.Suggest;
+import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest;
import com.android.inputmethod.latin.define.ProductionFlag;
import java.io.IOException;
@@ -75,9 +75,7 @@ public abstract class MainLogBuffer extends FixedLogBuffer {
// The size of the n-grams logged. E.g. N_GRAM_SIZE = 2 means to sample bigrams.
public static final int N_GRAM_SIZE = 2;
- // TODO: Remove dependence on Suggest, and pass in Dictionary as a parameter to an appropriate
- // method.
- private final Suggest mSuggest;
+ private final DictionaryFacilitatorForSuggest mDictionaryFacilitator;
@UsedForTesting
private Dictionary mDictionaryForTesting;
private boolean mIsStopping = false;
@@ -89,11 +87,11 @@ public abstract class MainLogBuffer extends FixedLogBuffer {
/* package for test */ int mNumWordsUntilSafeToSample;
public MainLogBuffer(final int wordsBetweenSamples, final int numInitialWordsToIgnore,
- final Suggest suggest) {
+ final DictionaryFacilitatorForSuggest dictionaryFacilitator) {
super(N_GRAM_SIZE + wordsBetweenSamples);
mNumWordsBetweenNGrams = wordsBetweenSamples;
mNumWordsUntilSafeToSample = DEBUG ? 0 : numInitialWordsToIgnore;
- mSuggest = suggest;
+ mDictionaryFacilitator = dictionaryFacilitator;
}
@UsedForTesting
@@ -105,8 +103,10 @@ public abstract class MainLogBuffer extends FixedLogBuffer {
if (mDictionaryForTesting != null) {
return mDictionaryForTesting;
}
- if (mSuggest == null || !mSuggest.hasMainDictionary()) return null;
- return mSuggest.getMainDictionary();
+ if (mDictionaryFacilitator == null || !mDictionaryFacilitator.hasMainDictionary()) {
+ return null;
+ }
+ return mDictionaryFacilitator.getMainDictionary();
}
public void setIsStopping() {
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 28a647b02..4447b1b70 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -53,10 +53,10 @@ import com.android.inputmethod.keyboard.KeyboardView;
import com.android.inputmethod.keyboard.MainKeyboardView;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest;
import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.RichInputConnection;
-import com.android.inputmethod.latin.Suggest;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.utils.InputTypeUtils;
@@ -166,7 +166,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
protected static final int SUSPEND_DURATION_IN_MINUTES = 1;
// used to check whether words are not unique
- private Suggest mSuggest;
+ private DictionaryFacilitatorForSuggest mDictionaryFacilitator;
private MainKeyboardView mMainKeyboardView;
// TODO: Check whether a superclass can be used instead of LatinIME.
/* package for test */ LatinIME mLatinIME;
@@ -205,8 +205,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
return sInstance;
}
- public void init(final LatinIME latinIME, final KeyboardSwitcher keyboardSwitcher,
- final Suggest suggest) {
+ public void init(final LatinIME latinIME, final KeyboardSwitcher keyboardSwitcher) {
assert latinIME != null;
mLatinIME = latinIME;
mPrefs = PreferenceManager.getDefaultSharedPreferences(latinIME);
@@ -242,7 +241,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
System.currentTimeMillis(), System.nanoTime()), mLatinIME);
final int numWordsToIgnore = new Random().nextInt(NUMBER_OF_WORDS_BETWEEN_SAMPLES + 1);
mMainLogBuffer = new MainLogBuffer(NUMBER_OF_WORDS_BETWEEN_SAMPLES, numWordsToIgnore,
- mSuggest) {
+ mDictionaryFacilitator) {
@Override
protected void publish(final ArrayList<LogUnit> logUnits,
boolean canIncludePrivateData) {
@@ -656,8 +655,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
mInFeedbackDialog = false;
}
- public void initSuggest(final Suggest suggest) {
- mSuggest = suggest;
+ public void initDictionary(final DictionaryFacilitatorForSuggest dictionaryFacilitator) {
+ mDictionaryFacilitator = dictionaryFacilitator;
// MainLogBuffer now has an out-of-date Suggest object. Close down MainLogBuffer and create
// a new one.
if (mMainLogBuffer != null) {
@@ -666,10 +665,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
private Dictionary getDictionary() {
- if (mSuggest == null) {
+ if (mDictionaryFacilitator == null) {
return null;
}
- return mSuggest.getMainDictionary();
+ return mDictionaryFacilitator.getMainDictionary();
}
private void setIsPasswordView(boolean isPasswordView) {