Extract dict operations from Suggest to a new class.
Bug: 8187060 Change-Id: I77775aa50763158d99753c2312fa11fe14267aef
This commit is contained in:
parent
5c45ff1214
commit
a374482719
6 changed files with 426 additions and 360 deletions
|
@ -0,0 +1,376 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2013 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* 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.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.inputmethod.annotations.UsedForTesting;
|
||||||
|
import com.android.inputmethod.keyboard.ProximityInfo;
|
||||||
|
import com.android.inputmethod.latin.Suggest.SuggestInitializationListener;
|
||||||
|
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
|
||||||
|
import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
|
||||||
|
import com.android.inputmethod.latin.personalization.PersonalizationHelper;
|
||||||
|
import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
|
||||||
|
import com.android.inputmethod.latin.settings.SettingsValues;
|
||||||
|
import com.android.inputmethod.latin.utils.CollectionUtils;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
// TODO: Consolidate dictionaries in native code.
|
||||||
|
public class DictionaryFacilitatorForSuggest {
|
||||||
|
public static final String TAG = DictionaryFacilitatorForSuggest.class.getSimpleName();
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
private final Locale mLocale;
|
||||||
|
|
||||||
|
private final ConcurrentHashMap<String, Dictionary> mDictionaries =
|
||||||
|
CollectionUtils.newConcurrentHashMap();
|
||||||
|
private HashSet<String> mOnlyDictionarySetForDebug = null;
|
||||||
|
|
||||||
|
private Dictionary mMainDictionary;
|
||||||
|
private ContactsBinaryDictionary mContactsDictionary;
|
||||||
|
private UserBinaryDictionary mUserDictionary;
|
||||||
|
private UserHistoryDictionary mUserHistoryDictionary;
|
||||||
|
private PersonalizationDictionary mPersonalizationDictionary;
|
||||||
|
|
||||||
|
@UsedForTesting
|
||||||
|
private boolean mIsCurrentlyWaitingForMainDictionary = false;
|
||||||
|
|
||||||
|
public DictionaryFacilitatorForSuggest(final Context context, final Locale locale,
|
||||||
|
final SettingsValues settingsValues, final SuggestInitializationListener listener) {
|
||||||
|
resetMainDict(context, locale, listener);
|
||||||
|
mContext = context;
|
||||||
|
mLocale = locale;
|
||||||
|
// initialize a debug flag for the personalization
|
||||||
|
if (settingsValues.mUseOnlyPersonalizationDictionaryForDebug) {
|
||||||
|
mOnlyDictionarySetForDebug = new HashSet<String>();
|
||||||
|
mOnlyDictionarySetForDebug.add(Dictionary.TYPE_PERSONALIZATION);
|
||||||
|
}
|
||||||
|
setUserDictionary(new UserBinaryDictionary(context, locale));
|
||||||
|
}
|
||||||
|
|
||||||
|
@UsedForTesting
|
||||||
|
DictionaryFacilitatorForSuggest(final Context context, final AssetFileAddress[] dictionaryList,
|
||||||
|
final Locale locale) {
|
||||||
|
final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(dictionaryList,
|
||||||
|
false /* useFullEditDistance */, locale);
|
||||||
|
mContext = context;
|
||||||
|
mLocale = locale;
|
||||||
|
mMainDictionary = mainDict;
|
||||||
|
addOrReplaceDictionary(Dictionary.TYPE_MAIN, mainDict);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
final HashSet<Dictionary> dictionaries = CollectionUtils.newHashSet();
|
||||||
|
dictionaries.addAll(mDictionaries.values());
|
||||||
|
for (final Dictionary dictionary : dictionaries) {
|
||||||
|
dictionary.close();
|
||||||
|
}
|
||||||
|
mMainDictionary = null;
|
||||||
|
mContactsDictionary = null;
|
||||||
|
mUserDictionary = null;
|
||||||
|
mUserHistoryDictionary = null;
|
||||||
|
mPersonalizationDictionary = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetMainDict(final Context context, final Locale locale,
|
||||||
|
final SuggestInitializationListener listener) {
|
||||||
|
mIsCurrentlyWaitingForMainDictionary = true;
|
||||||
|
mMainDictionary = null;
|
||||||
|
if (listener != null) {
|
||||||
|
listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
|
||||||
|
}
|
||||||
|
new Thread("InitializeBinaryDictionary") {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
final DictionaryCollection newMainDict =
|
||||||
|
DictionaryFactory.createMainDictionaryFromManager(context, locale);
|
||||||
|
setMainDictionary(newMainDict);
|
||||||
|
if (listener != null) {
|
||||||
|
listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
|
||||||
|
}
|
||||||
|
mIsCurrentlyWaitingForMainDictionary = false;
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The main dictionary could have been loaded asynchronously. Don't cache the return value
|
||||||
|
// of this method.
|
||||||
|
public boolean hasMainDictionary() {
|
||||||
|
return null != mMainDictionary && mMainDictionary.isInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
|
@UsedForTesting
|
||||||
|
public boolean isCurrentlyWaitingForMainDictionary() {
|
||||||
|
return mIsCurrentlyWaitingForMainDictionary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary getMainDictionary() {
|
||||||
|
return mMainDictionary;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setMainDictionary(final Dictionary mainDictionary) {
|
||||||
|
mMainDictionary = mainDictionary;
|
||||||
|
addOrReplaceDictionary(Dictionary.TYPE_MAIN, mainDictionary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an optional user dictionary resource to be loaded. The user dictionary is consulted
|
||||||
|
* before the main dictionary, if set. This refers to the system-managed user dictionary.
|
||||||
|
*/
|
||||||
|
@UsedForTesting
|
||||||
|
public void setUserDictionary(final UserBinaryDictionary userDictionary) {
|
||||||
|
mUserDictionary = userDictionary;
|
||||||
|
addOrReplaceDictionary(Dictionary.TYPE_USER, userDictionary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
@UsedForTesting
|
||||||
|
public void setContactsDictionary(final ContactsBinaryDictionary contactsDictionary) {
|
||||||
|
mContactsDictionary = contactsDictionary;
|
||||||
|
addOrReplaceDictionary(Dictionary.TYPE_CONTACTS, contactsDictionary);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) {
|
||||||
|
mUserHistoryDictionary = userHistoryDictionary;
|
||||||
|
addOrReplaceDictionary(Dictionary.TYPE_USER_HISTORY, userHistoryDictionary);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPersonalizationDictionary(
|
||||||
|
final PersonalizationDictionary personalizationDictionary) {
|
||||||
|
mPersonalizationDictionary = personalizationDictionary;
|
||||||
|
addOrReplaceDictionary(Dictionary.TYPE_PERSONALIZATION, personalizationDictionary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set dictionaries that can be turned off according to the user settings.
|
||||||
|
*
|
||||||
|
* @param oldDictionaryFacilitator the instance having old dictionaries
|
||||||
|
* @param settingsValues current SettingsValues
|
||||||
|
*/
|
||||||
|
public void setAdditionalDictionaries(
|
||||||
|
final DictionaryFacilitatorForSuggest oldDictionaryFacilitator,
|
||||||
|
final SettingsValues settingsValues) {
|
||||||
|
// Contacts dictionary
|
||||||
|
resetContactsDictionary(null != oldDictionaryFacilitator ?
|
||||||
|
oldDictionaryFacilitator.mContactsDictionary : null, settingsValues);
|
||||||
|
// User history dictionary & Personalization dictionary
|
||||||
|
resetPersonalizedDictionaries(oldDictionaryFacilitator, settingsValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the user history dictionary and personalization dictionary according to the user
|
||||||
|
* settings.
|
||||||
|
*
|
||||||
|
* @param oldDictionaryFacilitator the instance that has been used
|
||||||
|
* @param settingsValues current settingsValues
|
||||||
|
*/
|
||||||
|
// TODO: Consolidate resetPersonalizedDictionaries() and resetContactsDictionary(). Call up the
|
||||||
|
// new method for each dictionary.
|
||||||
|
private void resetPersonalizedDictionaries(
|
||||||
|
final DictionaryFacilitatorForSuggest oldDictionaryFacilitator,
|
||||||
|
final SettingsValues settingsValues) {
|
||||||
|
final boolean shouldSetDictionaries = settingsValues.mUsePersonalizedDicts;
|
||||||
|
|
||||||
|
final UserHistoryDictionary oldUserHistoryDictionary = (null == oldDictionaryFacilitator) ?
|
||||||
|
null : oldDictionaryFacilitator.mUserHistoryDictionary;
|
||||||
|
final PersonalizationDictionary oldPersonalizationDictionary =
|
||||||
|
(null == oldDictionaryFacilitator) ? null :
|
||||||
|
oldDictionaryFacilitator.mPersonalizationDictionary;
|
||||||
|
final UserHistoryDictionary userHistoryDictionaryToUse;
|
||||||
|
final PersonalizationDictionary personalizationDictionaryToUse;
|
||||||
|
if (!shouldSetDictionaries) {
|
||||||
|
userHistoryDictionaryToUse = null;
|
||||||
|
personalizationDictionaryToUse = null;
|
||||||
|
} else {
|
||||||
|
if (null != oldUserHistoryDictionary
|
||||||
|
&& oldUserHistoryDictionary.mLocale.equals(mLocale)) {
|
||||||
|
userHistoryDictionaryToUse = oldUserHistoryDictionary;
|
||||||
|
} else {
|
||||||
|
userHistoryDictionaryToUse =
|
||||||
|
PersonalizationHelper.getUserHistoryDictionary(mContext, mLocale);
|
||||||
|
}
|
||||||
|
if (null != oldPersonalizationDictionary
|
||||||
|
&& oldPersonalizationDictionary.mLocale.equals(mLocale)) {
|
||||||
|
personalizationDictionaryToUse = oldPersonalizationDictionary;
|
||||||
|
} else {
|
||||||
|
personalizationDictionaryToUse =
|
||||||
|
PersonalizationHelper.getPersonalizationDictionary(mContext, mLocale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setUserHistoryDictionary(userHistoryDictionaryToUse);
|
||||||
|
setPersonalizationDictionary(personalizationDictionaryToUse);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the contacts dictionary according to the user settings.
|
||||||
|
*
|
||||||
|
* This method takes an optional contacts dictionary to use when the locale hasn't changed
|
||||||
|
* since the contacts dictionary can be opened or closed as necessary depending on the settings.
|
||||||
|
*
|
||||||
|
* @param oldContactsDictionary an optional dictionary to use, or null
|
||||||
|
* @param settingsValues current settingsValues
|
||||||
|
*/
|
||||||
|
private void resetContactsDictionary(final ContactsBinaryDictionary oldContactsDictionary,
|
||||||
|
final SettingsValues settingsValues) {
|
||||||
|
final boolean shouldSetDictionary = settingsValues.mUseContactsDict;
|
||||||
|
final ContactsBinaryDictionary dictionaryToUse;
|
||||||
|
if (!shouldSetDictionary) {
|
||||||
|
// Make sure the dictionary is closed. If it is already closed, this is a no-op,
|
||||||
|
// so it's safe to call it anyways.
|
||||||
|
if (null != oldContactsDictionary) oldContactsDictionary.close();
|
||||||
|
dictionaryToUse = null;
|
||||||
|
} else {
|
||||||
|
if (null != oldContactsDictionary) {
|
||||||
|
if (!oldContactsDictionary.mLocale.equals(mLocale)) {
|
||||||
|
// If the locale has changed then recreate the contacts dictionary. This
|
||||||
|
// allows locale dependent rules for handling bigram name predictions.
|
||||||
|
oldContactsDictionary.close();
|
||||||
|
dictionaryToUse = new ContactsBinaryDictionary(mContext, mLocale);
|
||||||
|
} else {
|
||||||
|
// Make sure the old contacts dictionary is opened. If it is already open,
|
||||||
|
// this is a no-op, so it's safe to call it anyways.
|
||||||
|
oldContactsDictionary.reopen(mContext);
|
||||||
|
dictionaryToUse = oldContactsDictionary;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dictionaryToUse = new ContactsBinaryDictionary(mContext, mLocale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setContactsDictionary(dictionaryToUse);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUserDictionaryEnabled() {
|
||||||
|
if (mUserDictionary == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return mUserDictionary.mEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addWordToUserDictionary(String word) {
|
||||||
|
if (mUserDictionary == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mUserDictionary.addWordToUserDictionary(word);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String addToUserHistory(final WordComposer wordComposer, final String previousWord,
|
||||||
|
final String suggestion) {
|
||||||
|
if (mUserHistoryDictionary == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final String secondWord;
|
||||||
|
if (wordComposer.wasAutoCapitalized() && !wordComposer.isMostlyCaps()) {
|
||||||
|
secondWord = suggestion.toLowerCase(mLocale);
|
||||||
|
} else {
|
||||||
|
secondWord = suggestion;
|
||||||
|
}
|
||||||
|
// We demote unrecognized words (frequency < 0, below) by specifying them as "invalid".
|
||||||
|
// We don't add words with 0-frequency (assuming they would be profanity etc.).
|
||||||
|
final int maxFreq = getMaxFrequency(suggestion);
|
||||||
|
if (maxFreq == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final boolean isValid = maxFreq > 0;
|
||||||
|
final int timeStamp = (int)TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis()));
|
||||||
|
mUserHistoryDictionary.addToDictionary(previousWord, secondWord, isValid, timeStamp);
|
||||||
|
return previousWord;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancelAddingUserHistory(final String previousWord, final String committedWord) {
|
||||||
|
if (mUserHistoryDictionary != null) {
|
||||||
|
mUserHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Revise the way to fusion suggestion results.
|
||||||
|
public void getSuggestions(final WordComposer composer,
|
||||||
|
final String prevWord, final ProximityInfo proximityInfo,
|
||||||
|
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
|
||||||
|
final int sessionId, final Set<SuggestedWordInfo> suggestionSet) {
|
||||||
|
for (final String key : mDictionaries.keySet()) {
|
||||||
|
final Dictionary dictionary = mDictionaries.get(key);
|
||||||
|
suggestionSet.addAll(dictionary.getSuggestionsWithSessionId(composer, prevWord,
|
||||||
|
proximityInfo, blockOffensiveWords, additionalFeaturesOptions, sessionId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValidWord(final String word, final boolean ignoreCase) {
|
||||||
|
if (TextUtils.isEmpty(word)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final String lowerCasedWord = word.toLowerCase(mLocale);
|
||||||
|
for (final String key : mDictionaries.keySet()) {
|
||||||
|
final Dictionary dictionary = mDictionaries.get(key);
|
||||||
|
// It's unclear how realistically 'dictionary' can be null, but the monkey is somehow
|
||||||
|
// managing to get null in here. Presumably the language is changing to a language with
|
||||||
|
// no main dictionary and the monkey manages to type a whole word before the thread
|
||||||
|
// that reads the dictionary is started or something?
|
||||||
|
// Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
|
||||||
|
// would be immutable once it's finished initializing, but concretely a null test is
|
||||||
|
// probably good enough for the time being.
|
||||||
|
if (null == dictionary) continue;
|
||||||
|
if (dictionary.isValidWord(word)
|
||||||
|
|| (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getMaxFrequency(final String word) {
|
||||||
|
if (TextUtils.isEmpty(word)) {
|
||||||
|
return Dictionary.NOT_A_PROBABILITY;
|
||||||
|
}
|
||||||
|
int maxFreq = -1;
|
||||||
|
for (final String key : mDictionaries.keySet()) {
|
||||||
|
final Dictionary dictionary = mDictionaries.get(key);
|
||||||
|
if (null == dictionary) continue;
|
||||||
|
final int tempFreq = dictionary.getFrequency(word);
|
||||||
|
if (tempFreq >= maxFreq) {
|
||||||
|
maxFreq = tempFreq;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxFreq;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addOrReplaceDictionary(final String key, final Dictionary dict) {
|
||||||
|
if (mOnlyDictionarySetForDebug != null && !mOnlyDictionarySetForDebug.contains(key)) {
|
||||||
|
Log.w(TAG, "Ignore add " + key + " dictionary for debug.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final Dictionary oldDict;
|
||||||
|
if (dict == null) {
|
||||||
|
oldDict = mDictionaries.remove(key);
|
||||||
|
} else {
|
||||||
|
oldDict = mDictionaries.put(key, dict);
|
||||||
|
}
|
||||||
|
if (oldDict != null && dict != oldDict) {
|
||||||
|
oldDict.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -472,7 +472,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
initSuggest();
|
initSuggest();
|
||||||
|
|
||||||
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
|
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
|
||||||
ResearchLogger.getInstance().init(this, mKeyboardSwitcher, mInputLogic.mSuggest);
|
ResearchLogger.getInstance().init(this, mKeyboardSwitcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register to receive ringer mode change and network state change.
|
// Register to receive ringer mode change and network state change.
|
||||||
|
@ -516,8 +516,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
if (!mHandler.hasPendingReopenDictionaries() && mInputLogic.mSuggest != null) {
|
if (!mHandler.hasPendingReopenDictionaries() && mInputLogic.mSuggest != null) {
|
||||||
// May need to reset dictionaries depending on the user settings.
|
// May need to reset dictionaries depending on the user settings.
|
||||||
// TODO: Quit setting dictionaries from LatinIME.
|
// TODO: Quit setting dictionaries from LatinIME.
|
||||||
mInputLogic.mSuggest.setAdditionalDictionaries(mInputLogic.mSuggest /* oldSuggest */,
|
mInputLogic.mSuggest.mDictionaryFacilitator.setAdditionalDictionaries(
|
||||||
currentSettingsValues);
|
mInputLogic.mSuggest.mDictionaryFacilitator /* oldDictionaryFacilitator */,
|
||||||
|
currentSettingsValues);
|
||||||
}
|
}
|
||||||
if (currentSettingsValues.mUsePersonalizedDicts) {
|
if (currentSettingsValues.mUsePersonalizedDicts) {
|
||||||
PersonalizationDictionarySessionRegistrar.init(this);
|
PersonalizationDictionarySessionRegistrar.init(this);
|
||||||
|
@ -561,10 +562,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
|
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
|
||||||
ResearchLogger.getInstance().initSuggest(newSuggest);
|
ResearchLogger.getInstance().initDictionary(newSuggest.mDictionaryFacilitator);
|
||||||
}
|
}
|
||||||
// TODO: Quit setting dictionaries from LatinIME.
|
// TODO: Quit setting dictionaries from LatinIME.
|
||||||
newSuggest.setAdditionalDictionaries(mInputLogic.mSuggest /* oldSuggest */, settingsValues);
|
newSuggest.mDictionaryFacilitator.setAdditionalDictionaries(
|
||||||
|
(mInputLogic.mSuggest == null) ? null : mInputLogic.mSuggest.mDictionaryFacilitator
|
||||||
|
/* oldDictionaryFacilitator */, settingsValues);
|
||||||
final Suggest oldSuggest = mInputLogic.mSuggest;
|
final Suggest oldSuggest = mInputLogic.mSuggest;
|
||||||
mInputLogic.mSuggest = newSuggest;
|
mInputLogic.mSuggest = newSuggest;
|
||||||
if (oldSuggest != null) oldSuggest.close();
|
if (oldSuggest != null) oldSuggest.close();
|
||||||
|
@ -572,7 +575,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
|
|
||||||
/* package private */ void resetSuggestMainDict() {
|
/* package private */ void resetSuggestMainDict() {
|
||||||
final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
|
final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
|
||||||
mInputLogic.mSuggest.resetMainDict(this, subtypeLocale,
|
mInputLogic.mSuggest.mDictionaryFacilitator.resetMainDict(this, subtypeLocale,
|
||||||
this /* SuggestInitializationListener */);
|
this /* SuggestInitializationListener */);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -822,7 +825,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
mHandler.cancelDoubleSpacePeriodTimer();
|
mHandler.cancelDoubleSpacePeriodTimer();
|
||||||
|
|
||||||
mainKeyboardView.setMainDictionaryAvailability(null != suggest
|
mainKeyboardView.setMainDictionaryAvailability(null != suggest
|
||||||
? suggest.hasMainDictionary() : false);
|
? suggest.mDictionaryFacilitator.hasMainDictionary() : false);
|
||||||
mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn,
|
mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn,
|
||||||
currentSettingsValues.mKeyPreviewPopupDismissDelay);
|
currentSettingsValues.mKeyPreviewPopupDismissDelay);
|
||||||
mainKeyboardView.setSlidingKeyInputPreviewEnabled(
|
mainKeyboardView.setSlidingKeyInputPreviewEnabled(
|
||||||
|
@ -1203,7 +1206,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
} else {
|
} else {
|
||||||
wordToEdit = word;
|
wordToEdit = word;
|
||||||
}
|
}
|
||||||
mInputLogic.mSuggest.addWordToUserDictionary(wordToEdit);
|
mInputLogic.mSuggest.mDictionaryFacilitator.addWordToUserDictionary(wordToEdit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void displaySettingsDialog() {
|
public void displaySettingsDialog() {
|
||||||
|
@ -1724,13 +1727,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
|| SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind)
|
|| SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind)
|
||||||
&& suggest != null
|
&& suggest != null
|
||||||
// If the suggestion is not in the dictionary, the hint should be shown.
|
// If the suggestion is not in the dictionary, the hint should be shown.
|
||||||
&& !suggest.isValidWord(suggestion, true);
|
&& !suggest.mDictionaryFacilitator.isValidWord(suggestion,
|
||||||
|
true /* ignoreCase */);
|
||||||
|
|
||||||
if (currentSettings.mIsInternal) {
|
if (currentSettings.mIsInternal) {
|
||||||
LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE,
|
LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE,
|
||||||
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
|
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
|
||||||
}
|
}
|
||||||
if (showingAddToDictionaryHint && suggest.isUserDictionaryEnabled()) {
|
if (showingAddToDictionaryHint
|
||||||
|
&& suggest.mDictionaryFacilitator.isUserDictionaryEnabled()) {
|
||||||
mSuggestionStripView.showAddToDictionaryHint(
|
mSuggestionStripView.showAddToDictionaryHint(
|
||||||
suggestion, currentSettings.mHintToSaveText);
|
suggestion, currentSettings.mHintToSaveText);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1963,13 +1968,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
// DO NOT USE THIS for any other purpose than testing. This is information private to LatinIME.
|
// DO NOT USE THIS for any other purpose than testing. This is information private to LatinIME.
|
||||||
@UsedForTesting
|
@UsedForTesting
|
||||||
/* package for test */ boolean isCurrentlyWaitingForMainDictionary() {
|
/* package for test */ boolean isCurrentlyWaitingForMainDictionary() {
|
||||||
return mInputLogic.mSuggest.isCurrentlyWaitingForMainDictionary();
|
return mInputLogic.mSuggest.mDictionaryFacilitator.isCurrentlyWaitingForMainDictionary();
|
||||||
}
|
}
|
||||||
|
|
||||||
// DO NOT USE THIS for any other purpose than testing. This can break the keyboard badly.
|
// DO NOT USE THIS for any other purpose than testing. This can break the keyboard badly.
|
||||||
@UsedForTesting
|
@UsedForTesting
|
||||||
/* package for test */ void replaceMainDictionaryForTest(final Locale locale) {
|
/* package for test */ void replaceMainDictionaryForTest(final Locale locale) {
|
||||||
mInputLogic.mSuggest.resetMainDict(this, locale, null);
|
mInputLogic.mSuggest.mDictionaryFacilitator.resetMainDict(this, locale, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void debugDumpStateAndCrashWithException(final String context) {
|
public void debugDumpStateAndCrashWithException(final String context) {
|
||||||
|
|
|
@ -18,14 +18,10 @@ package com.android.inputmethod.latin;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.android.inputmethod.annotations.UsedForTesting;
|
import com.android.inputmethod.annotations.UsedForTesting;
|
||||||
import com.android.inputmethod.keyboard.ProximityInfo;
|
import com.android.inputmethod.keyboard.ProximityInfo;
|
||||||
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
|
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
|
||||||
import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
|
|
||||||
import com.android.inputmethod.latin.personalization.PersonalizationHelper;
|
|
||||||
import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
|
|
||||||
import com.android.inputmethod.latin.settings.SettingsValues;
|
import com.android.inputmethod.latin.settings.SettingsValues;
|
||||||
import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
|
import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
|
||||||
import com.android.inputmethod.latin.utils.BoundedTreeSet;
|
import com.android.inputmethod.latin.utils.BoundedTreeSet;
|
||||||
|
@ -34,16 +30,12 @@ import com.android.inputmethod.latin.utils.StringUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class loads a dictionary and provides a list of suggestions for a given sequence of
|
* This class loads a dictionary and provides a list of suggestions for a given sequence of
|
||||||
* characters. This includes corrections and completions.
|
* characters. This includes corrections and completions.
|
||||||
*/
|
*/
|
||||||
// TODO: Separate dictionary operations from suggestions handling logic.
|
|
||||||
public final class Suggest {
|
public final class Suggest {
|
||||||
public static final String TAG = Suggest.class.getSimpleName();
|
public static final String TAG = Suggest.class.getSimpleName();
|
||||||
|
|
||||||
|
@ -69,276 +61,25 @@ public final class Suggest {
|
||||||
|
|
||||||
private static final boolean DBG = LatinImeLogger.sDBG;
|
private static final boolean DBG = LatinImeLogger.sDBG;
|
||||||
|
|
||||||
private final ConcurrentHashMap<String, Dictionary> mDictionaries =
|
public final DictionaryFacilitatorForSuggest mDictionaryFacilitator;
|
||||||
CollectionUtils.newConcurrentHashMap();
|
|
||||||
private HashSet<String> mOnlyDictionarySetForDebug = null;
|
|
||||||
private Dictionary mMainDictionary;
|
|
||||||
private ContactsBinaryDictionary mContactsDictionary;
|
|
||||||
private UserBinaryDictionary mUserDictionary;
|
|
||||||
private UserHistoryDictionary mUserHistoryDictionary;
|
|
||||||
private PersonalizationDictionary mPersonalizationDictionary;
|
|
||||||
@UsedForTesting
|
|
||||||
private boolean mIsCurrentlyWaitingForMainDictionary = false;
|
|
||||||
|
|
||||||
private float mAutoCorrectionThreshold;
|
private float mAutoCorrectionThreshold;
|
||||||
|
|
||||||
// Locale used for upper- and title-casing words
|
// Locale used for upper- and title-casing words
|
||||||
public final Locale mLocale;
|
public final Locale mLocale;
|
||||||
|
|
||||||
private final Context mContext;
|
|
||||||
|
|
||||||
public Suggest(final Context context, final Locale locale, final SettingsValues settingsValues,
|
public Suggest(final Context context, final Locale locale, final SettingsValues settingsValues,
|
||||||
final SuggestInitializationListener listener) {
|
final SuggestInitializationListener listener) {
|
||||||
initAsynchronously(context, locale, listener);
|
|
||||||
mLocale = locale;
|
mLocale = locale;
|
||||||
mContext = context;
|
mDictionaryFacilitator = new DictionaryFacilitatorForSuggest(context, locale,
|
||||||
// initialize a debug flag for the personalization
|
settingsValues, listener);
|
||||||
if (settingsValues.mUseOnlyPersonalizationDictionaryForDebug) {
|
|
||||||
mOnlyDictionarySetForDebug = new HashSet<String>();
|
|
||||||
mOnlyDictionarySetForDebug.add(Dictionary.TYPE_PERSONALIZATION);
|
|
||||||
}
|
|
||||||
setUserDictionary(new UserBinaryDictionary(context, locale));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@UsedForTesting
|
@UsedForTesting
|
||||||
Suggest(final Context context, final AssetFileAddress[] dictionaryList, final Locale locale) {
|
Suggest(final Context context, final AssetFileAddress[] dictionaryList, final Locale locale) {
|
||||||
final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(dictionaryList,
|
|
||||||
false /* useFullEditDistance */, locale);
|
|
||||||
mLocale = locale;
|
mLocale = locale;
|
||||||
mContext = context;
|
mDictionaryFacilitator = new DictionaryFacilitatorForSuggest(context, dictionaryList,
|
||||||
mMainDictionary = mainDict;
|
locale);
|
||||||
addOrReplaceDictionaryInternal(Dictionary.TYPE_MAIN, mainDict);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initAsynchronously(final Context context, final Locale locale,
|
|
||||||
final SuggestInitializationListener listener) {
|
|
||||||
resetMainDict(context, locale, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addOrReplaceDictionaryInternal(final String key, final Dictionary dict) {
|
|
||||||
if (mOnlyDictionarySetForDebug != null && !mOnlyDictionarySetForDebug.contains(key)) {
|
|
||||||
Log.w(TAG, "Ignore add " + key + " dictionary for debug.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
addOrReplaceDictionary(mDictionaries, key, dict);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void addOrReplaceDictionary(
|
|
||||||
final ConcurrentHashMap<String, Dictionary> dictionaries,
|
|
||||||
final String key, final Dictionary dict) {
|
|
||||||
final Dictionary oldDict = (dict == null)
|
|
||||||
? dictionaries.remove(key)
|
|
||||||
: dictionaries.put(key, dict);
|
|
||||||
if (oldDict != null && dict != oldDict) {
|
|
||||||
oldDict.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetMainDict(final Context context, final Locale locale,
|
|
||||||
final SuggestInitializationListener listener) {
|
|
||||||
mIsCurrentlyWaitingForMainDictionary = true;
|
|
||||||
mMainDictionary = null;
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
|
|
||||||
}
|
|
||||||
new Thread("InitializeBinaryDictionary") {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
final DictionaryCollection newMainDict =
|
|
||||||
DictionaryFactory.createMainDictionaryFromManager(context, locale);
|
|
||||||
addOrReplaceDictionaryInternal(Dictionary.TYPE_MAIN, newMainDict);
|
|
||||||
mMainDictionary = newMainDict;
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
|
|
||||||
}
|
|
||||||
mIsCurrentlyWaitingForMainDictionary = false;
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
// The main dictionary could have been loaded asynchronously. Don't cache the return value
|
|
||||||
// of this method.
|
|
||||||
public boolean hasMainDictionary() {
|
|
||||||
return null != mMainDictionary && mMainDictionary.isInitialized();
|
|
||||||
}
|
|
||||||
|
|
||||||
@UsedForTesting
|
|
||||||
public boolean isCurrentlyWaitingForMainDictionary() {
|
|
||||||
return mIsCurrentlyWaitingForMainDictionary;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Dictionary getMainDictionary() {
|
|
||||||
return mMainDictionary;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets an optional user dictionary resource to be loaded. The user dictionary is consulted
|
|
||||||
* before the main dictionary, if set. This refers to the system-managed user dictionary.
|
|
||||||
*/
|
|
||||||
@UsedForTesting
|
|
||||||
public void setUserDictionary(final UserBinaryDictionary userDictionary) {
|
|
||||||
mUserDictionary = userDictionary;
|
|
||||||
addOrReplaceDictionaryInternal(Dictionary.TYPE_USER, userDictionary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
@UsedForTesting
|
|
||||||
public void setContactsDictionary(final ContactsBinaryDictionary contactsDictionary) {
|
|
||||||
mContactsDictionary = contactsDictionary;
|
|
||||||
addOrReplaceDictionaryInternal(Dictionary.TYPE_CONTACTS, contactsDictionary);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) {
|
|
||||||
mUserHistoryDictionary = userHistoryDictionary;
|
|
||||||
addOrReplaceDictionaryInternal(Dictionary.TYPE_USER_HISTORY, userHistoryDictionary);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setPersonalizationDictionary(
|
|
||||||
final PersonalizationDictionary personalizationDictionary) {
|
|
||||||
mPersonalizationDictionary = personalizationDictionary;
|
|
||||||
addOrReplaceDictionaryInternal(Dictionary.TYPE_PERSONALIZATION, personalizationDictionary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set dictionaries that can be turned off according to the user settings.
|
|
||||||
*
|
|
||||||
* @param oldSuggest the instance having old dictionaries
|
|
||||||
* @param settingsValues current SettingsValues
|
|
||||||
*/
|
|
||||||
public void setAdditionalDictionaries(final Suggest oldSuggest,
|
|
||||||
final SettingsValues settingsValues) {
|
|
||||||
// Contacts dictionary
|
|
||||||
resetContactsDictionary(null != oldSuggest ? oldSuggest.mContactsDictionary : null,
|
|
||||||
settingsValues);
|
|
||||||
// User history dictionary & Personalization dictionary
|
|
||||||
resetPersonalizedDictionaries(oldSuggest, settingsValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the user history dictionary and personalization dictionary according to the user
|
|
||||||
* settings.
|
|
||||||
*
|
|
||||||
* @param oldSuggest the instance that has been used
|
|
||||||
* @param settingsValues current settingsValues
|
|
||||||
*/
|
|
||||||
// TODO: Consolidate resetPersonalizedDictionaries() and resetContactsDictionary(). Call up the
|
|
||||||
// new method for each dictionary.
|
|
||||||
private void resetPersonalizedDictionaries(final Suggest oldSuggest,
|
|
||||||
final SettingsValues settingsValues) {
|
|
||||||
final boolean shouldSetDictionaries = settingsValues.mUsePersonalizedDicts;
|
|
||||||
|
|
||||||
final UserHistoryDictionary oldUserHistoryDictionary = (null == oldSuggest) ? null :
|
|
||||||
oldSuggest.mUserHistoryDictionary;
|
|
||||||
final PersonalizationDictionary oldPersonalizationDictionary = (null == oldSuggest) ? null :
|
|
||||||
oldSuggest.mPersonalizationDictionary;
|
|
||||||
final UserHistoryDictionary userHistoryDictionaryToUse;
|
|
||||||
final PersonalizationDictionary personalizationDictionaryToUse;
|
|
||||||
if (!shouldSetDictionaries) {
|
|
||||||
userHistoryDictionaryToUse = null;
|
|
||||||
personalizationDictionaryToUse = null;
|
|
||||||
} else {
|
|
||||||
if (null != oldUserHistoryDictionary
|
|
||||||
&& oldUserHistoryDictionary.mLocale.equals(mLocale)) {
|
|
||||||
userHistoryDictionaryToUse = oldUserHistoryDictionary;
|
|
||||||
} else {
|
|
||||||
userHistoryDictionaryToUse =
|
|
||||||
PersonalizationHelper.getUserHistoryDictionary(mContext, mLocale);
|
|
||||||
}
|
|
||||||
if (null != oldPersonalizationDictionary
|
|
||||||
&& oldPersonalizationDictionary.mLocale.equals(mLocale)) {
|
|
||||||
personalizationDictionaryToUse = oldPersonalizationDictionary;
|
|
||||||
} else {
|
|
||||||
personalizationDictionaryToUse =
|
|
||||||
PersonalizationHelper.getPersonalizationDictionary(mContext, mLocale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setUserHistoryDictionary(userHistoryDictionaryToUse);
|
|
||||||
setPersonalizationDictionary(personalizationDictionaryToUse);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the contacts dictionary according to the user settings.
|
|
||||||
*
|
|
||||||
* This method takes an optional contacts dictionary to use when the locale hasn't changed
|
|
||||||
* since the contacts dictionary can be opened or closed as necessary depending on the settings.
|
|
||||||
*
|
|
||||||
* @param oldContactsDictionary an optional dictionary to use, or null
|
|
||||||
* @param settingsValues current settingsValues
|
|
||||||
*/
|
|
||||||
private void resetContactsDictionary(final ContactsBinaryDictionary oldContactsDictionary,
|
|
||||||
final SettingsValues settingsValues) {
|
|
||||||
final boolean shouldSetDictionary = settingsValues.mUseContactsDict;
|
|
||||||
final ContactsBinaryDictionary dictionaryToUse;
|
|
||||||
if (!shouldSetDictionary) {
|
|
||||||
// Make sure the dictionary is closed. If it is already closed, this is a no-op,
|
|
||||||
// so it's safe to call it anyways.
|
|
||||||
if (null != oldContactsDictionary) oldContactsDictionary.close();
|
|
||||||
dictionaryToUse = null;
|
|
||||||
} else {
|
|
||||||
if (null != oldContactsDictionary) {
|
|
||||||
if (!oldContactsDictionary.mLocale.equals(mLocale)) {
|
|
||||||
// If the locale has changed then recreate the contacts dictionary. This
|
|
||||||
// allows locale dependent rules for handling bigram name predictions.
|
|
||||||
oldContactsDictionary.close();
|
|
||||||
dictionaryToUse = new ContactsBinaryDictionary(mContext, mLocale);
|
|
||||||
} else {
|
|
||||||
// Make sure the old contacts dictionary is opened. If it is already open,
|
|
||||||
// this is a no-op, so it's safe to call it anyways.
|
|
||||||
oldContactsDictionary.reopen(mContext);
|
|
||||||
dictionaryToUse = oldContactsDictionary;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dictionaryToUse = new ContactsBinaryDictionary(mContext, mLocale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setContactsDictionary(dictionaryToUse);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isUserDictionaryEnabled() {
|
|
||||||
if (mUserDictionary == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return mUserDictionary.mEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addWordToUserDictionary(String word) {
|
|
||||||
if (mUserDictionary == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mUserDictionary.addWordToUserDictionary(word);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String addToUserHistory(final WordComposer wordComposer, final String previousWord,
|
|
||||||
final String suggestion) {
|
|
||||||
if (mUserHistoryDictionary == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final String secondWord;
|
|
||||||
if (wordComposer.wasAutoCapitalized() && !wordComposer.isMostlyCaps()) {
|
|
||||||
secondWord = suggestion.toLowerCase(mLocale);
|
|
||||||
} else {
|
|
||||||
secondWord = suggestion;
|
|
||||||
}
|
|
||||||
// We demote unrecognized words (frequency < 0, below) by specifying them as "invalid".
|
|
||||||
// We don't add words with 0-frequency (assuming they would be profanity etc.).
|
|
||||||
final int maxFreq = getMaxFrequency(suggestion);
|
|
||||||
if (maxFreq == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final boolean isValid = maxFreq > 0;
|
|
||||||
final int timeStamp = (int)TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis()));
|
|
||||||
mUserHistoryDictionary.addToDictionary(previousWord, secondWord, isValid, timeStamp);
|
|
||||||
return previousWord;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cancelAddingUserHistory(final String previousWord, final String committedWord) {
|
|
||||||
if (mUserHistoryDictionary != null) {
|
|
||||||
mUserHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAutoCorrectionThreshold(float threshold) {
|
public void setAutoCorrectionThreshold(float threshold) {
|
||||||
|
@ -392,14 +133,8 @@ public final class Suggest {
|
||||||
} else {
|
} else {
|
||||||
wordComposerForLookup = wordComposer;
|
wordComposerForLookup = wordComposer;
|
||||||
}
|
}
|
||||||
|
mDictionaryFacilitator.getSuggestions(wordComposer, prevWordForBigram, proximityInfo,
|
||||||
for (final String key : mDictionaries.keySet()) {
|
blockOffensiveWords, additionalFeaturesOptions, SESSION_TYPING, suggestionsSet);
|
||||||
final Dictionary dictionary = mDictionaries.get(key);
|
|
||||||
suggestionsSet.addAll(dictionary.getSuggestions(wordComposerForLookup,
|
|
||||||
prevWordForBigram, proximityInfo, blockOffensiveWords,
|
|
||||||
additionalFeaturesOptions));
|
|
||||||
}
|
|
||||||
|
|
||||||
final String whitelistedWord;
|
final String whitelistedWord;
|
||||||
if (suggestionsSet.isEmpty()) {
|
if (suggestionsSet.isEmpty()) {
|
||||||
whitelistedWord = null;
|
whitelistedWord = null;
|
||||||
|
@ -413,8 +148,8 @@ public final class Suggest {
|
||||||
// or if it's a 2+ characters non-word (i.e. it's not in the dictionary).
|
// or if it's a 2+ characters non-word (i.e. it's not in the dictionary).
|
||||||
final boolean allowsToBeAutoCorrected = (null != whitelistedWord
|
final boolean allowsToBeAutoCorrected = (null != whitelistedWord
|
||||||
&& !whitelistedWord.equals(consideredWord))
|
&& !whitelistedWord.equals(consideredWord))
|
||||||
|| (consideredWord.length() > 1
|
|| (consideredWord.length() > 1 && !mDictionaryFacilitator.isValidWord(
|
||||||
&& !isValidWord(consideredWord, wordComposer.isFirstCharCapitalized()));
|
consideredWord, wordComposer.isFirstCharCapitalized()));
|
||||||
|
|
||||||
final boolean hasAutoCorrection;
|
final boolean hasAutoCorrection;
|
||||||
// TODO: using isCorrectionEnabled here is not very good. It's probably useless, because
|
// TODO: using isCorrectionEnabled here is not very good. It's probably useless, because
|
||||||
|
@ -424,7 +159,8 @@ public final class Suggest {
|
||||||
// the word *would* have been auto-corrected.
|
// the word *would* have been auto-corrected.
|
||||||
if (!isCorrectionEnabled || !allowsToBeAutoCorrected || !wordComposer.isComposingWord()
|
if (!isCorrectionEnabled || !allowsToBeAutoCorrected || !wordComposer.isComposingWord()
|
||||||
|| suggestionsSet.isEmpty() || wordComposer.hasDigits()
|
|| suggestionsSet.isEmpty() || wordComposer.hasDigits()
|
||||||
|| wordComposer.isMostlyCaps() || wordComposer.isResumed() || !hasMainDictionary()
|
|| wordComposer.isMostlyCaps() || wordComposer.isResumed()
|
||||||
|
|| !mDictionaryFacilitator.hasMainDictionary()
|
||||||
|| SuggestedWordInfo.KIND_SHORTCUT == suggestionsSet.first().mKind) {
|
|| SuggestedWordInfo.KIND_SHORTCUT == suggestionsSet.first().mKind) {
|
||||||
// If we don't have a main dictionary, we never want to auto-correct. The reason for
|
// 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
|
// this is, the user may have a contact whose name happens to match a valid word in
|
||||||
|
@ -497,15 +233,8 @@ public final class Suggest {
|
||||||
final OnGetSuggestedWordsCallback callback) {
|
final OnGetSuggestedWordsCallback callback) {
|
||||||
final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
|
final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
|
||||||
MAX_SUGGESTIONS);
|
MAX_SUGGESTIONS);
|
||||||
|
mDictionaryFacilitator.getSuggestions(wordComposer, prevWordForBigram, proximityInfo,
|
||||||
// At second character typed, search the unigrams (scores being affected by bigrams)
|
blockOffensiveWords, additionalFeaturesOptions, sessionId, suggestionsSet);
|
||||||
for (final String key : mDictionaries.keySet()) {
|
|
||||||
final Dictionary dictionary = mDictionaries.get(key);
|
|
||||||
suggestionsSet.addAll(dictionary.getSuggestionsWithSessionId(wordComposer,
|
|
||||||
prevWordForBigram, proximityInfo, blockOffensiveWords,
|
|
||||||
additionalFeaturesOptions, sessionId));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (SuggestedWordInfo wordInfo : suggestionsSet) {
|
for (SuggestedWordInfo wordInfo : suggestionsSet) {
|
||||||
LatinImeLogger.onAddSuggestedWord(wordInfo.mWord, wordInfo.mSourceDict.mDictType);
|
LatinImeLogger.onAddSuggestedWord(wordInfo.mWord, wordInfo.mSourceDict.mDictType);
|
||||||
}
|
}
|
||||||
|
@ -618,51 +347,7 @@ public final class Suggest {
|
||||||
wordInfo.mAutoCommitFirstWordConfidence);
|
wordInfo.mAutoCommitFirstWordConfidence);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isValidWord(final String word, final boolean ignoreCase) {
|
|
||||||
if (TextUtils.isEmpty(word)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final String lowerCasedWord = word.toLowerCase(mLocale);
|
|
||||||
for (final String key : mDictionaries.keySet()) {
|
|
||||||
final Dictionary dictionary = mDictionaries.get(key);
|
|
||||||
// It's unclear how realistically 'dictionary' can be null, but the monkey is somehow
|
|
||||||
// managing to get null in here. Presumably the language is changing to a language with
|
|
||||||
// no main dictionary and the monkey manages to type a whole word before the thread
|
|
||||||
// that reads the dictionary is started or something?
|
|
||||||
// Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
|
|
||||||
// would be immutable once it's finished initializing, but concretely a null test is
|
|
||||||
// probably good enough for the time being.
|
|
||||||
if (null == dictionary) continue;
|
|
||||||
if (dictionary.isValidWord(word)
|
|
||||||
|| (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getMaxFrequency(final String word) {
|
|
||||||
if (TextUtils.isEmpty(word)) {
|
|
||||||
return Dictionary.NOT_A_PROBABILITY;
|
|
||||||
}
|
|
||||||
int maxFreq = -1;
|
|
||||||
for (final String key : mDictionaries.keySet()) {
|
|
||||||
final Dictionary dictionary = mDictionaries.get(key);
|
|
||||||
if (null == dictionary) continue;
|
|
||||||
final int tempFreq = dictionary.getFrequency(word);
|
|
||||||
if (tempFreq >= maxFreq) {
|
|
||||||
maxFreq = tempFreq;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return maxFreq;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
final HashSet<Dictionary> dictionaries = CollectionUtils.newHashSet();
|
mDictionaryFacilitator.close();
|
||||||
dictionaries.addAll(mDictionaries.values());
|
|
||||||
for (final Dictionary dictionary : dictionaries) {
|
|
||||||
dictionary.close();
|
|
||||||
}
|
|
||||||
mMainDictionary = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -986,7 +986,7 @@ public final class InputLogic {
|
||||||
if (suggest == null) return null;
|
if (suggest == null) return null;
|
||||||
|
|
||||||
final String prevWord = mConnection.getNthPreviousWord(settingsValues, 2);
|
final String prevWord = mConnection.getNthPreviousWord(settingsValues, 2);
|
||||||
return suggest.addToUserHistory(mWordComposer, prevWord, suggestion);
|
return suggest.mDictionaryFacilitator.addToUserHistory(mWordComposer, prevWord, suggestion);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void performUpdateSuggestionStripSync(final SettingsValues settingsValues,
|
public void performUpdateSuggestionStripSync(final SettingsValues settingsValues,
|
||||||
|
@ -1190,7 +1190,8 @@ public final class InputLogic {
|
||||||
mConnection.deleteSurroundingText(deleteLength, 0);
|
mConnection.deleteSurroundingText(deleteLength, 0);
|
||||||
if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
|
if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
|
||||||
if (mSuggest != null) {
|
if (mSuggest != null) {
|
||||||
mSuggest.cancelAddingUserHistory(previousWord, committedWord);
|
mSuggest.mDictionaryFacilitator.cancelAddingUserHistory(
|
||||||
|
previousWord, committedWord);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString;
|
final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString;
|
||||||
|
|
|
@ -20,7 +20,7 @@ import android.util.Log;
|
||||||
|
|
||||||
import com.android.inputmethod.annotations.UsedForTesting;
|
import com.android.inputmethod.annotations.UsedForTesting;
|
||||||
import com.android.inputmethod.latin.Dictionary;
|
import com.android.inputmethod.latin.Dictionary;
|
||||||
import com.android.inputmethod.latin.Suggest;
|
import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest;
|
||||||
import com.android.inputmethod.latin.define.ProductionFlag;
|
import com.android.inputmethod.latin.define.ProductionFlag;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -75,9 +75,7 @@ public abstract class MainLogBuffer extends FixedLogBuffer {
|
||||||
// The size of the n-grams logged. E.g. N_GRAM_SIZE = 2 means to sample bigrams.
|
// The size of the n-grams logged. E.g. N_GRAM_SIZE = 2 means to sample bigrams.
|
||||||
public static final int N_GRAM_SIZE = 2;
|
public static final int N_GRAM_SIZE = 2;
|
||||||
|
|
||||||
// TODO: Remove dependence on Suggest, and pass in Dictionary as a parameter to an appropriate
|
private final DictionaryFacilitatorForSuggest mDictionaryFacilitator;
|
||||||
// method.
|
|
||||||
private final Suggest mSuggest;
|
|
||||||
@UsedForTesting
|
@UsedForTesting
|
||||||
private Dictionary mDictionaryForTesting;
|
private Dictionary mDictionaryForTesting;
|
||||||
private boolean mIsStopping = false;
|
private boolean mIsStopping = false;
|
||||||
|
@ -89,11 +87,11 @@ public abstract class MainLogBuffer extends FixedLogBuffer {
|
||||||
/* package for test */ int mNumWordsUntilSafeToSample;
|
/* package for test */ int mNumWordsUntilSafeToSample;
|
||||||
|
|
||||||
public MainLogBuffer(final int wordsBetweenSamples, final int numInitialWordsToIgnore,
|
public MainLogBuffer(final int wordsBetweenSamples, final int numInitialWordsToIgnore,
|
||||||
final Suggest suggest) {
|
final DictionaryFacilitatorForSuggest dictionaryFacilitator) {
|
||||||
super(N_GRAM_SIZE + wordsBetweenSamples);
|
super(N_GRAM_SIZE + wordsBetweenSamples);
|
||||||
mNumWordsBetweenNGrams = wordsBetweenSamples;
|
mNumWordsBetweenNGrams = wordsBetweenSamples;
|
||||||
mNumWordsUntilSafeToSample = DEBUG ? 0 : numInitialWordsToIgnore;
|
mNumWordsUntilSafeToSample = DEBUG ? 0 : numInitialWordsToIgnore;
|
||||||
mSuggest = suggest;
|
mDictionaryFacilitator = dictionaryFacilitator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@UsedForTesting
|
@UsedForTesting
|
||||||
|
@ -105,8 +103,10 @@ public abstract class MainLogBuffer extends FixedLogBuffer {
|
||||||
if (mDictionaryForTesting != null) {
|
if (mDictionaryForTesting != null) {
|
||||||
return mDictionaryForTesting;
|
return mDictionaryForTesting;
|
||||||
}
|
}
|
||||||
if (mSuggest == null || !mSuggest.hasMainDictionary()) return null;
|
if (mDictionaryFacilitator == null || !mDictionaryFacilitator.hasMainDictionary()) {
|
||||||
return mSuggest.getMainDictionary();
|
return null;
|
||||||
|
}
|
||||||
|
return mDictionaryFacilitator.getMainDictionary();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setIsStopping() {
|
public void setIsStopping() {
|
||||||
|
|
|
@ -53,10 +53,10 @@ import com.android.inputmethod.keyboard.KeyboardView;
|
||||||
import com.android.inputmethod.keyboard.MainKeyboardView;
|
import com.android.inputmethod.keyboard.MainKeyboardView;
|
||||||
import com.android.inputmethod.latin.Constants;
|
import com.android.inputmethod.latin.Constants;
|
||||||
import com.android.inputmethod.latin.Dictionary;
|
import com.android.inputmethod.latin.Dictionary;
|
||||||
|
import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest;
|
||||||
import com.android.inputmethod.latin.LatinIME;
|
import com.android.inputmethod.latin.LatinIME;
|
||||||
import com.android.inputmethod.latin.R;
|
import com.android.inputmethod.latin.R;
|
||||||
import com.android.inputmethod.latin.RichInputConnection;
|
import com.android.inputmethod.latin.RichInputConnection;
|
||||||
import com.android.inputmethod.latin.Suggest;
|
|
||||||
import com.android.inputmethod.latin.SuggestedWords;
|
import com.android.inputmethod.latin.SuggestedWords;
|
||||||
import com.android.inputmethod.latin.define.ProductionFlag;
|
import com.android.inputmethod.latin.define.ProductionFlag;
|
||||||
import com.android.inputmethod.latin.utils.InputTypeUtils;
|
import com.android.inputmethod.latin.utils.InputTypeUtils;
|
||||||
|
@ -166,7 +166,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
|
||||||
protected static final int SUSPEND_DURATION_IN_MINUTES = 1;
|
protected static final int SUSPEND_DURATION_IN_MINUTES = 1;
|
||||||
|
|
||||||
// used to check whether words are not unique
|
// used to check whether words are not unique
|
||||||
private Suggest mSuggest;
|
private DictionaryFacilitatorForSuggest mDictionaryFacilitator;
|
||||||
private MainKeyboardView mMainKeyboardView;
|
private MainKeyboardView mMainKeyboardView;
|
||||||
// TODO: Check whether a superclass can be used instead of LatinIME.
|
// TODO: Check whether a superclass can be used instead of LatinIME.
|
||||||
/* package for test */ LatinIME mLatinIME;
|
/* package for test */ LatinIME mLatinIME;
|
||||||
|
@ -205,8 +205,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
|
||||||
return sInstance;
|
return sInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init(final LatinIME latinIME, final KeyboardSwitcher keyboardSwitcher,
|
public void init(final LatinIME latinIME, final KeyboardSwitcher keyboardSwitcher) {
|
||||||
final Suggest suggest) {
|
|
||||||
assert latinIME != null;
|
assert latinIME != null;
|
||||||
mLatinIME = latinIME;
|
mLatinIME = latinIME;
|
||||||
mPrefs = PreferenceManager.getDefaultSharedPreferences(latinIME);
|
mPrefs = PreferenceManager.getDefaultSharedPreferences(latinIME);
|
||||||
|
@ -242,7 +241,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
|
||||||
System.currentTimeMillis(), System.nanoTime()), mLatinIME);
|
System.currentTimeMillis(), System.nanoTime()), mLatinIME);
|
||||||
final int numWordsToIgnore = new Random().nextInt(NUMBER_OF_WORDS_BETWEEN_SAMPLES + 1);
|
final int numWordsToIgnore = new Random().nextInt(NUMBER_OF_WORDS_BETWEEN_SAMPLES + 1);
|
||||||
mMainLogBuffer = new MainLogBuffer(NUMBER_OF_WORDS_BETWEEN_SAMPLES, numWordsToIgnore,
|
mMainLogBuffer = new MainLogBuffer(NUMBER_OF_WORDS_BETWEEN_SAMPLES, numWordsToIgnore,
|
||||||
mSuggest) {
|
mDictionaryFacilitator) {
|
||||||
@Override
|
@Override
|
||||||
protected void publish(final ArrayList<LogUnit> logUnits,
|
protected void publish(final ArrayList<LogUnit> logUnits,
|
||||||
boolean canIncludePrivateData) {
|
boolean canIncludePrivateData) {
|
||||||
|
@ -656,8 +655,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
|
||||||
mInFeedbackDialog = false;
|
mInFeedbackDialog = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initSuggest(final Suggest suggest) {
|
public void initDictionary(final DictionaryFacilitatorForSuggest dictionaryFacilitator) {
|
||||||
mSuggest = suggest;
|
mDictionaryFacilitator = dictionaryFacilitator;
|
||||||
// MainLogBuffer now has an out-of-date Suggest object. Close down MainLogBuffer and create
|
// MainLogBuffer now has an out-of-date Suggest object. Close down MainLogBuffer and create
|
||||||
// a new one.
|
// a new one.
|
||||||
if (mMainLogBuffer != null) {
|
if (mMainLogBuffer != null) {
|
||||||
|
@ -666,10 +665,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dictionary getDictionary() {
|
private Dictionary getDictionary() {
|
||||||
if (mSuggest == null) {
|
if (mDictionaryFacilitator == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return mSuggest.getMainDictionary();
|
return mDictionaryFacilitator.getMainDictionary();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setIsPasswordView(boolean isPasswordView) {
|
private void setIsPasswordView(boolean isPasswordView) {
|
||||||
|
|
Loading…
Reference in a new issue