From a374482719ef9f38395e7e39884433cc72e9cdb7 Mon Sep 17 00:00:00 2001 From: Keisuke Kuroyanagi Date: Thu, 26 Dec 2013 20:28:37 +0900 Subject: [PATCH] Extract dict operations from Suggest to a new class. Bug: 8187060 Change-Id: I77775aa50763158d99753c2312fa11fe14267aef --- .../DictionaryFacilitatorForSuggest.java | 376 ++++++++++++++++++ .../android/inputmethod/latin/LatinIME.java | 29 +- .../android/inputmethod/latin/Suggest.java | 343 +--------------- .../latin/inputlogic/InputLogic.java | 5 +- .../inputmethod/research/MainLogBuffer.java | 16 +- .../inputmethod/research/ResearchLogger.java | 17 +- 6 files changed, 426 insertions(+), 360 deletions(-) create mode 100644 java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java 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 mDictionaries = + CollectionUtils.newConcurrentHashMap(); + private HashSet 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(); + 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 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 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 mDictionaries = - CollectionUtils.newConcurrentHashMap(); - private HashSet 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(); - 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 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 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 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) {