aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
diff options
context:
space:
mode:
authorKeisuke Kuroyanagi <ksk@google.com>2013-12-26 03:55:47 -0800
committerAndroid Git Automerger <android-git-automerger@android.com>2013-12-26 03:55:47 -0800
commitce3fbd8486aa1f40eb68c87a04495f8f05bb224e (patch)
treec4c2a9a6e6e83fc2cc52e3e3c751b3b456655933 /java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
parent1212122d921bd8704f4020ae2b39a67778d4c2df (diff)
parenta374482719ef9f38395e7e39884433cc72e9cdb7 (diff)
downloadlatinime-ce3fbd8486aa1f40eb68c87a04495f8f05bb224e.tar.gz
latinime-ce3fbd8486aa1f40eb68c87a04495f8f05bb224e.tar.xz
latinime-ce3fbd8486aa1f40eb68c87a04495f8f05bb224e.zip
am a3744827: Extract dict operations from Suggest to a new class.
* commit 'a374482719ef9f38395e7e39884433cc72e9cdb7': Extract dict operations from Suggest to a new class.
Diffstat (limited to 'java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java')
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java376
1 files changed, 376 insertions, 0 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();
+ }
+ }
+}