2009-03-13 22:11:42 +00:00
|
|
|
/*
|
2010-03-26 22:07:10 +00:00
|
|
|
* Copyright (C) 2008 The Android Open Source Project
|
2009-03-13 22:11:42 +00:00
|
|
|
*
|
|
|
|
* 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;
|
|
|
|
|
2010-11-29 08:57:48 +00:00
|
|
|
import java.nio.ByteBuffer;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Arrays;
|
|
|
|
|
2009-03-13 22:11:42 +00:00
|
|
|
/**
|
|
|
|
* This class loads a dictionary and provides a list of suggestions for a given sequence of
|
|
|
|
* characters. This includes corrections and completions.
|
|
|
|
*/
|
|
|
|
public class Suggest implements Dictionary.WordCallback {
|
|
|
|
|
2010-12-08 07:04:16 +00:00
|
|
|
public static final String TAG = "Suggest";
|
|
|
|
|
2010-08-20 05:35:02 +00:00
|
|
|
public static final int APPROX_MAX_WORD_LENGTH = 32;
|
|
|
|
|
2009-03-13 22:11:42 +00:00
|
|
|
public static final int CORRECTION_NONE = 0;
|
|
|
|
public static final int CORRECTION_BASIC = 1;
|
|
|
|
public static final int CORRECTION_FULL = 2;
|
2010-08-20 05:35:02 +00:00
|
|
|
public static final int CORRECTION_FULL_BIGRAM = 3;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Words that appear in both bigram and unigram data gets multiplier ranging from
|
|
|
|
* BIGRAM_MULTIPLIER_MIN to BIGRAM_MULTIPLIER_MAX depending on the frequency score from
|
|
|
|
* bigram data.
|
|
|
|
*/
|
|
|
|
public static final double BIGRAM_MULTIPLIER_MIN = 1.2;
|
|
|
|
public static final double BIGRAM_MULTIPLIER_MAX = 1.5;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Maximum possible bigram frequency. Will depend on how many bits are being used in data
|
|
|
|
* structure. Maximum bigram freqeuncy will get the BIGRAM_MULTIPLIER_MAX as the multiplier.
|
|
|
|
*/
|
|
|
|
public static final int MAXIMUM_BIGRAM_FREQUENCY = 127;
|
|
|
|
|
|
|
|
public static final int DIC_USER_TYPED = 0;
|
|
|
|
public static final int DIC_MAIN = 1;
|
|
|
|
public static final int DIC_USER = 2;
|
|
|
|
public static final int DIC_AUTO = 3;
|
|
|
|
public static final int DIC_CONTACTS = 4;
|
|
|
|
// If you add a type of dictionary, increment DIC_TYPE_LAST_ID
|
|
|
|
public static final int DIC_TYPE_LAST_ID = 4;
|
2009-07-17 19:51:45 +00:00
|
|
|
|
2010-03-17 22:36:23 +00:00
|
|
|
static final int LARGE_DICTIONARY_THRESHOLD = 200 * 1000;
|
2009-12-18 21:39:18 +00:00
|
|
|
|
|
|
|
private BinaryDictionary mMainDict;
|
2009-07-17 19:51:45 +00:00
|
|
|
|
2009-03-13 22:11:42 +00:00
|
|
|
private Dictionary mUserDictionary;
|
2009-07-17 19:51:45 +00:00
|
|
|
|
|
|
|
private Dictionary mAutoDictionary;
|
|
|
|
|
2009-07-28 23:48:47 +00:00
|
|
|
private Dictionary mContactsDictionary;
|
|
|
|
|
2010-08-20 05:35:02 +00:00
|
|
|
private Dictionary mUserBigramDictionary;
|
|
|
|
|
2009-03-13 22:11:42 +00:00
|
|
|
private int mPrefMaxSuggestions = 12;
|
2009-07-17 19:51:45 +00:00
|
|
|
|
2010-08-20 05:35:02 +00:00
|
|
|
private static final int PREF_MAX_BIGRAMS = 60;
|
|
|
|
|
2010-02-18 19:09:43 +00:00
|
|
|
private boolean mAutoTextEnabled;
|
|
|
|
|
2010-12-11 08:06:24 +00:00
|
|
|
private double mAutoCorrectionThreshold;
|
2009-03-13 22:11:42 +00:00
|
|
|
private int[] mPriorities = new int[mPrefMaxSuggestions];
|
2010-08-20 05:35:02 +00:00
|
|
|
private int[] mBigramPriorities = new int[PREF_MAX_BIGRAMS];
|
|
|
|
|
2010-02-05 22:07:04 +00:00
|
|
|
// Handle predictive correction for only the first 1280 characters for performance reasons
|
|
|
|
// If we support scripts that need latin characters beyond that, we should probably use some
|
|
|
|
// kind of a sparse array or language specific list with a mapping lookup table.
|
|
|
|
// 1280 is the size of the BASE_CHARS array in ExpandableDictionary, which is a basic set of
|
|
|
|
// latin characters.
|
|
|
|
private int[] mNextLettersFrequencies = new int[1280];
|
2009-10-28 01:08:19 +00:00
|
|
|
private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>();
|
2010-08-20 05:35:02 +00:00
|
|
|
ArrayList<CharSequence> mBigramSuggestions = new ArrayList<CharSequence>();
|
2009-10-28 01:08:19 +00:00
|
|
|
private ArrayList<CharSequence> mStringPool = new ArrayList<CharSequence>();
|
2009-03-13 22:11:42 +00:00
|
|
|
private boolean mHaveCorrection;
|
|
|
|
private CharSequence mOriginalWord;
|
|
|
|
private String mLowerOriginalWord;
|
2010-09-27 15:32:35 +00:00
|
|
|
|
|
|
|
// TODO: Remove these member variables by passing more context to addWord() callback method
|
|
|
|
private boolean mIsFirstCharCapitalized;
|
|
|
|
private boolean mIsAllUpperCase;
|
2009-03-13 22:11:42 +00:00
|
|
|
|
|
|
|
private int mCorrectionMode = CORRECTION_BASIC;
|
|
|
|
|
2010-08-20 05:35:02 +00:00
|
|
|
public Suggest(Context context, int[] dictionaryResId) {
|
|
|
|
mMainDict = new BinaryDictionary(context, dictionaryResId, DIC_MAIN);
|
|
|
|
initPool();
|
|
|
|
}
|
|
|
|
|
|
|
|
public Suggest(Context context, ByteBuffer byteBuffer) {
|
|
|
|
mMainDict = new BinaryDictionary(context, byteBuffer, DIC_MAIN);
|
|
|
|
initPool();
|
|
|
|
}
|
2009-03-13 22:11:42 +00:00
|
|
|
|
2010-08-20 05:35:02 +00:00
|
|
|
private void initPool() {
|
2009-03-13 22:11:42 +00:00
|
|
|
for (int i = 0; i < mPrefMaxSuggestions; i++) {
|
2010-08-20 05:35:02 +00:00
|
|
|
StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
|
2009-03-13 22:11:42 +00:00
|
|
|
mStringPool.add(sb);
|
|
|
|
}
|
|
|
|
}
|
2009-07-17 19:51:45 +00:00
|
|
|
|
2010-02-18 19:09:43 +00:00
|
|
|
public void setAutoTextEnabled(boolean enabled) {
|
|
|
|
mAutoTextEnabled = enabled;
|
|
|
|
}
|
|
|
|
|
2009-03-13 22:11:42 +00:00
|
|
|
public int getCorrectionMode() {
|
|
|
|
return mCorrectionMode;
|
|
|
|
}
|
2009-07-17 19:51:45 +00:00
|
|
|
|
2009-03-13 22:11:42 +00:00
|
|
|
public void setCorrectionMode(int mode) {
|
|
|
|
mCorrectionMode = mode;
|
|
|
|
}
|
|
|
|
|
2009-12-18 21:39:18 +00:00
|
|
|
public boolean hasMainDictionary() {
|
|
|
|
return mMainDict.getSize() > LARGE_DICTIONARY_THRESHOLD;
|
|
|
|
}
|
|
|
|
|
2010-08-20 05:35:02 +00:00
|
|
|
public int getApproxMaxWordLength() {
|
|
|
|
return APPROX_MAX_WORD_LENGTH;
|
|
|
|
}
|
|
|
|
|
2009-03-13 22:11:42 +00:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
2009-07-28 23:48:47 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets an optional contacts dictionary resource to be loaded.
|
|
|
|
*/
|
|
|
|
public void setContactsDictionary(Dictionary userDictionary) {
|
|
|
|
mContactsDictionary = userDictionary;
|
|
|
|
}
|
2009-07-17 19:51:45 +00:00
|
|
|
|
|
|
|
public void setAutoDictionary(Dictionary autoDictionary) {
|
|
|
|
mAutoDictionary = autoDictionary;
|
|
|
|
}
|
2009-03-13 22:11:42 +00:00
|
|
|
|
2010-08-20 05:35:02 +00:00
|
|
|
public void setUserBigramDictionary(Dictionary userBigramDictionary) {
|
|
|
|
mUserBigramDictionary = userBigramDictionary;
|
|
|
|
}
|
|
|
|
|
2010-12-11 08:06:24 +00:00
|
|
|
public void setAutoCorrectionThreshold(double threshold) {
|
|
|
|
mAutoCorrectionThreshold = threshold;
|
2010-09-28 03:23:26 +00:00
|
|
|
}
|
|
|
|
|
2009-03-13 22:11:42 +00:00
|
|
|
/**
|
|
|
|
* 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];
|
2010-08-20 05:35:02 +00:00
|
|
|
mBigramPriorities = new int[PREF_MAX_BIGRAMS];
|
|
|
|
collectGarbage(mSuggestions, mPrefMaxSuggestions);
|
2009-03-13 22:11:42 +00:00
|
|
|
while (mStringPool.size() < mPrefMaxSuggestions) {
|
2010-08-20 05:35:02 +00:00
|
|
|
StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
|
2009-03-13 22:11:42 +00:00
|
|
|
mStringPool.add(sb);
|
|
|
|
}
|
|
|
|
}
|
2009-07-30 17:11:33 +00:00
|
|
|
|
2009-03-13 22:11:42 +00:00
|
|
|
/**
|
2010-12-10 11:50:30 +00:00
|
|
|
* Returns a object which represents suggested words that match the list of character codes
|
|
|
|
* passed in. This object contents will be overwritten the next time this function is called.
|
2010-08-20 05:35:02 +00:00
|
|
|
* @param view a view for retrieving the context for AutoText
|
|
|
|
* @param wordComposer contains what is currently being typed
|
|
|
|
* @param prevWordForBigram previous word (used only for bigram)
|
2010-12-10 11:50:30 +00:00
|
|
|
* @return suggested words object.
|
2009-03-13 22:11:42 +00:00
|
|
|
*/
|
2010-12-10 11:50:30 +00:00
|
|
|
public SuggestedWords getSuggestions(View view, WordComposer wordComposer,
|
|
|
|
boolean includeTypedWordIfValid, CharSequence prevWordForBigram) {
|
|
|
|
return getSuggestedWordBuilder(view, wordComposer, includeTypedWordIfValid,
|
|
|
|
prevWordForBigram).build();
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder
|
|
|
|
public SuggestedWords.Builder getSuggestedWordBuilder(View view, WordComposer wordComposer,
|
2010-08-20 05:35:02 +00:00
|
|
|
boolean includeTypedWordIfValid, CharSequence prevWordForBigram) {
|
|
|
|
LatinImeLogger.onStartSuggestion(prevWordForBigram);
|
2009-03-13 22:11:42 +00:00
|
|
|
mHaveCorrection = false;
|
2010-09-27 15:32:35 +00:00
|
|
|
mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
|
|
|
|
mIsAllUpperCase = wordComposer.isAllUpperCase();
|
2010-08-20 05:35:02 +00:00
|
|
|
collectGarbage(mSuggestions, mPrefMaxSuggestions);
|
2009-03-13 22:11:42 +00:00
|
|
|
Arrays.fill(mPriorities, 0);
|
2010-02-05 22:07:04 +00:00
|
|
|
Arrays.fill(mNextLettersFrequencies, 0);
|
|
|
|
|
2009-03-13 22:11:42 +00:00
|
|
|
// Save a lowercase version of the original word
|
|
|
|
mOriginalWord = wordComposer.getTypedWord();
|
|
|
|
if (mOriginalWord != null) {
|
2010-08-20 05:35:02 +00:00
|
|
|
final String mOriginalWordString = mOriginalWord.toString();
|
|
|
|
mOriginalWord = mOriginalWordString;
|
|
|
|
mLowerOriginalWord = mOriginalWordString.toLowerCase();
|
|
|
|
// Treating USER_TYPED as UNIGRAM suggestion for logging now.
|
|
|
|
LatinImeLogger.onAddSuggestedWord(mOriginalWordString, Suggest.DIC_USER_TYPED,
|
|
|
|
Dictionary.DataType.UNIGRAM);
|
2009-03-13 22:11:42 +00:00
|
|
|
} else {
|
|
|
|
mLowerOriginalWord = "";
|
|
|
|
}
|
2010-08-20 05:35:02 +00:00
|
|
|
|
|
|
|
if (wordComposer.size() == 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM
|
|
|
|
|| mCorrectionMode == CORRECTION_BASIC)) {
|
|
|
|
// At first character typed, search only the bigrams
|
|
|
|
Arrays.fill(mBigramPriorities, 0);
|
|
|
|
collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS);
|
|
|
|
|
|
|
|
if (!TextUtils.isEmpty(prevWordForBigram)) {
|
|
|
|
CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase();
|
|
|
|
if (mMainDict.isValidWord(lowerPrevWord)) {
|
|
|
|
prevWordForBigram = lowerPrevWord;
|
|
|
|
}
|
|
|
|
if (mUserBigramDictionary != null) {
|
|
|
|
mUserBigramDictionary.getBigrams(wordComposer, prevWordForBigram, this,
|
|
|
|
mNextLettersFrequencies);
|
|
|
|
}
|
|
|
|
if (mContactsDictionary != null) {
|
|
|
|
mContactsDictionary.getBigrams(wordComposer, prevWordForBigram, this,
|
|
|
|
mNextLettersFrequencies);
|
|
|
|
}
|
|
|
|
if (mMainDict != null) {
|
|
|
|
mMainDict.getBigrams(wordComposer, prevWordForBigram, this,
|
|
|
|
mNextLettersFrequencies);
|
|
|
|
}
|
|
|
|
char currentChar = wordComposer.getTypedWord().charAt(0);
|
|
|
|
char currentCharUpper = Character.toUpperCase(currentChar);
|
|
|
|
int count = 0;
|
|
|
|
int bigramSuggestionSize = mBigramSuggestions.size();
|
|
|
|
for (int i = 0; i < bigramSuggestionSize; i++) {
|
|
|
|
if (mBigramSuggestions.get(i).charAt(0) == currentChar
|
|
|
|
|| mBigramSuggestions.get(i).charAt(0) == currentCharUpper) {
|
|
|
|
int poolSize = mStringPool.size();
|
|
|
|
StringBuilder sb = poolSize > 0 ?
|
|
|
|
(StringBuilder) mStringPool.remove(poolSize - 1)
|
|
|
|
: new StringBuilder(getApproxMaxWordLength());
|
|
|
|
sb.setLength(0);
|
|
|
|
sb.append(mBigramSuggestions.get(i));
|
|
|
|
mSuggestions.add(count++, sb);
|
|
|
|
if (count > mPrefMaxSuggestions) break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (wordComposer.size() > 1) {
|
|
|
|
// At second character typed, search the unigrams (scores being affected by bigrams)
|
2009-07-28 23:48:47 +00:00
|
|
|
if (mUserDictionary != null || mContactsDictionary != null) {
|
|
|
|
if (mUserDictionary != null) {
|
2010-02-05 22:07:04 +00:00
|
|
|
mUserDictionary.getWords(wordComposer, this, mNextLettersFrequencies);
|
2009-07-28 23:48:47 +00:00
|
|
|
}
|
|
|
|
if (mContactsDictionary != null) {
|
2010-02-05 22:07:04 +00:00
|
|
|
mContactsDictionary.getWords(wordComposer, this, mNextLettersFrequencies);
|
2009-07-28 23:48:47 +00:00
|
|
|
}
|
|
|
|
|
2010-02-20 00:38:58 +00:00
|
|
|
if (mSuggestions.size() > 0 && isValidWord(mOriginalWord)
|
2010-08-20 05:35:02 +00:00
|
|
|
&& (mCorrectionMode == CORRECTION_FULL
|
|
|
|
|| mCorrectionMode == CORRECTION_FULL_BIGRAM)) {
|
2009-03-13 22:11:42 +00:00
|
|
|
mHaveCorrection = true;
|
|
|
|
}
|
|
|
|
}
|
2010-02-05 22:07:04 +00:00
|
|
|
mMainDict.getWords(wordComposer, this, mNextLettersFrequencies);
|
2010-08-20 05:35:02 +00:00
|
|
|
if ((mCorrectionMode == CORRECTION_FULL || mCorrectionMode == CORRECTION_FULL_BIGRAM)
|
2010-09-28 03:23:26 +00:00
|
|
|
&& mSuggestions.size() > 0 && mPriorities.length > 0) {
|
|
|
|
// TODO: when the normalized score of the first suggestion is nearly equals to
|
|
|
|
// the normalized score of the second suggestion, behave less aggressive.
|
2010-12-09 12:06:26 +00:00
|
|
|
final double normalizedScore = Utils.calcNormalizedScore(
|
2010-09-28 03:23:26 +00:00
|
|
|
mOriginalWord, mSuggestions.get(0), mPriorities[0]);
|
2010-12-08 07:04:16 +00:00
|
|
|
if (LatinImeLogger.sDBG) {
|
|
|
|
Log.d(TAG, "Normalized " + mOriginalWord + "," + mSuggestions.get(0) + ","
|
|
|
|
+ mPriorities[0] + normalizedScore
|
2010-12-11 08:06:24 +00:00
|
|
|
+ "(" + mAutoCorrectionThreshold + ")");
|
2010-12-08 07:04:16 +00:00
|
|
|
}
|
2010-12-11 08:06:24 +00:00
|
|
|
if (normalizedScore >= mAutoCorrectionThreshold) {
|
2010-09-28 03:23:26 +00:00
|
|
|
mHaveCorrection = true;
|
|
|
|
}
|
2009-03-13 22:11:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (mOriginalWord != null) {
|
|
|
|
mSuggestions.add(0, mOriginalWord.toString());
|
|
|
|
}
|
2010-02-18 19:09:43 +00:00
|
|
|
if (mAutoTextEnabled) {
|
|
|
|
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);
|
2010-11-01 11:25:07 +00:00
|
|
|
// Is there an AutoText (also known as Quick Fixes) correction?
|
2010-02-18 19:09:43 +00:00
|
|
|
boolean canAdd = autoText != null;
|
2010-11-01 11:25:07 +00:00
|
|
|
// Capitalize as needed
|
|
|
|
final int autoTextLength = autoText != null ? autoText.length() : 0;
|
|
|
|
if (autoTextLength > 0 && (mIsAllUpperCase || mIsFirstCharCapitalized)) {
|
|
|
|
int poolSize = mStringPool.size();
|
|
|
|
StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove(
|
|
|
|
poolSize - 1) : new StringBuilder(getApproxMaxWordLength());
|
|
|
|
sb.setLength(0);
|
|
|
|
if (mIsAllUpperCase) {
|
|
|
|
sb.append(autoText.toString().toUpperCase());
|
|
|
|
} else if (mIsFirstCharCapitalized) {
|
|
|
|
sb.append(Character.toUpperCase(autoText.charAt(0)));
|
|
|
|
if (autoTextLength > 1) {
|
|
|
|
sb.append(autoText.subSequence(1, autoTextLength));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
autoText = sb.toString();
|
|
|
|
}
|
2010-02-18 19:09:43 +00:00
|
|
|
// 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++;
|
|
|
|
}
|
2009-03-13 22:11:42 +00:00
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
2009-10-28 01:08:19 +00:00
|
|
|
removeDupes();
|
2010-12-10 11:50:30 +00:00
|
|
|
return new SuggestedWords.Builder().setWords(mSuggestions);
|
2009-03-13 22:11:42 +00:00
|
|
|
}
|
|
|
|
|
2010-02-05 22:07:04 +00:00
|
|
|
public int[] getNextLettersFrequencies() {
|
|
|
|
return mNextLettersFrequencies;
|
|
|
|
}
|
|
|
|
|
2009-10-28 01:08:19 +00:00
|
|
|
private void removeDupes() {
|
|
|
|
final ArrayList<CharSequence> suggestions = mSuggestions;
|
|
|
|
if (suggestions.size() < 2) return;
|
|
|
|
int i = 1;
|
|
|
|
// Don't cache suggestions.size(), since we may be removing items
|
|
|
|
while (i < suggestions.size()) {
|
|
|
|
final CharSequence cur = suggestions.get(i);
|
|
|
|
// Compare each candidate with each previous candidate
|
|
|
|
for (int j = 0; j < i; j++) {
|
|
|
|
CharSequence previous = suggestions.get(j);
|
|
|
|
if (TextUtils.equals(cur, previous)) {
|
|
|
|
removeFromSuggestions(i);
|
|
|
|
i--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void removeFromSuggestions(int index) {
|
|
|
|
CharSequence garbage = mSuggestions.remove(index);
|
|
|
|
if (garbage != null && garbage instanceof StringBuilder) {
|
|
|
|
mStringPool.add(garbage);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-03-13 22:11:42 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2010-12-02 09:46:21 +00:00
|
|
|
@Override
|
2010-08-20 05:35:02 +00:00
|
|
|
public boolean addWord(final char[] word, final int offset, final int length, int freq,
|
|
|
|
final int dicTypeId, final Dictionary.DataType dataType) {
|
|
|
|
Dictionary.DataType dataTypeForLog = dataType;
|
|
|
|
ArrayList<CharSequence> suggestions;
|
|
|
|
int[] priorities;
|
|
|
|
int prefMaxSuggestions;
|
|
|
|
if(dataType == Dictionary.DataType.BIGRAM) {
|
|
|
|
suggestions = mBigramSuggestions;
|
|
|
|
priorities = mBigramPriorities;
|
|
|
|
prefMaxSuggestions = PREF_MAX_BIGRAMS;
|
|
|
|
} else {
|
|
|
|
suggestions = mSuggestions;
|
|
|
|
priorities = mPriorities;
|
|
|
|
prefMaxSuggestions = mPrefMaxSuggestions;
|
|
|
|
}
|
|
|
|
|
2009-03-13 22:11:42 +00:00
|
|
|
int pos = 0;
|
2010-08-20 05:35:02 +00:00
|
|
|
|
2009-03-13 22:11:42 +00:00
|
|
|
// Check if it's the same word, only caps are different
|
|
|
|
if (compareCaseInsensitive(mLowerOriginalWord, word, offset, length)) {
|
|
|
|
pos = 0;
|
|
|
|
} else {
|
2010-08-20 05:35:02 +00:00
|
|
|
if (dataType == Dictionary.DataType.UNIGRAM) {
|
|
|
|
// Check if the word was already added before (by bigram data)
|
|
|
|
int bigramSuggestion = searchBigramSuggestion(word,offset,length);
|
|
|
|
if(bigramSuggestion >= 0) {
|
|
|
|
dataTypeForLog = Dictionary.DataType.BIGRAM;
|
|
|
|
// turn freq from bigram into multiplier specified above
|
|
|
|
double multiplier = (((double) mBigramPriorities[bigramSuggestion])
|
|
|
|
/ MAXIMUM_BIGRAM_FREQUENCY)
|
|
|
|
* (BIGRAM_MULTIPLIER_MAX - BIGRAM_MULTIPLIER_MIN)
|
|
|
|
+ BIGRAM_MULTIPLIER_MIN;
|
|
|
|
/* Log.d(TAG,"bigram num: " + bigramSuggestion
|
|
|
|
+ " wordB: " + mBigramSuggestions.get(bigramSuggestion).toString()
|
|
|
|
+ " currentPriority: " + freq + " bigramPriority: "
|
|
|
|
+ mBigramPriorities[bigramSuggestion]
|
|
|
|
+ " multiplier: " + multiplier); */
|
|
|
|
freq = (int)Math.round((freq * multiplier));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-03-13 22:11:42 +00:00
|
|
|
// Check the last one's priority and bail
|
|
|
|
if (priorities[prefMaxSuggestions - 1] >= freq) return true;
|
|
|
|
while (pos < prefMaxSuggestions) {
|
|
|
|
if (priorities[pos] < freq
|
2010-08-20 05:35:02 +00:00
|
|
|
|| (priorities[pos] == freq && length < suggestions.get(pos).length())) {
|
2009-03-13 22:11:42 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
pos++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (pos >= prefMaxSuggestions) {
|
|
|
|
return true;
|
|
|
|
}
|
2010-08-20 05:35:02 +00:00
|
|
|
|
2010-11-01 11:25:07 +00:00
|
|
|
System.arraycopy(priorities, pos, priorities, pos + 1, prefMaxSuggestions - pos - 1);
|
2009-03-13 22:11:42 +00:00
|
|
|
priorities[pos] = freq;
|
|
|
|
int poolSize = mStringPool.size();
|
|
|
|
StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1)
|
2010-08-20 05:35:02 +00:00
|
|
|
: new StringBuilder(getApproxMaxWordLength());
|
2009-03-13 22:11:42 +00:00
|
|
|
sb.setLength(0);
|
2010-09-27 15:32:35 +00:00
|
|
|
if (mIsAllUpperCase) {
|
|
|
|
sb.append(new String(word, offset, length).toUpperCase());
|
|
|
|
} else if (mIsFirstCharCapitalized) {
|
2010-01-16 02:21:58 +00:00
|
|
|
sb.append(Character.toUpperCase(word[offset]));
|
|
|
|
if (length > 1) {
|
|
|
|
sb.append(word, offset + 1, length - 1);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
sb.append(word, offset, length);
|
|
|
|
}
|
2010-08-20 05:35:02 +00:00
|
|
|
suggestions.add(pos, sb);
|
|
|
|
if (suggestions.size() > prefMaxSuggestions) {
|
|
|
|
CharSequence garbage = suggestions.remove(prefMaxSuggestions);
|
2009-03-13 22:11:42 +00:00
|
|
|
if (garbage instanceof StringBuilder) {
|
|
|
|
mStringPool.add(garbage);
|
|
|
|
}
|
2010-08-20 05:35:02 +00:00
|
|
|
} else {
|
|
|
|
LatinImeLogger.onAddSuggestedWord(sb.toString(), dicTypeId, dataTypeForLog);
|
2009-03-13 22:11:42 +00:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-08-20 05:35:02 +00:00
|
|
|
private int searchBigramSuggestion(final char[] word, final int offset, final int length) {
|
|
|
|
// TODO This is almost O(n^2). Might need fix.
|
|
|
|
// search whether the word appeared in bigram data
|
|
|
|
int bigramSuggestSize = mBigramSuggestions.size();
|
|
|
|
for(int i = 0; i < bigramSuggestSize; i++) {
|
|
|
|
if(mBigramSuggestions.get(i).length() == length) {
|
|
|
|
boolean chk = true;
|
|
|
|
for(int j = 0; j < length; j++) {
|
|
|
|
if(mBigramSuggestions.get(i).charAt(j) != word[offset+j]) {
|
|
|
|
chk = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(chk) return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2009-03-13 22:11:42 +00:00
|
|
|
public boolean isValidWord(final CharSequence word) {
|
|
|
|
if (word == null || word.length() == 0) {
|
|
|
|
return false;
|
|
|
|
}
|
2010-03-24 23:44:31 +00:00
|
|
|
return mMainDict.isValidWord(word)
|
|
|
|
|| (mUserDictionary != null && mUserDictionary.isValidWord(word))
|
|
|
|
|| (mAutoDictionary != null && mAutoDictionary.isValidWord(word))
|
|
|
|
|| (mContactsDictionary != null && mContactsDictionary.isValidWord(word));
|
2009-03-13 22:11:42 +00:00
|
|
|
}
|
|
|
|
|
2010-08-20 05:35:02 +00:00
|
|
|
private void collectGarbage(ArrayList<CharSequence> suggestions, int prefMaxSuggestions) {
|
2009-03-13 22:11:42 +00:00
|
|
|
int poolSize = mStringPool.size();
|
2010-08-20 05:35:02 +00:00
|
|
|
int garbageSize = suggestions.size();
|
|
|
|
while (poolSize < prefMaxSuggestions && garbageSize > 0) {
|
|
|
|
CharSequence garbage = suggestions.get(garbageSize - 1);
|
2009-03-13 22:11:42 +00:00
|
|
|
if (garbage != null && garbage instanceof StringBuilder) {
|
|
|
|
mStringPool.add(garbage);
|
|
|
|
poolSize++;
|
|
|
|
}
|
|
|
|
garbageSize--;
|
|
|
|
}
|
2010-08-20 05:35:02 +00:00
|
|
|
if (poolSize == prefMaxSuggestions + 1) {
|
2009-03-13 22:11:42 +00:00
|
|
|
Log.w("Suggest", "String pool got too big: " + poolSize);
|
|
|
|
}
|
2010-08-20 05:35:02 +00:00
|
|
|
suggestions.clear();
|
2009-03-13 22:11:42 +00:00
|
|
|
}
|
2009-10-12 20:48:35 +00:00
|
|
|
|
|
|
|
public void close() {
|
|
|
|
if (mMainDict != null) {
|
|
|
|
mMainDict.close();
|
|
|
|
}
|
|
|
|
}
|
2009-03-13 22:11:42 +00:00
|
|
|
}
|