Only set LOOKS_LIKE_TYPO if the max score meets a threshold

Bug: 5240463
Change-Id: I51e85edae57789d638aa1e12b82e6a75c49d33c7
main
Jean Chalard 2011-09-02 14:36:04 +09:00
parent 787bac0603
commit 59b501a050
2 changed files with 86 additions and 23 deletions

View File

@ -80,6 +80,8 @@
will be subject to auto-correction. --> will be subject to auto-correction. -->
<item>0</item> <item>0</item>
</string-array> </string-array>
<!-- Threshold of the normalized score of the best suggestion for the spell checker to declare a word to be a typo -->
<string name="spellchecker_typo_threshold_value">0.11</string>
<!-- Screen metrics for logging. 0 = "mdpi", 1 = "hdpi", 2 = "xlarge" --> <!-- Screen metrics for logging. 0 = "mdpi", 1 = "hdpi", 2 = "xlarge" -->
<integer name="log_screen_metrics">0</integer> <integer name="log_screen_metrics">0</integer>
<bool name="config_require_umlaut_processing">false</bool> <bool name="config_require_umlaut_processing">false</bool>

View File

@ -34,6 +34,7 @@ import com.android.inputmethod.latin.Dictionary.WordCallback;
import com.android.inputmethod.latin.DictionaryCollection; import com.android.inputmethod.latin.DictionaryCollection;
import com.android.inputmethod.latin.DictionaryFactory; import com.android.inputmethod.latin.DictionaryFactory;
import com.android.inputmethod.latin.LocaleUtils; import com.android.inputmethod.latin.LocaleUtils;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SynchronouslyLoadedUserDictionary; import com.android.inputmethod.latin.SynchronouslyLoadedUserDictionary;
import com.android.inputmethod.latin.UserDictionary; import com.android.inputmethod.latin.UserDictionary;
import com.android.inputmethod.latin.Utils; import com.android.inputmethod.latin.Utils;
@ -62,18 +63,38 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
private Map<String, Dictionary> mUserDictionaries = private Map<String, Dictionary> mUserDictionaries =
Collections.synchronizedMap(new TreeMap<String, Dictionary>()); Collections.synchronizedMap(new TreeMap<String, Dictionary>());
private double mTypoThreshold;
@Override public void onCreate() {
super.onCreate();
mTypoThreshold = Double.parseDouble(getString(R.string.spellchecker_typo_threshold_value));
}
@Override @Override
public Session createSession() { public Session createSession() {
return new AndroidSpellCheckerSession(); return new AndroidSpellCheckerSession(this);
} }
private static class SuggestionsGatherer implements WordCallback { private static class SuggestionsGatherer implements WordCallback {
public static class Result {
public final String[] mSuggestions;
public final boolean mLooksLikeTypo;
public Result(final String[] gatheredSuggestions, final boolean looksLikeTypo) {
mSuggestions = gatheredSuggestions;
mLooksLikeTypo = looksLikeTypo;
}
}
private final int DEFAULT_SUGGESTION_LENGTH = 16; private final int DEFAULT_SUGGESTION_LENGTH = 16;
private final ArrayList<CharSequence> mSuggestions; private final ArrayList<CharSequence> mSuggestions;
private final int[] mScores; private final int[] mScores;
private final int mMaxLength; private final int mMaxLength;
private int mLength = 0; private int mLength = 0;
private boolean mSeenSuggestions = false;
// The two following attributes are only ever filled if the requested max length
// is 0 (or less, which is treated the same).
private String mBestSuggestion = null;
private int mBestScore = Integer.MIN_VALUE; // As small as possible
SuggestionsGatherer(final int maxLength) { SuggestionsGatherer(final int maxLength) {
mMaxLength = maxLength; mMaxLength = maxLength;
@ -89,14 +110,26 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
// if it doesn't. See documentation for binarySearch. // if it doesn't. See documentation for binarySearch.
final int insertIndex = positionIndex >= 0 ? positionIndex : -positionIndex - 1; final int insertIndex = positionIndex >= 0 ? positionIndex : -positionIndex - 1;
mSeenSuggestions = true;
if (mLength < mMaxLength) { if (mLength < mMaxLength) {
final int copyLen = mLength - insertIndex; final int copyLen = mLength - insertIndex;
++mLength; ++mLength;
System.arraycopy(mScores, insertIndex, mScores, insertIndex + 1, copyLen); System.arraycopy(mScores, insertIndex, mScores, insertIndex + 1, copyLen);
mSuggestions.add(insertIndex, new String(word, wordOffset, wordLength)); mSuggestions.add(insertIndex, new String(word, wordOffset, wordLength));
} else { } else {
if (insertIndex == 0) return true; if (insertIndex == 0) {
// If the maxLength is 0 (should never be less, but if it is, it's treated as 0)
// then we need to keep track of the best suggestion in mBestScore and
// mBestSuggestion. This is so that we know whether the best suggestion makes
// the score cutoff, since we need to know that to return a meaningful
// looksLikeTypo.
if (0 >= mMaxLength) {
if (score > mBestScore) {
mBestScore = score;
mBestSuggestion = new String(word, wordOffset, wordLength);
}
}
return true;
}
System.arraycopy(mScores, 1, mScores, 0, insertIndex); System.arraycopy(mScores, 1, mScores, 0, insertIndex);
mSuggestions.add(insertIndex, new String(word, wordOffset, wordLength)); mSuggestions.add(insertIndex, new String(word, wordOffset, wordLength));
mSuggestions.remove(0); mSuggestions.remove(0);
@ -106,20 +139,41 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
return true; return true;
} }
public String[] getGatheredSuggestions() { public Result getResults(final CharSequence originalText, final double threshold) {
if (!mSeenSuggestions) return null; final String[] gatheredSuggestions;
if (0 == mLength) return EMPTY_STRING_ARRAY; final boolean looksLikeTypo;
if (0 == mLength) {
if (DBG) { // Either we found no suggestions, or we found some BUT the max length was 0.
if (mLength != mSuggestions.size()) { // If we found some mBestSuggestion will not be null. If it is null, then
Log.e(TAG, "Suggestion size is not the same as stored mLength"); // we found none, regardless of the max length.
if (null == mBestSuggestion) {
gatheredSuggestions = null;
looksLikeTypo = false;
} else {
gatheredSuggestions = EMPTY_STRING_ARRAY;
final double normalizedScore =
Utils.calcNormalizedScore(originalText, mBestSuggestion, mBestScore);
looksLikeTypo = (normalizedScore > threshold);
} }
} else {
if (DBG) {
if (mLength != mSuggestions.size()) {
Log.e(TAG, "Suggestion size is not the same as stored mLength");
}
}
Collections.reverse(mSuggestions);
Utils.removeDupes(mSuggestions);
// This returns a String[], while toArray() returns an Object[] which cannot be cast
// into a String[].
gatheredSuggestions = mSuggestions.toArray(EMPTY_STRING_ARRAY);
final int bestScore = mScores[0];
final CharSequence bestSuggestion = mSuggestions.get(0);
final double normalizedScore =
Utils.calcNormalizedScore(originalText, bestSuggestion, bestScore);
looksLikeTypo = (normalizedScore > threshold);
} }
Collections.reverse(mSuggestions); return new Result(gatheredSuggestions, looksLikeTypo);
Utils.removeDupes(mSuggestions);
// This returns a String[], while toArray() returns an Object[] which cannot be cast
// into a String[].
return mSuggestions.toArray(EMPTY_STRING_ARRAY);
} }
} }
@ -164,16 +218,22 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
return new DictAndProximity(dictionaryCollection, proximityInfo); return new DictAndProximity(dictionaryCollection, proximityInfo);
} }
private class AndroidSpellCheckerSession extends Session { private static class AndroidSpellCheckerSession extends Session {
// Immutable, but need the locale which is not available in the constructor yet // Immutable, but need the locale which is not available in the constructor yet
DictionaryPool mDictionaryPool; private DictionaryPool mDictionaryPool;
// Likewise // Likewise
Locale mLocale; private Locale mLocale;
private final AndroidSpellCheckerService mService;
AndroidSpellCheckerSession(final AndroidSpellCheckerService service) {
mService = service;
}
@Override @Override
public void onCreate() { public void onCreate() {
final String localeString = getLocale(); final String localeString = getLocale();
mDictionaryPool = getDictionaryPool(localeString); mDictionaryPool = mService.getDictionaryPool(localeString);
mLocale = LocaleUtils.constructLocaleFromString(localeString); mLocale = LocaleUtils.constructLocaleFromString(localeString);
} }
@ -242,13 +302,14 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
return EMPTY_SUGGESTIONS_INFO; return EMPTY_SUGGESTIONS_INFO;
} }
final String[] suggestions = suggestionsGatherer.getGatheredSuggestions(); final SuggestionsGatherer.Result result =
suggestionsGatherer.getResults(text, mService.mTypoThreshold);
final int flags = final int flags =
(isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY : 0) (isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY : 0)
| (null != suggestions | (result.mLooksLikeTypo
? SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO : 0); ? SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO : 0);
return new SuggestionsInfo(flags, suggestions); return new SuggestionsInfo(flags, result.mSuggestions);
} }
} }
} }