diff options
Diffstat (limited to 'src/com/android/inputmethod/latin/Suggest.java')
-rwxr-xr-x | src/com/android/inputmethod/latin/Suggest.java | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/src/com/android/inputmethod/latin/Suggest.java b/src/com/android/inputmethod/latin/Suggest.java new file mode 100755 index 000000000..91decd66a --- /dev/null +++ b/src/com/android/inputmethod/latin/Suggest.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * 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.content.Context; +import android.text.AutoText; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * This class loads a dictionary and provides a list of suggestions for a given sequence of + * characters. This includes corrections and completions. + * @hide pending API Council Approval + */ +public class Suggest implements Dictionary.WordCallback { + + public static final int CORRECTION_NONE = 0; + public static final int CORRECTION_BASIC = 1; + public static final int CORRECTION_FULL = 2; + + private Dictionary mMainDict; + + private Dictionary mUserDictionary; + + private int mPrefMaxSuggestions = 12; + + private int[] mPriorities = new int[mPrefMaxSuggestions]; + private List<CharSequence> mSuggestions = new ArrayList<CharSequence>(); + private boolean mIncludeTypedWordIfValid; + private List<CharSequence> mStringPool = new ArrayList<CharSequence>(); + private Context mContext; + private boolean mHaveCorrection; + private CharSequence mOriginalWord; + private String mLowerOriginalWord; + + private int mCorrectionMode = CORRECTION_BASIC; + + + public Suggest(Context context, int dictionaryResId) { + mContext = context; + mMainDict = new BinaryDictionary(context, dictionaryResId); + for (int i = 0; i < mPrefMaxSuggestions; i++) { + StringBuilder sb = new StringBuilder(32); + mStringPool.add(sb); + } + } + + public int getCorrectionMode() { + return mCorrectionMode; + } + + public void setCorrectionMode(int mode) { + mCorrectionMode = mode; + } + + /** + * Sets an optional user dictionary resource to be loaded. The user dictionary is consulted + * before the main dictionary, if set. + */ + public void setUserDictionary(Dictionary userDictionary) { + mUserDictionary = userDictionary; + } + + /** + * Number of suggestions to generate from the input key sequence. This has + * to be a number between 1 and 100 (inclusive). + * @param maxSuggestions + * @throws IllegalArgumentException if the number is out of range + */ + public void setMaxSuggestions(int maxSuggestions) { + if (maxSuggestions < 1 || maxSuggestions > 100) { + throw new IllegalArgumentException("maxSuggestions must be between 1 and 100"); + } + mPrefMaxSuggestions = maxSuggestions; + mPriorities = new int[mPrefMaxSuggestions]; + collectGarbage(); + while (mStringPool.size() < mPrefMaxSuggestions) { + StringBuilder sb = new StringBuilder(32); + mStringPool.add(sb); + } + } + + private boolean haveSufficientCommonality(String original, CharSequence suggestion) { + final int len = Math.min(original.length(), suggestion.length()); + if (len <= 2) return true; + int matching = 0; + for (int i = 0; i < len; i++) { + if (UserDictionary.toLowerCase(original.charAt(i)) + == UserDictionary.toLowerCase(suggestion.charAt(i))) { + matching++; + } + } + if (len <= 4) { + return matching >= 2; + } else { + return matching > len / 2; + } + } + + /** + * Returns a list of words that match the list of character codes passed in. + * This list will be overwritten the next time this function is called. + * @param a view for retrieving the context for AutoText + * @param codes the list of codes. Each list item contains an array of character codes + * in order of probability where the character at index 0 in the array has the highest + * probability. + * @return list of suggestions. + */ + public List<CharSequence> getSuggestions(View view, WordComposer wordComposer, + boolean includeTypedWordIfValid) { + mHaveCorrection = false; + collectGarbage(); + Arrays.fill(mPriorities, 0); + mIncludeTypedWordIfValid = includeTypedWordIfValid; + + // Save a lowercase version of the original word + mOriginalWord = wordComposer.getTypedWord(); + if (mOriginalWord != null) { + mOriginalWord = mOriginalWord.toString(); + mLowerOriginalWord = mOriginalWord.toString().toLowerCase(); + } else { + mLowerOriginalWord = ""; + } + // Search the dictionary only if there are at least 2 characters + if (wordComposer.size() > 1) { + if (mUserDictionary != null) { + mUserDictionary.getWords(wordComposer, this); + if (mSuggestions.size() > 0 && isValidWord(mOriginalWord)) { + mHaveCorrection = true; + } + } + mMainDict.getWords(wordComposer, this); + if (mCorrectionMode == CORRECTION_FULL && mSuggestions.size() > 0) { + mHaveCorrection = true; + } + } + if (mOriginalWord != null) { + mSuggestions.add(0, mOriginalWord.toString()); + } + + // Check if the first suggestion has a minimum number of characters in common + if (mCorrectionMode == CORRECTION_FULL && mSuggestions.size() > 1) { + if (!haveSufficientCommonality(mLowerOriginalWord, mSuggestions.get(1))) { + mHaveCorrection = false; + } + } + + int i = 0; + int max = 6; + // Don't autotext the suggestions from the dictionaries + if (mCorrectionMode == CORRECTION_BASIC) max = 1; + while (i < mSuggestions.size() && i < max) { + String suggestedWord = mSuggestions.get(i).toString().toLowerCase(); + CharSequence autoText = + AutoText.get(suggestedWord, 0, suggestedWord.length(), view); + // Is there an AutoText correction? + boolean canAdd = autoText != null; + // Is that correction already the current prediction (or original word)? + canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i)); + // Is that correction already the next predicted word? + if (canAdd && i + 1 < mSuggestions.size() && mCorrectionMode != CORRECTION_BASIC) { + canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i + 1)); + } + if (canAdd) { + mHaveCorrection = true; + mSuggestions.add(i + 1, autoText); + i++; + } + i++; + } + + return mSuggestions; + } + + public boolean hasMinimalCorrection() { + return mHaveCorrection; + } + + private boolean compareCaseInsensitive(final String mLowerOriginalWord, + final char[] word, final int offset, final int length) { + final int originalLength = mLowerOriginalWord.length(); + if (originalLength == length && Character.isUpperCase(word[offset])) { + for (int i = 0; i < originalLength; i++) { + if (mLowerOriginalWord.charAt(i) != Character.toLowerCase(word[offset+i])) { + return false; + } + } + return true; + } + return false; + } + + public boolean addWord(final char[] word, final int offset, final int length, final int freq) { + int pos = 0; + final int[] priorities = mPriorities; + final int prefMaxSuggestions = mPrefMaxSuggestions; + // Check if it's the same word, only caps are different + if (compareCaseInsensitive(mLowerOriginalWord, word, offset, length)) { + pos = 0; + } else { + // Check the last one's priority and bail + if (priorities[prefMaxSuggestions - 1] >= freq) return true; + while (pos < prefMaxSuggestions) { + if (priorities[pos] < freq + || (priorities[pos] == freq && length < mSuggestions + .get(pos).length())) { + break; + } + pos++; + } + } + + if (pos >= prefMaxSuggestions) { + return true; + } + System.arraycopy(priorities, pos, priorities, pos + 1, + prefMaxSuggestions - pos - 1); + priorities[pos] = freq; + int poolSize = mStringPool.size(); + StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1) + : new StringBuilder(32); + sb.setLength(0); + sb.append(word, offset, length); + mSuggestions.add(pos, sb); + if (mSuggestions.size() > prefMaxSuggestions) { + CharSequence garbage = mSuggestions.remove(prefMaxSuggestions); + if (garbage instanceof StringBuilder) { + mStringPool.add(garbage); + } + } + return true; + } + + public boolean isValidWord(final CharSequence word) { + if (word == null || word.length() == 0) { + return false; + } + return (mCorrectionMode == CORRECTION_FULL && mMainDict.isValidWord(word)) + || (mCorrectionMode > CORRECTION_NONE && + (mUserDictionary != null && mUserDictionary.isValidWord(word))); + } + + private void collectGarbage() { + int poolSize = mStringPool.size(); + int garbageSize = mSuggestions.size(); + while (poolSize < mPrefMaxSuggestions && garbageSize > 0) { + CharSequence garbage = mSuggestions.get(garbageSize - 1); + if (garbage != null && garbage instanceof StringBuilder) { + mStringPool.add(garbage); + poolSize++; + } + garbageSize--; + } + if (poolSize == mPrefMaxSuggestions + 1) { + Log.w("Suggest", "String pool got too big: " + poolSize); + } + mSuggestions.clear(); + } +} |