aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/latin/Suggest.java
diff options
context:
space:
mode:
authorAmin Bandali <bandali@kelar.org>2024-12-16 21:45:41 -0500
committerAmin Bandali <bandali@kelar.org>2025-01-11 14:17:35 -0500
commite9a0e66716dab4dd3184d009d8920de1961efdfa (patch)
tree02dcc096643d74645bf28459c2834c3d4a2ad7f2 /java/src/com/android/inputmethod/latin/Suggest.java
parentfb3b9360d70596d7e921de8bf7d3ca99564a077e (diff)
downloadlatinime-e9a0e66716dab4dd3184d009d8920de1961efdfa.tar.gz
latinime-e9a0e66716dab4dd3184d009d8920de1961efdfa.tar.xz
latinime-e9a0e66716dab4dd3184d009d8920de1961efdfa.zip
Rename to Kelar Keyboard (org.kelar.inputmethod.latin)
Diffstat (limited to 'java/src/com/android/inputmethod/latin/Suggest.java')
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java434
1 files changed, 0 insertions, 434 deletions
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
deleted file mode 100644
index da23617af..000000000
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ /dev/null
@@ -1,434 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.text.TextUtils;
-
-import static com.android.inputmethod.latin.define.DecoderSpecificConstants.SHOULD_AUTO_CORRECT_USING_NON_WHITE_LISTED_SUGGESTION;
-import static com.android.inputmethod.latin.define.DecoderSpecificConstants.SHOULD_REMOVE_PREVIOUSLY_REJECTED_SUGGESTION;
-
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.common.StringUtils;
-import com.android.inputmethod.latin.define.DebugFlags;
-import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
-import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
-import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
-import com.android.inputmethod.latin.utils.SuggestionResults;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Locale;
-
-import javax.annotation.Nonnull;
-
-/**
- * This class loads a dictionary and provides a list of suggestions for a given sequence of
- * characters. This includes corrections and completions.
- */
-public final class Suggest {
- public static final String TAG = Suggest.class.getSimpleName();
-
- // Session id for
- // {@link #getSuggestedWords(WordComposer,String,ProximityInfo,boolean,int)}.
- // We are sharing the same ID between typing and gesture to save RAM footprint.
- public static final int SESSION_ID_TYPING = 0;
- public static final int SESSION_ID_GESTURE = 0;
-
- // Close to -2**31
- private static final int SUPPRESS_SUGGEST_THRESHOLD = -2000000000;
-
- private static final boolean DBG = DebugFlags.DEBUG_ENABLED;
- private final DictionaryFacilitator mDictionaryFacilitator;
-
- private static final int MAXIMUM_AUTO_CORRECT_LENGTH_FOR_GERMAN = 12;
- private static final HashMap<String, Integer> sLanguageToMaximumAutoCorrectionWithSpaceLength =
- new HashMap<>();
- static {
- // TODO: should we add Finnish here?
- // TODO: This should not be hardcoded here but be written in the dictionary header
- sLanguageToMaximumAutoCorrectionWithSpaceLength.put(Locale.GERMAN.getLanguage(),
- MAXIMUM_AUTO_CORRECT_LENGTH_FOR_GERMAN);
- }
-
- 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);
- }
-
- public void getSuggestedWords(final WordComposer wordComposer,
- final NgramContext ngramContext, final Keyboard keyboard,
- final SettingsValuesForSuggestion settingsValuesForSuggestion,
- final boolean isCorrectionEnabled, final int inputStyle, final int sequenceNumber,
- final OnGetSuggestedWordsCallback callback) {
- if (wordComposer.isBatchMode()) {
- getSuggestedWordsForBatchInput(wordComposer, ngramContext, keyboard,
- settingsValuesForSuggestion, inputStyle, sequenceNumber, callback);
- } else {
- getSuggestedWordsForNonBatchInput(wordComposer, ngramContext, keyboard,
- settingsValuesForSuggestion, inputStyle, isCorrectionEnabled,
- sequenceNumber, callback);
- }
- }
-
- private static ArrayList<SuggestedWordInfo> getTransformedSuggestedWordInfoList(
- final WordComposer wordComposer, final SuggestionResults results,
- final int trailingSingleQuotesCount, final Locale defaultLocale) {
- final boolean shouldMakeSuggestionsAllUpperCase = wordComposer.isAllUpperCase()
- && !wordComposer.isResumed();
- final boolean isOnlyFirstCharCapitalized =
- wordComposer.isOrWillBeOnlyFirstCharCapitalized();
-
- final ArrayList<SuggestedWordInfo> suggestionsContainer = new ArrayList<>(results);
- final int suggestionsCount = suggestionsContainer.size();
- if (isOnlyFirstCharCapitalized || shouldMakeSuggestionsAllUpperCase
- || 0 != trailingSingleQuotesCount) {
- for (int i = 0; i < suggestionsCount; ++i) {
- final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
- final Locale wordLocale = wordInfo.mSourceDict.mLocale;
- final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
- wordInfo, null == wordLocale ? defaultLocale : wordLocale,
- shouldMakeSuggestionsAllUpperCase, isOnlyFirstCharCapitalized,
- trailingSingleQuotesCount);
- suggestionsContainer.set(i, transformedWordInfo);
- }
- }
- return suggestionsContainer;
- }
-
- private static SuggestedWordInfo getWhitelistedWordInfoOrNull(
- @Nonnull final ArrayList<SuggestedWordInfo> suggestions) {
- if (suggestions.isEmpty()) {
- return null;
- }
- final SuggestedWordInfo firstSuggestedWordInfo = suggestions.get(0);
- if (!firstSuggestedWordInfo.isKindOf(SuggestedWordInfo.KIND_WHITELIST)) {
- return null;
- }
- return firstSuggestedWordInfo;
- }
-
- // Retrieves suggestions for non-batch input (typing, recorrection, predictions...)
- // and calls the callback function with the suggestions.
- private void getSuggestedWordsForNonBatchInput(final WordComposer wordComposer,
- final NgramContext ngramContext, final Keyboard keyboard,
- final SettingsValuesForSuggestion settingsValuesForSuggestion,
- final int inputStyleIfNotPrediction, final boolean isCorrectionEnabled,
- final int sequenceNumber, final OnGetSuggestedWordsCallback callback) {
- final String typedWordString = wordComposer.getTypedWord();
- final int trailingSingleQuotesCount =
- StringUtils.getTrailingSingleQuotesCount(typedWordString);
- final String consideredWord = trailingSingleQuotesCount > 0
- ? typedWordString.substring(0, typedWordString.length() - trailingSingleQuotesCount)
- : typedWordString;
-
- final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
- wordComposer.getComposedDataSnapshot(), ngramContext, keyboard,
- settingsValuesForSuggestion, SESSION_ID_TYPING, inputStyleIfNotPrediction);
- final Locale locale = mDictionaryFacilitator.getLocale();
- final ArrayList<SuggestedWordInfo> suggestionsContainer =
- getTransformedSuggestedWordInfoList(wordComposer, suggestionResults,
- trailingSingleQuotesCount, locale);
-
- boolean foundInDictionary = false;
- 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 (!foundInDictionary && typedWordString.equals(info.mWord)) {
- // Use this source if the old match had lower quality than this match
- sourceDictionaryOfRemovedWord = info.mSourceDict;
- foundInDictionary = true;
- break;
- }
- }
-
- final int firstOcurrenceOfTypedWordInSuggestions =
- SuggestedWordInfo.removeDups(typedWordString, suggestionsContainer);
-
- final SuggestedWordInfo whitelistedWordInfo =
- getWhitelistedWordInfoOrNull(suggestionsContainer);
- final String whitelistedWord = whitelistedWordInfo == null
- ? null : whitelistedWordInfo.mWord;
- final boolean resultsArePredictions = !wordComposer.isComposingWord();
-
- // We allow auto-correction if whitelisting is not required or the word is whitelisted,
- // or if the word had more than one char and was not suggested.
- final boolean allowsToBeAutoCorrected =
- (SHOULD_AUTO_CORRECT_USING_NON_WHITE_LISTED_SUGGESTION || whitelistedWord != null)
- || (consideredWord.length() > 1 && (sourceDictionaryOfRemovedWord == null));
-
- final boolean hasAutoCorrection;
- // If correction is not enabled, we never auto-correct. This is for example for when
- // the setting "Auto-correction" is "off": we still suggest, but we don't auto-correct.
- if (!isCorrectionEnabled
- // If the word does not allow to be auto-corrected, then we don't auto-correct.
- || !allowsToBeAutoCorrected
- // If we are doing prediction, then we never auto-correct of course
- || resultsArePredictions
- // If we don't have suggestion results, we can't evaluate the first suggestion
- // for auto-correction
- || suggestionResults.isEmpty()
- // If the word has digits, we never auto-correct because it's likely the word
- // was type with a lot of care
- || wordComposer.hasDigits()
- // If the word is mostly caps, we never auto-correct because this is almost
- // certainly intentional (and careful input)
- || wordComposer.isMostlyCaps()
- // We never auto-correct when suggestions are resumed because it would be unexpected
- || wordComposer.isResumed()
- // If we don't have a main dictionary, we never want to auto-correct. The reason
- // for this is, the user may have a contact whose name happens to match a valid
- // word in their language, and it will unexpectedly auto-correct. For example, if
- // the user types in English with no dictionary and has a "Will" in their contact
- // list, "will" would always auto-correct to "Will" which is unwanted. Hence, no
- // main dict => no auto-correct. Also, it would probably get obnoxious quickly.
- // TODO: now that we have personalization, we may want to re-evaluate this decision
- || !mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary()
- // If the first suggestion is a shortcut we never auto-correct to it, regardless
- // of how strong it is (allowlist entries are not KIND_SHORTCUT but KIND_WHITELIST).
- // TODO: we may want to have shortcut-only entries auto-correct in the future.
- || suggestionResults.first().isKindOf(SuggestedWordInfo.KIND_SHORTCUT)) {
- hasAutoCorrection = false;
- } else {
- final SuggestedWordInfo firstSuggestion = suggestionResults.first();
- if (suggestionResults.mFirstSuggestionExceedsConfidenceThreshold
- && firstOcurrenceOfTypedWordInSuggestions != 0) {
- hasAutoCorrection = true;
- } else if (!AutoCorrectionUtils.suggestionExceedsThreshold(
- firstSuggestion, consideredWord, mAutoCorrectionThreshold)) {
- // Score is too low for autocorrect
- hasAutoCorrection = false;
- } else {
- // We have a high score, so we need to check if this suggestion is in the correct
- // form to allow auto-correcting to it in this language. For details of how this
- // is determined, see #isAllowedByAutoCorrectionWithSpaceFilter.
- // TODO: this should not have its own logic here but be handled by the dictionary.
- hasAutoCorrection = isAllowedByAutoCorrectionWithSpaceFilter(firstSuggestion);
- }
- }
-
- final SuggestedWordInfo typedWordInfo = new SuggestedWordInfo(typedWordString,
- "" /* prevWordsContext */, SuggestedWordInfo.MAX_SCORE,
- SuggestedWordInfo.KIND_TYPED,
- null == sourceDictionaryOfRemovedWord ? Dictionary.DICTIONARY_USER_TYPED
- : sourceDictionaryOfRemovedWord,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
- SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */);
- if (!TextUtils.isEmpty(typedWordString)) {
- suggestionsContainer.add(0, typedWordInfo);
- }
-
- final ArrayList<SuggestedWordInfo> suggestionsList;
- if (DBG && !suggestionsContainer.isEmpty()) {
- suggestionsList = getSuggestionsInfoListWithDebugInfo(typedWordString,
- suggestionsContainer);
- } else {
- suggestionsList = suggestionsContainer;
- }
-
- final int inputStyle;
- if (resultsArePredictions) {
- inputStyle = suggestionResults.mIsBeginningOfSentence
- ? SuggestedWords.INPUT_STYLE_BEGINNING_OF_SENTENCE_PREDICTION
- : SuggestedWords.INPUT_STYLE_PREDICTION;
- } else {
- inputStyle = inputStyleIfNotPrediction;
- }
-
- final boolean isTypedWordValid = firstOcurrenceOfTypedWordInSuggestions > -1
- || (!resultsArePredictions && !allowsToBeAutoCorrected);
- callback.onGetSuggestedWords(new SuggestedWords(suggestionsList,
- suggestionResults.mRawSuggestions, typedWordInfo,
- isTypedWordValid,
- hasAutoCorrection /* willAutoCorrect */,
- false /* isObsoleteSuggestions */, inputStyle, sequenceNumber));
- }
-
- // Retrieves suggestions for the batch input
- // and calls the callback function with the suggestions.
- private void getSuggestedWordsForBatchInput(final WordComposer wordComposer,
- final NgramContext ngramContext, final Keyboard keyboard,
- final SettingsValuesForSuggestion settingsValuesForSuggestion,
- final int inputStyle, final int sequenceNumber,
- final OnGetSuggestedWordsCallback callback) {
- final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
- wordComposer.getComposedDataSnapshot(), ngramContext, keyboard,
- settingsValuesForSuggestion, SESSION_ID_GESTURE, inputStyle);
- // For transforming words that don't come from a dictionary, because it's our best bet
- final Locale locale = mDictionaryFacilitator.getLocale();
- final ArrayList<SuggestedWordInfo> suggestionsContainer =
- new ArrayList<>(suggestionResults);
- final int suggestionsCount = suggestionsContainer.size();
- final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock();
- final boolean isAllUpperCase = wordComposer.isAllUpperCase();
- if (isFirstCharCapitalized || isAllUpperCase) {
- for (int i = 0; i < suggestionsCount; ++i) {
- final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
- final Locale wordlocale = wordInfo.mSourceDict.mLocale;
- final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
- wordInfo, null == wordlocale ? locale : wordlocale, isAllUpperCase,
- isFirstCharCapitalized, 0 /* trailingSingleQuotesCount */);
- suggestionsContainer.set(i, transformedWordInfo);
- }
- }
-
- if (SHOULD_REMOVE_PREVIOUSLY_REJECTED_SUGGESTION
- && suggestionsContainer.size() > 1
- && TextUtils.equals(suggestionsContainer.get(0).mWord,
- wordComposer.getRejectedBatchModeSuggestion())) {
- final SuggestedWordInfo rejected = suggestionsContainer.remove(0);
- suggestionsContainer.add(1, rejected);
- }
- 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.
- for (int i = suggestionsContainer.size() - 1; i >= 0; --i) {
- if (suggestionsContainer.get(i).mScore < SUPPRESS_SUGGEST_THRESHOLD) {
- suggestionsContainer.remove(i);
- }
- }
-
- // In the batch input mode, the most relevant suggested word should act as a "typed word"
- // (typedWordValid=true), not as an "auto correct word" (willAutoCorrect=false).
- // Note that because this method is never used to get predictions, there is no need to
- // modify inputType such in getSuggestedWordsForNonBatchInput.
- final SuggestedWordInfo pseudoTypedWordInfo = suggestionsContainer.isEmpty() ? null
- : suggestionsContainer.get(0);
-
- callback.onGetSuggestedWords(new SuggestedWords(suggestionsContainer,
- suggestionResults.mRawSuggestions,
- pseudoTypedWordInfo,
- true /* typedWordValid */,
- false /* willAutoCorrect */,
- false /* isObsoleteSuggestions */,
- inputStyle, sequenceNumber));
- }
-
- private static ArrayList<SuggestedWordInfo> getSuggestionsInfoListWithDebugInfo(
- final String typedWord, final ArrayList<SuggestedWordInfo> suggestions) {
- final SuggestedWordInfo typedWordInfo = suggestions.get(0);
- typedWordInfo.setDebugString("+");
- final int suggestionsSize = suggestions.size();
- final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<>(suggestionsSize);
- suggestionsList.add(typedWordInfo);
- // Note: i here is the index in mScores[], but the index in mSuggestions is one more
- // than i because we added the typed word to mSuggestions without touching mScores.
- for (int i = 0; i < suggestionsSize - 1; ++i) {
- final SuggestedWordInfo cur = suggestions.get(i + 1);
- final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore(
- typedWord, cur.toString(), cur.mScore);
- final String scoreInfoString;
- if (normalizedScore > 0) {
- scoreInfoString = String.format(
- Locale.ROOT, "%d (%4.2f), %s", cur.mScore, normalizedScore,
- cur.mSourceDict.mDictType);
- } else {
- scoreInfoString = Integer.toString(cur.mScore);
- }
- cur.setDebugString(scoreInfoString);
- suggestionsList.add(cur);
- }
- return suggestionsList;
- }
-
- /**
- * Computes whether this suggestion should be blocked or not in this language
- *
- * This function implements a filter that avoids auto-correcting to suggestions that contain
- * spaces that are above a certain language-dependent character limit. In languages like German
- * where it's possible to concatenate many words, it often happens our dictionary does not
- * have the longer words. In this case, we offer a lot of unhelpful suggestions that contain
- * one or several spaces. Ideally we should understand what the user wants and display useful
- * suggestions by improving the dictionary and possibly having some specific logic. Until
- * that's possible we should avoid displaying unhelpful suggestions. But it's hard to tell
- * whether a suggestion is useful or not. So at least for the time being we block
- * auto-correction when the suggestion is long and contains a space, which should avoid the
- * worst damage.
- * This function is implementing that filter. If the language enforces no such limit, then it
- * always returns true. If the suggestion contains no space, it also returns true. Otherwise,
- * it checks the length against the language-specific limit.
- *
- * @param info the suggestion info
- * @return whether it's fine to auto-correct to this.
- */
- private static boolean isAllowedByAutoCorrectionWithSpaceFilter(final SuggestedWordInfo info) {
- final Locale locale = info.mSourceDict.mLocale;
- if (null == locale) {
- return true;
- }
- final Integer maximumLengthForThisLanguage =
- sLanguageToMaximumAutoCorrectionWithSpaceLength.get(locale.getLanguage());
- if (null == maximumLengthForThisLanguage) {
- // This language does not enforce a maximum length to auto-correction
- return true;
- }
- return info.mWord.length() <= maximumLengthForThisLanguage
- || -1 == info.mWord.indexOf(Constants.CODE_SPACE);
- }
-
- /* package for test */ static SuggestedWordInfo getTransformedSuggestedWordInfo(
- final SuggestedWordInfo wordInfo, final Locale locale, final boolean isAllUpperCase,
- final boolean isOnlyFirstCharCapitalized, final int trailingSingleQuotesCount) {
- final StringBuilder sb = new StringBuilder(wordInfo.mWord.length());
- if (isAllUpperCase) {
- sb.append(wordInfo.mWord.toUpperCase(locale));
- } else if (isOnlyFirstCharCapitalized) {
- sb.append(StringUtils.capitalizeFirstCodePoint(wordInfo.mWord, locale));
- } else {
- sb.append(wordInfo.mWord);
- }
- // Appending quotes is here to help people quote words. However, it's not helpful
- // when they type words with quotes toward the end like "it's" or "didn't", where
- // it's more likely the user missed the last character (or didn't type it yet).
- final int quotesToAppend = trailingSingleQuotesCount
- - (-1 == wordInfo.mWord.indexOf(Constants.CODE_SINGLE_QUOTE) ? 0 : 1);
- for (int i = quotesToAppend - 1; i >= 0; --i) {
- sb.appendCodePoint(Constants.CODE_SINGLE_QUOTE);
- }
- return new SuggestedWordInfo(sb.toString(), wordInfo.mPrevWordsContext,
- wordInfo.mScore, wordInfo.mKindAndFlags,
- wordInfo.mSourceDict, wordInfo.mIndexOfTouchPointOfSecondWord,
- wordInfo.mAutoCommitFirstWordConfidence);
- }
-}