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 java.util.ArrayList; 23 import java.util.Arrays; 24 import java.util.HashSet; 25 26 public final class SuggestedWords { 27 private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST = 28 CollectionUtils.newArrayList(0); 29 public static final SuggestedWords EMPTY = new SuggestedWords( 30 EMPTY_WORD_INFO_LIST, false, false, false, false, false); 31 32 public final boolean mTypedWordValid; 33 // Note: this INCLUDES cases where the word will auto-correct to itself. A good definition 34 // of what this flag means would be "the top suggestion is strong enough to auto-correct", 35 // whether this exactly matches the user entry or not. 36 public final boolean mWillAutoCorrect; 37 public final boolean mIsPunctuationSuggestions; 38 public final boolean mIsObsoleteSuggestions; 39 public final boolean mIsPrediction; 40 private final ArrayList<SuggestedWordInfo> mSuggestedWordInfoList; 41 42 public SuggestedWords(final ArrayList<SuggestedWordInfo> suggestedWordInfoList, 43 final boolean typedWordValid, 44 final boolean willAutoCorrect, 45 final boolean isPunctuationSuggestions, 46 final boolean isObsoleteSuggestions, 47 final boolean isPrediction) { 48 mSuggestedWordInfoList = suggestedWordInfoList; 49 mTypedWordValid = typedWordValid; 50 mWillAutoCorrect = willAutoCorrect; 51 mIsPunctuationSuggestions = isPunctuationSuggestions; 52 mIsObsoleteSuggestions = isObsoleteSuggestions; 53 mIsPrediction = isPrediction; 54 } 55 56 public boolean isEmpty() { 57 return mSuggestedWordInfoList.isEmpty(); 58 } 59 60 public int size() { 61 return mSuggestedWordInfoList.size(); 62 } 63 64 public String getWord(int pos) { 65 return mSuggestedWordInfoList.get(pos).mWord; 66 } 67 68 public SuggestedWordInfo getInfo(int pos) { 69 return mSuggestedWordInfoList.get(pos); 70 } 71 72 public boolean willAutoCorrect() { 73 return mWillAutoCorrect; 74 } 75 76 @Override 77 public String toString() { 78 // Pretty-print method to help debug 79 return "SuggestedWords:" 80 + " mTypedWordValid=" + mTypedWordValid 81 + " mWillAutoCorrect=" + mWillAutoCorrect 82 + " mIsPunctuationSuggestions=" + mIsPunctuationSuggestions 83 + " words=" + Arrays.toString(mSuggestedWordInfoList.toArray()); 84 } 85 86 public static ArrayList<SuggestedWordInfo> getFromApplicationSpecifiedCompletions( 87 final CompletionInfo[] infos) { 88 final ArrayList<SuggestedWordInfo> result = CollectionUtils.newArrayList(); 89 for (final CompletionInfo info : infos) { 90 if (info == null) continue; 91 final CharSequence text = info.getText(); 92 if (null == text) continue; 93 final SuggestedWordInfo suggestedWordInfo = new SuggestedWordInfo(text.toString(), 94 SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_APP_DEFINED, 95 Dictionary.TYPE_APPLICATION_DEFINED); 96 result.add(suggestedWordInfo); 97 } 98 return result; 99 } 100 101 // Should get rid of the first one (what the user typed previously) from suggestions 102 // and replace it with what the user currently typed. 103 public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions( 104 final String typedWord, final SuggestedWords previousSuggestions) { 105 final ArrayList<SuggestedWordInfo> suggestionsList = CollectionUtils.newArrayList(); 106 final HashSet<String> alreadySeen = CollectionUtils.newHashSet(); 107 suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE, 108 SuggestedWordInfo.KIND_TYPED, Dictionary.TYPE_USER_TYPED)); 109 alreadySeen.add(typedWord.toString()); 110 final int previousSize = previousSuggestions.size(); 111 for (int pos = 1; pos < previousSize; pos++) { 112 final SuggestedWordInfo prevWordInfo = previousSuggestions.getInfo(pos); 113 final String prevWord = prevWordInfo.mWord; 114 // Filter out duplicate suggestion. 115 if (!alreadySeen.contains(prevWord)) { 116 suggestionsList.add(prevWordInfo); 117 alreadySeen.add(prevWord); 118 } 119 } 120 return suggestionsList; 121 } 122 123 public static final class SuggestedWordInfo { 124 public static final int MAX_SCORE = Integer.MAX_VALUE; 125 public static final int KIND_MASK_KIND = 0xFF; // Mask to get only the kind 126 public static final int KIND_TYPED = 0; // What user typed 127 public static final int KIND_CORRECTION = 1; // Simple correction/suggestion 128 public static final int KIND_COMPLETION = 2; // Completion (suggestion with appended chars) 129 public static final int KIND_WHITELIST = 3; // Whitelisted word 130 public static final int KIND_BLACKLIST = 4; // Blacklisted word 131 public static final int KIND_HARDCODED = 5; // Hardcoded suggestion, e.g. punctuation 132 public static final int KIND_APP_DEFINED = 6; // Suggested by the application 133 public static final int KIND_SHORTCUT = 7; // A shortcut 134 public static final int KIND_PREDICTION = 8; // A prediction (== a suggestion with no input) 135 public static final int KIND_RESUMED = 9; // A resumed suggestion (comes from a span) 136 137 public static final int KIND_MASK_FLAGS = 0xFFFFFF00; // Mask to get the flags 138 public static final int KIND_FLAG_POSSIBLY_OFFENSIVE = 0x80000000; 139 public static final int KIND_FLAG_EXACT_MATCH = 0x40000000; 140 141 public final String mWord; 142 public final int mScore; 143 public final int mKind; // one of the KIND_* constants above 144 public final int mCodePointCount; 145 public final String mSourceDict; 146 private String mDebugString = ""; 147 148 public SuggestedWordInfo(final String word, final int score, final int kind, 149 final String sourceDict) { 150 mWord = word; 151 mScore = score; 152 mKind = kind; 153 mSourceDict = sourceDict; 154 mCodePointCount = StringUtils.codePointCount(mWord); 155 } 156 157 158 public void setDebugString(final String str) { 159 if (null == str) throw new NullPointerException("Debug info is null"); 160 mDebugString = str; 161 } 162 163 public String getDebugString() { 164 return mDebugString; 165 } 166 167 public int codePointCount() { 168 return mCodePointCount; 169 } 170 171 public int codePointAt(int i) { 172 return mWord.codePointAt(i); 173 } 174 175 @Override 176 public String toString() { 177 if (TextUtils.isEmpty(mDebugString)) { 178 return mWord; 179 } else { 180 return mWord + " (" + mDebugString + ")"; 181 } 182 } 183 184 // TODO: Consolidate this method and StringUtils.removeDupes() in the future. 185 public static void removeDups(ArrayList<SuggestedWordInfo> candidates) { 186 if (candidates.size() <= 1) { 187 return; 188 } 189 int i = 1; 190 while (i < candidates.size()) { 191 final SuggestedWordInfo cur = candidates.get(i); 192 for (int j = 0; j < i; ++j) { 193 final SuggestedWordInfo previous = candidates.get(j); 194 if (cur.mWord.equals(previous.mWord)) { 195 candidates.remove(cur.mScore < previous.mScore ? i : j); 196 --i; 197 break; 198 } 199 } 200 ++i; 201 } 202 } 203 } 204 205 // SuggestedWords is an immutable object, as much as possible. We must not just remove 206 // words from the member ArrayList as some other parties may expect the object to never change. 207 public SuggestedWords getSuggestedWordsExcludingTypedWord() { 208 final ArrayList<SuggestedWordInfo> newSuggestions = CollectionUtils.newArrayList(); 209 for (int i = 0; i < mSuggestedWordInfoList.size(); ++i) { 210 final SuggestedWordInfo info = mSuggestedWordInfoList.get(i); 211 if (SuggestedWordInfo.KIND_TYPED != info.mKind) { 212 newSuggestions.add(info); 213 } 214 } 215 // We should never autocorrect, so we say the typed word is valid. Also, in this case, 216 // no auto-correction should take place hence willAutoCorrect = false. 217 return new SuggestedWords(newSuggestions, true /* typedWordValid */, 218 false /* willAutoCorrect */, mIsPunctuationSuggestions, mIsObsoleteSuggestions, 219 mIsPrediction); 220 } 221 } 222