Allow whitelist changes when no close word

When there isn't a close word in another language of the
current multi-language set, we allow whitelist entries
to take force even if we are not confident in the current
language.

Bug: 18063142
Bug: 18130489
Bug: 18132240
Bug: 18136721
Bug: 18200415
Change-Id: I044674ba7b70aa86ab2a48d2e4d53a1c8007b62c
main
Jean Chalard 2014-11-21 22:49:50 +09:00
parent e752aab70d
commit 56577461d6
7 changed files with 108 additions and 46 deletions

View File

@ -34,6 +34,12 @@
<item>floatNegativeInfinity</item>
</string-array>
<!-- Chosen to be slightly less than the "aggressive" threshold. This is the threshold for
a mildly plausible suggestion given the input; if no "plausible" suggestion is present
for a language, it's a strong indicator the user is not typing in this language, so we
may be more forgiving of whitelist entries in another language. -->
<string name="plausibility_threshold" translatable="false">0.065</string>
<!-- The index of the auto correction threshold values array. -->
<string name="auto_correction_threshold_mode_index_off" translatable="false">0</string>
<string name="auto_correction_threshold_mode_index_modest" translatable="false">1</string>

View File

@ -708,6 +708,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mInputLogic.mSuggest.setAutoCorrectionThreshold(
settingsValues.mAutoCorrectionThreshold);
}
mInputLogic.mSuggest.setPlausibilityThreshold(settingsValues.mPlausibilityThreshold);
}
/**
@ -1007,6 +1008,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
suggest.setAutoCorrectionThreshold(
currentSettingsValues.mAutoCorrectionThreshold);
}
suggest.setPlausibilityThreshold(currentSettingsValues.mPlausibilityThreshold);
switcher.loadKeyboard(editorInfo, currentSettingsValues, getCurrentAutoCapsState(),
getCurrentRecapitalizeState());

View File

@ -65,15 +65,30 @@ public final class Suggest {
}
private float mAutoCorrectionThreshold;
private float mPlausibilityThreshold;
public Suggest(final DictionaryFacilitator dictionaryFacilitator) {
mDictionaryFacilitator = dictionaryFacilitator;
}
/**
* Set the normalized-score threshold for a suggestion to be considered strong enough that we
* will auto-correct to this.
* @param threshold the threshold
*/
public void setAutoCorrectionThreshold(final float threshold) {
mAutoCorrectionThreshold = threshold;
}
/**
* Set the normalized-score threshold for what we consider a "plausible" suggestion, in
* the same dimension as the auto-correction threshold.
* @param threshold the threshold
*/
public void setPlausibilityThreshold(final float threshold) {
mPlausibilityThreshold = threshold;
}
public interface OnGetSuggestedWordsCallback {
public void onGetSuggestedWords(final SuggestedWords suggestedWords);
}
@ -130,6 +145,18 @@ public final class Suggest {
return firstSuggestedWordInfo;
}
// Quality constants for dictionary match
// In increasing order of quality
// This source dictionary does not match the typed word.
private static final int QUALITY_NO_MATCH = 0;
// This source dictionary has a null locale, and the preferred locale is also null.
private static final int QUALITY_MATCH_NULL = 1;
// This source dictionary has a non-null locale different from the preferred locale. The
// preferred locale may be null : this is still better than MATCH_NULL.
private static final int QUALITY_MATCH_OTHER_LOCALE = 2;
// This source dictionary matches the preferred locale.
private static final int QUALITY_MATCH_PREFERRED_LOCALE = 3;
// Retrieves suggestions for non-batch input (typing, recorrection, predictions...)
// and calls the callback function with the suggestions.
private void getSuggestedWordsForNonBatchInput(final WordComposer wordComposer,
@ -154,20 +181,52 @@ public final class Suggest {
// For transforming suggestions that don't come for any dictionary, we
// use the currently most probable locale as it's our best bet.
mostProbableLocale);
@Nullable final Dictionary sourceDictionaryOfRemovedWord =
SuggestedWordInfo.removeDupsAndReturnSourceOfTypedWord(wordComposer.getTypedWord(),
mostProbableLocale /* preferredLocale */, suggestionsContainer);
boolean typedWordExistsInAnotherLanguage = false;
int qualityOfFoundSourceDictionary = QUALITY_NO_MATCH;
@Nullable Dictionary sourceDictionaryOfRemovedWord = null;
for (final SuggestedWordInfo info : suggestionsContainer) {
// Search for the best dictionary, defined as the first one with the highest match
// quality we can find.
if (typedWordString.equals(info.mWord)) {
if (mostProbableLocale.equals(info.mSourceDict.mLocale)) {
if (qualityOfFoundSourceDictionary < QUALITY_MATCH_PREFERRED_LOCALE) {
// Use this source if the old match had lower quality than this match
sourceDictionaryOfRemovedWord = info.mSourceDict;
qualityOfFoundSourceDictionary = QUALITY_MATCH_PREFERRED_LOCALE;
}
} else {
final int matchQuality = (null == info.mSourceDict.mLocale)
? QUALITY_MATCH_NULL : QUALITY_MATCH_OTHER_LOCALE;
if (qualityOfFoundSourceDictionary < matchQuality) {
// Use this source if the old match had lower quality than this match
sourceDictionaryOfRemovedWord = info.mSourceDict;
qualityOfFoundSourceDictionary = matchQuality;
}
typedWordExistsInAnotherLanguage = true;
}
}
}
SuggestedWordInfo.removeDups(typedWordString, suggestionsContainer);
final SuggestedWordInfo whitelistedWordInfo =
getWhitelistedWordInfoOrNull(suggestionsContainer);
final String whitelistedWord;
if (null != whitelistedWordInfo &&
mDictionaryFacilitator.isConfidentAboutCurrentLanguageBeing(
whitelistedWordInfo.mSourceDict.mLocale)) {
(mDictionaryFacilitator.isConfidentAboutCurrentLanguageBeing(
whitelistedWordInfo.mSourceDict.mLocale)
|| (!typedWordExistsInAnotherLanguage
&& !hasPlausibleCandidateInAnyOtherLanguage(suggestionsContainer,
consideredWord, whitelistedWordInfo)))) {
// We'll use the whitelist candidate if we are confident the user is typing in the
// language of the dictionary it's coming from, or if there is no plausible candidate
// coming from another language.
whitelistedWord = whitelistedWordInfo.mWord;
} else {
// Even if we have a whitelist candidate, we don't use it unless we are confident
// the user is typing in the language this whitelist candidate comes from.
// If on the contrary we are not confident in the current language and we have
// at least a plausible candidate in any other language, then we don't use this
// whitelist candidate.
whitelistedWord = null;
}
final boolean resultsArePredictions = !wordComposer.isComposingWord();
@ -211,7 +270,7 @@ public final class Suggest {
hasAutoCorrection = false;
} else {
final SuggestedWordInfo firstSuggestion = suggestionResults.first();
if (!AutoCorrectionUtils.suggestionExceedsAutoCorrectionThreshold(
if (!AutoCorrectionUtils.suggestionExceedsThreshold(
firstSuggestion, consideredWord, mAutoCorrectionThreshold)) {
// Score is too low for autocorrect
hasAutoCorrection = false;
@ -260,6 +319,20 @@ public final class Suggest {
false /* isObsoleteSuggestions */, inputStyle, sequenceNumber));
}
private boolean hasPlausibleCandidateInAnyOtherLanguage(
final ArrayList<SuggestedWordInfo> suggestionsContainer, final String consideredWord,
final SuggestedWordInfo whitelistedWordInfo) {
for (final SuggestedWordInfo info : suggestionsContainer) {
if (whitelistedWordInfo.mSourceDict.mLocale.equals(info.mSourceDict.mLocale)) {
continue;
}
return AutoCorrectionUtils.suggestionExceedsThreshold(info, consideredWord,
mPlausibilityThreshold);
}
// No candidate in another language
return false;
}
// Retrieves suggestions for the batch input
// and calls the callback function with the suggestions.
private void getSuggestedWordsForBatchInput(final WordComposer wordComposer,
@ -293,8 +366,7 @@ public final class Suggest {
final SuggestedWordInfo rejected = suggestionsContainer.remove(0);
suggestionsContainer.add(1, rejected);
}
SuggestedWordInfo.removeDupsAndReturnSourceOfTypedWord(null /* typedWord */,
null /* preferredLocale */, suggestionsContainer);
SuggestedWordInfo.removeDups(null /* typedWord */, suggestionsContainer);
// For some reason some suggestions with MIN_VALUE are making their way here.
// TODO: Find a more robust way to detect distracters.

View File

@ -356,53 +356,30 @@ public class SuggestedWords {
}
// This will always remove the higher index if a duplicate is found.
// Returns null if the typed word is not found. Always return the dictionary for the
// highest suggestion matching the locale if found, otherwise return the dictionary for
// the highest suggestion.
@Nullable
public static Dictionary removeDupsAndReturnSourceOfTypedWord(
@Nullable final String typedWord,
@Nullable final Locale preferredLocale,
public static void removeDups(@Nullable final String typedWord,
@Nonnull ArrayList<SuggestedWordInfo> candidates) {
if (candidates.isEmpty()) {
return null;
return;
}
final Dictionary sourceDictionaryOfTypedWord;
if (!TextUtils.isEmpty(typedWord)) {
sourceDictionaryOfTypedWord =
removeSuggestedWordInfoFromListAndReturnSourceDictionary(typedWord,
preferredLocale, candidates, -1 /* startIndexExclusive */);
} else {
sourceDictionaryOfTypedWord = null;
removeSuggestedWordInfoFromList(typedWord, candidates, -1 /* startIndexExclusive */);
}
for (int i = 0; i < candidates.size(); ++i) {
removeSuggestedWordInfoFromListAndReturnSourceDictionary(candidates.get(i).mWord,
null /* preferredLocale */, candidates, i /* startIndexExclusive */);
removeSuggestedWordInfoFromList(candidates.get(i).mWord, candidates,
i /* startIndexExclusive */);
}
return sourceDictionaryOfTypedWord;
}
@Nullable
private static Dictionary removeSuggestedWordInfoFromListAndReturnSourceDictionary(
@Nonnull final String word, @Nullable final Locale preferredLocale,
@Nonnull final ArrayList<SuggestedWordInfo> candidates,
private static void removeSuggestedWordInfoFromList(
@Nonnull final String word, @Nonnull final ArrayList<SuggestedWordInfo> candidates,
final int startIndexExclusive) {
Dictionary sourceDictionaryOfTypedWord = null;
for (int i = startIndexExclusive + 1; i < candidates.size(); ++i) {
final SuggestedWordInfo previous = candidates.get(i);
if (word.equals(previous.mWord)) {
if (null == sourceDictionaryOfTypedWord
|| (null != preferredLocale
&& preferredLocale.equals(previous.mSourceDict.mLocale))) {
if (Dictionary.TYPE_USER_HISTORY != previous.mSourceDict.mDictType) {
sourceDictionaryOfTypedWord = previous.mSourceDict;
}
}
candidates.remove(i);
--i;
}
}
return sourceDictionaryOfTypedWord;
}
}

View File

@ -238,6 +238,10 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
return !currentAutoCorrectionSetting.equals(autoCorrectionOff);
}
public static float readPlausibilityThreshold(final Resources res) {
return Float.parseFloat(res.getString(R.string.plausibility_threshold));
}
public static boolean readBlockPotentiallyOffensive(final SharedPreferences prefs,
final Resources res) {
return prefs.getBoolean(PREF_BLOCK_POTENTIALLY_OFFENSIVE,

View File

@ -96,6 +96,7 @@ public class SettingsValues {
public final int mKeyPreviewPopupDismissDelay;
private final boolean mAutoCorrectEnabled;
public final float mAutoCorrectionThreshold;
public final float mPlausibilityThreshold;
public final boolean mAutoCorrectionEnabledPerUserSettings;
private final boolean mSuggestionsEnabledPerUserSettings;
private final AsyncResultHolder<AppWorkaroundsUtils> mAppWorkarounds;
@ -172,6 +173,7 @@ public class SettingsValues {
Settings.PREF_ENABLE_EMOJI_ALT_PHYSICAL_KEY, true);
mAutoCorrectionThreshold = readAutoCorrectionThreshold(res,
autoCorrectionThresholdRawValue);
mPlausibilityThreshold = Settings.readPlausibilityThreshold(res);
mGestureInputEnabled = Settings.readGestureInputEnabled(prefs, res);
mGestureTrailEnabled = prefs.getBoolean(Settings.PREF_GESTURE_PREVIEW_TRAIL, true);
mGestureFloatingPreviewTextEnabled = !mInputAttributes.mDisableGestureFloatingPreviewText

View File

@ -29,9 +29,8 @@ public final class AutoCorrectionUtils {
// Purely static class: can't instantiate.
}
public static boolean suggestionExceedsAutoCorrectionThreshold(
final SuggestedWordInfo suggestion, final String consideredWord,
final float autoCorrectionThreshold) {
public static boolean suggestionExceedsThreshold(final SuggestedWordInfo suggestion,
final String consideredWord, final float threshold) {
if (null != suggestion) {
// Shortlist a whitelisted word
if (suggestion.isKindOf(SuggestedWordInfo.KIND_WHITELIST)) {
@ -45,11 +44,11 @@ public final class AutoCorrectionUtils {
if (DBG) {
Log.d(TAG, "Normalized " + consideredWord + "," + suggestion + ","
+ autoCorrectionSuggestionScore + ", " + normalizedScore
+ "(" + autoCorrectionThreshold + ")");
+ "(" + threshold + ")");
}
if (normalizedScore >= autoCorrectionThreshold) {
if (normalizedScore >= threshold) {
if (DBG) {
Log.d(TAG, "Auto corrected by S-threshold.");
Log.d(TAG, "Exceeds threshold.");
}
return true;
}