diff options
Diffstat (limited to 'java/src/org/kelar/inputmethod/latin/SuggestedWords.java')
-rw-r--r-- | java/src/org/kelar/inputmethod/latin/SuggestedWords.java | 448 |
1 files changed, 448 insertions, 0 deletions
diff --git a/java/src/org/kelar/inputmethod/latin/SuggestedWords.java b/java/src/org/kelar/inputmethod/latin/SuggestedWords.java new file mode 100644 index 000000000..c704ef531 --- /dev/null +++ b/java/src/org/kelar/inputmethod/latin/SuggestedWords.java @@ -0,0 +1,448 @@ +/* + * Copyright (C) 2010 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 org.kelar.inputmethod.latin; + +import android.text.TextUtils; +import android.view.inputmethod.CompletionInfo; + +import org.kelar.inputmethod.annotations.UsedForTesting; +import org.kelar.inputmethod.latin.common.StringUtils; +import org.kelar.inputmethod.latin.define.DebugFlags; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SuggestedWords { + public static final int INDEX_OF_TYPED_WORD = 0; + public static final int INDEX_OF_AUTO_CORRECTION = 1; + public static final int NOT_A_SEQUENCE_NUMBER = -1; + + public static final int INPUT_STYLE_NONE = 0; + public static final int INPUT_STYLE_TYPING = 1; + public static final int INPUT_STYLE_UPDATE_BATCH = 2; + public static final int INPUT_STYLE_TAIL_BATCH = 3; + public static final int INPUT_STYLE_APPLICATION_SPECIFIED = 4; + public static final int INPUT_STYLE_RECORRECTION = 5; + public static final int INPUT_STYLE_PREDICTION = 6; + public static final int INPUT_STYLE_BEGINNING_OF_SENTENCE_PREDICTION = 7; + + // The maximum number of suggestions available. + public static final int MAX_SUGGESTIONS = 18; + + private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST = new ArrayList<>(0); + @Nonnull + private static final SuggestedWords EMPTY = new SuggestedWords( + EMPTY_WORD_INFO_LIST, null /* rawSuggestions */, null /* typedWord */, + false /* typedWordValid */, false /* willAutoCorrect */, + false /* isObsoleteSuggestions */, INPUT_STYLE_NONE, NOT_A_SEQUENCE_NUMBER); + + @Nullable + public final SuggestedWordInfo mTypedWordInfo; + public final boolean mTypedWordValid; + // Note: this INCLUDES cases where the word will auto-correct to itself. A good definition + // of what this flag means would be "the top suggestion is strong enough to auto-correct", + // whether this exactly matches the user entry or not. + public final boolean mWillAutoCorrect; + public final boolean mIsObsoleteSuggestions; + // How the input for these suggested words was done by the user. Must be one of the + // INPUT_STYLE_* constants above. + public final int mInputStyle; + public final int mSequenceNumber; // Sequence number for auto-commit. + @Nonnull + protected final ArrayList<SuggestedWordInfo> mSuggestedWordInfoList; + @Nullable + public final ArrayList<SuggestedWordInfo> mRawSuggestions; + + public SuggestedWords(@Nonnull final ArrayList<SuggestedWordInfo> suggestedWordInfoList, + @Nullable final ArrayList<SuggestedWordInfo> rawSuggestions, + @Nullable final SuggestedWordInfo typedWordInfo, + final boolean typedWordValid, + final boolean willAutoCorrect, + final boolean isObsoleteSuggestions, + final int inputStyle, + final int sequenceNumber) { + mSuggestedWordInfoList = suggestedWordInfoList; + mRawSuggestions = rawSuggestions; + mTypedWordValid = typedWordValid; + mWillAutoCorrect = willAutoCorrect; + mIsObsoleteSuggestions = isObsoleteSuggestions; + mInputStyle = inputStyle; + mSequenceNumber = sequenceNumber; + mTypedWordInfo = typedWordInfo; + } + + public boolean isEmpty() { + return mSuggestedWordInfoList.isEmpty(); + } + + public int size() { + return mSuggestedWordInfoList.size(); + } + + /** + * Get suggested word to show as suggestions to UI. + * + * @param shouldShowLxxSuggestionUi true if showing suggestion UI introduced in LXX and later. + * @return the count of suggested word to show as suggestions to UI. + */ + public int getWordCountToShow(final boolean shouldShowLxxSuggestionUi) { + if (isPrediction() || !shouldShowLxxSuggestionUi) { + return size(); + } + return size() - /* typed word */ 1; + } + + /** + * Get {@link SuggestedWordInfo} object for the typed word. + * @return The {@link SuggestedWordInfo} object for the typed word. + */ + public SuggestedWordInfo getTypedWordInfo() { + return mTypedWordInfo; + } + + /** + * Get suggested word at <code>index</code>. + * @param index The index of the suggested word. + * @return The suggested word. + */ + public String getWord(final int index) { + return mSuggestedWordInfoList.get(index).mWord; + } + + /** + * Get displayed text at <code>index</code>. + * In RTL languages, the displayed text on the suggestion strip may be different from the + * suggested word that is returned from {@link #getWord(int)}. For example the displayed text + * of punctuation suggestion "(" should be ")". + * @param index The index of the text to display. + * @return The text to be displayed. + */ + public String getLabel(final int index) { + return mSuggestedWordInfoList.get(index).mWord; + } + + /** + * Get {@link SuggestedWordInfo} object at <code>index</code>. + * @param index The index of the {@link SuggestedWordInfo}. + * @return The {@link SuggestedWordInfo} object. + */ + public SuggestedWordInfo getInfo(final int index) { + return mSuggestedWordInfoList.get(index); + } + + /** + * Gets the suggestion index from the suggestions list. + * @param suggestedWordInfo The {@link SuggestedWordInfo} to find the index. + * @return The position of the suggestion in the suggestion list. + */ + public int indexOf(SuggestedWordInfo suggestedWordInfo) { + return mSuggestedWordInfoList.indexOf(suggestedWordInfo); + } + + public String getDebugString(final int pos) { + if (!DebugFlags.DEBUG_ENABLED) { + return null; + } + final SuggestedWordInfo wordInfo = getInfo(pos); + if (wordInfo == null) { + return null; + } + final String debugString = wordInfo.getDebugString(); + if (TextUtils.isEmpty(debugString)) { + return null; + } + return debugString; + } + + /** + * The predicator to tell whether this object represents punctuation suggestions. + * @return false if this object desn't represent punctuation suggestions. + */ + public boolean isPunctuationSuggestions() { + return false; + } + + @Override + public String toString() { + // Pretty-print method to help debug + return "SuggestedWords:" + + " mTypedWordValid=" + mTypedWordValid + + " mWillAutoCorrect=" + mWillAutoCorrect + + " mInputStyle=" + mInputStyle + + " words=" + Arrays.toString(mSuggestedWordInfoList.toArray()); + } + + public static ArrayList<SuggestedWordInfo> getFromApplicationSpecifiedCompletions( + final CompletionInfo[] infos) { + final ArrayList<SuggestedWordInfo> result = new ArrayList<>(); + for (final CompletionInfo info : infos) { + if (null == info || null == info.getText()) { + continue; + } + result.add(new SuggestedWordInfo(info)); + } + return result; + } + + @Nonnull + public static final SuggestedWords getEmptyInstance() { + return SuggestedWords.EMPTY; + } + + // Should get rid of the first one (what the user typed previously) from suggestions + // and replace it with what the user currently typed. + public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions( + @Nonnull final SuggestedWordInfo typedWordInfo, + @Nonnull final SuggestedWords previousSuggestions) { + final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<>(); + final HashSet<String> alreadySeen = new HashSet<>(); + suggestionsList.add(typedWordInfo); + alreadySeen.add(typedWordInfo.mWord); + final int previousSize = previousSuggestions.size(); + for (int index = 1; index < previousSize; index++) { + final SuggestedWordInfo prevWordInfo = previousSuggestions.getInfo(index); + final String prevWord = prevWordInfo.mWord; + // Filter out duplicate suggestions. + if (!alreadySeen.contains(prevWord)) { + suggestionsList.add(prevWordInfo); + alreadySeen.add(prevWord); + } + } + return suggestionsList; + } + + public SuggestedWordInfo getAutoCommitCandidate() { + if (mSuggestedWordInfoList.size() <= 0) return null; + final SuggestedWordInfo candidate = mSuggestedWordInfoList.get(0); + return candidate.isEligibleForAutoCommit() ? candidate : null; + } + + // non-final for testability. + public static class SuggestedWordInfo { + public static final int NOT_AN_INDEX = -1; + public static final int NOT_A_CONFIDENCE = -1; + public static final int MAX_SCORE = Integer.MAX_VALUE; + + private static final int KIND_MASK_KIND = 0xFF; // Mask to get only the kind + public static final int KIND_TYPED = 0; // What user typed + public static final int KIND_CORRECTION = 1; // Simple correction/suggestion + public static final int KIND_COMPLETION = 2; // Completion (suggestion with appended chars) + public static final int KIND_WHITELIST = 3; // Whitelisted word + public static final int KIND_BLACKLIST = 4; // Blacklisted word + public static final int KIND_HARDCODED = 5; // Hardcoded suggestion, e.g. punctuation + public static final int KIND_APP_DEFINED = 6; // Suggested by the application + public static final int KIND_SHORTCUT = 7; // A shortcut + public static final int KIND_PREDICTION = 8; // A prediction (== a suggestion with no input) + // KIND_RESUMED: A resumed suggestion (comes from a span, currently this type is used only + // in java for re-correction) + public static final int KIND_RESUMED = 9; + public static final int KIND_OOV_CORRECTION = 10; // Most probable string correction + + public static final int KIND_FLAG_POSSIBLY_OFFENSIVE = 0x80000000; + public static final int KIND_FLAG_EXACT_MATCH = 0x40000000; + public static final int KIND_FLAG_EXACT_MATCH_WITH_INTENTIONAL_OMISSION = 0x20000000; + public static final int KIND_FLAG_APPROPRIATE_FOR_AUTO_CORRECTION = 0x10000000; + + public final String mWord; + public final String mPrevWordsContext; + // The completion info from the application. Null for suggestions that don't come from + // the application (including keyboard-computed ones, so this is almost always null) + public final CompletionInfo mApplicationSpecifiedCompletionInfo; + public final int mScore; + public final int mKindAndFlags; + public final int mCodePointCount; + @Deprecated + public final Dictionary mSourceDict; + // For auto-commit. This keeps track of the index inside the touch coordinates array + // passed to native code to get suggestions for a gesture that corresponds to the first + // letter of the second word. + public final int mIndexOfTouchPointOfSecondWord; + // For auto-commit. This is a measure of how confident we are that we can commit the + // first word of this suggestion. + public final int mAutoCommitFirstWordConfidence; + private String mDebugString = ""; + + /** + * Create a new suggested word info. + * @param word The string to suggest. + * @param prevWordsContext previous words context. + * @param score A measure of how likely this suggestion is. + * @param kindAndFlags The kind of suggestion, as one of the above KIND_* constants with + * flags. + * @param sourceDict What instance of Dictionary produced this suggestion. + * @param indexOfTouchPointOfSecondWord See mIndexOfTouchPointOfSecondWord. + * @param autoCommitFirstWordConfidence See mAutoCommitFirstWordConfidence. + */ + public SuggestedWordInfo(final String word, final String prevWordsContext, + final int score, final int kindAndFlags, + final Dictionary sourceDict, final int indexOfTouchPointOfSecondWord, + final int autoCommitFirstWordConfidence) { + mWord = word; + mPrevWordsContext = prevWordsContext; + mApplicationSpecifiedCompletionInfo = null; + mScore = score; + mKindAndFlags = kindAndFlags; + mSourceDict = sourceDict; + mCodePointCount = StringUtils.codePointCount(mWord); + mIndexOfTouchPointOfSecondWord = indexOfTouchPointOfSecondWord; + mAutoCommitFirstWordConfidence = autoCommitFirstWordConfidence; + } + + /** + * Create a new suggested word info from an application-specified completion. + * If the passed argument or its contained text is null, this throws a NPE. + * @param applicationSpecifiedCompletion The application-specified completion info. + */ + public SuggestedWordInfo(final CompletionInfo applicationSpecifiedCompletion) { + mWord = applicationSpecifiedCompletion.getText().toString(); + mPrevWordsContext = ""; + mApplicationSpecifiedCompletionInfo = applicationSpecifiedCompletion; + mScore = SuggestedWordInfo.MAX_SCORE; + mKindAndFlags = SuggestedWordInfo.KIND_APP_DEFINED; + mSourceDict = Dictionary.DICTIONARY_APPLICATION_DEFINED; + mCodePointCount = StringUtils.codePointCount(mWord); + mIndexOfTouchPointOfSecondWord = SuggestedWordInfo.NOT_AN_INDEX; + mAutoCommitFirstWordConfidence = SuggestedWordInfo.NOT_A_CONFIDENCE; + } + + public boolean isEligibleForAutoCommit() { + return (isKindOf(KIND_CORRECTION) && NOT_AN_INDEX != mIndexOfTouchPointOfSecondWord); + } + + public int getKind() { + return (mKindAndFlags & KIND_MASK_KIND); + } + + public boolean isKindOf(final int kind) { + return getKind() == kind; + } + + public boolean isPossiblyOffensive() { + return (mKindAndFlags & KIND_FLAG_POSSIBLY_OFFENSIVE) != 0; + } + + public boolean isExactMatch() { + return (mKindAndFlags & KIND_FLAG_EXACT_MATCH) != 0; + } + + public boolean isExactMatchWithIntentionalOmission() { + return (mKindAndFlags & KIND_FLAG_EXACT_MATCH_WITH_INTENTIONAL_OMISSION) != 0; + } + + public boolean isAprapreateForAutoCorrection() { + return (mKindAndFlags & KIND_FLAG_APPROPRIATE_FOR_AUTO_CORRECTION) != 0; + } + + public void setDebugString(final String str) { + if (null == str) throw new NullPointerException("Debug info is null"); + mDebugString = str; + } + + public String getDebugString() { + return mDebugString; + } + + public String getWord() { + return mWord; + } + + @Deprecated + public Dictionary getSourceDictionary() { + return mSourceDict; + } + + public int codePointAt(int i) { + return mWord.codePointAt(i); + } + + @Override + public String toString() { + if (TextUtils.isEmpty(mDebugString)) { + return mWord; + } + return mWord + " (" + mDebugString + ")"; + } + + /** + * This will always remove the higher index if a duplicate is found. + * + * @return position of typed word in the candidate list + */ + public static int removeDups( + @Nullable final String typedWord, + @Nonnull final ArrayList<SuggestedWordInfo> candidates) { + if (candidates.isEmpty()) { + return -1; + } + int firstOccurrenceOfWord = -1; + if (!TextUtils.isEmpty(typedWord)) { + firstOccurrenceOfWord = removeSuggestedWordInfoFromList( + typedWord, candidates, -1 /* startIndexExclusive */); + } + for (int i = 0; i < candidates.size(); ++i) { + removeSuggestedWordInfoFromList( + candidates.get(i).mWord, candidates, i /* startIndexExclusive */); + } + return firstOccurrenceOfWord; + } + + private static int removeSuggestedWordInfoFromList( + @Nonnull final String word, + @Nonnull final ArrayList<SuggestedWordInfo> candidates, + final int startIndexExclusive) { + int firstOccurrenceOfWord = -1; + for (int i = startIndexExclusive + 1; i < candidates.size(); ++i) { + final SuggestedWordInfo previous = candidates.get(i); + if (word.equals(previous.mWord)) { + if (firstOccurrenceOfWord == -1) { + firstOccurrenceOfWord = i; + } + candidates.remove(i); + --i; + } + } + return firstOccurrenceOfWord; + } + } + + private static boolean isPrediction(final int inputStyle) { + return INPUT_STYLE_PREDICTION == inputStyle + || INPUT_STYLE_BEGINNING_OF_SENTENCE_PREDICTION == inputStyle; + } + + public boolean isPrediction() { + return isPrediction(mInputStyle); + } + + /** + * @return the {@link SuggestedWordInfo} which corresponds to the word that is originally + * typed by the user. Otherwise returns {@code null}. Note that gesture input is not + * considered to be a typed word. + */ + @UsedForTesting + public SuggestedWordInfo getTypedWordInfoOrNull() { + if (SuggestedWords.INDEX_OF_TYPED_WORD >= size()) { + return null; + } + final SuggestedWordInfo info = getInfo(SuggestedWords.INDEX_OF_TYPED_WORD); + return (info.getKind() == SuggestedWordInfo.KIND_TYPED) ? info : null; + } +} |