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
main
Dan Zivkovic 2015-04-28 18:46:17 -07:00
parent 82bf4a6aee
commit 459b4f353e
4 changed files with 63 additions and 25 deletions

View File

@ -17,6 +17,7 @@
package com.android.inputmethod.latin; package com.android.inputmethod.latin;
import android.content.Context; import android.content.Context;
import android.util.LruCache;
import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.Keyboard;
@ -54,6 +55,18 @@ public interface DictionaryFacilitator {
Dictionary.TYPE_USER_HISTORY, Dictionary.TYPE_USER_HISTORY,
Dictionary.TYPE_USER}; Dictionary.TYPE_USER};
/**
* The facilitator will put words into the cache whenever it decodes them.
* @param cache
*/
void setValidSpellingWordReadCache(final LruCache<String, Boolean> cache);
/**
* The facilitator will get words from the cache whenever it needs to check their spelling.
* @param cache
*/
void setValidSpellingWordWriteCache(final LruCache<String, Boolean> cache);
/** /**
* Returns whether this facilitator is exactly for this locale. * Returns whether this facilitator is exactly for this locale.
* *

View File

@ -19,6 +19,7 @@ package com.android.inputmethod.latin;
import android.content.Context; import android.content.Context;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.util.LruCache;
import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.Keyboard;
@ -82,6 +83,19 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
private static final Class<?>[] DICT_FACTORY_METHOD_ARG_TYPES = private static final Class<?>[] DICT_FACTORY_METHOD_ARG_TYPES =
new Class[] { Context.class, Locale.class, File.class, String.class, String.class }; new Class[] { Context.class, Locale.class, File.class, String.class, String.class };
private LruCache<String, Boolean> mValidSpellingWordReadCache;
private LruCache<String, Boolean> mValidSpellingWordWriteCache;
@Override
public void setValidSpellingWordReadCache(final LruCache<String, Boolean> cache) {
mValidSpellingWordReadCache = cache;
}
@Override
public void setValidSpellingWordWriteCache(final LruCache<String, Boolean> cache) {
mValidSpellingWordWriteCache = cache;
}
@Override @Override
public boolean isForLocale(final Locale locale) { public boolean isForLocale(final Locale locale) {
return locale != null && locale.equals(mDictionaryGroup.mLocale); return locale != null && locale.equals(mDictionaryGroup.mLocale);
@ -341,6 +355,10 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
dictionarySetToCleanup.closeDict(dictType); dictionarySetToCleanup.closeDict(dictType);
} }
} }
if (mValidSpellingWordWriteCache != null) {
mValidSpellingWordWriteCache.evictAll();
}
} }
private void asyncReloadUninitializedMainDictionaries(final Context context, private void asyncReloadUninitializedMainDictionaries(final Context context,
@ -464,6 +482,10 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized, public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
@Nonnull final NgramContext ngramContext, final long timeStampInSeconds, @Nonnull final NgramContext ngramContext, final long timeStampInSeconds,
final boolean blockPotentiallyOffensive) { 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); final String[] words = suggestion.split(Constants.WORD_SEPARATOR);
NgramContext ngramContextForCurrentWord = ngramContext; NgramContext ngramContextForCurrentWord = ngramContext;
for (int i = 0; i < words.length; i++) { 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, private void addWordToUserHistory(final DictionaryGroup dictionaryGroup,
final NgramContext ngramContext, final String word, final boolean wasAutoCapitalized, final NgramContext ngramContext, final String word, final boolean wasAutoCapitalized,
final int timeStampInSeconds, final boolean blockPotentiallyOffensive) { final int timeStampInSeconds, final boolean blockPotentiallyOffensive) {
@ -543,6 +571,10 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
if (eventType != Constants.EVENT_BACKSPACE) { if (eventType != Constants.EVENT_BACKSPACE) {
removeWord(Dictionary.TYPE_USER_HISTORY, word); 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. // TODO: Revise the way to fusion suggestion results.
@ -577,6 +609,14 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
} }
public boolean isValidSpellingWord(final String word) { 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); return isValidWord(word, ALL_DICTIONARY_TYPES);
} }

View File

@ -84,8 +84,7 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
if (TextUtils.isEmpty(splitText)) { if (TextUtils.isEmpty(splitText)) {
continue; continue;
} }
if (mSuggestionsCache.getSuggestionsFromCache(splitText.toString(), ngramContext) if (mSuggestionsCache.getSuggestionsFromCache(splitText.toString()) == null) {
== null) {
continue; continue;
} }
final int newLength = splitText.length(); final int newLength = splitText.length();

View File

@ -71,30 +71,26 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
} }
protected static final class SuggestionsCache { protected static final class SuggestionsCache {
private static final char CHAR_DELIMITER = '\uFFFC';
private static final int MAX_CACHE_SIZE = 50; private static final int MAX_CACHE_SIZE = 50;
private final LruCache<String, SuggestionsParams> mUnigramSuggestionsInfoCache = private final LruCache<String, SuggestionsParams> mUnigramSuggestionsInfoCache =
new LruCache<>(MAX_CACHE_SIZE); new LruCache<>(MAX_CACHE_SIZE);
private static String generateKey(final String query, final NgramContext ngramContext) { private static String generateKey(final String query) {
if (TextUtils.isEmpty(query) || !ngramContext.isValid()) { return query + "";
return query;
}
return query + CHAR_DELIMITER + ngramContext;
} }
public SuggestionsParams getSuggestionsFromCache(String query, public SuggestionsParams getSuggestionsFromCache(final String query) {
final NgramContext ngramContext) { return mUnigramSuggestionsInfoCache.get(query);
return mUnigramSuggestionsInfoCache.get(generateKey(query, ngramContext));
} }
public void putSuggestionsToCache(final String query, final NgramContext ngramContext, public void putSuggestionsToCache(
final String[] suggestions, final int flags) { final String query, final String[] suggestions, final int flags) {
if (suggestions == null || TextUtils.isEmpty(query)) { if (suggestions == null || TextUtils.isEmpty(query)) {
return; return;
} }
mUnigramSuggestionsInfoCache.put( mUnigramSuggestionsInfoCache.put(
generateKey(query, ngramContext), new SuggestionsParams(suggestions, flags)); generateKey(query),
new SuggestionsParams(suggestions, flags));
} }
public void clearCache() { public void clearCache() {
@ -232,16 +228,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
AndroidSpellCheckerService.SINGLE_QUOTE). AndroidSpellCheckerService.SINGLE_QUOTE).
replaceAll("^" + quotesRegexp, ""). replaceAll("^" + quotesRegexp, "").
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)) { if (!mService.hasMainDictionaryForLocale(mLocale)) {
return AndroidSpellCheckerService.getNotInDictEmptySuggestions( return AndroidSpellCheckerService.getNotInDictEmptySuggestions(
false /* reportAsTypo */); false /* reportAsTypo */);
@ -329,8 +316,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
.getValueOf_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS() .getValueOf_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS()
: 0); : 0);
final SuggestionsInfo retval = new SuggestionsInfo(flags, result.mSuggestions); final SuggestionsInfo retval = new SuggestionsInfo(flags, result.mSuggestions);
mSuggestionsCache.putSuggestionsToCache(text, ngramContext, result.mSuggestions, mSuggestionsCache.putSuggestionsToCache(text, result.mSuggestions, flags);
flags);
return retval; return retval;
} catch (RuntimeException e) { } catch (RuntimeException e) {
// Don't kill the keyboard if there is a bug in the spell checker // Don't kill the keyboard if there is a bug in the spell checker