From 459b4f353e6138b644c1f06de68e93532ee0d856 Mon Sep 17 00:00:00 2001 From: Dan Zivkovic Date: Tue, 28 Apr 2015 18:46:17 -0700 Subject: [PATCH] Spelling cannot cache words across invocations. We want to let the facilitator decide if a word is valid or invalid, and cache the answer in the facilitator's cache. The spell checker session doesn't need its own word cache, except as a crutch to communicate suggestions to the code that populates the suggestion drop-down. We leave that in place. Bug 20018546. Change-Id: I3c3c53e0c1d709fa2f64a2952a232acd7380b57a --- .../latin/DictionaryFacilitator.java | 13 ++++++ .../latin/DictionaryFacilitatorImpl.java | 40 +++++++++++++++++++ .../AndroidSpellCheckerSession.java | 3 +- .../AndroidWordLevelSpellCheckerSession.java | 32 +++++---------- 4 files changed, 63 insertions(+), 25 deletions(-) diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java index ff798abd6..9d8bdb2d1 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java @@ -17,6 +17,7 @@ package com.android.inputmethod.latin; import android.content.Context; +import android.util.LruCache; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.Keyboard; @@ -54,6 +55,18 @@ public interface DictionaryFacilitator { Dictionary.TYPE_USER_HISTORY, Dictionary.TYPE_USER}; + /** + * The facilitator will put words into the cache whenever it decodes them. + * @param cache + */ + void setValidSpellingWordReadCache(final LruCache cache); + + /** + * The facilitator will get words from the cache whenever it needs to check their spelling. + * @param cache + */ + void setValidSpellingWordWriteCache(final LruCache cache); + /** * Returns whether this facilitator is exactly for this locale. * diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java index 7233d27ab..6508eb289 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java @@ -19,6 +19,7 @@ package com.android.inputmethod.latin; import android.content.Context; import android.text.TextUtils; import android.util.Log; +import android.util.LruCache; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.Keyboard; @@ -82,6 +83,19 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator { private static final Class[] DICT_FACTORY_METHOD_ARG_TYPES = new Class[] { Context.class, Locale.class, File.class, String.class, String.class }; + private LruCache mValidSpellingWordReadCache; + private LruCache mValidSpellingWordWriteCache; + + @Override + public void setValidSpellingWordReadCache(final LruCache cache) { + mValidSpellingWordReadCache = cache; + } + + @Override + public void setValidSpellingWordWriteCache(final LruCache cache) { + mValidSpellingWordWriteCache = cache; + } + @Override public boolean isForLocale(final Locale locale) { return locale != null && locale.equals(mDictionaryGroup.mLocale); @@ -341,6 +355,10 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator { dictionarySetToCleanup.closeDict(dictType); } } + + if (mValidSpellingWordWriteCache != null) { + mValidSpellingWordWriteCache.evictAll(); + } } private void asyncReloadUninitializedMainDictionaries(final Context context, @@ -464,6 +482,10 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator { public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized, @Nonnull final NgramContext ngramContext, final long timeStampInSeconds, final boolean blockPotentiallyOffensive) { + // Update the spelling cache before learning. Words that are not yet added to user history + // and appear in no other language model are not considered valid. + putWordIntoValidSpellingWordCache("addToUserHistory", suggestion); + final String[] words = suggestion.split(Constants.WORD_SEPARATOR); NgramContext ngramContextForCurrentWord = ngramContext; for (int i = 0; i < words.length; i++) { @@ -477,6 +499,12 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator { } } + private void putWordIntoValidSpellingWordCache(final String caller, final String word) { + final String spellingWord = word.toLowerCase(getLocale()); + final boolean isValid = isValidSpellingWord(spellingWord); + mValidSpellingWordWriteCache.put(spellingWord, isValid); + } + private void addWordToUserHistory(final DictionaryGroup dictionaryGroup, final NgramContext ngramContext, final String word, final boolean wasAutoCapitalized, final int timeStampInSeconds, final boolean blockPotentiallyOffensive) { @@ -543,6 +571,10 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator { if (eventType != Constants.EVENT_BACKSPACE) { removeWord(Dictionary.TYPE_USER_HISTORY, word); } + + // Update the spelling cache after unlearning. Words that are removed from user history + // and appear in no other language model are not considered valid. + putWordIntoValidSpellingWordCache("unlearnFromUserHistory", word.toLowerCase()); } // TODO: Revise the way to fusion suggestion results. @@ -577,6 +609,14 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator { } public boolean isValidSpellingWord(final String word) { + if (mValidSpellingWordReadCache != null) { + final String spellingWord = word.toLowerCase(getLocale()); + final Boolean cachedValue = mValidSpellingWordReadCache.get(spellingWord); + if (cachedValue != null) { + return cachedValue; + } + } + return isValidWord(word, ALL_DICTIONARY_TYPES); } diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java index 2c690aea7..c7622e7a1 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java @@ -84,8 +84,7 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck if (TextUtils.isEmpty(splitText)) { continue; } - if (mSuggestionsCache.getSuggestionsFromCache(splitText.toString(), ngramContext) - == null) { + if (mSuggestionsCache.getSuggestionsFromCache(splitText.toString()) == null) { continue; } final int newLength = splitText.length(); diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java index 1322ce240..9223923a7 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java @@ -71,30 +71,26 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { } protected static final class SuggestionsCache { - private static final char CHAR_DELIMITER = '\uFFFC'; private static final int MAX_CACHE_SIZE = 50; private final LruCache mUnigramSuggestionsInfoCache = new LruCache<>(MAX_CACHE_SIZE); - private static String generateKey(final String query, final NgramContext ngramContext) { - if (TextUtils.isEmpty(query) || !ngramContext.isValid()) { - return query; - } - return query + CHAR_DELIMITER + ngramContext; + private static String generateKey(final String query) { + return query + ""; } - public SuggestionsParams getSuggestionsFromCache(String query, - final NgramContext ngramContext) { - return mUnigramSuggestionsInfoCache.get(generateKey(query, ngramContext)); + public SuggestionsParams getSuggestionsFromCache(final String query) { + return mUnigramSuggestionsInfoCache.get(query); } - public void putSuggestionsToCache(final String query, final NgramContext ngramContext, - final String[] suggestions, final int flags) { + public void putSuggestionsToCache( + final String query, final String[] suggestions, final int flags) { if (suggestions == null || TextUtils.isEmpty(query)) { return; } mUnigramSuggestionsInfoCache.put( - generateKey(query, ngramContext), new SuggestionsParams(suggestions, flags)); + generateKey(query), + new SuggestionsParams(suggestions, flags)); } public void clearCache() { @@ -232,16 +228,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { AndroidSpellCheckerService.SINGLE_QUOTE). replaceAll("^" + quotesRegexp, ""). replaceAll(quotesRegexp + "$", ""); - final SuggestionsParams cachedSuggestionsParams = - mSuggestionsCache.getSuggestionsFromCache(text, ngramContext); - if (cachedSuggestionsParams != null) { - Log.d(TAG, "onGetSuggestionsInternal() : Cache hit for [" + text + "]"); - return new SuggestionsInfo( - cachedSuggestionsParams.mFlags, cachedSuggestionsParams.mSuggestions); - } - - // If spell checking is impossible, return early. if (!mService.hasMainDictionaryForLocale(mLocale)) { return AndroidSpellCheckerService.getNotInDictEmptySuggestions( false /* reportAsTypo */); @@ -329,8 +316,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { .getValueOf_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS() : 0); final SuggestionsInfo retval = new SuggestionsInfo(flags, result.mSuggestions); - mSuggestionsCache.putSuggestionsToCache(text, ngramContext, result.mSuggestions, - flags); + mSuggestionsCache.putSuggestionsToCache(text, result.mSuggestions, flags); return retval; } catch (RuntimeException e) { // Don't kill the keyboard if there is a bug in the spell checker