5551302d27
Currently, the Delight3DictionaryFacilitator sets a boolean flag when the top suggestion score exceeds the auto-correction threshold. This flag is used to trigger auto-correction of the typed word. Also, the existing logic assumes that if allowsToBeAutoCorrected then the word is invalid, which is no longer true after we stopped using whitelists. Bug 19518376. Change-Id: Ifa7f6a09c07d25ac68c6cf3aec91f358bd88689f
464 lines
19 KiB
Java
464 lines
19 KiB
Java
/*
|
|
* 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 com.android.inputmethod.latin;
|
|
|
|
import android.text.TextUtils;
|
|
import android.util.Log;
|
|
import android.view.inputmethod.CompletionInfo;
|
|
|
|
import com.android.inputmethod.annotations.UsedForTesting;
|
|
import com.android.inputmethod.latin.common.Constants;
|
|
import com.android.inputmethod.latin.common.StringUtils;
|
|
import com.android.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;
|
|
// 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;
|
|
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 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 int score, final int kindAndFlags,
|
|
final Dictionary sourceDict, final int indexOfTouchPointOfSecondWord,
|
|
final int autoCommitFirstWordConfidence) {
|
|
mWord = word;
|
|
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();
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// Creates a new SuggestedWordInfo from the currently suggested words that removes all but the
|
|
// last word of all suggestions, separated by a space. This is necessary because when we commit
|
|
// a multiple-word suggestion, the IME only retains the last word as the composing word, and
|
|
// we should only suggest replacements for this last word.
|
|
// TODO: make this work with languages without spaces.
|
|
public SuggestedWords getSuggestedWordsForLastWordOfPhraseGesture() {
|
|
final ArrayList<SuggestedWordInfo> newSuggestions = new ArrayList<>();
|
|
for (int i = 0; i < mSuggestedWordInfoList.size(); ++i) {
|
|
final SuggestedWordInfo info = mSuggestedWordInfoList.get(i);
|
|
final int indexOfLastSpace = info.mWord.lastIndexOf(Constants.CODE_SPACE) + 1;
|
|
final String lastWord = info.mWord.substring(indexOfLastSpace);
|
|
newSuggestions.add(new SuggestedWordInfo(lastWord, info.mScore, info.mKindAndFlags,
|
|
info.mSourceDict, SuggestedWordInfo.NOT_AN_INDEX,
|
|
SuggestedWordInfo.NOT_A_CONFIDENCE));
|
|
}
|
|
return new SuggestedWords(newSuggestions, null /* rawSuggestions */,
|
|
newSuggestions.isEmpty() ? null : newSuggestions.get(0) /* typedWordInfo */,
|
|
mTypedWordValid, mWillAutoCorrect, mIsObsoleteSuggestions, INPUT_STYLE_TAIL_BATCH,
|
|
NOT_A_SEQUENCE_NUMBER);
|
|
}
|
|
|
|
/**
|
|
* @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;
|
|
}
|
|
}
|