1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.inputmethod.latin; 18 19 import android.text.TextUtils; 20 import android.view.inputmethod.CompletionInfo; 21 22 import com.android.inputmethod.latin.utils.CollectionUtils; 23 import com.android.inputmethod.latin.utils.StringUtils; 24 25 import java.util.ArrayList; 26 import java.util.Arrays; 27 import java.util.HashSet; 28 29 public final class SuggestedWords { 30 public static final int INDEX_OF_TYPED_WORD = 0; 31 public static final int INDEX_OF_AUTO_CORRECTION = 1; 32 public static final int NOT_A_SEQUENCE_NUMBER = -1; 33 34 private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST = 35 CollectionUtils.newArrayList(0); 36 public static final SuggestedWords EMPTY = new SuggestedWords( 37 EMPTY_WORD_INFO_LIST, false, false, false, false, false); 38 39 public final boolean mTypedWordValid; 40 // Note: this INCLUDES cases where the word will auto-correct to itself. A good definition 41 // of what this flag means would be "the top suggestion is strong enough to auto-correct", 42 // whether this exactly matches the user entry or not. 43 public final boolean mWillAutoCorrect; 44 public final boolean mIsPunctuationSuggestions; 45 public final boolean mIsObsoleteSuggestions; 46 public final boolean mIsPrediction; 47 public final int mSequenceNumber; // Sequence number for auto-commit. 48 private final ArrayList<SuggestedWordInfo> mSuggestedWordInfoList; 49 50 public SuggestedWords(final ArrayList<SuggestedWordInfo> suggestedWordInfoList, 51 final boolean typedWordValid, 52 final boolean willAutoCorrect, 53 final boolean isPunctuationSuggestions, 54 final boolean isObsoleteSuggestions, 55 final boolean isPrediction) { 56 this(suggestedWordInfoList, typedWordValid, willAutoCorrect, isPunctuationSuggestions, 57 isObsoleteSuggestions, isPrediction, NOT_A_SEQUENCE_NUMBER); 58 } 59 60 public SuggestedWords(final ArrayList<SuggestedWordInfo> suggestedWordInfoList, 61 final boolean typedWordValid, 62 final boolean willAutoCorrect, 63 final boolean isPunctuationSuggestions, 64 final boolean isObsoleteSuggestions, 65 final boolean isPrediction, 66 final int sequenceNumber) { 67 mSuggestedWordInfoList = suggestedWordInfoList; 68 mTypedWordValid = typedWordValid; 69 mWillAutoCorrect = willAutoCorrect; 70 mIsPunctuationSuggestions = isPunctuationSuggestions; 71 mIsObsoleteSuggestions = isObsoleteSuggestions; 72 mIsPrediction = isPrediction; 73 mSequenceNumber = sequenceNumber; 74 } 75 76 public boolean isEmpty() { 77 return mSuggestedWordInfoList.isEmpty(); 78 } 79 80 public int size() { 81 return mSuggestedWordInfoList.size(); 82 } 83 84 public String getWord(final int index) { 85 return mSuggestedWordInfoList.get(index).mWord; 86 } 87 88 public SuggestedWordInfo getInfo(final int index) { 89 return mSuggestedWordInfoList.get(index); 90 } 91 92 public String getDebugString(final int pos) { 93 if (!LatinImeLogger.sDBG) { 94 return null; 95 } 96 final SuggestedWordInfo wordInfo = getInfo(pos); 97 if (wordInfo == null) { 98 return null; 99 } 100 final String debugString = wordInfo.getDebugString(); 101 if (TextUtils.isEmpty(debugString)) { 102 return null; 103 } 104 return debugString; 105 } 106 107 public boolean willAutoCorrect() { 108 return mWillAutoCorrect; 109 } 110 111 @Override 112 public String toString() { 113 // Pretty-print method to help debug 114 return "SuggestedWords:" 115 + " mTypedWordValid=" + mTypedWordValid 116 + " mWillAutoCorrect=" + mWillAutoCorrect 117 + " mIsPunctuationSuggestions=" + mIsPunctuationSuggestions 118 + " words=" + Arrays.toString(mSuggestedWordInfoList.toArray()); 119 } 120 121 public static ArrayList<SuggestedWordInfo> getFromApplicationSpecifiedCompletions( 122 final CompletionInfo[] infos) { 123 final ArrayList<SuggestedWordInfo> result = CollectionUtils.newArrayList(); 124 for (final CompletionInfo info : infos) { 125 if (info == null) continue; 126 final CharSequence text = info.getText(); 127 if (null == text) continue; 128 final SuggestedWordInfo suggestedWordInfo = new SuggestedWordInfo(text.toString(), 129 SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_APP_DEFINED, 130 Dictionary.DICTIONARY_APPLICATION_DEFINED, 131 SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */, 132 SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */); 133 result.add(suggestedWordInfo); 134 } 135 return result; 136 } 137 138 // Should get rid of the first one (what the user typed previously) from suggestions 139 // and replace it with what the user currently typed. 140 public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions( 141 final String typedWord, final SuggestedWords previousSuggestions) { 142 final ArrayList<SuggestedWordInfo> suggestionsList = CollectionUtils.newArrayList(); 143 final HashSet<String> alreadySeen = CollectionUtils.newHashSet(); 144 suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE, 145 SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED, 146 SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */, 147 SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */)); 148 alreadySeen.add(typedWord.toString()); 149 final int previousSize = previousSuggestions.size(); 150 for (int index = 1; index < previousSize; index++) { 151 final SuggestedWordInfo prevWordInfo = previousSuggestions.getInfo(index); 152 final String prevWord = prevWordInfo.mWord; 153 // Filter out duplicate suggestion. 154 if (!alreadySeen.contains(prevWord)) { 155 suggestionsList.add(prevWordInfo); 156 alreadySeen.add(prevWord); 157 } 158 } 159 return suggestionsList; 160 } 161 162 public SuggestedWordInfo getAutoCommitCandidate() { 163 if (mSuggestedWordInfoList.size() <= 0) return null; 164 final SuggestedWordInfo candidate = mSuggestedWordInfoList.get(0); 165 return candidate.isEligibleForAutoCommit() ? candidate : null; 166 } 167 168 public static final class SuggestedWordInfo { 169 public static final int NOT_AN_INDEX = -1; 170 public static final int NOT_A_CONFIDENCE = -1; 171 public static final int MAX_SCORE = Integer.MAX_VALUE; 172 public static final int KIND_MASK_KIND = 0xFF; // Mask to get only the kind 173 public static final int KIND_TYPED = 0; // What user typed 174 public static final int KIND_CORRECTION = 1; // Simple correction/suggestion 175 public static final int KIND_COMPLETION = 2; // Completion (suggestion with appended chars) 176 public static final int KIND_WHITELIST = 3; // Whitelisted word 177 public static final int KIND_BLACKLIST = 4; // Blacklisted word 178 public static final int KIND_HARDCODED = 5; // Hardcoded suggestion, e.g. punctuation 179 public static final int KIND_APP_DEFINED = 6; // Suggested by the application 180 public static final int KIND_SHORTCUT = 7; // A shortcut 181 public static final int KIND_PREDICTION = 8; // A prediction (== a suggestion with no input) 182 // KIND_RESUMED: A resumed suggestion (comes from a span, currently this type is used only 183 // in java for re-correction) 184 public static final int KIND_RESUMED = 9; 185 public static final int KIND_OOV_CORRECTION = 10; // Most probable string correction 186 187 public static final int KIND_MASK_FLAGS = 0xFFFFFF00; // Mask to get the flags 188 public static final int KIND_FLAG_POSSIBLY_OFFENSIVE = 0x80000000; 189 public static final int KIND_FLAG_EXACT_MATCH = 0x40000000; 190 191 public final String mWord; 192 public final int mScore; 193 public final int mKind; // one of the KIND_* constants above 194 public final int mCodePointCount; 195 public final Dictionary mSourceDict; 196 // For auto-commit. This keeps track of the index inside the touch coordinates array 197 // passed to native code to get suggestions for a gesture that corresponds to the first 198 // letter of the second word. 199 public final int mIndexOfTouchPointOfSecondWord; 200 // For auto-commit. This is a measure of how confident we are that we can commit the 201 // first word of this suggestion. 202 public final int mAutoCommitFirstWordConfidence; 203 private String mDebugString = ""; 204 205 /** 206 * Create a new suggested word info. 207 * @param word The string to suggest. 208 * @param score A measure of how likely this suggestion is. 209 * @param kind The kind of suggestion, as one of the above KIND_* constants. 210 * @param sourceDict What instance of Dictionary produced this suggestion. 211 * @param indexOfTouchPointOfSecondWord See mIndexOfTouchPointOfSecondWord. 212 * @param autoCommitFirstWordConfidence See mAutoCommitFirstWordConfidence. 213 */ 214 public SuggestedWordInfo(final String word, final int score, final int kind, 215 final Dictionary sourceDict, final int indexOfTouchPointOfSecondWord, 216 final int autoCommitFirstWordConfidence) { 217 mWord = word; 218 mScore = score; 219 mKind = kind; 220 mSourceDict = sourceDict; 221 mCodePointCount = StringUtils.codePointCount(mWord); 222 mIndexOfTouchPointOfSecondWord = indexOfTouchPointOfSecondWord; 223 mAutoCommitFirstWordConfidence = autoCommitFirstWordConfidence; 224 } 225 226 public boolean isEligibleForAutoCommit() { 227 return (KIND_CORRECTION == mKind && NOT_AN_INDEX != mIndexOfTouchPointOfSecondWord); 228 } 229 230 public void setDebugString(final String str) { 231 if (null == str) throw new NullPointerException("Debug info is null"); 232 mDebugString = str; 233 } 234 235 public String getDebugString() { 236 return mDebugString; 237 } 238 239 public int codePointCount() { 240 return mCodePointCount; 241 } 242 243 public int codePointAt(int i) { 244 return mWord.codePointAt(i); 245 } 246 247 @Override 248 public String toString() { 249 if (TextUtils.isEmpty(mDebugString)) { 250 return mWord; 251 } else { 252 return mWord + " (" + mDebugString + ")"; 253 } 254 } 255 256 // TODO: Consolidate this method and StringUtils.removeDupes() in the future. 257 public static void removeDups(ArrayList<SuggestedWordInfo> candidates) { 258 if (candidates.size() <= 1) { 259 return; 260 } 261 int i = 1; 262 while (i < candidates.size()) { 263 final SuggestedWordInfo cur = candidates.get(i); 264 for (int j = 0; j < i; ++j) { 265 final SuggestedWordInfo previous = candidates.get(j); 266 if (cur.mWord.equals(previous.mWord)) { 267 candidates.remove(cur.mScore < previous.mScore ? i : j); 268 --i; 269 break; 270 } 271 } 272 ++i; 273 } 274 } 275 } 276 277 // SuggestedWords is an immutable object, as much as possible. We must not just remove 278 // words from the member ArrayList as some other parties may expect the object to never change. 279 public SuggestedWords getSuggestedWordsExcludingTypedWord() { 280 final ArrayList<SuggestedWordInfo> newSuggestions = CollectionUtils.newArrayList(); 281 for (int i = 0; i < mSuggestedWordInfoList.size(); ++i) { 282 final SuggestedWordInfo info = mSuggestedWordInfoList.get(i); 283 if (SuggestedWordInfo.KIND_TYPED != info.mKind) { 284 newSuggestions.add(info); 285 } 286 } 287 // We should never autocorrect, so we say the typed word is valid. Also, in this case, 288 // no auto-correction should take place hence willAutoCorrect = false. 289 return new SuggestedWords(newSuggestions, true /* typedWordValid */, 290 false /* willAutoCorrect */, mIsPunctuationSuggestions, mIsObsoleteSuggestions, 291 mIsPrediction); 292 } 293 294 // Creates a new SuggestedWordInfo from the currently suggested words that removes all but the 295 // last word of all suggestions, separated by a space. This is necessary because when we commit 296 // a multiple-word suggestion, the IME only retains the last word as the composing word, and 297 // we should only suggest replacements for this last word. 298 // TODO: make this work with languages without spaces. 299 public SuggestedWords getSuggestedWordsForLastWordOfPhraseGesture() { 300 final ArrayList<SuggestedWordInfo> newSuggestions = CollectionUtils.newArrayList(); 301 for (int i = 0; i < mSuggestedWordInfoList.size(); ++i) { 302 final SuggestedWordInfo info = mSuggestedWordInfoList.get(i); 303 final int indexOfLastSpace = info.mWord.lastIndexOf(Constants.CODE_SPACE) + 1; 304 final String lastWord = info.mWord.substring(indexOfLastSpace); 305 newSuggestions.add(new SuggestedWordInfo(lastWord, info.mScore, info.mKind, 306 info.mSourceDict, SuggestedWordInfo.NOT_AN_INDEX, 307 SuggestedWordInfo.NOT_A_CONFIDENCE)); 308 } 309 return new SuggestedWords(newSuggestions, mTypedWordValid, 310 mWillAutoCorrect, mIsPunctuationSuggestions, mIsObsoleteSuggestions, 311 mIsPrediction); 312 } 313 } 314