From a1035be6d877cafda95b2761f9697474b79deeb8 Mon Sep 17 00:00:00 2001 From: Keisuke Kuroyanagi Date: Fri, 23 May 2014 09:30:55 +0900 Subject: Have DictionaryFacilitator in LatinIME. Bug: 13142176 Bug: 15094186 Change-Id: I9da67352ff2abcd289fa5885389895ced878a413 --- .../inputmethod/latin/DictionaryFacilitator.java | 564 +++++++++++++++++++++ .../latin/DictionaryFacilitatorForSuggest.java | 564 --------------------- .../com/android/inputmethod/latin/LatinIME.java | 51 +- .../src/com/android/inputmethod/latin/Suggest.java | 7 +- .../inputmethod/latin/inputlogic/InputLogic.java | 25 +- .../PersonalizationDictionarySessionRegistrar.java | 6 +- .../inputmethod/latin/utils/DistracterFilter.java | 11 +- .../latin/utils/LanguageModelParam.java | 6 +- .../inputmethod/research/MainLogBuffer.java | 6 +- .../inputmethod/research/ResearchLogger.java | 6 +- 10 files changed, 623 insertions(+), 623 deletions(-) create mode 100644 java/src/com/android/inputmethod/latin/DictionaryFacilitator.java delete mode 100644 java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java new file mode 100644 index 000000000..a97916733 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java @@ -0,0 +1,564 @@ +/* + * 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.SuggestedWords.SuggestedWordInfo; +import com.android.inputmethod.latin.personalization.ContextualDictionary; +import com.android.inputmethod.latin.personalization.PersonalizationDictionary; +import com.android.inputmethod.latin.personalization.UserHistoryDictionary; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.ExecutorUtils; +import com.android.inputmethod.latin.utils.LanguageModelParam; +import com.android.inputmethod.latin.utils.SuggestionResults; + +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +// TODO: Consolidate dictionaries in native code. +public class DictionaryFacilitator { + public static final String TAG = DictionaryFacilitator.class.getSimpleName(); + + // HACK: This threshold is being used when adding a capitalized entry in the User History + // dictionary. + private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140; + + private Dictionaries mDictionaries = new Dictionaries(); + private boolean mIsUserDictEnabled = false; + private volatile CountDownLatch mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0); + // To synchronize assigning mDictionaries to ensure closing dictionaries. + private final Object mLock = new Object(); + + private static final String[] DICT_TYPES_ORDERED_TO_GET_SUGGESTION = + new String[] { + Dictionary.TYPE_MAIN, + Dictionary.TYPE_USER_HISTORY, + Dictionary.TYPE_PERSONALIZATION, + Dictionary.TYPE_USER, + Dictionary.TYPE_CONTACTS, + Dictionary.TYPE_CONTEXTUAL + }; + + private static final Map> + DICT_TYPE_TO_CLASS = CollectionUtils.newHashMap(); + + static { + DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER_HISTORY, UserHistoryDictionary.class); + DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_PERSONALIZATION, PersonalizationDictionary.class); + DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER, UserBinaryDictionary.class); + DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTACTS, ContactsBinaryDictionary.class); + DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTEXTUAL, ContextualDictionary.class); + } + + private static final String DICT_FACTORY_METHOD_NAME = "getDictionary"; + private static final Class[] DICT_FACTORY_METHOD_ARG_TYPES = + new Class[] { Context.class, Locale.class, File.class }; + + private static final String[] SUB_DICT_TYPES = + Arrays.copyOfRange(DICT_TYPES_ORDERED_TO_GET_SUGGESTION, 1 /* start */, + DICT_TYPES_ORDERED_TO_GET_SUGGESTION.length); + + /** + * Class contains dictionaries for a locale. + */ + private static class Dictionaries { + public final Locale mLocale; + private Dictionary mMainDict; + public final ConcurrentHashMap mSubDictMap = + CollectionUtils.newConcurrentHashMap(); + + public Dictionaries() { + mLocale = null; + } + + public Dictionaries(final Locale locale, final Dictionary mainDict, + final Map subDicts) { + mLocale = locale; + // Main dictionary can be asynchronously loaded. + setMainDict(mainDict); + for (final Map.Entry entry : subDicts.entrySet()) { + setSubDict(entry.getKey(), entry.getValue()); + } + } + + private void setSubDict(final String dictType, final ExpandableBinaryDictionary dict) { + if (dict != null) { + mSubDictMap.put(dictType, dict); + } + } + + public void setMainDict(final Dictionary mainDict) { + // Close old dictionary if exists. Main dictionary can be assigned multiple times. + final Dictionary oldDict = mMainDict; + mMainDict = mainDict; + if (oldDict != null && mainDict != oldDict) { + oldDict.close(); + } + } + + public Dictionary getDict(final String dictType) { + if (Dictionary.TYPE_MAIN.equals(dictType)) { + return mMainDict; + } else { + return getSubDict(dictType); + } + } + + public ExpandableBinaryDictionary getSubDict(final String dictType) { + return mSubDictMap.get(dictType); + } + + public boolean hasDict(final String dictType) { + if (Dictionary.TYPE_MAIN.equals(dictType)) { + return mMainDict != null; + } else { + return mSubDictMap.containsKey(dictType); + } + } + + public void closeDict(final String dictType) { + final Dictionary dict; + if (Dictionary.TYPE_MAIN.equals(dictType)) { + dict = mMainDict; + } else { + dict = mSubDictMap.remove(dictType); + } + if (dict != null) { + dict.close(); + } + } + } + + public interface DictionaryInitializationListener { + public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable); + } + + public DictionaryFacilitator() {} + + public Locale getLocale() { + return mDictionaries.mLocale; + } + + private static ExpandableBinaryDictionary getSubDict(final String dictType, + final Context context, final Locale locale, final File dictFile) { + final Class dictClass = + DICT_TYPE_TO_CLASS.get(dictType); + if (dictClass == null) { + return null; + } + try { + final Method factoryMethod = dictClass.getMethod(DICT_FACTORY_METHOD_NAME, + DICT_FACTORY_METHOD_ARG_TYPES); + final Object dict = factoryMethod.invoke(null /* obj */, + new Object[] { context, locale, dictFile }); + return (ExpandableBinaryDictionary) dict; + } catch (final NoSuchMethodException | SecurityException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException e) { + Log.e(TAG, "Cannot create dictionary: " + dictType, e); + return null; + } + } + + public void resetDictionaries(final Context context, final Locale newLocale, + final boolean useContactsDict, final boolean usePersonalizedDicts, + final boolean forceReloadMainDictionary, + final DictionaryInitializationListener listener) { + final boolean localeHasBeenChanged = !newLocale.equals(mDictionaries.mLocale); + // We always try to have the main dictionary. Other dictionaries can be unused. + final boolean reloadMainDictionary = localeHasBeenChanged || forceReloadMainDictionary; + // TODO: Make subDictTypesToUse configurable by resource or a static final list. + final Set subDictTypesToUse = CollectionUtils.newHashSet(); + if (useContactsDict) { + subDictTypesToUse.add(Dictionary.TYPE_CONTACTS); + } + subDictTypesToUse.add(Dictionary.TYPE_USER); + if (usePersonalizedDicts) { + subDictTypesToUse.add(Dictionary.TYPE_USER_HISTORY); + subDictTypesToUse.add(Dictionary.TYPE_PERSONALIZATION); + subDictTypesToUse.add(Dictionary.TYPE_CONTEXTUAL); + } + + final Dictionary newMainDict; + if (reloadMainDictionary) { + // The main dictionary will be asynchronously loaded. + newMainDict = null; + } else { + newMainDict = mDictionaries.getDict(Dictionary.TYPE_MAIN); + } + + final Map subDicts = CollectionUtils.newHashMap(); + for (final String dictType : SUB_DICT_TYPES) { + if (!subDictTypesToUse.contains(dictType)) { + // This dictionary will not be used. + continue; + } + final ExpandableBinaryDictionary dict; + if (!localeHasBeenChanged && mDictionaries.hasDict(dictType)) { + // Continue to use current dictionary. + dict = mDictionaries.getSubDict(dictType); + } else { + // Start to use new dictionary. + dict = getSubDict(dictType, context, newLocale, null /* dictFile */); + } + subDicts.put(dictType, dict); + } + + // Replace Dictionaries. + final Dictionaries newDictionaries = new Dictionaries(newLocale, newMainDict, subDicts); + final Dictionaries oldDictionaries; + synchronized (mLock) { + oldDictionaries = mDictionaries; + mDictionaries = newDictionaries; + mIsUserDictEnabled = UserBinaryDictionary.isEnabled(context); + if (reloadMainDictionary) { + asyncReloadMainDictionary(context, newLocale, listener); + } + } + if (listener != null) { + listener.onUpdateMainDictionaryAvailability(hasInitializedMainDictionary()); + } + // Clean up old dictionaries. + if (reloadMainDictionary) { + oldDictionaries.closeDict(Dictionary.TYPE_MAIN); + } + for (final String dictType : SUB_DICT_TYPES) { + if (localeHasBeenChanged || !subDictTypesToUse.contains(dictType)) { + oldDictionaries.closeDict(dictType); + } + } + oldDictionaries.mSubDictMap.clear(); + } + + private void asyncReloadMainDictionary(final Context context, final Locale locale, + final DictionaryInitializationListener listener) { + final CountDownLatch latchForWaitingLoadingMainDictionary = new CountDownLatch(1); + mLatchForWaitingLoadingMainDictionary = latchForWaitingLoadingMainDictionary; + ExecutorUtils.getExecutor("InitializeBinaryDictionary").execute(new Runnable() { + @Override + public void run() { + final Dictionary mainDict = + DictionaryFactory.createMainDictionaryFromManager(context, locale); + synchronized (mLock) { + if (locale.equals(mDictionaries.mLocale)) { + mDictionaries.setMainDict(mainDict); + } else { + // Dictionary facilitator has been reset for another locale. + mainDict.close(); + } + } + if (listener != null) { + listener.onUpdateMainDictionaryAvailability(hasInitializedMainDictionary()); + } + latchForWaitingLoadingMainDictionary.countDown(); + } + }); + } + + @UsedForTesting + public void resetDictionariesForTesting(final Context context, final Locale locale, + final ArrayList dictionaryTypes, final HashMap dictionaryFiles, + final Map> additionalDictAttributes) { + Dictionary mainDictionary = null; + final Map subDicts = CollectionUtils.newHashMap(); + + for (final String dictType : dictionaryTypes) { + if (dictType.equals(Dictionary.TYPE_MAIN)) { + mainDictionary = DictionaryFactory.createMainDictionaryFromManager(context, locale); + } else { + final File dictFile = dictionaryFiles.get(dictType); + final ExpandableBinaryDictionary dict = getSubDict( + dictType, context, locale, dictFile); + if (additionalDictAttributes.containsKey(dictType)) { + dict.clearAndFlushDictionaryWithAdditionalAttributes( + additionalDictAttributes.get(dictType)); + } + if (dict == null) { + throw new RuntimeException("Unknown dictionary type: " + dictType); + } + dict.reloadDictionaryIfRequired(); + dict.waitAllTasksForTests(); + subDicts.put(dictType, dict); + } + } + mDictionaries = new Dictionaries(locale, mainDictionary, subDicts); + } + + public void closeDictionaries() { + final Dictionaries dictionaries; + synchronized (mLock) { + dictionaries = mDictionaries; + mDictionaries = new Dictionaries(); + } + for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTION) { + dictionaries.closeDict(dictType); + } + } + + // The main dictionary could have been loaded asynchronously. Don't cache the return value + // of this method. + public boolean hasInitializedMainDictionary() { + final Dictionary mainDict = mDictionaries.getDict(Dictionary.TYPE_MAIN); + return mainDict != null && mainDict.isInitialized(); + } + + public boolean hasPersonalizationDictionary() { + return mDictionaries.hasDict(Dictionary.TYPE_PERSONALIZATION); + } + + public void flushPersonalizationDictionary() { + final ExpandableBinaryDictionary personalizationDict = + mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION); + if (personalizationDict != null) { + personalizationDict.asyncFlushBinaryDictionary(); + } + } + + public void waitForLoadingMainDictionary(final long timeout, final TimeUnit unit) + throws InterruptedException { + mLatchForWaitingLoadingMainDictionary.await(timeout, unit); + } + + @UsedForTesting + public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit) + throws InterruptedException { + waitForLoadingMainDictionary(timeout, unit); + final Map dictMap = mDictionaries.mSubDictMap; + for (final ExpandableBinaryDictionary dict : dictMap.values()) { + dict.waitAllTasksForTests(); + } + } + + public boolean isUserDictionaryEnabled() { + return mIsUserDictEnabled; + } + + public void addWordToUserDictionary(final Context context, final String word) { + final Locale locale = getLocale(); + if (locale == null) { + return; + } + UserBinaryDictionary.addWordToUserDictionary(context, locale, word); + } + + public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized, + final PrevWordsInfo prevWordsInfo, final int timeStampInSeconds, + final boolean blockPotentiallyOffensive) { + final Dictionaries dictionaries = mDictionaries; + final String[] words = suggestion.split(Constants.WORD_SEPARATOR); + for (int i = 0; i < words.length; i++) { + final String currentWord = words[i]; + final PrevWordsInfo prevWordsInfoForCurrentWord = + (i == 0) ? prevWordsInfo : new PrevWordsInfo(words[i - 1]); + final boolean wasCurrentWordAutoCapitalized = (i == 0) ? wasAutoCapitalized : false; + addWordToUserHistory(dictionaries, prevWordsInfoForCurrentWord, currentWord, + wasCurrentWordAutoCapitalized, timeStampInSeconds, blockPotentiallyOffensive); + } + } + + private void addWordToUserHistory(final Dictionaries dictionaries, + final PrevWordsInfo prevWordsInfo, final String word, final boolean wasAutoCapitalized, + final int timeStampInSeconds, final boolean blockPotentiallyOffensive) { + final ExpandableBinaryDictionary userHistoryDictionary = + dictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY); + if (userHistoryDictionary == null) { + return; + } + final int maxFreq = getMaxFrequency(word); + if (maxFreq == 0 && blockPotentiallyOffensive) { + return; + } + final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale); + final String secondWord; + if (wasAutoCapitalized) { + if (isValidWord(word, false /* ignoreCase */) + && !isValidWord(lowerCasedWord, false /* ignoreCase */)) { + // If the word was auto-capitalized and exists only as a capitalized word in the + // dictionary, then we must not downcase it before registering it. For example, + // the name of the contacts in start-of-sentence position would come here with the + // wasAutoCapitalized flag: if we downcase it, we'd register a lower-case version + // of that contact's name which would end up popping in suggestions. + secondWord = word; + } else { + // If however the word is not in the dictionary, or exists as a lower-case word + // only, then we consider that was a lower-case word that had been auto-capitalized. + secondWord = lowerCasedWord; + } + } else { + // HACK: We'd like to avoid adding the capitalized form of common words to the User + // History dictionary in order to avoid suggesting them until the dictionary + // consolidation is done. + // TODO: Remove this hack when ready. + final int lowerCaseFreqInMainDict = dictionaries.hasDict(Dictionary.TYPE_MAIN) ? + dictionaries.getDict(Dictionary.TYPE_MAIN).getFrequency(lowerCasedWord) : + Dictionary.NOT_A_PROBABILITY; + if (maxFreq < lowerCaseFreqInMainDict + && lowerCaseFreqInMainDict >= CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT) { + // Use lower cased word as the word can be a distracter of the popular word. + secondWord = lowerCasedWord; + } else { + secondWord = word; + } + } + // 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 boolean isValid = maxFreq > 0; + UserHistoryDictionary.addToDictionary(userHistoryDictionary, prevWordsInfo, secondWord, + isValid, timeStampInSeconds); + } + + public void cancelAddingUserHistory(final PrevWordsInfo prevWordsInfo, + final String committedWord) { + final ExpandableBinaryDictionary userHistoryDictionary = + mDictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY); + if (userHistoryDictionary != null) { + userHistoryDictionary.removeNgramDynamically(prevWordsInfo, committedWord); + } + } + + // TODO: Revise the way to fusion suggestion results. + public SuggestionResults getSuggestionResults(final WordComposer composer, + final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo, + final boolean blockOffensiveWords, final int[] additionalFeaturesOptions, + final int sessionId, final ArrayList rawSuggestions) { + final Dictionaries dictionaries = mDictionaries; + final SuggestionResults suggestionResults = + new SuggestionResults(dictionaries.mLocale, SuggestedWords.MAX_SUGGESTIONS); + final float[] languageWeight = new float[] { Dictionary.NOT_A_LANGUAGE_WEIGHT }; + for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTION) { + final Dictionary dictionary = dictionaries.getDict(dictType); + if (null == dictionary) continue; + final ArrayList dictionarySuggestions = + dictionary.getSuggestions(composer, prevWordsInfo, proximityInfo, + blockOffensiveWords, additionalFeaturesOptions, sessionId, + languageWeight); + if (null == dictionarySuggestions) continue; + suggestionResults.addAll(dictionarySuggestions); + if (null != rawSuggestions) { + rawSuggestions.addAll(dictionarySuggestions); + } + } + return suggestionResults; + } + + public boolean isValidMainDictWord(final String word) { + final Dictionary mainDict = mDictionaries.getDict(Dictionary.TYPE_MAIN); + if (TextUtils.isEmpty(word) || mainDict == null) { + return false; + } + return mainDict.isValidWord(word); + } + + public boolean isValidWord(final String word, final boolean ignoreCase) { + if (TextUtils.isEmpty(word)) { + return false; + } + final Dictionaries dictionaries = mDictionaries; + if (dictionaries.mLocale == null) { + return false; + } + final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale); + for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTION) { + final Dictionary dictionary = dictionaries.getDict(dictType); + // 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; + final Dictionaries dictionaries = mDictionaries; + for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTION) { + final Dictionary dictionary = dictionaries.getDict(dictType); + if (dictionary == null) continue; + final int tempFreq = dictionary.getFrequency(word); + if (tempFreq >= maxFreq) { + maxFreq = tempFreq; + } + } + return maxFreq; + } + + public void clearUserHistoryDictionary() { + final ExpandableBinaryDictionary userHistoryDict = + mDictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY); + if (userHistoryDict == null) { + return; + } + userHistoryDict.clear(); + } + + // This method gets called only when the IME receives a notification to remove the + // personalization dictionary. + public void clearPersonalizationDictionary() { + final ExpandableBinaryDictionary personalizationDict = + mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION); + if (personalizationDict == null) { + return; + } + personalizationDict.clear(); + } + + public void addMultipleDictionaryEntriesToPersonalizationDictionary( + final ArrayList languageModelParams, + final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) { + final ExpandableBinaryDictionary personalizationDict = + mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION); + if (personalizationDict == null || languageModelParams == null + || languageModelParams.isEmpty()) { + if (callback != null) { + callback.onFinished(); + } + return; + } + personalizationDict.addMultipleDictionaryEntriesDynamically(languageModelParams, callback); + } + + public void dumpDictionaryForDebug(final String dictName) { + final ExpandableBinaryDictionary dictToDump = mDictionaries.getSubDict(dictName); + if (dictToDump == null) { + Log.e(TAG, "Cannot dump " + dictName + ". " + + "The dictionary is not being used for suggestion or cannot be dumped."); + return; + } + dictToDump.dumpAllWordsForDebug(); + } +} diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java deleted file mode 100644 index 301b832b6..000000000 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java +++ /dev/null @@ -1,564 +0,0 @@ -/* - * 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.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.personalization.ContextualDictionary; -import com.android.inputmethod.latin.personalization.PersonalizationDictionary; -import com.android.inputmethod.latin.personalization.UserHistoryDictionary; -import com.android.inputmethod.latin.utils.CollectionUtils; -import com.android.inputmethod.latin.utils.ExecutorUtils; -import com.android.inputmethod.latin.utils.LanguageModelParam; -import com.android.inputmethod.latin.utils.SuggestionResults; - -import java.io.File; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -// TODO: Consolidate dictionaries in native code. -public class DictionaryFacilitatorForSuggest { - public static final String TAG = DictionaryFacilitatorForSuggest.class.getSimpleName(); - - // HACK: This threshold is being used when adding a capitalized entry in the User History - // dictionary. - private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140; - - private Dictionaries mDictionaries = new Dictionaries(); - private boolean mIsUserDictEnabled = false; - private volatile CountDownLatch mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0); - // To synchronize assigning mDictionaries to ensure closing dictionaries. - private final Object mLock = new Object(); - - private static final String[] DICT_TYPES_ORDERED_TO_GET_SUGGESTION = - new String[] { - Dictionary.TYPE_MAIN, - Dictionary.TYPE_USER_HISTORY, - Dictionary.TYPE_PERSONALIZATION, - Dictionary.TYPE_USER, - Dictionary.TYPE_CONTACTS, - Dictionary.TYPE_CONTEXTUAL - }; - - private static final Map> - DICT_TYPE_TO_CLASS = CollectionUtils.newHashMap(); - - static { - DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER_HISTORY, UserHistoryDictionary.class); - DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_PERSONALIZATION, PersonalizationDictionary.class); - DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER, UserBinaryDictionary.class); - DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTACTS, ContactsBinaryDictionary.class); - DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTEXTUAL, ContextualDictionary.class); - } - - private static final String DICT_FACTORY_METHOD_NAME = "getDictionary"; - private static final Class[] DICT_FACTORY_METHOD_ARG_TYPES = - new Class[] { Context.class, Locale.class, File.class }; - - private static final String[] SUB_DICT_TYPES = - Arrays.copyOfRange(DICT_TYPES_ORDERED_TO_GET_SUGGESTION, 1 /* start */, - DICT_TYPES_ORDERED_TO_GET_SUGGESTION.length); - - /** - * Class contains dictionaries for a locale. - */ - private static class Dictionaries { - public final Locale mLocale; - private Dictionary mMainDict; - public final ConcurrentHashMap mSubDictMap = - CollectionUtils.newConcurrentHashMap(); - - public Dictionaries() { - mLocale = null; - } - - public Dictionaries(final Locale locale, final Dictionary mainDict, - final Map subDicts) { - mLocale = locale; - // Main dictionary can be asynchronously loaded. - setMainDict(mainDict); - for (final Map.Entry entry : subDicts.entrySet()) { - setSubDict(entry.getKey(), entry.getValue()); - } - } - - private void setSubDict(final String dictType, final ExpandableBinaryDictionary dict) { - if (dict != null) { - mSubDictMap.put(dictType, dict); - } - } - - public void setMainDict(final Dictionary mainDict) { - // Close old dictionary if exists. Main dictionary can be assigned multiple times. - final Dictionary oldDict = mMainDict; - mMainDict = mainDict; - if (oldDict != null && mainDict != oldDict) { - oldDict.close(); - } - } - - public Dictionary getDict(final String dictType) { - if (Dictionary.TYPE_MAIN.equals(dictType)) { - return mMainDict; - } else { - return getSubDict(dictType); - } - } - - public ExpandableBinaryDictionary getSubDict(final String dictType) { - return mSubDictMap.get(dictType); - } - - public boolean hasDict(final String dictType) { - if (Dictionary.TYPE_MAIN.equals(dictType)) { - return mMainDict != null; - } else { - return mSubDictMap.containsKey(dictType); - } - } - - public void closeDict(final String dictType) { - final Dictionary dict; - if (Dictionary.TYPE_MAIN.equals(dictType)) { - dict = mMainDict; - } else { - dict = mSubDictMap.remove(dictType); - } - if (dict != null) { - dict.close(); - } - } - } - - public interface DictionaryInitializationListener { - public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable); - } - - public DictionaryFacilitatorForSuggest() {} - - public Locale getLocale() { - return mDictionaries.mLocale; - } - - private static ExpandableBinaryDictionary getSubDict(final String dictType, - final Context context, final Locale locale, final File dictFile) { - final Class dictClass = - DICT_TYPE_TO_CLASS.get(dictType); - if (dictClass == null) { - return null; - } - try { - final Method factoryMethod = dictClass.getMethod(DICT_FACTORY_METHOD_NAME, - DICT_FACTORY_METHOD_ARG_TYPES); - final Object dict = factoryMethod.invoke(null /* obj */, - new Object[] { context, locale, dictFile }); - return (ExpandableBinaryDictionary) dict; - } catch (final NoSuchMethodException | SecurityException | IllegalAccessException - | IllegalArgumentException | InvocationTargetException e) { - Log.e(TAG, "Cannot create dictionary: " + dictType, e); - return null; - } - } - - public void resetDictionaries(final Context context, final Locale newLocale, - final boolean useContactsDict, final boolean usePersonalizedDicts, - final boolean forceReloadMainDictionary, - final DictionaryInitializationListener listener) { - final boolean localeHasBeenChanged = !newLocale.equals(mDictionaries.mLocale); - // We always try to have the main dictionary. Other dictionaries can be unused. - final boolean reloadMainDictionary = localeHasBeenChanged || forceReloadMainDictionary; - // TODO: Make subDictTypesToUse configurable by resource or a static final list. - final Set subDictTypesToUse = CollectionUtils.newHashSet(); - if (useContactsDict) { - subDictTypesToUse.add(Dictionary.TYPE_CONTACTS); - } - subDictTypesToUse.add(Dictionary.TYPE_USER); - if (usePersonalizedDicts) { - subDictTypesToUse.add(Dictionary.TYPE_USER_HISTORY); - subDictTypesToUse.add(Dictionary.TYPE_PERSONALIZATION); - subDictTypesToUse.add(Dictionary.TYPE_CONTEXTUAL); - } - - final Dictionary newMainDict; - if (reloadMainDictionary) { - // The main dictionary will be asynchronously loaded. - newMainDict = null; - } else { - newMainDict = mDictionaries.getDict(Dictionary.TYPE_MAIN); - } - - final Map subDicts = CollectionUtils.newHashMap(); - for (final String dictType : SUB_DICT_TYPES) { - if (!subDictTypesToUse.contains(dictType)) { - // This dictionary will not be used. - continue; - } - final ExpandableBinaryDictionary dict; - if (!localeHasBeenChanged && mDictionaries.hasDict(dictType)) { - // Continue to use current dictionary. - dict = mDictionaries.getSubDict(dictType); - } else { - // Start to use new dictionary. - dict = getSubDict(dictType, context, newLocale, null /* dictFile */); - } - subDicts.put(dictType, dict); - } - - // Replace Dictionaries. - final Dictionaries newDictionaries = new Dictionaries(newLocale, newMainDict, subDicts); - final Dictionaries oldDictionaries; - synchronized (mLock) { - oldDictionaries = mDictionaries; - mDictionaries = newDictionaries; - mIsUserDictEnabled = UserBinaryDictionary.isEnabled(context); - if (reloadMainDictionary) { - asyncReloadMainDictionary(context, newLocale, listener); - } - } - if (listener != null) { - listener.onUpdateMainDictionaryAvailability(hasInitializedMainDictionary()); - } - // Clean up old dictionaries. - if (reloadMainDictionary) { - oldDictionaries.closeDict(Dictionary.TYPE_MAIN); - } - for (final String dictType : SUB_DICT_TYPES) { - if (localeHasBeenChanged || !subDictTypesToUse.contains(dictType)) { - oldDictionaries.closeDict(dictType); - } - } - oldDictionaries.mSubDictMap.clear(); - } - - private void asyncReloadMainDictionary(final Context context, final Locale locale, - final DictionaryInitializationListener listener) { - final CountDownLatch latchForWaitingLoadingMainDictionary = new CountDownLatch(1); - mLatchForWaitingLoadingMainDictionary = latchForWaitingLoadingMainDictionary; - ExecutorUtils.getExecutor("InitializeBinaryDictionary").execute(new Runnable() { - @Override - public void run() { - final Dictionary mainDict = - DictionaryFactory.createMainDictionaryFromManager(context, locale); - synchronized (mLock) { - if (locale.equals(mDictionaries.mLocale)) { - mDictionaries.setMainDict(mainDict); - } else { - // Dictionary facilitator has been reset for another locale. - mainDict.close(); - } - } - if (listener != null) { - listener.onUpdateMainDictionaryAvailability(hasInitializedMainDictionary()); - } - latchForWaitingLoadingMainDictionary.countDown(); - } - }); - } - - @UsedForTesting - public void resetDictionariesForTesting(final Context context, final Locale locale, - final ArrayList dictionaryTypes, final HashMap dictionaryFiles, - final Map> additionalDictAttributes) { - Dictionary mainDictionary = null; - final Map subDicts = CollectionUtils.newHashMap(); - - for (final String dictType : dictionaryTypes) { - if (dictType.equals(Dictionary.TYPE_MAIN)) { - mainDictionary = DictionaryFactory.createMainDictionaryFromManager(context, locale); - } else { - final File dictFile = dictionaryFiles.get(dictType); - final ExpandableBinaryDictionary dict = getSubDict( - dictType, context, locale, dictFile); - if (additionalDictAttributes.containsKey(dictType)) { - dict.clearAndFlushDictionaryWithAdditionalAttributes( - additionalDictAttributes.get(dictType)); - } - if (dict == null) { - throw new RuntimeException("Unknown dictionary type: " + dictType); - } - dict.reloadDictionaryIfRequired(); - dict.waitAllTasksForTests(); - subDicts.put(dictType, dict); - } - } - mDictionaries = new Dictionaries(locale, mainDictionary, subDicts); - } - - public void closeDictionaries() { - final Dictionaries dictionaries; - synchronized (mLock) { - dictionaries = mDictionaries; - mDictionaries = new Dictionaries(); - } - for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTION) { - dictionaries.closeDict(dictType); - } - } - - // The main dictionary could have been loaded asynchronously. Don't cache the return value - // of this method. - public boolean hasInitializedMainDictionary() { - final Dictionary mainDict = mDictionaries.getDict(Dictionary.TYPE_MAIN); - return mainDict != null && mainDict.isInitialized(); - } - - public boolean hasPersonalizationDictionary() { - return mDictionaries.hasDict(Dictionary.TYPE_PERSONALIZATION); - } - - public void flushPersonalizationDictionary() { - final ExpandableBinaryDictionary personalizationDict = - mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION); - if (personalizationDict != null) { - personalizationDict.asyncFlushBinaryDictionary(); - } - } - - public void waitForLoadingMainDictionary(final long timeout, final TimeUnit unit) - throws InterruptedException { - mLatchForWaitingLoadingMainDictionary.await(timeout, unit); - } - - @UsedForTesting - public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit) - throws InterruptedException { - waitForLoadingMainDictionary(timeout, unit); - final Map dictMap = mDictionaries.mSubDictMap; - for (final ExpandableBinaryDictionary dict : dictMap.values()) { - dict.waitAllTasksForTests(); - } - } - - public boolean isUserDictionaryEnabled() { - return mIsUserDictEnabled; - } - - public void addWordToUserDictionary(final Context context, final String word) { - final Locale locale = getLocale(); - if (locale == null) { - return; - } - UserBinaryDictionary.addWordToUserDictionary(context, locale, word); - } - - public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized, - final PrevWordsInfo prevWordsInfo, final int timeStampInSeconds, - final boolean blockPotentiallyOffensive) { - final Dictionaries dictionaries = mDictionaries; - final String[] words = suggestion.split(Constants.WORD_SEPARATOR); - for (int i = 0; i < words.length; i++) { - final String currentWord = words[i]; - final PrevWordsInfo prevWordsInfoForCurrentWord = - (i == 0) ? prevWordsInfo : new PrevWordsInfo(words[i - 1]); - final boolean wasCurrentWordAutoCapitalized = (i == 0) ? wasAutoCapitalized : false; - addWordToUserHistory(dictionaries, prevWordsInfoForCurrentWord, currentWord, - wasCurrentWordAutoCapitalized, timeStampInSeconds, blockPotentiallyOffensive); - } - } - - private void addWordToUserHistory(final Dictionaries dictionaries, - final PrevWordsInfo prevWordsInfo, final String word, final boolean wasAutoCapitalized, - final int timeStampInSeconds, final boolean blockPotentiallyOffensive) { - final ExpandableBinaryDictionary userHistoryDictionary = - dictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY); - if (userHistoryDictionary == null) { - return; - } - final int maxFreq = getMaxFrequency(word); - if (maxFreq == 0 && blockPotentiallyOffensive) { - return; - } - final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale); - final String secondWord; - if (wasAutoCapitalized) { - if (isValidWord(word, false /* ignoreCase */) - && !isValidWord(lowerCasedWord, false /* ignoreCase */)) { - // If the word was auto-capitalized and exists only as a capitalized word in the - // dictionary, then we must not downcase it before registering it. For example, - // the name of the contacts in start-of-sentence position would come here with the - // wasAutoCapitalized flag: if we downcase it, we'd register a lower-case version - // of that contact's name which would end up popping in suggestions. - secondWord = word; - } else { - // If however the word is not in the dictionary, or exists as a lower-case word - // only, then we consider that was a lower-case word that had been auto-capitalized. - secondWord = lowerCasedWord; - } - } else { - // HACK: We'd like to avoid adding the capitalized form of common words to the User - // History dictionary in order to avoid suggesting them until the dictionary - // consolidation is done. - // TODO: Remove this hack when ready. - final int lowerCaseFreqInMainDict = dictionaries.hasDict(Dictionary.TYPE_MAIN) ? - dictionaries.getDict(Dictionary.TYPE_MAIN).getFrequency(lowerCasedWord) : - Dictionary.NOT_A_PROBABILITY; - if (maxFreq < lowerCaseFreqInMainDict - && lowerCaseFreqInMainDict >= CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT) { - // Use lower cased word as the word can be a distracter of the popular word. - secondWord = lowerCasedWord; - } else { - secondWord = word; - } - } - // 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 boolean isValid = maxFreq > 0; - UserHistoryDictionary.addToDictionary(userHistoryDictionary, prevWordsInfo, secondWord, - isValid, timeStampInSeconds); - } - - public void cancelAddingUserHistory(final PrevWordsInfo prevWordsInfo, - final String committedWord) { - final ExpandableBinaryDictionary userHistoryDictionary = - mDictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY); - if (userHistoryDictionary != null) { - userHistoryDictionary.removeNgramDynamically(prevWordsInfo, committedWord); - } - } - - // TODO: Revise the way to fusion suggestion results. - public SuggestionResults getSuggestionResults(final WordComposer composer, - final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo, - final boolean blockOffensiveWords, final int[] additionalFeaturesOptions, - final int sessionId, final ArrayList rawSuggestions) { - final Dictionaries dictionaries = mDictionaries; - final SuggestionResults suggestionResults = - new SuggestionResults(dictionaries.mLocale, SuggestedWords.MAX_SUGGESTIONS); - final float[] languageWeight = new float[] { Dictionary.NOT_A_LANGUAGE_WEIGHT }; - for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTION) { - final Dictionary dictionary = dictionaries.getDict(dictType); - if (null == dictionary) continue; - final ArrayList dictionarySuggestions = - dictionary.getSuggestions(composer, prevWordsInfo, proximityInfo, - blockOffensiveWords, additionalFeaturesOptions, sessionId, - languageWeight); - if (null == dictionarySuggestions) continue; - suggestionResults.addAll(dictionarySuggestions); - if (null != rawSuggestions) { - rawSuggestions.addAll(dictionarySuggestions); - } - } - return suggestionResults; - } - - public boolean isValidMainDictWord(final String word) { - final Dictionary mainDict = mDictionaries.getDict(Dictionary.TYPE_MAIN); - if (TextUtils.isEmpty(word) || mainDict == null) { - return false; - } - return mainDict.isValidWord(word); - } - - public boolean isValidWord(final String word, final boolean ignoreCase) { - if (TextUtils.isEmpty(word)) { - return false; - } - final Dictionaries dictionaries = mDictionaries; - if (dictionaries.mLocale == null) { - return false; - } - final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale); - for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTION) { - final Dictionary dictionary = dictionaries.getDict(dictType); - // 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; - final Dictionaries dictionaries = mDictionaries; - for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTION) { - final Dictionary dictionary = dictionaries.getDict(dictType); - if (dictionary == null) continue; - final int tempFreq = dictionary.getFrequency(word); - if (tempFreq >= maxFreq) { - maxFreq = tempFreq; - } - } - return maxFreq; - } - - public void clearUserHistoryDictionary() { - final ExpandableBinaryDictionary userHistoryDict = - mDictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY); - if (userHistoryDict == null) { - return; - } - userHistoryDict.clear(); - } - - // This method gets called only when the IME receives a notification to remove the - // personalization dictionary. - public void clearPersonalizationDictionary() { - final ExpandableBinaryDictionary personalizationDict = - mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION); - if (personalizationDict == null) { - return; - } - personalizationDict.clear(); - } - - public void addMultipleDictionaryEntriesToPersonalizationDictionary( - final ArrayList languageModelParams, - final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) { - final ExpandableBinaryDictionary personalizationDict = - mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION); - if (personalizationDict == null || languageModelParams == null - || languageModelParams.isEmpty()) { - if (callback != null) { - callback.onFinished(); - } - return; - } - personalizationDict.addMultipleDictionaryEntriesDynamically(languageModelParams, callback); - } - - public void dumpDictionaryForDebug(final String dictName) { - final ExpandableBinaryDictionary dictToDump = mDictionaries.getSubDict(dictName); - if (dictToDump == null) { - Log.e(TAG, "Cannot dump " + dictName + ". " - + "The dictionary is not being used for suggestion or cannot be dumped."); - return; - } - dictToDump.dumpAllWordsForDebug(); - } -} diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 217b0b162..4863326ab 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -103,7 +103,7 @@ import java.util.concurrent.TimeUnit; */ public class LatinIME extends InputMethodService implements KeyboardActionListener, SuggestionStripView.Listener, SuggestionStripViewAccessor, - DictionaryFacilitatorForSuggest.DictionaryInitializationListener, + DictionaryFacilitator.DictionaryInitializationListener, ImportantNoticeDialog.ImportantNoticeDialogListener { private static final String TAG = LatinIME.class.getSimpleName(); private static final boolean TRACE = false; @@ -122,8 +122,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private static final String SCHEME_PACKAGE = "package"; private final Settings mSettings; + private final DictionaryFacilitator mDictionaryFacilitator = new DictionaryFacilitator(); private final InputLogic mInputLogic = new InputLogic(this /* LatinIME */, - this /* SuggestionStripViewAccessor */); + this /* SuggestionStripViewAccessor */, mDictionaryFacilitator); // We expect to have only one decoder in almost all cases, hence the default capacity of 1. // If it turns out we need several, it will get grown seamlessly. final SparseArray mHardwareEventDecoders @@ -493,8 +494,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.getInstance().init(this, mKeyboardSwitcher); - ResearchLogger.getInstance().initDictionary( - mInputLogic.mSuggest.mDictionaryFacilitator); + ResearchLogger.getInstance().initDictionary(mDictionaryFacilitator); } // Register to receive ringer mode change and network state change. @@ -543,8 +543,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private void refreshPersonalizationDictionarySession() { - final DictionaryFacilitatorForSuggest dictionaryFacilitator = - mInputLogic.mSuggest.mDictionaryFacilitator; final boolean shouldKeepUserHistoryDictionaries; final boolean shouldKeepPersonalizationDictionaries; if (mSettings.getCurrent().mUsePersonalizedDicts) { @@ -559,7 +557,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (!shouldKeepUserHistoryDictionaries) { // Remove user history dictionaries. PersonalizationHelper.removeAllUserHistoryDictionaries(this); - dictionaryFacilitator.clearUserHistoryDictionary(); + mDictionaryFacilitator.clearUserHistoryDictionary(); } if (!shouldKeepPersonalizationDictionaries) { // Remove personalization dictionaries. @@ -568,7 +566,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } else { final DistracterFilter distracterFilter = createDistracterFilter(); PersonalizationDictionarySessionRegistrar.init( - this, dictionaryFacilitator, distracterFilter); + this, mDictionaryFacilitator, distracterFilter); } } @@ -606,10 +604,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen * @param locale the locale */ private void resetSuggestForLocale(final Locale locale) { - final DictionaryFacilitatorForSuggest dictionaryFacilitator = - mInputLogic.mSuggest.mDictionaryFacilitator; final SettingsValues settingsValues = mSettings.getCurrent(); - dictionaryFacilitator.resetDictionaries(this /* context */, locale, + mDictionaryFacilitator.resetDictionaries(this /* context */, locale, settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts, false /* forceReloadMainDictionary */, this); if (settingsValues.mCorrectionEnabled) { @@ -622,17 +618,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen * Reset suggest by loading the main dictionary of the current locale. */ /* package private */ void resetSuggestMainDict() { - final DictionaryFacilitatorForSuggest dictionaryFacilitator = - mInputLogic.mSuggest.mDictionaryFacilitator; final SettingsValues settingsValues = mSettings.getCurrent(); - dictionaryFacilitator.resetDictionaries(this /* context */, - dictionaryFacilitator.getLocale(), settingsValues.mUseContactsDict, + mDictionaryFacilitator.resetDictionaries(this /* context */, + mDictionaryFacilitator.getLocale(), settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts, true /* forceReloadMainDictionary */, this); } @Override public void onDestroy() { - mInputLogic.mSuggest.mDictionaryFacilitator.closeDictionaries(); + mDictionaryFacilitator.closeDictionaries(); mSettings.onDestroy(); unregisterReceiver(mConnectivityAndRingerModeChangeReceiver); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { @@ -668,7 +662,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } final DistracterFilter distracterFilter = createDistracterFilter(); PersonalizationDictionarySessionRegistrar.onConfigurationChanged(this, conf, - mInputLogic.mSuggest.mDictionaryFacilitator, distracterFilter); + mDictionaryFacilitator, distracterFilter); super.onConfigurationChanged(conf); } @@ -841,7 +835,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen currentSettingsValues = mSettings.getCurrent(); if (currentSettingsValues.mCorrectionEnabled) { - suggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold); + suggest.setAutoCorrectionThreshold( + currentSettingsValues.mAutoCorrectionThreshold); } switcher.loadKeyboard(editorInfo, currentSettingsValues, getCurrentAutoCapsState(), @@ -870,7 +865,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mHandler.cancelUpdateSuggestionStrip(); mainKeyboardView.setMainDictionaryAvailability( - suggest.mDictionaryFacilitator.hasInitializedMainDictionary()); + mDictionaryFacilitator.hasInitializedMainDictionary()); mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn, currentSettingsValues.mKeyPreviewPopupDismissDelay); mainKeyboardView.setSlidingKeyInputPreviewEnabled( @@ -1167,8 +1162,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } else { wordToEdit = word; } - mInputLogic.mSuggest.mDictionaryFacilitator.addWordToUserDictionary( - this /* context */, wordToEdit); + mDictionaryFacilitator.addWordToUserDictionary(this /* context */, wordToEdit); } // Callback for the {@link SuggestionStripView}, to call when the important notice strip is @@ -1725,15 +1719,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @UsedForTesting /* package for test */ void waitForLoadingDictionaries(final long timeout, final TimeUnit unit) throws InterruptedException { - mInputLogic.mSuggest.mDictionaryFacilitator.waitForLoadingDictionariesForTesting( - timeout, unit); + mDictionaryFacilitator.waitForLoadingDictionariesForTesting(timeout, unit); } // DO NOT USE THIS for any other purpose than testing. This can break the keyboard badly. @UsedForTesting /* package for test */ void replaceDictionariesForTest(final Locale locale) { final SettingsValues settingsValues = mSettings.getCurrent(); - mInputLogic.mSuggest.mDictionaryFacilitator.resetDictionaries(this, locale, + mDictionaryFacilitator.resetDictionaries(this, locale, settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts, false /* forceReloadMainDictionary */, this /* listener */); } @@ -1741,8 +1734,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // DO NOT USE THIS for any other purpose than testing. @UsedForTesting /* package for test */ void clearPersonalizedDictionariesForTest() { - mInputLogic.mSuggest.mDictionaryFacilitator.clearUserHistoryDictionary(); - mInputLogic.mSuggest.mDictionaryFacilitator.clearPersonalizationDictionary(); + mDictionaryFacilitator.clearUserHistoryDictionary(); + mDictionaryFacilitator.clearPersonalizationDictionary(); } @UsedForTesting @@ -1754,12 +1747,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } public void dumpDictionaryForDebug(final String dictName) { - final DictionaryFacilitatorForSuggest dictionaryFacilitator = - mInputLogic.mSuggest.mDictionaryFacilitator; - if (dictionaryFacilitator.getLocale() == null) { + if (mDictionaryFacilitator.getLocale() == null) { resetSuggest(); } - mInputLogic.mSuggest.mDictionaryFacilitator.dumpDictionaryForDebug(dictName); + mDictionaryFacilitator.dumpDictionaryForDebug(dictName); } 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 43daee4d2..1eccf2ca2 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -52,11 +52,14 @@ public final class Suggest { private static final int SUPPRESS_SUGGEST_THRESHOLD = -2000000000; private static final boolean DBG = LatinImeLogger.sDBG; - public final DictionaryFacilitatorForSuggest mDictionaryFacilitator = - new DictionaryFacilitatorForSuggest(); + private final DictionaryFacilitator mDictionaryFacilitator; private float mAutoCorrectionThreshold; + public Suggest(final DictionaryFacilitator dictionaryFacilitator) { + mDictionaryFacilitator = dictionaryFacilitator; + } + public Locale getLocale() { return mDictionaryFacilitator.getLocale(); } diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java index 7536ff94c..78d4bc807 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java @@ -32,7 +32,7 @@ import com.android.inputmethod.event.InputTransaction; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.Dictionary; -import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest; +import com.android.inputmethod.latin.DictionaryFacilitator; import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.LastComposedWord; import com.android.inputmethod.latin.LatinIME; @@ -79,7 +79,8 @@ public final class InputLogic { private int mSpaceState; // Never null public SuggestedWords mSuggestedWords = SuggestedWords.EMPTY; - public final Suggest mSuggest = new Suggest(); + public final Suggest mSuggest; + private final DictionaryFacilitator mDictionaryFacilitator; public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD; public final WordComposer mWordComposer; @@ -102,14 +103,19 @@ public final class InputLogic { * Create a new instance of the input logic. * @param latinIME the instance of the parent LatinIME. We should remove this when we can. * @param suggestionStripViewAccessor an object to access the suggestion strip view. + * @param dictionaryFacilitator facilitator for getting suggestions and updating user history + * dictionary. */ public InputLogic(final LatinIME latinIME, - final SuggestionStripViewAccessor suggestionStripViewAccessor) { + final SuggestionStripViewAccessor suggestionStripViewAccessor, + final DictionaryFacilitator dictionaryFacilitator) { mLatinIME = latinIME; mSuggestionStripViewAccessor = suggestionStripViewAccessor; mWordComposer = new WordComposer(); mConnection = new RichInputConnection(latinIME); mInputLogicHandler = InputLogicHandler.NULL_HANDLER; + mSuggest = new Suggest(dictionaryFacilitator); + mDictionaryFacilitator = dictionaryFacilitator; } /** @@ -173,7 +179,7 @@ public final class InputLogic { final InputLogicHandler inputLogicHandler = mInputLogicHandler; mInputLogicHandler = InputLogicHandler.NULL_HANDLER; inputLogicHandler.destroy(); - mSuggest.mDictionaryFacilitator.closeDictionaries(); + mDictionaryFacilitator.closeDictionaries(); } /** @@ -295,18 +301,16 @@ public final class InputLogic { // We should show the "Touch again to save" hint if the user pressed the first entry // AND it's in none of our current dictionaries (main, user or otherwise). - final DictionaryFacilitatorForSuggest dictionaryFacilitator = - mSuggest.mDictionaryFacilitator; final boolean showingAddToDictionaryHint = (SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind) - && !dictionaryFacilitator.isValidWord(suggestion, true /* ignoreCase */); + && !mDictionaryFacilitator.isValidWord(suggestion, true /* ignoreCase */); if (settingsValues.mIsInternal) { LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); } - if (showingAddToDictionaryHint && dictionaryFacilitator.isUserDictionaryEnabled()) { + if (showingAddToDictionaryHint && mDictionaryFacilitator.isUserDictionaryEnabled()) { mSuggestionStripViewAccessor.showAddToDictionaryHint(suggestion); } else { // If we're not showing the "Touch again to save", then update the suggestion strip. @@ -1250,7 +1254,7 @@ public final class InputLogic { mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps(); final int timeStampInSeconds = (int)TimeUnit.MILLISECONDS.toSeconds( System.currentTimeMillis()); - mSuggest.mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized, + mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized, prevWordsInfo, timeStampInSeconds, settingsValues.mBlockPotentiallyOffensive); } @@ -1462,8 +1466,7 @@ public final class InputLogic { } mConnection.deleteSurroundingText(deleteLength, 0); if (!TextUtils.isEmpty(prevWordsInfo.mPrevWord) && !TextUtils.isEmpty(committedWord)) { - mSuggest.mDictionaryFacilitator.cancelAddingUserHistory( - prevWordsInfo, committedWordString); + mDictionaryFacilitator.cancelAddingUserHistory(prevWordsInfo, committedWordString); } final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString; final SpannableString textToCommit = new SpannableString(stringToCommit); diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java index 9bef7a198..805f4220f 100644 --- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java +++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java @@ -19,17 +19,17 @@ package com.android.inputmethod.latin.personalization; import android.content.Context; import android.content.res.Configuration; -import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest; +import com.android.inputmethod.latin.DictionaryFacilitator; import com.android.inputmethod.latin.utils.DistracterFilter; public class PersonalizationDictionarySessionRegistrar { public static void init(final Context context, - final DictionaryFacilitatorForSuggest dictionaryFacilitator, + final DictionaryFacilitator dictionaryFacilitator, final DistracterFilter distracterFilter) { } public static void onConfigurationChanged(final Context context, final Configuration conf, - final DictionaryFacilitatorForSuggest dictionaryFacilitator, + final DictionaryFacilitator dictionaryFacilitator, final DistracterFilter distracterFilter) { } diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java index f1057da0b..19a01eea8 100644 --- a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java +++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java @@ -34,6 +34,7 @@ import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.keyboard.KeyboardLayoutSet; import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.DictionaryFacilitator; import com.android.inputmethod.latin.PrevWordsInfo; import com.android.inputmethod.latin.Suggest; import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback; @@ -53,6 +54,7 @@ public class DistracterFilter { private final Context mContext; private final Map mLocaleToSubtypeMap; private final Map mLocaleToKeyboardMap; + private final DictionaryFacilitator mDictionaryFacilitator; private final Suggest mSuggest; private Keyboard mKeyboard; @@ -89,7 +91,8 @@ public class DistracterFilter { } } mLocaleToKeyboardMap = new HashMap<>(); - mSuggest = new Suggest(); + mDictionaryFacilitator = new DictionaryFacilitator(); + mSuggest = new Suggest(mDictionaryFacilitator); mKeyboard = null; } @@ -132,10 +135,10 @@ public class DistracterFilter { } private void loadDictionariesForLocale(final Locale newlocale) throws InterruptedException { - mSuggest.mDictionaryFacilitator.resetDictionaries(mContext, newlocale, + mDictionaryFacilitator.resetDictionaries(mContext, newlocale, false /* useContactsDict */, false /* usePersonalizedDicts */, false /* forceReloadMainDictionary */, null /* listener */); - mSuggest.mDictionaryFacilitator.waitForLoadingMainDictionary( + mDictionaryFacilitator.waitForLoadingMainDictionary( TIMEOUT_TO_WAIT_LOADING_DICTIONARIES_IN_SECONDS, TimeUnit.SECONDS); } @@ -153,7 +156,7 @@ public class DistracterFilter { if (locale == null) { return false; } - if (!locale.equals(mSuggest.mDictionaryFacilitator.getLocale())) { + if (!locale.equals(mDictionaryFacilitator.getLocale())) { if (!mLocaleToSubtypeMap.containsKey(locale)) { Log.e(TAG, "Locale " + locale + " is not enabled."); // TODO: Investigate what we should do for disabled locales. diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java index aaf4a4064..36543cca6 100644 --- a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java +++ b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java @@ -19,7 +19,7 @@ package com.android.inputmethod.latin.utils; import android.util.Log; import com.android.inputmethod.latin.Dictionary; -import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest; +import com.android.inputmethod.latin.DictionaryFacilitator; import com.android.inputmethod.latin.PrevWordsInfo; import com.android.inputmethod.latin.settings.SpacingAndPunctuations; @@ -80,7 +80,7 @@ public final class LanguageModelParam { // Process a list of words and return a list of {@link LanguageModelParam} objects. public static ArrayList createLanguageModelParamsFrom( final ArrayList tokens, final int timestamp, - final DictionaryFacilitatorForSuggest dictionaryFacilitator, + final DictionaryFacilitator dictionaryFacilitator, final SpacingAndPunctuations spacingAndPunctuations, final DistracterFilter distracterFilter) { final ArrayList languageModelParams = @@ -124,7 +124,7 @@ public final class LanguageModelParam { private static LanguageModelParam detectWhetherVaildWordOrNotAndGetLanguageModelParam( final PrevWordsInfo prevWordsInfo, final String targetWord, final int timestamp, - final DictionaryFacilitatorForSuggest dictionaryFacilitator, + final DictionaryFacilitator dictionaryFacilitator, final DistracterFilter distracterFilter) { final Locale locale = dictionaryFacilitator.getLocale(); if (locale == null) { diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java index ffdb43c15..3806ac755 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.DictionaryFacilitatorForSuggest; +import com.android.inputmethod.latin.DictionaryFacilitator; import com.android.inputmethod.latin.define.ProductionFlag; import java.io.IOException; @@ -75,7 +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; - private final DictionaryFacilitatorForSuggest mDictionaryFacilitator; + private final DictionaryFacilitator mDictionaryFacilitator; @UsedForTesting private Dictionary mDictionaryForTesting; private boolean mIsStopping = false; @@ -87,7 +87,7 @@ public abstract class MainLogBuffer extends FixedLogBuffer { /* package for test */ int mNumWordsUntilSafeToSample; public MainLogBuffer(final int wordsBetweenSamples, final int numInitialWordsToIgnore, - final DictionaryFacilitatorForSuggest dictionaryFacilitator) { + final DictionaryFacilitator dictionaryFacilitator) { super(N_GRAM_SIZE + wordsBetweenSamples); mNumWordsBetweenNGrams = wordsBetweenSamples; mNumWordsUntilSafeToSample = DEBUG ? 0 : numInitialWordsToIgnore; diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java index d907dd1b0..d73f9c41c 100644 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -52,7 +52,7 @@ import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.KeyboardView; import com.android.inputmethod.keyboard.MainKeyboardView; import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest; +import com.android.inputmethod.latin.DictionaryFacilitator; import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.RichInputConnection; @@ -167,7 +167,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 DictionaryFacilitatorForSuggest mDictionaryFacilitator; + private DictionaryFacilitator mDictionaryFacilitator; private MainKeyboardView mMainKeyboardView; // TODO: Check whether a superclass can be used instead of LatinIME. /* package for test */ LatinIME mLatinIME; @@ -656,7 +656,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang mInFeedbackDialog = false; } - public void initDictionary(final DictionaryFacilitatorForSuggest dictionaryFacilitator) { + public void initDictionary(final DictionaryFacilitator dictionaryFacilitator) { mDictionaryFacilitator = dictionaryFacilitator; // MainLogBuffer now has an out-of-date Suggest object. Close down MainLogBuffer and create // a new one. -- cgit v1.2.3-83-g751a