diff --git a/java/res/values/config-auto-correction-thresholds.xml b/java/res/values/config-auto-correction-thresholds.xml index 7d94a42a4..fc701c7ff 100644 --- a/java/res/values/config-auto-correction-thresholds.xml +++ b/java/res/values/config-auto-correction-thresholds.xml @@ -34,6 +34,12 @@ floatNegativeInfinity + + 0.065 + 0 1 diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 7b7b6d35e..66746cb6a 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -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()); diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 93a21043c..0bf0f687a 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -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 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. diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index cbf48f0c0..390b311e2 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -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 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 candidates, + private static void removeSuggestedWordInfoFromList( + @Nonnull final String word, @Nonnull final ArrayList 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; } } diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java index 16c053474..490fa827c 100644 --- a/java/src/com/android/inputmethod/latin/settings/Settings.java +++ b/java/src/com/android/inputmethod/latin/settings/Settings.java @@ -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, diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java index 26415e7d4..c3755792c 100644 --- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java @@ -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 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 diff --git a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java index 120cffbde..2fd257922 100644 --- a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java @@ -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; }