diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java index d6178fcee..8b8d5776e 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java @@ -26,15 +26,14 @@ 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 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.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -50,197 +49,314 @@ public class DictionaryFacilitatorForSuggest { // dictionary. private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140; - private final Context mContext; - public final Locale mLocale; + private Dictionaries mDictionaries = new Dictionaries(); + private volatile CountDownLatch mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0); + // To synchronize assigning mDictionaries to ensure closing dictionaries. + private Object mLock = new Object(); - private final ConcurrentHashMap mDictionaries = - CollectionUtils.newConcurrentHashMap(); + /** + * Class contains dictionaries for a locale. + */ + private static class Dictionaries { + public final Locale mLocale; + public final ConcurrentHashMap mDictMap = + CollectionUtils.newConcurrentHashMap(); + // Main dictionary will be asynchronously loaded. + public Dictionary mMainDictionary; + public final ContactsBinaryDictionary mContactsDictionary; + public final UserBinaryDictionary mUserDictionary; + public final UserHistoryDictionary mUserHistoryDictionary; + public final PersonalizationDictionary mPersonalizationDictionary; - private Dictionary mMainDictionary; - private ContactsBinaryDictionary mContactsDictionary; - private UserBinaryDictionary mUserDictionary; - private UserHistoryDictionary mUserHistoryDictionary; - private PersonalizationDictionary mPersonalizationDictionary; + public Dictionaries() { + mLocale = null; + mMainDictionary = null; + mContactsDictionary = null; + mUserDictionary = null; + mUserHistoryDictionary = null; + mPersonalizationDictionary = null; + } - private final CountDownLatch mLatchForWaitingLoadingMainDictionary; + public Dictionaries(final Locale locale, final Dictionary mainDict, + final ContactsBinaryDictionary contactsDict, final UserBinaryDictionary userDict, + final UserHistoryDictionary userHistoryDict, + final PersonalizationDictionary personalizationDict) { + mLocale = locale; + setMainDict(mainDict); + mContactsDictionary = contactsDict; + if (mContactsDictionary != null) { + mDictMap.put(Dictionary.TYPE_CONTACTS, mContactsDictionary); + } + mUserDictionary = userDict; + if (mUserDictionary != null) { + mDictMap.put(Dictionary.TYPE_USER, mUserDictionary); + } + mUserHistoryDictionary = userHistoryDict; + if (mUserHistoryDictionary != null) { + mDictMap.put(Dictionary.TYPE_USER_HISTORY, mUserHistoryDictionary); + } + mPersonalizationDictionary = personalizationDict; + if (mPersonalizationDictionary != null) { + mDictMap.put(Dictionary.TYPE_PERSONALIZATION, mPersonalizationDictionary); + } + } + + public void setMainDict(final Dictionary mainDict) { + mMainDictionary = mainDict; + // Close old dictionary if exists. Main dictionary can be assigned multiple times. + final Dictionary oldDict; + if (mMainDictionary != null) { + oldDict = mDictMap.put(Dictionary.TYPE_MAIN, mMainDictionary); + } else { + oldDict = mDictMap.remove(Dictionary.TYPE_MAIN); + } + if (oldDict != null && mMainDictionary != oldDict) { + oldDict.close(); + } + } + + public boolean hasMainDict() { + return mMainDictionary != null; + } + + public boolean hasContactsDict() { + return mContactsDictionary != null; + } + + public boolean hasUserDict() { + return mUserDictionary != null; + } + + public boolean hasUserHistoryDict() { + return mUserHistoryDictionary != null; + } + + public boolean hasPersonalizationDict() { + return mPersonalizationDictionary != null; + } + } public interface DictionaryInitializationListener { public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable); } - /** - * Creates instance for initialization or when the locale is changed. - * - * @param context the context - * @param locale the locale - * @param settingsValues current settings values to control what dictionaries should be used - * @param listener the listener - * @param oldDictionaryFacilitator the instance having old dictionaries. This is null when the - * instance is initially created. - */ - public DictionaryFacilitatorForSuggest(final Context context, final Locale locale, - final SettingsValues settingsValues, final DictionaryInitializationListener listener, - final DictionaryFacilitatorForSuggest oldDictionaryFacilitator) { - mContext = context; - mLocale = locale; - mLatchForWaitingLoadingMainDictionary = new CountDownLatch(1); - loadMainDict(context, locale, listener); - setUserDictionary(new UserBinaryDictionary(context, locale)); - resetAdditionalDictionaries(oldDictionaryFacilitator, settingsValues); + public DictionaryFacilitatorForSuggest() {} + + public Locale getLocale() { + return mDictionaries.mLocale; } - /** - * Creates instance for reloading the main dict. - * - * @param listener the listener - * @param oldDictionaryFacilitator the instance having old dictionaries. This must not be null. - */ - public DictionaryFacilitatorForSuggest(final DictionaryInitializationListener listener, - final DictionaryFacilitatorForSuggest oldDictionaryFacilitator) { - mContext = oldDictionaryFacilitator.mContext; - mLocale = oldDictionaryFacilitator.mLocale; - mLatchForWaitingLoadingMainDictionary = new CountDownLatch(1); - loadMainDict(mContext, mLocale, listener); - // Transfer user dictionary. - setUserDictionary(oldDictionaryFacilitator.mUserDictionary); - oldDictionaryFacilitator.removeDictionary(Dictionary.TYPE_USER); - // Transfer contacts dictionary. - setContactsDictionary(oldDictionaryFacilitator.mContactsDictionary); - oldDictionaryFacilitator.removeDictionary(Dictionary.TYPE_CONTACTS); - // Transfer user history dictionary. - setUserHistoryDictionary(oldDictionaryFacilitator.mUserHistoryDictionary); - oldDictionaryFacilitator.removeDictionary(Dictionary.TYPE_USER_HISTORY); - // Transfer personalization dictionary. - setPersonalizationDictionary(oldDictionaryFacilitator.mPersonalizationDictionary); - oldDictionaryFacilitator.removeDictionary(Dictionary.TYPE_PERSONALIZATION); + 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; + final boolean closeContactsDictionary = localeHasBeenChanged || !useContactsDict; + final boolean closeUserDictionary = localeHasBeenChanged; + final boolean closeUserHistoryDictionary = localeHasBeenChanged || !usePersonalizedDicts; + final boolean closePersonalizationDictionary = + localeHasBeenChanged || !usePersonalizedDicts; + + final Dictionary newMainDict; + if (reloadMainDictionary) { + // The main dictionary will be asynchronously loaded. + newMainDict = null; + } else { + newMainDict = mDictionaries.mMainDictionary; + } + + // Open or move contacts dictionary. + final ContactsBinaryDictionary newContactsDict; + if (!closeContactsDictionary && mDictionaries.hasContactsDict()) { + newContactsDict = mDictionaries.mContactsDictionary; + } else if (useContactsDict) { + newContactsDict = new ContactsBinaryDictionary(context, newLocale); + } else { + newContactsDict = null; + } + + // Open or move user dictionary. + final UserBinaryDictionary newUserDictionary; + if (!closeUserDictionary && mDictionaries.hasUserDict()) { + newUserDictionary = mDictionaries.mUserDictionary; + } else { + newUserDictionary = new UserBinaryDictionary(context, newLocale); + } + + // Open or move user history dictionary. + final UserHistoryDictionary newUserHistoryDict; + if (!closeUserHistoryDictionary && mDictionaries.hasUserHistoryDict()) { + newUserHistoryDict = mDictionaries.mUserHistoryDictionary; + } else if (usePersonalizedDicts) { + newUserHistoryDict = PersonalizationHelper.getUserHistoryDictionary(context, newLocale); + } else { + newUserHistoryDict = null; + } + + // Open or move personalization dictionary. + final PersonalizationDictionary newPersonalizationDict; + if (!closePersonalizationDictionary && mDictionaries.hasPersonalizationDict()) { + newPersonalizationDict = mDictionaries.mPersonalizationDictionary; + } else if (usePersonalizedDicts) { + newPersonalizationDict = + PersonalizationHelper.getPersonalizationDictionary(context, newLocale); + } else { + newPersonalizationDict = null; + } + + // Replace Dictionaries. + final Dictionaries newDictionaries = new Dictionaries(newLocale, newMainDict, + newContactsDict, newUserDictionary, newUserHistoryDict, newPersonalizationDict); + if (listener != null) { + listener.onUpdateMainDictionaryAvailability(newDictionaries.hasMainDict()); + } + final Dictionaries oldDictionaries; + synchronized (mLock) { + oldDictionaries = mDictionaries; + mDictionaries = newDictionaries; + if (reloadMainDictionary) { + asyncReloadMainDictionary(context, newLocale, listener); + } + } + + // Clean up old dictionaries. + oldDictionaries.mDictMap.clear(); + if (reloadMainDictionary && oldDictionaries.hasMainDict()) { + oldDictionaries.mMainDictionary.close(); + } + if (closeContactsDictionary && oldDictionaries.hasContactsDict()) { + oldDictionaries.mContactsDictionary.close(); + } + if (closeUserDictionary && oldDictionaries.hasUserDict()) { + oldDictionaries.mUserDictionary.close(); + } + if (closeUserHistoryDictionary && oldDictionaries.hasUserHistoryDict()) { + oldDictionaries.mUserHistoryDictionary.close(); + } + if (closePersonalizationDictionary && oldDictionaries.hasPersonalizationDict()) { + oldDictionaries.mPersonalizationDictionary.close(); + } } - /** - * Creates instance for when the settings values have been changed. - * - * @param settingsValues the new settings values - * @param oldDictionaryFacilitator the instance having old dictionaries. This must not be null. - */ - // - public DictionaryFacilitatorForSuggest(final SettingsValues settingsValues, - final DictionaryFacilitatorForSuggest oldDictionaryFacilitator) { - mContext = oldDictionaryFacilitator.mContext; - mLocale = oldDictionaryFacilitator.mLocale; - mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0); - // Transfer main dictionary. - setMainDictionary(oldDictionaryFacilitator.mMainDictionary); - oldDictionaryFacilitator.removeDictionary(Dictionary.TYPE_MAIN); - // Transfer user dictionary. - setUserDictionary(oldDictionaryFacilitator.mUserDictionary); - oldDictionaryFacilitator.removeDictionary(Dictionary.TYPE_USER); - // Transfer or create additional dictionaries depending on the settings values. - resetAdditionalDictionaries(oldDictionaryFacilitator, settingsValues); + 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(mDictionaries.hasMainDict()); + } + latchForWaitingLoadingMainDictionary.countDown(); + } + }); } @UsedForTesting - public DictionaryFacilitatorForSuggest(final Context context, final Locale locale, + public void resetDictionariesForTesting(final Context context, final Locale locale, final ArrayList dictionaryTypes, final HashMap dictionaryFiles, final Map> additionalDictAttributes) { - mContext = context; - mLocale = locale; - mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0); + Dictionary mainDictionary = null; + ContactsBinaryDictionary contactsDictionary = null; + UserBinaryDictionary userDictionary = null; + UserHistoryDictionary userHistoryDictionary = null; + PersonalizationDictionary personalizationDictionary = null; + for (final String dictType : dictionaryTypes) { if (dictType.equals(Dictionary.TYPE_MAIN)) { - final DictionaryCollection mainDictionary = - DictionaryFactory.createMainDictionaryFromManager(context, locale); - setMainDictionary(mainDictionary); + mainDictionary = DictionaryFactory.createMainDictionaryFromManager(context, locale); } else if (dictType.equals(Dictionary.TYPE_USER_HISTORY)) { - final UserHistoryDictionary userHistoryDictionary = + userHistoryDictionary = PersonalizationHelper.getUserHistoryDictionary(context, locale); // Staring with an empty user history dictionary for testing. // Testing program may populate this dictionary before actual testing. userHistoryDictionary.reloadDictionaryIfRequired(); userHistoryDictionary.waitAllTasksForTests(); - setUserHistoryDictionary(userHistoryDictionary); if (additionalDictAttributes.containsKey(dictType)) { userHistoryDictionary.clearAndFlushDictionaryWithAdditionalAttributes( additionalDictAttributes.get(dictType)); } } else if (dictType.equals(Dictionary.TYPE_PERSONALIZATION)) { - final PersonalizationDictionary personalizationDictionary = + personalizationDictionary = PersonalizationHelper.getPersonalizationDictionary(context, locale); // Staring with an empty personalization dictionary for testing. // Testing program may populate this dictionary before actual testing. personalizationDictionary.reloadDictionaryIfRequired(); personalizationDictionary.waitAllTasksForTests(); - setPersonalizationDictionary(personalizationDictionary); if (additionalDictAttributes.containsKey(dictType)) { personalizationDictionary.clearAndFlushDictionaryWithAdditionalAttributes( additionalDictAttributes.get(dictType)); } } else if (dictType.equals(Dictionary.TYPE_USER)) { final File file = dictionaryFiles.get(dictType); - final UserBinaryDictionary userDictionary = new UserBinaryDictionary( - context, locale, file); + userDictionary = new UserBinaryDictionary(context, locale, file); userDictionary.reloadDictionaryIfRequired(); userDictionary.waitAllTasksForTests(); - setUserDictionary(userDictionary); } else if (dictType.equals(Dictionary.TYPE_CONTACTS)) { final File file = dictionaryFiles.get(dictType); - final ContactsBinaryDictionary contactsDictionary = new ContactsBinaryDictionary( - context, locale, file); + contactsDictionary = new ContactsBinaryDictionary(context, locale, file); contactsDictionary.reloadDictionaryIfRequired(); contactsDictionary.waitAllTasksForTests(); - setContactsDictionary(contactsDictionary); } else { throw new RuntimeException("Unknown dictionary type: " + dictType); } } + mDictionaries = new Dictionaries(locale, mainDictionary, contactsDictionary, + userDictionary, userHistoryDictionary, personalizationDictionary); } - public boolean needsToBeRecreated(final Locale newLocale, - final SettingsValues newSettingsValues) { - return !mLocale.equals(newLocale) - || (newSettingsValues.mUseContactsDict != (mContactsDictionary != null)) - || (newSettingsValues.mUsePersonalizedDicts != (mUserHistoryDictionary != null)) - || (newSettingsValues.mUsePersonalizedDicts != hasPersonalizationDictionary()); - } - - public void close() { - final HashSet dictionaries = CollectionUtils.newHashSet(); - dictionaries.addAll(mDictionaries.values()); - for (final Dictionary dictionary : dictionaries) { - dictionary.close(); + public void closeDictionaries() { + final Dictionaries dictionaries; + synchronized (mLock) { + dictionaries = mDictionaries; + mDictionaries = new Dictionaries(); } - } - - private void loadMainDict(final Context context, final Locale locale, - final DictionaryInitializationListener listener) { - mMainDictionary = null; - if (listener != null) { - listener.onUpdateMainDictionaryAvailability(hasMainDictionary()); + if (dictionaries.hasMainDict()) { + dictionaries.mMainDictionary.close(); + } + if (dictionaries.hasContactsDict()) { + dictionaries.mContactsDictionary.close(); + } + if (dictionaries.hasUserDict()) { + dictionaries.mUserDictionary.close(); + } + if (dictionaries.hasUserHistoryDict()) { + dictionaries.mUserHistoryDictionary.close(); + } + if (dictionaries.hasPersonalizationDict()) { + dictionaries.mPersonalizationDictionary.close(); } - ExecutorUtils.getExecutor("InitializeBinaryDictionary").execute(new Runnable() { - public void run() { - final DictionaryCollection newMainDict = - DictionaryFactory.createMainDictionaryFromManager(context, locale); - setMainDictionary(newMainDict); - if (listener != null) { - listener.onUpdateMainDictionaryAvailability(hasMainDictionary()); - } - mLatchForWaitingLoadingMainDictionary.countDown(); - } - }); } // 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(); + public boolean hasInitializedMainDictionary() { + final Dictionaries dictionaries = mDictionaries; + return dictionaries.hasMainDict() && dictionaries.mMainDictionary.isInitialized(); } public boolean hasPersonalizationDictionary() { - return null != mPersonalizationDictionary; + return mDictionaries.hasPersonalizationDict(); } public void flushPersonalizationDictionary() { - if (hasPersonalizationDictionary()) { - mPersonalizationDictionary.flush(); + final PersonalizationDictionary personalizationDict = + mDictionaries.mPersonalizationDictionary; + if (personalizationDict != null) { + personalizationDict.flush(); } } @@ -253,177 +369,48 @@ public class DictionaryFacilitatorForSuggest { public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit) throws InterruptedException { waitForLoadingMainDictionary(timeout, unit); - if (mContactsDictionary != null) { - mContactsDictionary.waitAllTasksForTests(); + final Dictionaries dictionaries = mDictionaries; + if (dictionaries.hasContactsDict()) { + dictionaries.mContactsDictionary.waitAllTasksForTests(); } - if (mUserDictionary != null) { - mUserDictionary.waitAllTasksForTests(); + if (dictionaries.hasUserDict()) { + dictionaries.mUserDictionary.waitAllTasksForTests(); } - if (mUserHistoryDictionary != null) { - mUserHistoryDictionary.waitAllTasksForTests(); + if (dictionaries.hasUserHistoryDict()) { + dictionaries.mUserHistoryDictionary.waitAllTasksForTests(); } - if (mPersonalizationDictionary != null) { - mPersonalizationDictionary.waitAllTasksForTests(); + if (dictionaries.hasPersonalizationDict()) { + dictionaries.mPersonalizationDictionary.waitAllTasksForTests(); } } - 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. - */ - private 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. - */ - private 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); - } - - /** - * Reset dictionaries that can be turned off according to the user settings. - * - * @param oldDictionaryFacilitator the instance having old dictionaries - * @param settingsValues current SettingsValues - */ - private void resetAdditionalDictionaries( - 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) { + final UserBinaryDictionary userDictionary = mDictionaries.mUserDictionary; + if (userDictionary == null) { return false; } - return mUserDictionary.mEnabled; + return userDictionary.mEnabled; } public void addWordToUserDictionary(String word) { - if (mUserDictionary == null) { + final UserBinaryDictionary userDictionary = mDictionaries.mUserDictionary; + if (userDictionary == null) { return; } - mUserDictionary.addWordToUserDictionary(word); + userDictionary.addWordToUserDictionary(word); } public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized, final String previousWord, final int timeStampInSeconds) { - if (mUserHistoryDictionary == null) { + final Dictionaries dictionaries = mDictionaries; + if (!dictionaries.hasUserHistoryDict()) { return; } final int maxFreq = getMaxFrequency(suggestion); if (maxFreq == 0) { return; } - final String suggestionLowerCase = suggestion.toLowerCase(mLocale); + final String suggestionLowerCase = suggestion.toLowerCase(dictionaries.mLocale); final String secondWord; if (wasAutoCapitalized) { secondWord = suggestionLowerCase; @@ -432,11 +419,11 @@ public class DictionaryFacilitatorForSuggest { // History dictionary in order to avoid suggesting them until the dictionary // consolidation is done. // TODO: Remove this hack when ready. - final int lowerCasefreqInMainDict = mMainDictionary != null ? - mMainDictionary.getFrequency(suggestionLowerCase) : + final int lowerCaseFreqInMainDict = dictionaries.hasMainDict() ? + dictionaries.mMainDictionary.getFrequency(suggestionLowerCase) : Dictionary.NOT_A_PROBABILITY; - if (maxFreq < lowerCasefreqInMainDict - && lowerCasefreqInMainDict >= CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT) { + 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 = suggestionLowerCase; } else { @@ -446,54 +433,56 @@ public class DictionaryFacilitatorForSuggest { // 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; - mUserHistoryDictionary.addToDictionary( + dictionaries.mUserHistoryDictionary.addToDictionary( previousWord, secondWord, isValid, timeStampInSeconds); } public void cancelAddingUserHistory(final String previousWord, final String committedWord) { - if (mUserHistoryDictionary != null) { - mUserHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord); + final UserHistoryDictionary userHistoryDictionary = mDictionaries.mUserHistoryDictionary; + if (userHistoryDictionary != null) { + userHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord); } } // TODO: Revise the way to fusion suggestion results. - public void getSuggestions(final WordComposer composer, + public SuggestionResults getSuggestionResults(final WordComposer composer, final String prevWord, final ProximityInfo proximityInfo, final boolean blockOffensiveWords, final int[] additionalFeaturesOptions, - final int sessionId, final Set suggestionSet, - final ArrayList rawSuggestions) { - for (final String key : mDictionaries.keySet()) { - final Dictionary dictionary = mDictionaries.get(key); + final int sessionId, final ArrayList rawSuggestions) { + final Dictionaries dictionaries = mDictionaries; + final Map dictMap = dictionaries.mDictMap; + final SuggestionResults suggestionResults = + new SuggestionResults(dictionaries.mLocale, SuggestedWords.MAX_SUGGESTIONS); + for (final Dictionary dictionary : dictMap.values()) { if (null == dictionary) continue; final ArrayList dictionarySuggestions = dictionary.getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords, additionalFeaturesOptions, sessionId); if (null == dictionarySuggestions) continue; - suggestionSet.addAll(dictionarySuggestions); + suggestionResults.addAll(dictionarySuggestions); if (null != rawSuggestions) { rawSuggestions.addAll(dictionarySuggestions); } } + return suggestionResults; } public boolean isValidMainDictWord(final String word) { - if (TextUtils.isEmpty(word) || !hasMainDictionary()) { + final Dictionaries dictionaries = mDictionaries; + if (TextUtils.isEmpty(word) || !dictionaries.hasMainDict()) { return false; } - return mMainDictionary.isValidWord(word); + return dictionaries.mMainDictionary.isValidWord(word); } 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? + final Dictionaries dictionaries = mDictionaries; + final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale); + final Map dictMap = dictionaries.mDictMap; + for (final Dictionary dictionary : dictMap.values()) { // 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. @@ -511,9 +500,8 @@ public class DictionaryFacilitatorForSuggest { 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 Map dictMap = mDictionaries.mDictMap; + for (final Dictionary dictionary : dictMap.values()) { final int tempFreq = dictionary.getFrequency(word); if (tempFreq >= maxFreq) { maxFreq = tempFreq; @@ -522,61 +510,50 @@ public class DictionaryFacilitatorForSuggest { return maxFreq; } - private void removeDictionary(final String key) { - mDictionaries.remove(key); - } - - private void addOrReplaceDictionary(final String key, final Dictionary dict) { - final Dictionary oldDict; - if (dict == null) { - oldDict = mDictionaries.remove(key); - } else { - oldDict = mDictionaries.put(key, dict); - } - if (oldDict != null && dict != oldDict) { - oldDict.close(); - } - } public void clearUserHistoryDictionary() { - if (mUserHistoryDictionary == null) { + final UserHistoryDictionary userHistoryDict = mDictionaries.mUserHistoryDictionary; + if (userHistoryDict == null) { return; } - mUserHistoryDictionary.clearAndFlushDictionary(); + userHistoryDict.clearAndFlushDictionary(); } // This method gets called only when the IME receives a notification to remove the // personalization dictionary. public void clearPersonalizationDictionary() { - if (!hasPersonalizationDictionary()) { + final PersonalizationDictionary personalizationDict = + mDictionaries.mPersonalizationDictionary; + if (personalizationDict == null) { return; } - mPersonalizationDictionary.clearAndFlushDictionary(); + personalizationDict.clearAndFlushDictionary(); } public void addMultipleDictionaryEntriesToPersonalizationDictionary( final ArrayList languageModelParams, final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) { - if (!hasPersonalizationDictionary()) { + final PersonalizationDictionary personalizationDict = + mDictionaries.mPersonalizationDictionary; + if (personalizationDict == null) { if (callback != null) { callback.onFinished(); } return; } - mPersonalizationDictionary.addMultipleDictionaryEntriesToDictionary(languageModelParams, - callback); + personalizationDict.addMultipleDictionaryEntriesToDictionary(languageModelParams, callback); } public void dumpDictionaryForDebug(final String dictName) { final ExpandableBinaryDictionary dictToDump; if (dictName.equals(Dictionary.TYPE_CONTACTS)) { - dictToDump = mContactsDictionary; + dictToDump = mDictionaries.mContactsDictionary; } else if (dictName.equals(Dictionary.TYPE_USER)) { - dictToDump = mUserDictionary; + dictToDump = mDictionaries.mUserDictionary; } else if (dictName.equals(Dictionary.TYPE_USER_HISTORY)) { - dictToDump = mUserHistoryDictionary; + dictToDump = mDictionaries.mUserHistoryDictionary; } else if (dictName.equals(Dictionary.TYPE_PERSONALIZATION)) { - dictToDump = mPersonalizationDictionary; + dictToDump = mDictionaries.mPersonalizationDictionary; } else { dictToDump = null; } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 38e386493..0c0be82df 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -215,7 +215,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen false /* includeResumedWordInSuggestions */); break; case MSG_REOPEN_DICTIONARIES: - latinIme.initSuggest(); + latinIme.resetSuggest(); // In theory we could call latinIme.updateSuggestionStrip() right away, but // in the practice, the dictionary is not finished opening yet so we wouldn't // get any suggestions. Wait one frame. @@ -478,10 +478,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // TODO: Resolve mutual dependencies of {@link #loadSettings()} and {@link #initSuggest()}. loadSettings(); - initSuggest(); + resetSuggest(); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.getInstance().init(this, mKeyboardSwitcher); + ResearchLogger.getInstance().initDictionary( + mInputLogic.mSuggest.mDictionaryFacilitator); } // Register to receive ringer mode change and network state change. @@ -520,31 +522,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // This method is called on startup and language switch, before the new layout has // been displayed. Opening dictionaries never affects responsivity as dictionaries are // asynchronously loaded. - initOrResetSuggestForSettingsValues(mInputLogic.mSuggest, locale, currentSettingsValues); - } - - private void initOrResetSuggestForSettingsValues(final Suggest oldSuggest, - final Locale locale, final SettingsValues settingsValues) { - if (!mHandler.hasPendingReopenDictionaries() && oldSuggest != null) { - // May need to reset dictionaries depending on the user settings. - final DictionaryFacilitatorForSuggest oldDictionaryFacilitator = - oldSuggest.mDictionaryFacilitator; - if (!oldDictionaryFacilitator.needsToBeRecreated(locale, settingsValues)) { - // Continue to use the same dictionary facilitator if no configuration has changed. - refreshPersonalizationDictionarySession(); - return; - } - final DictionaryFacilitatorForSuggest dictionaryFacilitator = - new DictionaryFacilitatorForSuggest(settingsValues, oldDictionaryFacilitator); - // Create Suggest instance with the new dictionary facilitator. - replaceSuggest(new Suggest(oldSuggest, dictionaryFacilitator)); - } else if (oldSuggest == null) { - initSuggest(); + if (!mHandler.hasPendingReopenDictionaries()) { + resetSuggestForLocale(locale); } + refreshPersonalizationDictionarySession(); } private void refreshPersonalizationDictionarySession() { - final Suggest suggest = mInputLogic.mSuggest; + final DictionaryFacilitatorForSuggest dictionaryFacilitator = + mInputLogic.mSuggest.mDictionaryFacilitator; final boolean shouldKeepUserHistoryDictionaries; final boolean shouldKeepPersonalizationDictionaries; if (mSettings.getCurrent().mUsePersonalizedDicts) { @@ -559,17 +545,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (!shouldKeepUserHistoryDictionaries) { // Remove user history dictionaries. PersonalizationHelper.removeAllUserHistoryDictionaries(this); - if (suggest != null) { - suggest.mDictionaryFacilitator.clearUserHistoryDictionary(); - } + dictionaryFacilitator.clearUserHistoryDictionary(); } if (!shouldKeepPersonalizationDictionaries) { // Remove personalization dictionaries. PersonalizationHelper.removeAllPersonalizationDictionaries(this); PersonalizationDictionarySessionRegistrar.resetAll(this); } else { - final DictionaryFacilitatorForSuggest dictionaryFacilitator = - (suggest == null) ? null : suggest.mDictionaryFacilitator; PersonalizationDictionarySessionRegistrar.init(this, dictionaryFacilitator); } } @@ -583,7 +565,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - private void initSuggest() { + private void resetSuggest() { final Locale switcherSubtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); final String switcherLocaleStr = switcherSubtypeLocale.toString(); final Locale subtypeLocale; @@ -599,47 +581,42 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } else { subtypeLocale = switcherSubtypeLocale; } - initSuggestForLocale(mInputLogic.mSuggest, subtypeLocale); + resetSuggestForLocale(subtypeLocale); } - private void initSuggestForLocale(final Suggest oldSuggest, final Locale locale) { - final SettingsValues settingsValues = mSettings.getCurrent(); - final DictionaryFacilitatorForSuggest oldDictionaryFacilitator = - (oldSuggest == null) ? null : oldSuggest.mDictionaryFacilitator; - // Creates new dictionary facilitator for the new locale. + /** + * Reset suggest by loading dictionaries for the locale and the current settings values. + * + * @param locale the locale + */ + private void resetSuggestForLocale(final Locale locale) { final DictionaryFacilitatorForSuggest dictionaryFacilitator = - new DictionaryFacilitatorForSuggest(this /* context */, locale, settingsValues, - this /* DictionaryInitializationListener */, oldDictionaryFacilitator); - final Suggest newSuggest = new Suggest(locale, dictionaryFacilitator); - if (settingsValues.mCorrectionEnabled) { - newSuggest.setAutoCorrectionThreshold(settingsValues.mAutoCorrectionThreshold); - } - replaceSuggest(newSuggest); - } - - /* package private */ void resetSuggestMainDict() { - final DictionaryFacilitatorForSuggest oldDictionaryFacilitator = mInputLogic.mSuggest.mDictionaryFacilitator; - final DictionaryFacilitatorForSuggest dictionaryFacilitator = - new DictionaryFacilitatorForSuggest(this /* listener */, oldDictionaryFacilitator); - replaceSuggest(new Suggest(mInputLogic.mSuggest /* oldSuggest */, dictionaryFacilitator)); + final SettingsValues settingsValues = mSettings.getCurrent(); + dictionaryFacilitator.resetDictionaries(this /* context */, locale, + settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts, + false /* forceReloadMainDictionary */, this); + if (settingsValues.mCorrectionEnabled) { + mInputLogic.mSuggest.setAutoCorrectionThreshold( + settingsValues.mAutoCorrectionThreshold); + } } - private void replaceSuggest(final Suggest newSuggest) { - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.getInstance().initDictionary(newSuggest.mDictionaryFacilitator); - } - mInputLogic.replaceSuggest(newSuggest); - refreshPersonalizationDictionarySession(); + /** + * 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, + settingsValues.mUsePersonalizedDicts, true /* forceReloadMainDictionary */, this); } @Override public void onDestroy() { - final Suggest suggest = mInputLogic.mSuggest; - if (suggest != null) { - suggest.close(); - mInputLogic.mSuggest = null; - } + mInputLogic.mSuggest.mDictionaryFacilitator.closeDictionaries(); mSettings.onDestroy(); unregisterReceiver(mReceiver); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { @@ -802,10 +779,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Note: the following does a round-trip IPC on the main thread: be careful final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); - Suggest suggest = mInputLogic.mSuggest; - if (null != suggest && null != currentLocale && !currentLocale.equals(suggest.mLocale)) { - initSuggest(); - suggest = mInputLogic.mSuggest; + final Suggest suggest = mInputLogic.mSuggest; + if (null != currentLocale && !currentLocale.equals(suggest.getLocale())) { + // TODO: Do this automatically. + resetSuggest(); } // TODO[IL]: Can the following be moved to InputLogic#startInput? @@ -833,13 +810,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (isDifferentTextField || !currentSettingsValues.hasSameOrientation(getResources().getConfiguration())) { loadSettings(); - suggest = mInputLogic.mSuggest; } if (isDifferentTextField) { mainKeyboardView.closing(); currentSettingsValues = mSettings.getCurrent(); - if (suggest != null && currentSettingsValues.mCorrectionEnabled) { + if (currentSettingsValues.mCorrectionEnabled) { suggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold); } @@ -865,8 +841,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mHandler.cancelUpdateSuggestionStrip(); - mainKeyboardView.setMainDictionaryAvailability(null != suggest - ? suggest.mDictionaryFacilitator.hasMainDictionary() : false); + mainKeyboardView.setMainDictionaryAvailability( + suggest.mDictionaryFacilitator.hasInitializedMainDictionary()); mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn, currentSettingsValues.mKeyPreviewPopupDismissDelay); mainKeyboardView.setSlidingKeyInputPreviewEnabled( @@ -1407,8 +1383,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen public void getSuggestedWords(final int sessionId, final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); - final Suggest suggest = mInputLogic.mSuggest; - if (keyboard == null || suggest == null) { + if (keyboard == null) { callback.onGetSuggestedWords(SuggestedWords.EMPTY); return; } @@ -1437,7 +1412,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } } - suggest.getSuggestedWords(mInputLogic.mWordComposer, + mInputLogic.mSuggest.getSuggestedWords(mInputLogic.mWordComposer, mInputLogic.mWordComposer.getPreviousWordForSuggestion(), keyboard.getProximityInfo(), currentSettings.mBlockPotentiallyOffensive, currentSettings.mCorrectionEnabled, additionalFeaturesOptions, sessionId, @@ -1722,12 +1697,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // 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 DictionaryFacilitatorForSuggest oldDictionaryFacilitator = - mInputLogic.mSuggest.mDictionaryFacilitator; - final DictionaryFacilitatorForSuggest dictionaryFacilitator = - new DictionaryFacilitatorForSuggest(this, locale, mSettings.getCurrent(), - this /* listener */, oldDictionaryFacilitator); - replaceSuggest(new Suggest(locale, dictionaryFacilitator)); + final SettingsValues settingsValues = mSettings.getCurrent(); + mInputLogic.mSuggest.mDictionaryFacilitator.resetDictionaries(this, locale, + settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts, + false /* forceReloadMainDictionary */, this /* listener */); } // DO NOT USE THIS for any other purpose than testing. @@ -1738,8 +1711,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } public void dumpDictionaryForDebug(final String dictName) { - if (mInputLogic.mSuggest == null) { - initSuggest(); + final DictionaryFacilitatorForSuggest dictionaryFacilitator = + mInputLogic.mSuggest.mDictionaryFacilitator; + if (dictionaryFacilitator.getLocale() == null) { + resetSuggest(); } mInputLogic.mSuggest.mDictionaryFacilitator.dumpDictionaryForDebug(dictName); } diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index ba64028ca..9877dc853 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -23,12 +23,11 @@ import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.utils.AutoCorrectionUtils; import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; -import com.android.inputmethod.latin.utils.BoundedTreeSet; import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.StringUtils; +import com.android.inputmethod.latin.utils.SuggestionResults; import java.util.ArrayList; -import java.util.Comparator; import java.util.Locale; /** @@ -53,29 +52,16 @@ public final class Suggest { private static final int SUPPRESS_SUGGEST_THRESHOLD = -2000000000; private static final boolean DBG = LatinImeLogger.sDBG; - public final DictionaryFacilitatorForSuggest mDictionaryFacilitator; + public final DictionaryFacilitatorForSuggest mDictionaryFacilitator = + new DictionaryFacilitatorForSuggest(); private float mAutoCorrectionThreshold; - // Locale used for upper- and title-casing words - public final Locale mLocale; - - // TODO: Move dictionaryFacilitator constructing logics from LatinIME to Suggest. - public Suggest(final Locale locale, - final DictionaryFacilitatorForSuggest dictionaryFacilitator) { - mLocale = locale; - mDictionaryFacilitator = dictionaryFacilitator; + public Locale getLocale() { + return mDictionaryFacilitator.getLocale(); } - // Creates instance with new dictionary facilitator. - public Suggest(final Suggest oldSuggst, - final DictionaryFacilitatorForSuggest dictionaryFacilitator) { - mLocale = oldSuggst.mLocale; - mAutoCorrectionThreshold = oldSuggst.mAutoCorrectionThreshold; - mDictionaryFacilitator = dictionaryFacilitator; - } - - public void setAutoCorrectionThreshold(float threshold) { + public void setAutoCorrectionThreshold(final float threshold) { mAutoCorrectionThreshold = threshold; } @@ -108,9 +94,6 @@ public final class Suggest { final int[] additionalFeaturesOptions, final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { final int trailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount(); - final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator, - SuggestedWords.MAX_SUGGESTIONS); - final String typedWord = wordComposer.getTypedWord(); final String consideredWord = trailingSingleQuotesCount > 0 ? typedWord.substring(0, typedWord.length() - trailingSingleQuotesCount) @@ -132,20 +115,20 @@ public final class Suggest { } else { rawSuggestions = null; } - mDictionaryFacilitator.getSuggestions(wordComposerForLookup, prevWordForBigram, - proximityInfo, blockOffensiveWords, additionalFeaturesOptions, SESSION_TYPING, - suggestionsSet, rawSuggestions); + final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults( + wordComposerForLookup, prevWordForBigram, proximityInfo, blockOffensiveWords, + additionalFeaturesOptions, SESSION_TYPING, rawSuggestions); final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized(); final boolean isAllUpperCase = wordComposer.isAllUpperCase(); final String firstSuggestion; final String whitelistedWord; - if (suggestionsSet.isEmpty()) { + if (suggestionResults.isEmpty()) { whitelistedWord = firstSuggestion = null; } else { final SuggestedWordInfo firstSuggestedWordInfo = getTransformedSuggestedWordInfo( - suggestionsSet.first(), mLocale, isAllUpperCase, isFirstCharCapitalized, - trailingSingleQuotesCount); + suggestionResults.first(), suggestionResults.mLocale, isAllUpperCase, + isFirstCharCapitalized, trailingSingleQuotesCount); firstSuggestion = firstSuggestedWordInfo.mWord; if (SuggestedWordInfo.KIND_WHITELIST != firstSuggestedWordInfo.mKind) { whitelistedWord = null; @@ -175,10 +158,10 @@ public final class Suggest { // the current settings. It may also be useful to know, when the setting is off, whether // the word *would* have been auto-corrected. if (!isCorrectionEnabled || !allowsToBeAutoCorrected || isPrediction - || suggestionsSet.isEmpty() || wordComposer.hasDigits() + || suggestionResults.isEmpty() || wordComposer.hasDigits() || wordComposer.isMostlyCaps() || wordComposer.isResumed() - || !mDictionaryFacilitator.hasMainDictionary() - || SuggestedWordInfo.KIND_SHORTCUT == suggestionsSet.first().mKind) { + || !mDictionaryFacilitator.hasInitializedMainDictionary() + || SuggestedWordInfo.KIND_SHORTCUT == suggestionResults.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 // their language, and it will unexpectedly auto-correct. For example, if the user @@ -190,17 +173,17 @@ public final class Suggest { hasAutoCorrection = false; } else { hasAutoCorrection = AutoCorrectionUtils.suggestionExceedsAutoCorrectionThreshold( - suggestionsSet.first(), consideredWord, mAutoCorrectionThreshold); + suggestionResults.first(), consideredWord, mAutoCorrectionThreshold); } final ArrayList suggestionsContainer = - CollectionUtils.newArrayList(suggestionsSet); + CollectionUtils.newArrayList(suggestionResults); final int suggestionsCount = suggestionsContainer.size(); if (isFirstCharCapitalized || isAllUpperCase || 0 != trailingSingleQuotesCount) { for (int i = 0; i < suggestionsCount; ++i) { final SuggestedWordInfo wordInfo = suggestionsContainer.get(i); final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo( - wordInfo, mLocale, isAllUpperCase, isFirstCharCapitalized, + wordInfo, suggestionResults.mLocale, isAllUpperCase, isFirstCharCapitalized, trailingSingleQuotesCount); suggestionsContainer.set(i, transformedWordInfo); } @@ -244,23 +227,21 @@ public final class Suggest { final boolean blockOffensiveWords, final int[] additionalFeaturesOptions, final int sessionId, final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { - final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator, - SuggestedWords.MAX_SUGGESTIONS); final ArrayList rawSuggestions; if (ProductionFlag.INCLUDE_RAW_SUGGESTIONS) { rawSuggestions = CollectionUtils.newArrayList(); } else { rawSuggestions = null; } - mDictionaryFacilitator.getSuggestions(wordComposer, prevWordForBigram, proximityInfo, - blockOffensiveWords, additionalFeaturesOptions, sessionId, suggestionsSet, - rawSuggestions); - for (SuggestedWordInfo wordInfo : suggestionsSet) { + final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults( + wordComposer, prevWordForBigram, proximityInfo, blockOffensiveWords, + additionalFeaturesOptions, sessionId, rawSuggestions); + for (SuggestedWordInfo wordInfo : suggestionResults) { LatinImeLogger.onAddSuggestedWord(wordInfo.mWord, wordInfo.mSourceDict.mDictType); } final ArrayList suggestionsContainer = - CollectionUtils.newArrayList(suggestionsSet); + CollectionUtils.newArrayList(suggestionResults); final int suggestionsCount = suggestionsContainer.size(); final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock(); final boolean isAllUpperCase = wordComposer.isAllUpperCase(); @@ -268,7 +249,7 @@ public final class Suggest { for (int i = 0; i < suggestionsCount; ++i) { final SuggestedWordInfo wordInfo = suggestionsContainer.get(i); final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo( - wordInfo, mLocale, isAllUpperCase, isFirstCharCapitalized, + wordInfo, suggestionResults.mLocale, isAllUpperCase, isFirstCharCapitalized, 0 /* trailingSingleQuotesCount */); suggestionsContainer.set(i, transformedWordInfo); } @@ -326,22 +307,6 @@ public final class Suggest { return suggestionsList; } - private static final class SuggestedWordInfoComparator - implements Comparator { - // This comparator ranks the word info with the higher frequency first. That's because - // that's the order we want our elements in. - @Override - public int compare(final SuggestedWordInfo o1, final SuggestedWordInfo o2) { - if (o1.mScore > o2.mScore) return -1; - if (o1.mScore < o2.mScore) return 1; - if (o1.mCodePointCount < o2.mCodePointCount) return -1; - if (o1.mCodePointCount > o2.mCodePointCount) return 1; - return o1.mWord.compareTo(o2.mWord); - } - } - private static final SuggestedWordInfoComparator sSuggestedWordInfoComparator = - new SuggestedWordInfoComparator(); - /* package for test */ static SuggestedWordInfo getTransformedSuggestedWordInfo( final SuggestedWordInfo wordInfo, final Locale locale, final boolean isAllUpperCase, final boolean isFirstCharCapitalized, final int trailingSingleQuotesCount) { @@ -365,8 +330,4 @@ public final class Suggest { wordInfo.mSourceDict, wordInfo.mIndexOfTouchPointOfSecondWord, wordInfo.mAutoCommitFirstWordConfidence); } - - public void close() { - 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 36b30eabe..ffa5e8e89 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java @@ -32,6 +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.InputPointers; import com.android.inputmethod.latin.LastComposedWord; import com.android.inputmethod.latin.LatinIME; @@ -77,8 +78,7 @@ public final class InputLogic { private int mSpaceState; // Never null public SuggestedWords mSuggestedWords = SuggestedWords.EMPTY; - // TODO: mSuggest should be touched by a single thread. - public volatile Suggest mSuggest; + public final Suggest mSuggest = new Suggest(); public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD; public final WordComposer mWordComposer; @@ -106,15 +106,6 @@ public final class InputLogic { mInputLogicHandler = InputLogicHandler.NULL_HANDLER; } - // Replace the old Suggest with the passed Suggest and close it. - public void replaceSuggest(final Suggest newSuggest) { - final Suggest oldSuggest = mSuggest; - mSuggest = newSuggest; - if (oldSuggest != null) { - oldSuggest.close(); - } - } - /** * Initializes the input logic for input in an editor. * @@ -283,23 +274,18 @@ 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). - // Please note that if mSuggest is null, it means that everything is off: suggestion - // and correction, so we shouldn't try to show the hint - final Suggest suggest = mSuggest; + final DictionaryFacilitatorForSuggest dictionaryFacilitator = + mSuggest.mDictionaryFacilitator; final boolean showingAddToDictionaryHint = (SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind) - && suggest != null - // If the suggestion is not in the dictionary, the hint should be shown. - && !suggest.mDictionaryFacilitator.isValidWord(suggestion, - true /* ignoreCase */); + && !dictionaryFacilitator.isValidWord(suggestion, true /* ignoreCase */); if (settingsValues.mIsInternal) { LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); } - if (showingAddToDictionaryHint - && suggest.mDictionaryFacilitator.isUserDictionaryEnabled()) { + if (showingAddToDictionaryHint && dictionaryFacilitator.isUserDictionaryEnabled()) { mSuggestionStripViewAccessor.showAddToDictionaryHint(suggestion); } else { // If we're not showing the "Touch again to save", then update the suggestion strip. @@ -1231,20 +1217,17 @@ public final class InputLogic { if (!settingsValues.mCorrectionEnabled) return; if (TextUtils.isEmpty(suggestion)) return; - final Suggest suggest = mSuggest; - if (suggest == null) return; - final boolean wasAutoCapitalized = mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps(); final int timeStampInSeconds = (int)TimeUnit.MILLISECONDS.toSeconds( System.currentTimeMillis()); - suggest.mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized, prevWord, + mSuggest.mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized, prevWord, timeStampInSeconds); } public void performUpdateSuggestionStripSync(final SettingsValues settingsValues) { // Check if we have a suggestion engine attached. - if (mSuggest == null || !settingsValues.isSuggestionsRequested()) { + if (!settingsValues.isSuggestionsRequested()) { if (mWordComposer.isComposingWord()) { Log.w(TAG, "Called updateSuggestionsOrPredictions but suggestions were not " + "requested!"); @@ -1446,10 +1429,8 @@ public final class InputLogic { } mConnection.deleteSurroundingText(deleteLength, 0); if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) { - if (mSuggest != null) { - mSuggest.mDictionaryFacilitator.cancelAddingUserHistory( - previousWord, committedWordString); - } + mSuggest.mDictionaryFacilitator.cancelAddingUserHistory( + previousWord, committedWordString); } final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString; final SpannableString textToCommit = new SpannableString(stringToCommit); diff --git a/java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java b/java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java deleted file mode 100644 index ae1fd3f79..000000000 --- a/java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2012 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.utils; - -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; - -import java.util.Collection; -import java.util.Comparator; -import java.util.TreeSet; - -/** - * A TreeSet that is bounded in size and throws everything that's smaller than its limit - */ -public final class BoundedTreeSet extends TreeSet { - private final int mCapacity; - public BoundedTreeSet(final Comparator comparator, final int capacity) { - super(comparator); - mCapacity = capacity; - } - - @Override - public boolean add(final SuggestedWordInfo e) { - if (size() < mCapacity) return super.add(e); - if (comparator().compare(e, last()) > 0) return false; - super.add(e); - pollLast(); // removes the last element - return true; - } - - @Override - public boolean addAll(final Collection e) { - if (null == e) return false; - return super.addAll(e); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java index 562ff9e8d..acd16a9e4 100644 --- a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java +++ b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java @@ -119,7 +119,7 @@ public final class LanguageModelParam { private static LanguageModelParam detectWhetherVaildWordOrNotAndGetLanguageModelParam( final String prevWord, final String targetWord, final int timestamp, final DictionaryFacilitatorForSuggest dictionaryFacilitator) { - final Locale locale = dictionaryFacilitator.mLocale; + final Locale locale = dictionaryFacilitator.getLocale(); if (!dictionaryFacilitator.isValidWord(targetWord, true /* ignoreCase */)) { // OOV word. return createAndGetLanguageModelParamOfWord(prevWord, targetWord, timestamp, diff --git a/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java new file mode 100644 index 000000000..0b362c48a --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2014 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.utils; + +import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Locale; +import java.util.TreeSet; + +/** + * A TreeSet of SuggestedWordInfo that is bounded in size and throws everything that's smaller + * than its limit + */ +public final class SuggestionResults extends TreeSet { + public final Locale mLocale; + private final int mCapacity; + + public SuggestionResults(final Locale locale, final int capacity) { + this(locale, sSuggestedWordInfoComparator, capacity); + } + + public SuggestionResults(final Locale locale, final Comparator comparator, + final int capacity) { + super(comparator); + mLocale = locale; + mCapacity = capacity; + } + + @Override + public boolean add(final SuggestedWordInfo e) { + if (size() < mCapacity) return super.add(e); + if (comparator().compare(e, last()) > 0) return false; + super.add(e); + pollLast(); // removes the last element + return true; + } + + @Override + public boolean addAll(final Collection e) { + if (null == e) return false; + return super.addAll(e); + } + + private static final class SuggestedWordInfoComparator + implements Comparator { + // This comparator ranks the word info with the higher frequency first. That's because + // that's the order we want our elements in. + @Override + public int compare(final SuggestedWordInfo o1, final SuggestedWordInfo o2) { + if (o1.mScore > o2.mScore) return -1; + if (o1.mScore < o2.mScore) return 1; + if (o1.mCodePointCount < o2.mCodePointCount) return -1; + if (o1.mCodePointCount > o2.mCodePointCount) return 1; + return o1.mWord.compareTo(o2.mWord); + } + } + + private static final SuggestedWordInfoComparator sSuggestedWordInfoComparator = + new SuggestedWordInfoComparator(); +} diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java index 9b1d8c535..ffdb43c15 100644 --- a/java/src/com/android/inputmethod/research/MainLogBuffer.java +++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java @@ -155,8 +155,9 @@ public abstract class MainLogBuffer extends FixedLogBuffer { } // Reload the dictionary in case it has changed (e.g., because the user has changed // languages). - if ((mDictionaryFacilitator == null || !mDictionaryFacilitator.hasMainDictionary()) - && mDictionaryForTesting == null) { + if ((mDictionaryFacilitator == null + || !mDictionaryFacilitator.hasInitializedMainDictionary()) + && mDictionaryForTesting == null) { // Main dictionary is unavailable. Since we cannot check it, we cannot tell if a // word is out-of-vocabulary or not. Therefore, we must judge the entire buffer // contents to potentially pose a privacy risk.