Home | History | Annotate | Download | only in latin
      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