2009-03-13 22:11:42 +00:00
|
|
|
/*
|
2010-03-26 22:07:10 +00:00
|
|
|
* Copyright (C) 2008 The Android Open Source Project
|
2011-01-07 06:01:51 +00:00
|
|
|
*
|
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
|
2011-01-07 06:01:51 +00:00
|
|
|
*
|
2009-03-13 22:11:42 +00:00
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
2011-01-07 06:01:51 +00:00
|
|
|
*
|
2009-03-13 22:11:42 +00:00
|
|
|
* 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.TextUtils;
|
|
|
|
import android.util.Log;
|
|
|
|
|
2011-11-18 11:03:38 +00:00
|
|
|
import com.android.inputmethod.keyboard.Keyboard;
|
2011-08-04 03:08:22 +00:00
|
|
|
import com.android.inputmethod.keyboard.ProximityInfo;
|
2012-03-14 09:33:57 +00:00
|
|
|
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
|
2011-08-04 03:08:22 +00:00
|
|
|
|
2011-02-08 08:12:13 +00:00
|
|
|
import java.io.File;
|
2010-11-29 08:57:48 +00:00
|
|
|
import java.util.ArrayList;
|
2012-06-19 17:59:37 +00:00
|
|
|
import java.util.HashMap;
|
2011-03-04 00:02:28 +00:00
|
|
|
import java.util.HashSet;
|
2011-03-14 18:46:15 +00:00
|
|
|
import java.util.Locale;
|
2012-05-24 10:30:19 +00:00
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
2010-11-29 08:57:48 +00:00
|
|
|
|
2009-03-13 22:11:42 +00:00
|
|
|
/**
|
2011-01-07 06:01:51 +00:00
|
|
|
* This class loads a dictionary and provides a list of suggestions for a given sequence of
|
2009-03-13 22:11:42 +00:00
|
|
|
* characters. This includes corrections and completions.
|
|
|
|
*/
|
|
|
|
public class Suggest implements Dictionary.WordCallback {
|
2011-01-18 08:22:01 +00:00
|
|
|
public static final String TAG = Suggest.class.getSimpleName();
|
2010-12-08 07:04:16 +00:00
|
|
|
|
2010-08-20 05:35:02 +00:00
|
|
|
public static final int APPROX_MAX_WORD_LENGTH = 32;
|
|
|
|
|
2012-06-16 01:46:18 +00:00
|
|
|
// TODO: rename this to CORRECTION_OFF
|
2009-03-13 22:11:42 +00:00
|
|
|
public static final int CORRECTION_NONE = 0;
|
2012-06-16 01:46:18 +00:00
|
|
|
// TODO: rename this to CORRECTION_ON
|
2011-12-08 13:04:06 +00:00
|
|
|
public static final int CORRECTION_FULL = 1;
|
2010-08-20 05:35:02 +00:00
|
|
|
|
2011-07-14 22:57:26 +00:00
|
|
|
// It seems the following values are only used for logging.
|
2010-08-20 05:35:02 +00:00
|
|
|
public static final int DIC_USER_TYPED = 0;
|
|
|
|
public static final int DIC_MAIN = 1;
|
|
|
|
public static final int DIC_USER = 2;
|
2012-03-16 09:01:27 +00:00
|
|
|
public static final int DIC_USER_HISTORY = 3;
|
2010-08-20 05:35:02 +00:00
|
|
|
public static final int DIC_CONTACTS = 4;
|
2011-10-06 06:57:43 +00:00
|
|
|
public static final int DIC_WHITELIST = 6;
|
2010-08-20 05:35:02 +00:00
|
|
|
// If you add a type of dictionary, increment DIC_TYPE_LAST_ID
|
2011-07-14 22:57:26 +00:00
|
|
|
// TODO: this value seems unused. Remove it?
|
2011-10-06 06:57:43 +00:00
|
|
|
public static final int DIC_TYPE_LAST_ID = 6;
|
2011-03-04 07:56:10 +00:00
|
|
|
public static final String DICT_KEY_MAIN = "main";
|
|
|
|
public static final String DICT_KEY_CONTACTS = "contacts";
|
2011-07-14 22:57:26 +00:00
|
|
|
// User dictionary, the system-managed one.
|
2011-03-04 07:56:10 +00:00
|
|
|
public static final String DICT_KEY_USER = "user";
|
2012-03-16 09:01:27 +00:00
|
|
|
// User history dictionary for the unigram map, internal to LatinIME
|
|
|
|
public static final String DICT_KEY_USER_HISTORY_UNIGRAM = "history_unigram";
|
|
|
|
// User history dictionary for the bigram map, internal to LatinIME
|
|
|
|
public static final String DICT_KEY_USER_HISTORY_BIGRAM = "history_bigram";
|
2011-03-04 07:56:10 +00:00
|
|
|
public static final String DICT_KEY_WHITELIST ="whitelist";
|
2012-06-19 17:59:37 +00:00
|
|
|
// TODO: remove this map. This only serves as backward compatibility with a feature
|
|
|
|
// that has never been used and has been broken for a while.
|
|
|
|
private static final HashMap<String, Integer> sDictKeyToDictIndex
|
|
|
|
= new HashMap<String, Integer>();
|
|
|
|
static {
|
|
|
|
sDictKeyToDictIndex.put(DICT_KEY_MAIN, DIC_MAIN);
|
|
|
|
sDictKeyToDictIndex.put(DICT_KEY_USER, DIC_USER);
|
|
|
|
sDictKeyToDictIndex.put(DICT_KEY_USER_HISTORY_UNIGRAM, DIC_USER_HISTORY);
|
|
|
|
sDictKeyToDictIndex.put(DICT_KEY_USER_HISTORY_BIGRAM, DIC_USER_HISTORY);
|
|
|
|
sDictKeyToDictIndex.put(DICT_KEY_CONTACTS, DIC_CONTACTS);
|
|
|
|
sDictKeyToDictIndex.put(DICT_KEY_WHITELIST, DIC_WHITELIST);
|
|
|
|
}
|
2011-03-04 07:56:10 +00:00
|
|
|
|
2011-01-31 05:49:33 +00:00
|
|
|
private static final boolean DBG = LatinImeLogger.sDBG;
|
2011-01-18 08:22:01 +00:00
|
|
|
|
2012-06-16 00:49:42 +00:00
|
|
|
private Dictionary mMainDictionary;
|
2012-06-08 08:01:35 +00:00
|
|
|
private ContactsBinaryDictionary mContactsDict;
|
2011-03-04 07:56:10 +00:00
|
|
|
private WhitelistDictionary mWhiteListDictionary;
|
2012-05-24 10:30:19 +00:00
|
|
|
private final ConcurrentHashMap<String, Dictionary> mUnigramDictionaries =
|
|
|
|
new ConcurrentHashMap<String, Dictionary>();
|
|
|
|
private final ConcurrentHashMap<String, Dictionary> mBigramDictionaries =
|
|
|
|
new ConcurrentHashMap<String, Dictionary>();
|
2010-08-20 05:35:02 +00:00
|
|
|
|
2012-06-12 16:45:11 +00:00
|
|
|
public static final int MAX_SUGGESTIONS = 18;
|
2009-07-17 19:51:45 +00:00
|
|
|
|
2012-05-16 11:42:12 +00:00
|
|
|
private float mAutoCorrectionThreshold;
|
2010-08-20 05:35:02 +00:00
|
|
|
|
2012-04-02 07:33:24 +00:00
|
|
|
private ArrayList<SuggestedWordInfo> mSuggestions = new ArrayList<SuggestedWordInfo>();
|
2011-11-18 11:03:38 +00:00
|
|
|
private CharSequence mConsideredWord;
|
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;
|
2011-11-29 05:15:41 +00:00
|
|
|
private int mTrailingSingleQuotesCount;
|
2009-03-13 22:11:42 +00:00
|
|
|
|
2012-03-08 08:07:02 +00:00
|
|
|
private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4;
|
|
|
|
|
2012-04-11 12:02:26 +00:00
|
|
|
public Suggest(final Context context, final Locale locale) {
|
|
|
|
initAsynchronously(context, locale);
|
2010-08-20 05:35:02 +00:00
|
|
|
}
|
|
|
|
|
2012-04-06 10:12:05 +00:00
|
|
|
/* package for test */ Suggest(final Context context, final File dictionary,
|
|
|
|
final long startOffset, final long length, final Locale locale) {
|
2012-05-16 06:08:45 +00:00
|
|
|
final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(context, dictionary,
|
|
|
|
startOffset, length /* useFullEditDistance */, false, locale);
|
2012-06-16 00:49:42 +00:00
|
|
|
mMainDictionary = mainDict;
|
2012-05-16 06:08:45 +00:00
|
|
|
addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_MAIN, mainDict);
|
|
|
|
addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_MAIN, mainDict);
|
|
|
|
initWhitelistAndAutocorrectAndPool(context, locale);
|
2011-03-02 06:40:08 +00:00
|
|
|
}
|
|
|
|
|
2011-10-06 06:57:43 +00:00
|
|
|
private void initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale) {
|
|
|
|
mWhiteListDictionary = new WhitelistDictionary(context, locale);
|
2011-06-20 09:01:38 +00:00
|
|
|
addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_WHITELIST, mWhiteListDictionary);
|
2011-02-08 08:12:13 +00:00
|
|
|
}
|
|
|
|
|
2012-04-11 12:02:26 +00:00
|
|
|
private void initAsynchronously(final Context context, final Locale locale) {
|
|
|
|
resetMainDict(context, locale);
|
2011-07-21 09:01:58 +00:00
|
|
|
|
|
|
|
// TODO: read the whitelist and init the pool asynchronously too.
|
2011-08-17 08:32:25 +00:00
|
|
|
// initPool should be done asynchronously now that the pool is thread-safe.
|
2011-10-06 06:57:43 +00:00
|
|
|
initWhitelistAndAutocorrectAndPool(context, locale);
|
2011-07-21 09:01:58 +00:00
|
|
|
}
|
|
|
|
|
2012-05-24 10:30:19 +00:00
|
|
|
private static void addOrReplaceDictionary(
|
|
|
|
final ConcurrentHashMap<String, Dictionary> dictionaries,
|
|
|
|
final String key, final Dictionary dict) {
|
2011-06-20 09:01:38 +00:00
|
|
|
final Dictionary oldDict = (dict == null)
|
|
|
|
? dictionaries.remove(key)
|
|
|
|
: dictionaries.put(key, dict);
|
|
|
|
if (oldDict != null && dict != oldDict) {
|
|
|
|
oldDict.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-04-11 12:02:26 +00:00
|
|
|
public void resetMainDict(final Context context, final Locale locale) {
|
2012-06-16 00:49:42 +00:00
|
|
|
mMainDictionary = null;
|
2011-07-21 09:01:58 +00:00
|
|
|
new Thread("InitializeBinaryDictionary") {
|
2011-10-03 05:28:40 +00:00
|
|
|
@Override
|
2011-07-21 09:01:58 +00:00
|
|
|
public void run() {
|
2012-05-16 06:08:45 +00:00
|
|
|
final DictionaryCollection newMainDict =
|
|
|
|
DictionaryFactory.createMainDictionaryFromManager(context, locale);
|
2011-07-21 09:01:58 +00:00
|
|
|
addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_MAIN, newMainDict);
|
|
|
|
addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_MAIN, newMainDict);
|
2012-06-16 00:49:42 +00:00
|
|
|
mMainDictionary = newMainDict;
|
2011-07-21 09:01:58 +00:00
|
|
|
}
|
|
|
|
}.start();
|
2011-03-14 18:46:15 +00:00
|
|
|
}
|
|
|
|
|
2011-08-08 09:33:56 +00:00
|
|
|
// The main dictionary could have been loaded asynchronously. Don't cache the return value
|
|
|
|
// of this method.
|
2009-12-18 21:39:18 +00:00
|
|
|
public boolean hasMainDictionary() {
|
2012-06-16 00:49:42 +00:00
|
|
|
return null != mMainDictionary && mMainDictionary.isInitialized();
|
|
|
|
}
|
|
|
|
|
|
|
|
public Dictionary getMainDictionary() {
|
|
|
|
return mMainDictionary;
|
2009-12-18 21:39:18 +00:00
|
|
|
}
|
|
|
|
|
2012-06-08 08:01:35 +00:00
|
|
|
public ContactsBinaryDictionary getContactsDictionary() {
|
2011-08-09 02:54:10 +00:00
|
|
|
return mContactsDict;
|
|
|
|
}
|
|
|
|
|
2012-05-24 10:30:19 +00:00
|
|
|
public ConcurrentHashMap<String, Dictionary> getUnigramDictionaries() {
|
2011-03-04 07:56:10 +00:00
|
|
|
return mUnigramDictionaries;
|
|
|
|
}
|
|
|
|
|
2012-03-13 09:34:19 +00:00
|
|
|
public static int getApproxMaxWordLength() {
|
2010-08-20 05:35:02 +00:00
|
|
|
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
|
2011-07-14 22:57:26 +00:00
|
|
|
* before the main dictionary, if set. This refers to the system-managed user dictionary.
|
2009-03-13 22:11:42 +00:00
|
|
|
*/
|
2012-06-08 08:01:35 +00:00
|
|
|
public void setUserDictionary(UserBinaryDictionary userDictionary) {
|
2011-06-20 09:01:38 +00:00
|
|
|
addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_USER, userDictionary);
|
2009-03-13 22:11:42 +00:00
|
|
|
}
|
2009-07-28 23:48:47 +00:00
|
|
|
|
|
|
|
/**
|
2011-06-10 09:16:21 +00:00
|
|
|
* Sets an optional contacts dictionary resource to be loaded. It is also possible to remove
|
|
|
|
* the contacts dictionary by passing null to this method. In this case no contacts dictionary
|
|
|
|
* won't be used.
|
2009-07-28 23:48:47 +00:00
|
|
|
*/
|
2012-06-08 08:01:35 +00:00
|
|
|
public void setContactsDictionary(ContactsBinaryDictionary contactsDictionary) {
|
2011-08-09 02:54:10 +00:00
|
|
|
mContactsDict = contactsDictionary;
|
2011-06-20 09:01:38 +00:00
|
|
|
addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary);
|
|
|
|
addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary);
|
2009-07-28 23:48:47 +00:00
|
|
|
}
|
2011-01-07 06:01:51 +00:00
|
|
|
|
2012-06-08 08:01:35 +00:00
|
|
|
public void setUserHistoryDictionary(UserHistoryDictionary userHistoryDictionary) {
|
2012-03-16 09:01:27 +00:00
|
|
|
addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_USER_HISTORY_UNIGRAM,
|
|
|
|
userHistoryDictionary);
|
|
|
|
addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_USER_HISTORY_BIGRAM,
|
|
|
|
userHistoryDictionary);
|
2010-08-20 05:35:02 +00:00
|
|
|
}
|
|
|
|
|
2012-05-16 11:42:12 +00:00
|
|
|
public void setAutoCorrectionThreshold(float threshold) {
|
2010-12-11 08:06:24 +00:00
|
|
|
mAutoCorrectionThreshold = threshold;
|
2010-09-28 03:23:26 +00:00
|
|
|
}
|
|
|
|
|
2012-03-13 09:34:19 +00:00
|
|
|
private static CharSequence capitalizeWord(final boolean all, final boolean first,
|
|
|
|
final CharSequence word) {
|
2011-03-04 07:56:10 +00:00
|
|
|
if (TextUtils.isEmpty(word) || !(all || first)) return word;
|
|
|
|
final int wordLength = word.length();
|
2012-03-13 07:52:42 +00:00
|
|
|
final StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
|
2011-06-21 07:00:58 +00:00
|
|
|
// TODO: Must pay attention to locale when changing case.
|
2011-03-04 07:56:10 +00:00
|
|
|
if (all) {
|
|
|
|
sb.append(word.toString().toUpperCase());
|
|
|
|
} else if (first) {
|
|
|
|
sb.append(Character.toUpperCase(word.charAt(0)));
|
|
|
|
if (wordLength > 1) {
|
|
|
|
sb.append(word.subSequence(1, wordLength));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return sb;
|
|
|
|
}
|
|
|
|
|
2012-06-26 06:17:51 +00:00
|
|
|
// Compatibility for tests. TODO: remove this
|
2012-06-16 01:51:04 +00:00
|
|
|
public SuggestedWords getSuggestedWords(
|
|
|
|
final WordComposer wordComposer, CharSequence prevWordForBigram,
|
|
|
|
final ProximityInfo proximityInfo, final boolean isCorrectionEnabled) {
|
2012-06-26 06:17:51 +00:00
|
|
|
return getSuggestedWords(wordComposer, prevWordForBigram, proximityInfo,
|
|
|
|
isCorrectionEnabled, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder
|
|
|
|
public SuggestedWords getSuggestedWords(
|
|
|
|
final WordComposer wordComposer, CharSequence prevWordForBigram,
|
|
|
|
final ProximityInfo proximityInfo, final boolean isCorrectionEnabled,
|
|
|
|
final boolean isPrediction) {
|
2010-08-20 05:35:02 +00:00
|
|
|
LatinImeLogger.onStartSuggestion(prevWordForBigram);
|
2012-06-26 06:17:51 +00:00
|
|
|
mIsFirstCharCapitalized = !isPrediction && wordComposer.isFirstCharCapitalized();
|
|
|
|
mIsAllUpperCase = !isPrediction && wordComposer.isAllUpperCase();
|
2011-11-29 05:15:41 +00:00
|
|
|
mTrailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
|
2012-06-12 16:45:11 +00:00
|
|
|
mSuggestions = new ArrayList<SuggestedWordInfo>(MAX_SUGGESTIONS);
|
2010-02-05 22:07:04 +00:00
|
|
|
|
2011-11-18 11:03:38 +00:00
|
|
|
final String typedWord = wordComposer.getTypedWord();
|
2011-11-29 05:15:41 +00:00
|
|
|
final String consideredWord = mTrailingSingleQuotesCount > 0
|
|
|
|
? typedWord.substring(0, typedWord.length() - mTrailingSingleQuotesCount)
|
|
|
|
: typedWord;
|
2012-03-09 05:48:03 +00:00
|
|
|
// Treating USER_TYPED as UNIGRAM suggestion for logging now.
|
2012-03-14 09:50:08 +00:00
|
|
|
LatinImeLogger.onAddSuggestedWord(typedWord, Suggest.DIC_USER_TYPED, Dictionary.UNIGRAM);
|
2011-11-18 11:03:38 +00:00
|
|
|
mConsideredWord = consideredWord;
|
2010-08-20 05:35:02 +00:00
|
|
|
|
2012-06-16 01:51:04 +00:00
|
|
|
if (wordComposer.size() <= 1 && isCorrectionEnabled) {
|
2010-08-20 05:35:02 +00:00
|
|
|
// At first character typed, search only the bigrams
|
|
|
|
if (!TextUtils.isEmpty(prevWordForBigram)) {
|
2012-06-26 06:29:01 +00:00
|
|
|
final CharSequence lowerPrevWord;
|
2012-06-26 06:22:26 +00:00
|
|
|
if (StringUtils.hasUpperCase(prevWordForBigram)) {
|
|
|
|
// TODO: Must pay attention to locale when changing case.
|
2012-06-26 06:29:01 +00:00
|
|
|
lowerPrevWord = prevWordForBigram.toString().toLowerCase();
|
|
|
|
} else {
|
|
|
|
lowerPrevWord = null;
|
2012-06-26 06:22:26 +00:00
|
|
|
}
|
2012-06-26 06:29:01 +00:00
|
|
|
for (final String key : mBigramDictionaries.keySet()) {
|
2012-06-19 17:59:37 +00:00
|
|
|
final int dicTypeId = sDictKeyToDictIndex.get(key);
|
2012-06-26 06:29:01 +00:00
|
|
|
final Dictionary dictionary = mBigramDictionaries.get(key);
|
2012-06-19 17:59:37 +00:00
|
|
|
final ArrayList<SuggestedWordInfo> suggestions =
|
|
|
|
dictionary.getBigrams(wordComposer, prevWordForBigram, this);
|
2012-06-26 06:29:01 +00:00
|
|
|
if (null != lowerPrevWord) {
|
2012-06-19 17:59:37 +00:00
|
|
|
suggestions.addAll(dictionary.getBigrams(wordComposer, lowerPrevWord,
|
|
|
|
this));
|
|
|
|
}
|
|
|
|
for (final SuggestedWordInfo suggestion : suggestions) {
|
|
|
|
final String suggestionStr = suggestion.mWord.toString();
|
|
|
|
oldAddWord(suggestionStr.toCharArray(), null, 0, suggestionStr.length(),
|
|
|
|
suggestion.mScore, dicTypeId, Dictionary.BIGRAM);
|
2012-06-26 06:29:01 +00:00
|
|
|
}
|
2012-06-26 06:22:26 +00:00
|
|
|
}
|
2010-08-20 05:35:02 +00:00
|
|
|
}
|
|
|
|
} else if (wordComposer.size() > 1) {
|
2012-03-14 09:29:06 +00:00
|
|
|
final WordComposer wordComposerForLookup;
|
|
|
|
if (mTrailingSingleQuotesCount > 0) {
|
|
|
|
wordComposerForLookup = new WordComposer(wordComposer);
|
|
|
|
for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
|
|
|
|
wordComposerForLookup.deleteLast();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
wordComposerForLookup = wordComposer;
|
|
|
|
}
|
2010-08-20 05:35:02 +00:00
|
|
|
// At second character typed, search the unigrams (scores being affected by bigrams)
|
2011-03-04 00:02:28 +00:00
|
|
|
for (final String key : mUnigramDictionaries.keySet()) {
|
2011-07-14 22:57:26 +00:00
|
|
|
// Skip UserUnigramDictionary and WhitelistDictionary to lookup
|
2012-03-16 09:01:27 +00:00
|
|
|
if (key.equals(DICT_KEY_USER_HISTORY_UNIGRAM) || key.equals(DICT_KEY_WHITELIST))
|
2011-03-04 00:02:28 +00:00
|
|
|
continue;
|
2012-06-19 17:59:37 +00:00
|
|
|
final int dicTypeId = sDictKeyToDictIndex.get(key);
|
2011-03-04 00:02:28 +00:00
|
|
|
final Dictionary dictionary = mUnigramDictionaries.get(key);
|
2012-06-19 17:59:37 +00:00
|
|
|
final ArrayList<SuggestedWordInfo> suggestions =
|
|
|
|
dictionary.getWords(wordComposerForLookup, prevWordForBigram, this,
|
|
|
|
proximityInfo);
|
|
|
|
for (final SuggestedWordInfo suggestion : suggestions) {
|
|
|
|
final String suggestionStr = suggestion.mWord.toString();
|
|
|
|
oldAddWord(suggestionStr.toCharArray(), null, 0, suggestionStr.length(),
|
|
|
|
suggestion.mScore, dicTypeId, Dictionary.UNIGRAM);
|
|
|
|
}
|
2009-03-13 22:11:42 +00:00
|
|
|
}
|
|
|
|
}
|
2011-03-02 06:40:08 +00:00
|
|
|
|
2012-03-14 09:50:08 +00:00
|
|
|
final CharSequence whitelistedWord = capitalizeWord(mIsAllUpperCase,
|
|
|
|
mIsFirstCharCapitalized, mWhiteListDictionary.getWhitelistedWord(consideredWord));
|
2011-03-04 07:56:10 +00:00
|
|
|
|
2012-03-09 08:53:20 +00:00
|
|
|
final boolean hasAutoCorrection;
|
2012-06-16 01:51:04 +00:00
|
|
|
if (isCorrectionEnabled) {
|
2012-03-09 03:51:15 +00:00
|
|
|
final CharSequence autoCorrection =
|
|
|
|
AutoCorrection.computeAutoCorrectionWord(mUnigramDictionaries, wordComposer,
|
2012-04-02 07:33:24 +00:00
|
|
|
mSuggestions, consideredWord, mAutoCorrectionThreshold,
|
2012-03-09 03:51:15 +00:00
|
|
|
whitelistedWord);
|
2012-03-09 08:53:20 +00:00
|
|
|
hasAutoCorrection = (null != autoCorrection);
|
2012-03-09 03:51:15 +00:00
|
|
|
} else {
|
2012-03-09 08:53:20 +00:00
|
|
|
hasAutoCorrection = false;
|
2012-03-09 03:51:15 +00:00
|
|
|
}
|
2011-03-02 06:40:08 +00:00
|
|
|
|
2011-03-04 07:56:10 +00:00
|
|
|
if (whitelistedWord != null) {
|
2011-11-29 05:15:41 +00:00
|
|
|
if (mTrailingSingleQuotesCount > 0) {
|
|
|
|
final StringBuilder sb = new StringBuilder(whitelistedWord);
|
|
|
|
for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
|
|
|
|
sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE);
|
|
|
|
}
|
2012-06-12 16:10:18 +00:00
|
|
|
mSuggestions.add(0, new SuggestedWordInfo(sb.toString(),
|
|
|
|
SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_WHITELIST));
|
2011-11-29 05:15:41 +00:00
|
|
|
} else {
|
2012-06-12 16:10:18 +00:00
|
|
|
mSuggestions.add(0, new SuggestedWordInfo(whitelistedWord,
|
|
|
|
SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_WHITELIST));
|
2011-11-29 05:15:41 +00:00
|
|
|
}
|
2011-03-04 07:56:10 +00:00
|
|
|
}
|
|
|
|
|
2012-06-26 06:17:51 +00:00
|
|
|
if (!isPrediction) {
|
|
|
|
mSuggestions.add(0, new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE,
|
|
|
|
SuggestedWordInfo.KIND_TYPED));
|
|
|
|
}
|
2012-04-02 07:33:24 +00:00
|
|
|
SuggestedWordInfo.removeDups(mSuggestions);
|
2011-03-02 06:40:08 +00:00
|
|
|
|
2012-03-14 09:33:57 +00:00
|
|
|
final ArrayList<SuggestedWordInfo> suggestionsList;
|
2012-06-26 06:17:51 +00:00
|
|
|
if (DBG && !mSuggestions.isEmpty()) {
|
2012-04-02 07:33:24 +00:00
|
|
|
suggestionsList = getSuggestionsInfoListWithDebugInfo(typedWord, mSuggestions);
|
2012-03-09 10:42:20 +00:00
|
|
|
} else {
|
2012-04-02 07:33:24 +00:00
|
|
|
suggestionsList = mSuggestions;
|
2011-01-31 05:49:33 +00:00
|
|
|
}
|
2012-03-09 10:42:20 +00:00
|
|
|
|
2012-03-15 03:23:16 +00:00
|
|
|
// TODO: Change this scheme - a boolean is not enough. A whitelisted word may be "valid"
|
|
|
|
// but still autocorrected from - in the case the whitelist only capitalizes the word.
|
|
|
|
// The whitelist should be case-insensitive, so it's not possible to be consistent with
|
|
|
|
// a boolean flag. Right now this is handled with a slight hack in
|
|
|
|
// WhitelistDictionary#shouldForciblyAutoCorrectFrom.
|
|
|
|
final boolean allowsToBeAutoCorrected = AutoCorrection.allowsToBeAutoCorrected(
|
2012-05-16 06:08:45 +00:00
|
|
|
getUnigramDictionaries(), consideredWord, wordComposer.isFirstCharCapitalized())
|
|
|
|
// If we don't have a main dictionary, we never want to auto-correct. The reason for this
|
|
|
|
// is, the user may have a contact whose name happens to match a valid word in their
|
|
|
|
// language, and it will unexpectedly auto-correct. For example, if the user types in
|
|
|
|
// English with no dictionary and has a "Will" in their contact list, "will" would
|
|
|
|
// always auto-correct to "Will" which is unwanted. Hence, no main dict => no auto-correct.
|
2012-06-16 00:49:42 +00:00
|
|
|
&& hasMainDictionary();
|
2012-03-15 03:23:16 +00:00
|
|
|
|
2012-03-09 10:42:20 +00:00
|
|
|
boolean autoCorrectionAvailable = hasAutoCorrection;
|
2012-06-16 01:51:04 +00:00
|
|
|
if (isCorrectionEnabled) {
|
2012-03-09 10:42:20 +00:00
|
|
|
autoCorrectionAvailable |= !allowsToBeAutoCorrected;
|
|
|
|
}
|
|
|
|
// Don't auto-correct words with multiple capital letter
|
|
|
|
autoCorrectionAvailable &= !wordComposer.isMostlyCaps();
|
2012-04-26 09:01:13 +00:00
|
|
|
autoCorrectionAvailable &= !wordComposer.isResumed();
|
2012-03-14 09:29:06 +00:00
|
|
|
if (allowsToBeAutoCorrected && suggestionsList.size() > 1 && mAutoCorrectionThreshold > 0
|
2012-03-13 11:26:47 +00:00
|
|
|
&& Suggest.shouldBlockAutoCorrectionBySafetyNet(typedWord,
|
2012-03-14 09:29:06 +00:00
|
|
|
suggestionsList.get(1).mWord)) {
|
2012-03-14 09:50:08 +00:00
|
|
|
autoCorrectionAvailable = false;
|
2012-03-13 11:26:47 +00:00
|
|
|
}
|
2012-03-14 09:29:06 +00:00
|
|
|
return new SuggestedWords(suggestionsList,
|
2012-06-26 06:17:51 +00:00
|
|
|
!isPrediction && !allowsToBeAutoCorrected /* typedWordValid */,
|
|
|
|
!isPrediction && autoCorrectionAvailable /* hasAutoCorrectionCandidate */,
|
|
|
|
!isPrediction && allowsToBeAutoCorrected /* allowsToBeAutoCorrected */,
|
2012-03-15 04:12:08 +00:00
|
|
|
false /* isPunctuationSuggestions */,
|
2012-05-14 05:42:40 +00:00
|
|
|
false /* isObsoleteSuggestions */,
|
2012-06-26 06:17:51 +00:00
|
|
|
isPrediction);
|
2009-03-13 22:11:42 +00:00
|
|
|
}
|
|
|
|
|
2012-03-14 09:43:32 +00:00
|
|
|
private static ArrayList<SuggestedWordInfo> getSuggestionsInfoListWithDebugInfo(
|
2012-04-02 07:33:24 +00:00
|
|
|
final String typedWord, final ArrayList<SuggestedWordInfo> suggestions) {
|
|
|
|
final SuggestedWordInfo typedWordInfo = suggestions.get(0);
|
|
|
|
typedWordInfo.setDebugString("+");
|
2012-03-14 09:43:32 +00:00
|
|
|
final int suggestionsSize = suggestions.size();
|
|
|
|
final ArrayList<SuggestedWordInfo> suggestionsList =
|
|
|
|
new ArrayList<SuggestedWordInfo>(suggestionsSize);
|
2012-04-02 07:33:24 +00:00
|
|
|
suggestionsList.add(typedWordInfo);
|
2012-03-14 09:43:32 +00:00
|
|
|
// Note: i here is the index in mScores[], but the index in mSuggestions is one more
|
|
|
|
// than i because we added the typed word to mSuggestions without touching mScores.
|
2012-04-02 07:33:24 +00:00
|
|
|
for (int i = 0; i < suggestionsSize - 1; ++i) {
|
|
|
|
final SuggestedWordInfo cur = suggestions.get(i + 1);
|
2012-05-16 11:42:12 +00:00
|
|
|
final float normalizedScore = BinaryDictionary.calcNormalizedScore(
|
2012-05-15 08:43:31 +00:00
|
|
|
typedWord, cur.toString(), cur.mScore);
|
2012-03-14 09:43:32 +00:00
|
|
|
final String scoreInfoString;
|
|
|
|
if (normalizedScore > 0) {
|
2012-04-02 07:33:24 +00:00
|
|
|
scoreInfoString = String.format("%d (%4.2f)", cur.mScore, normalizedScore);
|
2012-03-14 09:43:32 +00:00
|
|
|
} else {
|
2012-04-02 07:33:24 +00:00
|
|
|
scoreInfoString = Integer.toString(cur.mScore);
|
2012-03-14 09:43:32 +00:00
|
|
|
}
|
2012-04-02 07:33:24 +00:00
|
|
|
cur.setDebugString(scoreInfoString);
|
|
|
|
suggestionsList.add(cur);
|
2012-03-14 09:43:32 +00:00
|
|
|
}
|
|
|
|
return suggestionsList;
|
|
|
|
}
|
|
|
|
|
2012-06-19 17:59:37 +00:00
|
|
|
// TODO: Remove this method
|
2010-12-02 09:46:21 +00:00
|
|
|
@Override
|
2012-06-25 08:44:54 +00:00
|
|
|
public boolean addWord(final char[] word, int[] indices, final int offset, final int length,
|
|
|
|
int score, final int dicTypeId, final int dataType) {
|
2012-06-19 17:59:37 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Use codepoint instead of char
|
|
|
|
public boolean oldAddWord(final char[] word, int[] indices, final int offset, final int length,
|
|
|
|
int score, final int dicTypeId, final int dataType) {
|
2012-01-26 03:49:43 +00:00
|
|
|
int dataTypeForLog = dataType;
|
2012-04-02 07:33:24 +00:00
|
|
|
final ArrayList<SuggestedWordInfo> suggestions;
|
2011-03-15 18:46:32 +00:00
|
|
|
final int prefMaxSuggestions;
|
2012-06-26 05:49:05 +00:00
|
|
|
suggestions = mSuggestions;
|
|
|
|
prefMaxSuggestions = MAX_SUGGESTIONS;
|
2010-08-20 05:35:02 +00:00
|
|
|
|
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
|
2012-03-08 08:07:02 +00:00
|
|
|
if (StringUtils.equalsIgnoreCase(mConsideredWord, word, offset, length)) {
|
Avoid the removal of high-ranking exactly typed candidates.
It used to be the case that the scoring system turns up the same word
that was entered with a different capitalization, but with a lower
score than some other, more frequent word. To cope with this, there
was code that would order such candidates in the first slot no matter
what. This processing is now useless because fully matching words now
have a huge boost that ensures they will get to the top of the list,
before any non-fully matching word (which means, differing only by
capitalization or accents).
The bug that did happen with this was, if a fully-matching word got
matched by several processing passes, and the (chronologically) later
score affected to this word was weaker, it would result in the
duplicate removal pass removing the stronger score. This in turn would
mess with autocorrect.
In an effort to keep the risk at a minimum for MR1, this change does
not actually remove the useless code, but adds a check in the odd case
to avoid the bad situation. Another change will remove the code for
ICS release.
bug: 4100269
Change-Id: I18c0575332981ffec0e257e26a360995838d521e
2011-03-15 22:56:29 +00:00
|
|
|
// TODO: remove this surrounding if clause and move this logic to
|
|
|
|
// getSuggestedWordBuilder.
|
|
|
|
if (suggestions.size() > 0) {
|
2012-04-02 07:33:24 +00:00
|
|
|
final SuggestedWordInfo currentHighestWord = suggestions.get(0);
|
Avoid the removal of high-ranking exactly typed candidates.
It used to be the case that the scoring system turns up the same word
that was entered with a different capitalization, but with a lower
score than some other, more frequent word. To cope with this, there
was code that would order such candidates in the first slot no matter
what. This processing is now useless because fully matching words now
have a huge boost that ensures they will get to the top of the list,
before any non-fully matching word (which means, differing only by
capitalization or accents).
The bug that did happen with this was, if a fully-matching word got
matched by several processing passes, and the (chronologically) later
score affected to this word was weaker, it would result in the
duplicate removal pass removing the stronger score. This in turn would
mess with autocorrect.
In an effort to keep the risk at a minimum for MR1, this change does
not actually remove the useless code, but adds a check in the odd case
to avoid the bad situation. Another change will remove the code for
ICS release.
bug: 4100269
Change-Id: I18c0575332981ffec0e257e26a360995838d521e
2011-03-15 22:56:29 +00:00
|
|
|
// If the current highest word is also equal to typed word, we need to compare
|
|
|
|
// frequency to determine the insertion position. This does not ensure strictly
|
|
|
|
// correct ordering, but ensures the top score is on top which is enough for
|
|
|
|
// removing duplicates correctly.
|
2012-04-02 07:33:24 +00:00
|
|
|
if (StringUtils.equalsIgnoreCase(currentHighestWord.mWord, word, offset, length)
|
|
|
|
&& score <= currentHighestWord.mScore) {
|
Avoid the removal of high-ranking exactly typed candidates.
It used to be the case that the scoring system turns up the same word
that was entered with a different capitalization, but with a lower
score than some other, more frequent word. To cope with this, there
was code that would order such candidates in the first slot no matter
what. This processing is now useless because fully matching words now
have a huge boost that ensures they will get to the top of the list,
before any non-fully matching word (which means, differing only by
capitalization or accents).
The bug that did happen with this was, if a fully-matching word got
matched by several processing passes, and the (chronologically) later
score affected to this word was weaker, it would result in the
duplicate removal pass removing the stronger score. This in turn would
mess with autocorrect.
In an effort to keep the risk at a minimum for MR1, this change does
not actually remove the useless code, but adds a check in the odd case
to avoid the bad situation. Another change will remove the code for
ICS release.
bug: 4100269
Change-Id: I18c0575332981ffec0e257e26a360995838d521e
2011-03-15 22:56:29 +00:00
|
|
|
pos = 1;
|
|
|
|
}
|
|
|
|
}
|
2009-03-13 22:11:42 +00:00
|
|
|
} else {
|
2011-03-15 18:46:32 +00:00
|
|
|
// Check the last one's score and bail
|
2012-04-02 07:33:24 +00:00
|
|
|
if (suggestions.size() >= prefMaxSuggestions
|
|
|
|
&& suggestions.get(prefMaxSuggestions - 1).mScore >= score) return true;
|
|
|
|
while (pos < suggestions.size()) {
|
|
|
|
final int curScore = suggestions.get(pos).mScore;
|
|
|
|
if (curScore < score
|
|
|
|
|| (curScore == score && length < suggestions.get(pos).codePointCount())) {
|
2009-03-13 22:11:42 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
pos++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (pos >= prefMaxSuggestions) {
|
|
|
|
return true;
|
|
|
|
}
|
2010-08-20 05:35:02 +00:00
|
|
|
|
2012-03-13 07:52:42 +00:00
|
|
|
final StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
|
2011-06-21 07:00:58 +00:00
|
|
|
// TODO: Must pay attention to locale when changing case.
|
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);
|
|
|
|
}
|
2011-11-29 05:15:41 +00:00
|
|
|
for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
|
2011-11-18 11:03:38 +00:00
|
|
|
sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE);
|
|
|
|
}
|
2012-06-12 16:10:18 +00:00
|
|
|
// TODO: figure out what type of suggestion this is
|
|
|
|
suggestions.add(pos, new SuggestedWordInfo(sb, score, SuggestedWordInfo.KIND_CORRECTION));
|
2010-08-20 05:35:02 +00:00
|
|
|
if (suggestions.size() > prefMaxSuggestions) {
|
2012-03-13 07:52:42 +00:00
|
|
|
suggestions.remove(prefMaxSuggestions);
|
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;
|
|
|
|
}
|
|
|
|
|
2009-10-12 20:48:35 +00:00
|
|
|
public void close() {
|
2012-03-16 15:50:51 +00:00
|
|
|
final HashSet<Dictionary> dictionaries = new HashSet<Dictionary>();
|
2011-03-04 00:02:28 +00:00
|
|
|
dictionaries.addAll(mUnigramDictionaries.values());
|
|
|
|
dictionaries.addAll(mBigramDictionaries.values());
|
|
|
|
for (final Dictionary dictionary : dictionaries) {
|
|
|
|
dictionary.close();
|
2009-10-12 20:48:35 +00:00
|
|
|
}
|
2012-06-16 00:49:42 +00:00
|
|
|
mMainDictionary = null;
|
2009-10-12 20:48:35 +00:00
|
|
|
}
|
2012-03-08 08:07:02 +00:00
|
|
|
|
|
|
|
// TODO: Resolve the inconsistencies between the native auto correction algorithms and
|
|
|
|
// this safety net
|
2012-03-09 12:44:40 +00:00
|
|
|
public static boolean shouldBlockAutoCorrectionBySafetyNet(final String typedWord,
|
|
|
|
final CharSequence suggestion) {
|
2012-03-08 08:07:02 +00:00
|
|
|
// Safety net for auto correction.
|
2012-03-09 12:33:47 +00:00
|
|
|
// Actually if we hit this safety net, it's a bug.
|
2012-03-08 08:07:02 +00:00
|
|
|
// If user selected aggressive auto correction mode, there is no need to use the safety
|
|
|
|
// net.
|
|
|
|
// If the length of typed word is less than MINIMUM_SAFETY_NET_CHAR_LENGTH,
|
|
|
|
// we should not use net because relatively edit distance can be big.
|
2012-03-09 12:44:40 +00:00
|
|
|
final int typedWordLength = typedWord.length();
|
|
|
|
if (typedWordLength < Suggest.MINIMUM_SAFETY_NET_CHAR_LENGTH) {
|
2012-03-08 08:07:02 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
final int maxEditDistanceOfNativeDictionary =
|
|
|
|
(typedWordLength < 5 ? 2 : typedWordLength / 2) + 1;
|
2012-03-09 12:44:40 +00:00
|
|
|
final int distance = BinaryDictionary.editDistance(typedWord, suggestion.toString());
|
2012-03-08 08:07:02 +00:00
|
|
|
if (DBG) {
|
|
|
|
Log.d(TAG, "Autocorrected edit distance = " + distance
|
|
|
|
+ ", " + maxEditDistanceOfNativeDictionary);
|
|
|
|
}
|
|
|
|
if (distance > maxEditDistanceOfNativeDictionary) {
|
|
|
|
if (DBG) {
|
2012-03-09 12:44:40 +00:00
|
|
|
Log.e(TAG, "Safety net: before = " + typedWord + ", after = " + suggestion);
|
2012-03-08 08:07:02 +00:00
|
|
|
Log.e(TAG, "(Error) The edit distance of this correction exceeds limit. "
|
|
|
|
+ "Turning off auto-correction.");
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2009-03-13 22:11:42 +00:00
|
|
|
}
|