Remove logic related to dictionary loading from LatinIME.

Make mSuggest final and give DictionaryFacilitator the
responsibility to manage dictionary loading state.
This can simplify the logic to decide how to deal with
additional dictionaries when loading settings or language
switching.

Bug: 13273534
Change-Id: I9f3d328272f25addfa186fbeedaaf8417455ba99
main
Keisuke Kuroyanagi 2014-03-25 15:35:20 +09:00
parent 37b9562fd7
commit adfb262797
8 changed files with 471 additions and 549 deletions

View File

@ -26,15 +26,14 @@ import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.personalization.PersonalizationDictionary; import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
import com.android.inputmethod.latin.personalization.PersonalizationHelper; import com.android.inputmethod.latin.personalization.PersonalizationHelper;
import com.android.inputmethod.latin.personalization.UserHistoryDictionary; import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
import com.android.inputmethod.latin.settings.SettingsValues;
import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.ExecutorUtils; import com.android.inputmethod.latin.utils.ExecutorUtils;
import com.android.inputmethod.latin.utils.LanguageModelParam; import com.android.inputmethod.latin.utils.LanguageModelParam;
import com.android.inputmethod.latin.utils.SuggestionResults;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -50,197 +49,314 @@ public class DictionaryFacilitatorForSuggest {
// dictionary. // dictionary.
private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140; private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140;
private final Context mContext; private Dictionaries mDictionaries = new Dictionaries();
public final Locale mLocale; private volatile CountDownLatch mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0);
// To synchronize assigning mDictionaries to ensure closing dictionaries.
private Object mLock = new Object();
private final ConcurrentHashMap<String, Dictionary> mDictionaries = /**
CollectionUtils.newConcurrentHashMap(); * Class contains dictionaries for a locale.
*/
private static class Dictionaries {
public final Locale mLocale;
public final ConcurrentHashMap<String, Dictionary> mDictMap =
CollectionUtils.newConcurrentHashMap();
// Main dictionary will be asynchronously loaded.
public Dictionary mMainDictionary;
public final ContactsBinaryDictionary mContactsDictionary;
public final UserBinaryDictionary mUserDictionary;
public final UserHistoryDictionary mUserHistoryDictionary;
public final PersonalizationDictionary mPersonalizationDictionary;
private Dictionary mMainDictionary; public Dictionaries() {
private ContactsBinaryDictionary mContactsDictionary; mLocale = null;
private UserBinaryDictionary mUserDictionary; mMainDictionary = null;
private UserHistoryDictionary mUserHistoryDictionary; mContactsDictionary = null;
private PersonalizationDictionary mPersonalizationDictionary; mUserDictionary = null;
mUserHistoryDictionary = null;
mPersonalizationDictionary = null;
}
private final CountDownLatch mLatchForWaitingLoadingMainDictionary; public Dictionaries(final Locale locale, final Dictionary mainDict,
final ContactsBinaryDictionary contactsDict, final UserBinaryDictionary userDict,
final UserHistoryDictionary userHistoryDict,
final PersonalizationDictionary personalizationDict) {
mLocale = locale;
setMainDict(mainDict);
mContactsDictionary = contactsDict;
if (mContactsDictionary != null) {
mDictMap.put(Dictionary.TYPE_CONTACTS, mContactsDictionary);
}
mUserDictionary = userDict;
if (mUserDictionary != null) {
mDictMap.put(Dictionary.TYPE_USER, mUserDictionary);
}
mUserHistoryDictionary = userHistoryDict;
if (mUserHistoryDictionary != null) {
mDictMap.put(Dictionary.TYPE_USER_HISTORY, mUserHistoryDictionary);
}
mPersonalizationDictionary = personalizationDict;
if (mPersonalizationDictionary != null) {
mDictMap.put(Dictionary.TYPE_PERSONALIZATION, mPersonalizationDictionary);
}
}
public void setMainDict(final Dictionary mainDict) {
mMainDictionary = mainDict;
// Close old dictionary if exists. Main dictionary can be assigned multiple times.
final Dictionary oldDict;
if (mMainDictionary != null) {
oldDict = mDictMap.put(Dictionary.TYPE_MAIN, mMainDictionary);
} else {
oldDict = mDictMap.remove(Dictionary.TYPE_MAIN);
}
if (oldDict != null && mMainDictionary != oldDict) {
oldDict.close();
}
}
public boolean hasMainDict() {
return mMainDictionary != null;
}
public boolean hasContactsDict() {
return mContactsDictionary != null;
}
public boolean hasUserDict() {
return mUserDictionary != null;
}
public boolean hasUserHistoryDict() {
return mUserHistoryDictionary != null;
}
public boolean hasPersonalizationDict() {
return mPersonalizationDictionary != null;
}
}
public interface DictionaryInitializationListener { public interface DictionaryInitializationListener {
public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable); public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable);
} }
/** public DictionaryFacilitatorForSuggest() {}
* Creates instance for initialization or when the locale is changed.
* public Locale getLocale() {
* @param context the context return mDictionaries.mLocale;
* @param locale the locale
* @param settingsValues current settings values to control what dictionaries should be used
* @param listener the listener
* @param oldDictionaryFacilitator the instance having old dictionaries. This is null when the
* instance is initially created.
*/
public DictionaryFacilitatorForSuggest(final Context context, final Locale locale,
final SettingsValues settingsValues, final DictionaryInitializationListener listener,
final DictionaryFacilitatorForSuggest oldDictionaryFacilitator) {
mContext = context;
mLocale = locale;
mLatchForWaitingLoadingMainDictionary = new CountDownLatch(1);
loadMainDict(context, locale, listener);
setUserDictionary(new UserBinaryDictionary(context, locale));
resetAdditionalDictionaries(oldDictionaryFacilitator, settingsValues);
} }
/** public void resetDictionaries(final Context context, final Locale newLocale,
* Creates instance for reloading the main dict. final boolean useContactsDict, final boolean usePersonalizedDicts,
* final boolean forceReloadMainDictionary,
* @param listener the listener final DictionaryInitializationListener listener) {
* @param oldDictionaryFacilitator the instance having old dictionaries. This must not be null. final boolean localeHasBeenChanged = !newLocale.equals(mDictionaries.mLocale);
*/ // We always try to have the main dictionary. Other dictionaries can be unused.
public DictionaryFacilitatorForSuggest(final DictionaryInitializationListener listener, final boolean reloadMainDictionary = localeHasBeenChanged || forceReloadMainDictionary;
final DictionaryFacilitatorForSuggest oldDictionaryFacilitator) { final boolean closeContactsDictionary = localeHasBeenChanged || !useContactsDict;
mContext = oldDictionaryFacilitator.mContext; final boolean closeUserDictionary = localeHasBeenChanged;
mLocale = oldDictionaryFacilitator.mLocale; final boolean closeUserHistoryDictionary = localeHasBeenChanged || !usePersonalizedDicts;
mLatchForWaitingLoadingMainDictionary = new CountDownLatch(1); final boolean closePersonalizationDictionary =
loadMainDict(mContext, mLocale, listener); localeHasBeenChanged || !usePersonalizedDicts;
// Transfer user dictionary.
setUserDictionary(oldDictionaryFacilitator.mUserDictionary); final Dictionary newMainDict;
oldDictionaryFacilitator.removeDictionary(Dictionary.TYPE_USER); if (reloadMainDictionary) {
// Transfer contacts dictionary. // The main dictionary will be asynchronously loaded.
setContactsDictionary(oldDictionaryFacilitator.mContactsDictionary); newMainDict = null;
oldDictionaryFacilitator.removeDictionary(Dictionary.TYPE_CONTACTS); } else {
// Transfer user history dictionary. newMainDict = mDictionaries.mMainDictionary;
setUserHistoryDictionary(oldDictionaryFacilitator.mUserHistoryDictionary); }
oldDictionaryFacilitator.removeDictionary(Dictionary.TYPE_USER_HISTORY);
// Transfer personalization dictionary. // Open or move contacts dictionary.
setPersonalizationDictionary(oldDictionaryFacilitator.mPersonalizationDictionary); final ContactsBinaryDictionary newContactsDict;
oldDictionaryFacilitator.removeDictionary(Dictionary.TYPE_PERSONALIZATION); if (!closeContactsDictionary && mDictionaries.hasContactsDict()) {
newContactsDict = mDictionaries.mContactsDictionary;
} else if (useContactsDict) {
newContactsDict = new ContactsBinaryDictionary(context, newLocale);
} else {
newContactsDict = null;
}
// Open or move user dictionary.
final UserBinaryDictionary newUserDictionary;
if (!closeUserDictionary && mDictionaries.hasUserDict()) {
newUserDictionary = mDictionaries.mUserDictionary;
} else {
newUserDictionary = new UserBinaryDictionary(context, newLocale);
}
// Open or move user history dictionary.
final UserHistoryDictionary newUserHistoryDict;
if (!closeUserHistoryDictionary && mDictionaries.hasUserHistoryDict()) {
newUserHistoryDict = mDictionaries.mUserHistoryDictionary;
} else if (usePersonalizedDicts) {
newUserHistoryDict = PersonalizationHelper.getUserHistoryDictionary(context, newLocale);
} else {
newUserHistoryDict = null;
}
// Open or move personalization dictionary.
final PersonalizationDictionary newPersonalizationDict;
if (!closePersonalizationDictionary && mDictionaries.hasPersonalizationDict()) {
newPersonalizationDict = mDictionaries.mPersonalizationDictionary;
} else if (usePersonalizedDicts) {
newPersonalizationDict =
PersonalizationHelper.getPersonalizationDictionary(context, newLocale);
} else {
newPersonalizationDict = null;
}
// Replace Dictionaries.
final Dictionaries newDictionaries = new Dictionaries(newLocale, newMainDict,
newContactsDict, newUserDictionary, newUserHistoryDict, newPersonalizationDict);
if (listener != null) {
listener.onUpdateMainDictionaryAvailability(newDictionaries.hasMainDict());
}
final Dictionaries oldDictionaries;
synchronized (mLock) {
oldDictionaries = mDictionaries;
mDictionaries = newDictionaries;
if (reloadMainDictionary) {
asyncReloadMainDictionary(context, newLocale, listener);
}
}
// Clean up old dictionaries.
oldDictionaries.mDictMap.clear();
if (reloadMainDictionary && oldDictionaries.hasMainDict()) {
oldDictionaries.mMainDictionary.close();
}
if (closeContactsDictionary && oldDictionaries.hasContactsDict()) {
oldDictionaries.mContactsDictionary.close();
}
if (closeUserDictionary && oldDictionaries.hasUserDict()) {
oldDictionaries.mUserDictionary.close();
}
if (closeUserHistoryDictionary && oldDictionaries.hasUserHistoryDict()) {
oldDictionaries.mUserHistoryDictionary.close();
}
if (closePersonalizationDictionary && oldDictionaries.hasPersonalizationDict()) {
oldDictionaries.mPersonalizationDictionary.close();
}
} }
/** private void asyncReloadMainDictionary(final Context context, final Locale locale,
* Creates instance for when the settings values have been changed. final DictionaryInitializationListener listener) {
* final CountDownLatch latchForWaitingLoadingMainDictionary = new CountDownLatch(1);
* @param settingsValues the new settings values mLatchForWaitingLoadingMainDictionary = latchForWaitingLoadingMainDictionary;
* @param oldDictionaryFacilitator the instance having old dictionaries. This must not be null. ExecutorUtils.getExecutor("InitializeBinaryDictionary").execute(new Runnable() {
*/ @Override
// public void run() {
public DictionaryFacilitatorForSuggest(final SettingsValues settingsValues, final Dictionary mainDict =
final DictionaryFacilitatorForSuggest oldDictionaryFacilitator) { DictionaryFactory.createMainDictionaryFromManager(context, locale);
mContext = oldDictionaryFacilitator.mContext; synchronized (mLock) {
mLocale = oldDictionaryFacilitator.mLocale; if (locale.equals(mDictionaries.mLocale)) {
mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0); mDictionaries.setMainDict(mainDict);
// Transfer main dictionary. } else {
setMainDictionary(oldDictionaryFacilitator.mMainDictionary); // Dictionary facilitator has been reset for another locale.
oldDictionaryFacilitator.removeDictionary(Dictionary.TYPE_MAIN); mainDict.close();
// Transfer user dictionary. }
setUserDictionary(oldDictionaryFacilitator.mUserDictionary); }
oldDictionaryFacilitator.removeDictionary(Dictionary.TYPE_USER); if (listener != null) {
// Transfer or create additional dictionaries depending on the settings values. listener.onUpdateMainDictionaryAvailability(mDictionaries.hasMainDict());
resetAdditionalDictionaries(oldDictionaryFacilitator, settingsValues); }
latchForWaitingLoadingMainDictionary.countDown();
}
});
} }
@UsedForTesting @UsedForTesting
public DictionaryFacilitatorForSuggest(final Context context, final Locale locale, public void resetDictionariesForTesting(final Context context, final Locale locale,
final ArrayList<String> dictionaryTypes, final HashMap<String, File> dictionaryFiles, final ArrayList<String> dictionaryTypes, final HashMap<String, File> dictionaryFiles,
final Map<String, Map<String, String>> additionalDictAttributes) { final Map<String, Map<String, String>> additionalDictAttributes) {
mContext = context; Dictionary mainDictionary = null;
mLocale = locale; ContactsBinaryDictionary contactsDictionary = null;
mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0); UserBinaryDictionary userDictionary = null;
UserHistoryDictionary userHistoryDictionary = null;
PersonalizationDictionary personalizationDictionary = null;
for (final String dictType : dictionaryTypes) { for (final String dictType : dictionaryTypes) {
if (dictType.equals(Dictionary.TYPE_MAIN)) { if (dictType.equals(Dictionary.TYPE_MAIN)) {
final DictionaryCollection mainDictionary = mainDictionary = DictionaryFactory.createMainDictionaryFromManager(context, locale);
DictionaryFactory.createMainDictionaryFromManager(context, locale);
setMainDictionary(mainDictionary);
} else if (dictType.equals(Dictionary.TYPE_USER_HISTORY)) { } else if (dictType.equals(Dictionary.TYPE_USER_HISTORY)) {
final UserHistoryDictionary userHistoryDictionary = userHistoryDictionary =
PersonalizationHelper.getUserHistoryDictionary(context, locale); PersonalizationHelper.getUserHistoryDictionary(context, locale);
// Staring with an empty user history dictionary for testing. // Staring with an empty user history dictionary for testing.
// Testing program may populate this dictionary before actual testing. // Testing program may populate this dictionary before actual testing.
userHistoryDictionary.reloadDictionaryIfRequired(); userHistoryDictionary.reloadDictionaryIfRequired();
userHistoryDictionary.waitAllTasksForTests(); userHistoryDictionary.waitAllTasksForTests();
setUserHistoryDictionary(userHistoryDictionary);
if (additionalDictAttributes.containsKey(dictType)) { if (additionalDictAttributes.containsKey(dictType)) {
userHistoryDictionary.clearAndFlushDictionaryWithAdditionalAttributes( userHistoryDictionary.clearAndFlushDictionaryWithAdditionalAttributes(
additionalDictAttributes.get(dictType)); additionalDictAttributes.get(dictType));
} }
} else if (dictType.equals(Dictionary.TYPE_PERSONALIZATION)) { } else if (dictType.equals(Dictionary.TYPE_PERSONALIZATION)) {
final PersonalizationDictionary personalizationDictionary = personalizationDictionary =
PersonalizationHelper.getPersonalizationDictionary(context, locale); PersonalizationHelper.getPersonalizationDictionary(context, locale);
// Staring with an empty personalization dictionary for testing. // Staring with an empty personalization dictionary for testing.
// Testing program may populate this dictionary before actual testing. // Testing program may populate this dictionary before actual testing.
personalizationDictionary.reloadDictionaryIfRequired(); personalizationDictionary.reloadDictionaryIfRequired();
personalizationDictionary.waitAllTasksForTests(); personalizationDictionary.waitAllTasksForTests();
setPersonalizationDictionary(personalizationDictionary);
if (additionalDictAttributes.containsKey(dictType)) { if (additionalDictAttributes.containsKey(dictType)) {
personalizationDictionary.clearAndFlushDictionaryWithAdditionalAttributes( personalizationDictionary.clearAndFlushDictionaryWithAdditionalAttributes(
additionalDictAttributes.get(dictType)); additionalDictAttributes.get(dictType));
} }
} else if (dictType.equals(Dictionary.TYPE_USER)) { } else if (dictType.equals(Dictionary.TYPE_USER)) {
final File file = dictionaryFiles.get(dictType); final File file = dictionaryFiles.get(dictType);
final UserBinaryDictionary userDictionary = new UserBinaryDictionary( userDictionary = new UserBinaryDictionary(context, locale, file);
context, locale, file);
userDictionary.reloadDictionaryIfRequired(); userDictionary.reloadDictionaryIfRequired();
userDictionary.waitAllTasksForTests(); userDictionary.waitAllTasksForTests();
setUserDictionary(userDictionary);
} else if (dictType.equals(Dictionary.TYPE_CONTACTS)) { } else if (dictType.equals(Dictionary.TYPE_CONTACTS)) {
final File file = dictionaryFiles.get(dictType); final File file = dictionaryFiles.get(dictType);
final ContactsBinaryDictionary contactsDictionary = new ContactsBinaryDictionary( contactsDictionary = new ContactsBinaryDictionary(context, locale, file);
context, locale, file);
contactsDictionary.reloadDictionaryIfRequired(); contactsDictionary.reloadDictionaryIfRequired();
contactsDictionary.waitAllTasksForTests(); contactsDictionary.waitAllTasksForTests();
setContactsDictionary(contactsDictionary);
} else { } else {
throw new RuntimeException("Unknown dictionary type: " + dictType); throw new RuntimeException("Unknown dictionary type: " + dictType);
} }
} }
mDictionaries = new Dictionaries(locale, mainDictionary, contactsDictionary,
userDictionary, userHistoryDictionary, personalizationDictionary);
} }
public boolean needsToBeRecreated(final Locale newLocale, public void closeDictionaries() {
final SettingsValues newSettingsValues) { final Dictionaries dictionaries;
return !mLocale.equals(newLocale) synchronized (mLock) {
|| (newSettingsValues.mUseContactsDict != (mContactsDictionary != null)) dictionaries = mDictionaries;
|| (newSettingsValues.mUsePersonalizedDicts != (mUserHistoryDictionary != null)) mDictionaries = new Dictionaries();
|| (newSettingsValues.mUsePersonalizedDicts != hasPersonalizationDictionary());
}
public void close() {
final HashSet<Dictionary> dictionaries = CollectionUtils.newHashSet();
dictionaries.addAll(mDictionaries.values());
for (final Dictionary dictionary : dictionaries) {
dictionary.close();
} }
} if (dictionaries.hasMainDict()) {
dictionaries.mMainDictionary.close();
private void loadMainDict(final Context context, final Locale locale, }
final DictionaryInitializationListener listener) { if (dictionaries.hasContactsDict()) {
mMainDictionary = null; dictionaries.mContactsDictionary.close();
if (listener != null) { }
listener.onUpdateMainDictionaryAvailability(hasMainDictionary()); if (dictionaries.hasUserDict()) {
dictionaries.mUserDictionary.close();
}
if (dictionaries.hasUserHistoryDict()) {
dictionaries.mUserHistoryDictionary.close();
}
if (dictionaries.hasPersonalizationDict()) {
dictionaries.mPersonalizationDictionary.close();
} }
ExecutorUtils.getExecutor("InitializeBinaryDictionary").execute(new Runnable() {
public void run() {
final DictionaryCollection newMainDict =
DictionaryFactory.createMainDictionaryFromManager(context, locale);
setMainDictionary(newMainDict);
if (listener != null) {
listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
}
mLatchForWaitingLoadingMainDictionary.countDown();
}
});
} }
// The main dictionary could have been loaded asynchronously. Don't cache the return value // The main dictionary could have been loaded asynchronously. Don't cache the return value
// of this method. // of this method.
public boolean hasMainDictionary() { public boolean hasInitializedMainDictionary() {
return null != mMainDictionary && mMainDictionary.isInitialized(); final Dictionaries dictionaries = mDictionaries;
return dictionaries.hasMainDict() && dictionaries.mMainDictionary.isInitialized();
} }
public boolean hasPersonalizationDictionary() { public boolean hasPersonalizationDictionary() {
return null != mPersonalizationDictionary; return mDictionaries.hasPersonalizationDict();
} }
public void flushPersonalizationDictionary() { public void flushPersonalizationDictionary() {
if (hasPersonalizationDictionary()) { final PersonalizationDictionary personalizationDict =
mPersonalizationDictionary.flush(); mDictionaries.mPersonalizationDictionary;
if (personalizationDict != null) {
personalizationDict.flush();
} }
} }
@ -253,177 +369,48 @@ public class DictionaryFacilitatorForSuggest {
public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit) public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit)
throws InterruptedException { throws InterruptedException {
waitForLoadingMainDictionary(timeout, unit); waitForLoadingMainDictionary(timeout, unit);
if (mContactsDictionary != null) { final Dictionaries dictionaries = mDictionaries;
mContactsDictionary.waitAllTasksForTests(); if (dictionaries.hasContactsDict()) {
dictionaries.mContactsDictionary.waitAllTasksForTests();
} }
if (mUserDictionary != null) { if (dictionaries.hasUserDict()) {
mUserDictionary.waitAllTasksForTests(); dictionaries.mUserDictionary.waitAllTasksForTests();
} }
if (mUserHistoryDictionary != null) { if (dictionaries.hasUserHistoryDict()) {
mUserHistoryDictionary.waitAllTasksForTests(); dictionaries.mUserHistoryDictionary.waitAllTasksForTests();
} }
if (mPersonalizationDictionary != null) { if (dictionaries.hasPersonalizationDict()) {
mPersonalizationDictionary.waitAllTasksForTests(); dictionaries.mPersonalizationDictionary.waitAllTasksForTests();
} }
} }
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.
*/
private 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.
*/
private 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);
}
/**
* Reset dictionaries that can be turned off according to the user settings.
*
* @param oldDictionaryFacilitator the instance having old dictionaries
* @param settingsValues current SettingsValues
*/
private void resetAdditionalDictionaries(
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() { public boolean isUserDictionaryEnabled() {
if (mUserDictionary == null) { final UserBinaryDictionary userDictionary = mDictionaries.mUserDictionary;
if (userDictionary == null) {
return false; return false;
} }
return mUserDictionary.mEnabled; return userDictionary.mEnabled;
} }
public void addWordToUserDictionary(String word) { public void addWordToUserDictionary(String word) {
if (mUserDictionary == null) { final UserBinaryDictionary userDictionary = mDictionaries.mUserDictionary;
if (userDictionary == null) {
return; return;
} }
mUserDictionary.addWordToUserDictionary(word); userDictionary.addWordToUserDictionary(word);
} }
public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized, public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
final String previousWord, final int timeStampInSeconds) { final String previousWord, final int timeStampInSeconds) {
if (mUserHistoryDictionary == null) { final Dictionaries dictionaries = mDictionaries;
if (!dictionaries.hasUserHistoryDict()) {
return; return;
} }
final int maxFreq = getMaxFrequency(suggestion); final int maxFreq = getMaxFrequency(suggestion);
if (maxFreq == 0) { if (maxFreq == 0) {
return; return;
} }
final String suggestionLowerCase = suggestion.toLowerCase(mLocale); final String suggestionLowerCase = suggestion.toLowerCase(dictionaries.mLocale);
final String secondWord; final String secondWord;
if (wasAutoCapitalized) { if (wasAutoCapitalized) {
secondWord = suggestionLowerCase; secondWord = suggestionLowerCase;
@ -432,11 +419,11 @@ public class DictionaryFacilitatorForSuggest {
// History dictionary in order to avoid suggesting them until the dictionary // History dictionary in order to avoid suggesting them until the dictionary
// consolidation is done. // consolidation is done.
// TODO: Remove this hack when ready. // TODO: Remove this hack when ready.
final int lowerCasefreqInMainDict = mMainDictionary != null ? final int lowerCaseFreqInMainDict = dictionaries.hasMainDict() ?
mMainDictionary.getFrequency(suggestionLowerCase) : dictionaries.mMainDictionary.getFrequency(suggestionLowerCase) :
Dictionary.NOT_A_PROBABILITY; Dictionary.NOT_A_PROBABILITY;
if (maxFreq < lowerCasefreqInMainDict if (maxFreq < lowerCaseFreqInMainDict
&& lowerCasefreqInMainDict >= CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT) { && lowerCaseFreqInMainDict >= CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT) {
// Use lower cased word as the word can be a distracter of the popular word. // Use lower cased word as the word can be a distracter of the popular word.
secondWord = suggestionLowerCase; secondWord = suggestionLowerCase;
} else { } else {
@ -446,54 +433,56 @@ public class DictionaryFacilitatorForSuggest {
// We demote unrecognized words (frequency < 0, below) by specifying them as "invalid". // 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.). // We don't add words with 0-frequency (assuming they would be profanity etc.).
final boolean isValid = maxFreq > 0; final boolean isValid = maxFreq > 0;
mUserHistoryDictionary.addToDictionary( dictionaries.mUserHistoryDictionary.addToDictionary(
previousWord, secondWord, isValid, timeStampInSeconds); previousWord, secondWord, isValid, timeStampInSeconds);
} }
public void cancelAddingUserHistory(final String previousWord, final String committedWord) { public void cancelAddingUserHistory(final String previousWord, final String committedWord) {
if (mUserHistoryDictionary != null) { final UserHistoryDictionary userHistoryDictionary = mDictionaries.mUserHistoryDictionary;
mUserHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord); if (userHistoryDictionary != null) {
userHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord);
} }
} }
// TODO: Revise the way to fusion suggestion results. // TODO: Revise the way to fusion suggestion results.
public void getSuggestions(final WordComposer composer, public SuggestionResults getSuggestionResults(final WordComposer composer,
final String prevWord, final ProximityInfo proximityInfo, final String prevWord, final ProximityInfo proximityInfo,
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions, final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
final int sessionId, final Set<SuggestedWordInfo> suggestionSet, final int sessionId, final ArrayList<SuggestedWordInfo> rawSuggestions) {
final ArrayList<SuggestedWordInfo> rawSuggestions) { final Dictionaries dictionaries = mDictionaries;
for (final String key : mDictionaries.keySet()) { final Map<String, Dictionary> dictMap = dictionaries.mDictMap;
final Dictionary dictionary = mDictionaries.get(key); final SuggestionResults suggestionResults =
new SuggestionResults(dictionaries.mLocale, SuggestedWords.MAX_SUGGESTIONS);
for (final Dictionary dictionary : dictMap.values()) {
if (null == dictionary) continue; if (null == dictionary) continue;
final ArrayList<SuggestedWordInfo> dictionarySuggestions = final ArrayList<SuggestedWordInfo> dictionarySuggestions =
dictionary.getSuggestionsWithSessionId(composer, prevWord, proximityInfo, dictionary.getSuggestionsWithSessionId(composer, prevWord, proximityInfo,
blockOffensiveWords, additionalFeaturesOptions, sessionId); blockOffensiveWords, additionalFeaturesOptions, sessionId);
if (null == dictionarySuggestions) continue; if (null == dictionarySuggestions) continue;
suggestionSet.addAll(dictionarySuggestions); suggestionResults.addAll(dictionarySuggestions);
if (null != rawSuggestions) { if (null != rawSuggestions) {
rawSuggestions.addAll(dictionarySuggestions); rawSuggestions.addAll(dictionarySuggestions);
} }
} }
return suggestionResults;
} }
public boolean isValidMainDictWord(final String word) { public boolean isValidMainDictWord(final String word) {
if (TextUtils.isEmpty(word) || !hasMainDictionary()) { final Dictionaries dictionaries = mDictionaries;
if (TextUtils.isEmpty(word) || !dictionaries.hasMainDict()) {
return false; return false;
} }
return mMainDictionary.isValidWord(word); return dictionaries.mMainDictionary.isValidWord(word);
} }
public boolean isValidWord(final String word, final boolean ignoreCase) { public boolean isValidWord(final String word, final boolean ignoreCase) {
if (TextUtils.isEmpty(word)) { if (TextUtils.isEmpty(word)) {
return false; return false;
} }
final String lowerCasedWord = word.toLowerCase(mLocale); final Dictionaries dictionaries = mDictionaries;
for (final String key : mDictionaries.keySet()) { final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale);
final Dictionary dictionary = mDictionaries.get(key); final Map<String, Dictionary> dictMap = dictionaries.mDictMap;
// It's unclear how realistically 'dictionary' can be null, but the monkey is somehow for (final Dictionary dictionary : dictMap.values()) {
// 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 // 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 // would be immutable once it's finished initializing, but concretely a null test is
// probably good enough for the time being. // probably good enough for the time being.
@ -511,9 +500,8 @@ public class DictionaryFacilitatorForSuggest {
return Dictionary.NOT_A_PROBABILITY; return Dictionary.NOT_A_PROBABILITY;
} }
int maxFreq = -1; int maxFreq = -1;
for (final String key : mDictionaries.keySet()) { final Map<String, Dictionary> dictMap = mDictionaries.mDictMap;
final Dictionary dictionary = mDictionaries.get(key); for (final Dictionary dictionary : dictMap.values()) {
if (null == dictionary) continue;
final int tempFreq = dictionary.getFrequency(word); final int tempFreq = dictionary.getFrequency(word);
if (tempFreq >= maxFreq) { if (tempFreq >= maxFreq) {
maxFreq = tempFreq; maxFreq = tempFreq;
@ -522,61 +510,50 @@ public class DictionaryFacilitatorForSuggest {
return maxFreq; return maxFreq;
} }
private void removeDictionary(final String key) {
mDictionaries.remove(key);
}
private void addOrReplaceDictionary(final String key, final Dictionary dict) {
final Dictionary oldDict;
if (dict == null) {
oldDict = mDictionaries.remove(key);
} else {
oldDict = mDictionaries.put(key, dict);
}
if (oldDict != null && dict != oldDict) {
oldDict.close();
}
}
public void clearUserHistoryDictionary() { public void clearUserHistoryDictionary() {
if (mUserHistoryDictionary == null) { final UserHistoryDictionary userHistoryDict = mDictionaries.mUserHistoryDictionary;
if (userHistoryDict == null) {
return; return;
} }
mUserHistoryDictionary.clearAndFlushDictionary(); userHistoryDict.clearAndFlushDictionary();
} }
// This method gets called only when the IME receives a notification to remove the // This method gets called only when the IME receives a notification to remove the
// personalization dictionary. // personalization dictionary.
public void clearPersonalizationDictionary() { public void clearPersonalizationDictionary() {
if (!hasPersonalizationDictionary()) { final PersonalizationDictionary personalizationDict =
mDictionaries.mPersonalizationDictionary;
if (personalizationDict == null) {
return; return;
} }
mPersonalizationDictionary.clearAndFlushDictionary(); personalizationDict.clearAndFlushDictionary();
} }
public void addMultipleDictionaryEntriesToPersonalizationDictionary( public void addMultipleDictionaryEntriesToPersonalizationDictionary(
final ArrayList<LanguageModelParam> languageModelParams, final ArrayList<LanguageModelParam> languageModelParams,
final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) { final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) {
if (!hasPersonalizationDictionary()) { final PersonalizationDictionary personalizationDict =
mDictionaries.mPersonalizationDictionary;
if (personalizationDict == null) {
if (callback != null) { if (callback != null) {
callback.onFinished(); callback.onFinished();
} }
return; return;
} }
mPersonalizationDictionary.addMultipleDictionaryEntriesToDictionary(languageModelParams, personalizationDict.addMultipleDictionaryEntriesToDictionary(languageModelParams, callback);
callback);
} }
public void dumpDictionaryForDebug(final String dictName) { public void dumpDictionaryForDebug(final String dictName) {
final ExpandableBinaryDictionary dictToDump; final ExpandableBinaryDictionary dictToDump;
if (dictName.equals(Dictionary.TYPE_CONTACTS)) { if (dictName.equals(Dictionary.TYPE_CONTACTS)) {
dictToDump = mContactsDictionary; dictToDump = mDictionaries.mContactsDictionary;
} else if (dictName.equals(Dictionary.TYPE_USER)) { } else if (dictName.equals(Dictionary.TYPE_USER)) {
dictToDump = mUserDictionary; dictToDump = mDictionaries.mUserDictionary;
} else if (dictName.equals(Dictionary.TYPE_USER_HISTORY)) { } else if (dictName.equals(Dictionary.TYPE_USER_HISTORY)) {
dictToDump = mUserHistoryDictionary; dictToDump = mDictionaries.mUserHistoryDictionary;
} else if (dictName.equals(Dictionary.TYPE_PERSONALIZATION)) { } else if (dictName.equals(Dictionary.TYPE_PERSONALIZATION)) {
dictToDump = mPersonalizationDictionary; dictToDump = mDictionaries.mPersonalizationDictionary;
} else { } else {
dictToDump = null; dictToDump = null;
} }

View File

@ -215,7 +215,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
false /* includeResumedWordInSuggestions */); false /* includeResumedWordInSuggestions */);
break; break;
case MSG_REOPEN_DICTIONARIES: case MSG_REOPEN_DICTIONARIES:
latinIme.initSuggest(); latinIme.resetSuggest();
// In theory we could call latinIme.updateSuggestionStrip() right away, but // In theory we could call latinIme.updateSuggestionStrip() right away, but
// in the practice, the dictionary is not finished opening yet so we wouldn't // in the practice, the dictionary is not finished opening yet so we wouldn't
// get any suggestions. Wait one frame. // get any suggestions. Wait one frame.
@ -478,10 +478,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// TODO: Resolve mutual dependencies of {@link #loadSettings()} and {@link #initSuggest()}. // TODO: Resolve mutual dependencies of {@link #loadSettings()} and {@link #initSuggest()}.
loadSettings(); loadSettings();
initSuggest(); resetSuggest();
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.getInstance().init(this, mKeyboardSwitcher); ResearchLogger.getInstance().init(this, mKeyboardSwitcher);
ResearchLogger.getInstance().initDictionary(
mInputLogic.mSuggest.mDictionaryFacilitator);
} }
// Register to receive ringer mode change and network state change. // Register to receive ringer mode change and network state change.
@ -520,31 +522,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// This method is called on startup and language switch, before the new layout has // This method is called on startup and language switch, before the new layout has
// been displayed. Opening dictionaries never affects responsivity as dictionaries are // been displayed. Opening dictionaries never affects responsivity as dictionaries are
// asynchronously loaded. // asynchronously loaded.
initOrResetSuggestForSettingsValues(mInputLogic.mSuggest, locale, currentSettingsValues); if (!mHandler.hasPendingReopenDictionaries()) {
} resetSuggestForLocale(locale);
private void initOrResetSuggestForSettingsValues(final Suggest oldSuggest,
final Locale locale, final SettingsValues settingsValues) {
if (!mHandler.hasPendingReopenDictionaries() && oldSuggest != null) {
// May need to reset dictionaries depending on the user settings.
final DictionaryFacilitatorForSuggest oldDictionaryFacilitator =
oldSuggest.mDictionaryFacilitator;
if (!oldDictionaryFacilitator.needsToBeRecreated(locale, settingsValues)) {
// Continue to use the same dictionary facilitator if no configuration has changed.
refreshPersonalizationDictionarySession();
return;
}
final DictionaryFacilitatorForSuggest dictionaryFacilitator =
new DictionaryFacilitatorForSuggest(settingsValues, oldDictionaryFacilitator);
// Create Suggest instance with the new dictionary facilitator.
replaceSuggest(new Suggest(oldSuggest, dictionaryFacilitator));
} else if (oldSuggest == null) {
initSuggest();
} }
refreshPersonalizationDictionarySession();
} }
private void refreshPersonalizationDictionarySession() { private void refreshPersonalizationDictionarySession() {
final Suggest suggest = mInputLogic.mSuggest; final DictionaryFacilitatorForSuggest dictionaryFacilitator =
mInputLogic.mSuggest.mDictionaryFacilitator;
final boolean shouldKeepUserHistoryDictionaries; final boolean shouldKeepUserHistoryDictionaries;
final boolean shouldKeepPersonalizationDictionaries; final boolean shouldKeepPersonalizationDictionaries;
if (mSettings.getCurrent().mUsePersonalizedDicts) { if (mSettings.getCurrent().mUsePersonalizedDicts) {
@ -559,17 +545,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (!shouldKeepUserHistoryDictionaries) { if (!shouldKeepUserHistoryDictionaries) {
// Remove user history dictionaries. // Remove user history dictionaries.
PersonalizationHelper.removeAllUserHistoryDictionaries(this); PersonalizationHelper.removeAllUserHistoryDictionaries(this);
if (suggest != null) { dictionaryFacilitator.clearUserHistoryDictionary();
suggest.mDictionaryFacilitator.clearUserHistoryDictionary();
}
} }
if (!shouldKeepPersonalizationDictionaries) { if (!shouldKeepPersonalizationDictionaries) {
// Remove personalization dictionaries. // Remove personalization dictionaries.
PersonalizationHelper.removeAllPersonalizationDictionaries(this); PersonalizationHelper.removeAllPersonalizationDictionaries(this);
PersonalizationDictionarySessionRegistrar.resetAll(this); PersonalizationDictionarySessionRegistrar.resetAll(this);
} else { } else {
final DictionaryFacilitatorForSuggest dictionaryFacilitator =
(suggest == null) ? null : suggest.mDictionaryFacilitator;
PersonalizationDictionarySessionRegistrar.init(this, dictionaryFacilitator); PersonalizationDictionarySessionRegistrar.init(this, dictionaryFacilitator);
} }
} }
@ -583,7 +565,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} }
} }
private void initSuggest() { private void resetSuggest() {
final Locale switcherSubtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); final Locale switcherSubtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
final String switcherLocaleStr = switcherSubtypeLocale.toString(); final String switcherLocaleStr = switcherSubtypeLocale.toString();
final Locale subtypeLocale; final Locale subtypeLocale;
@ -599,47 +581,42 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} else { } else {
subtypeLocale = switcherSubtypeLocale; subtypeLocale = switcherSubtypeLocale;
} }
initSuggestForLocale(mInputLogic.mSuggest, subtypeLocale); resetSuggestForLocale(subtypeLocale);
} }
private void initSuggestForLocale(final Suggest oldSuggest, final Locale locale) { /**
final SettingsValues settingsValues = mSettings.getCurrent(); * Reset suggest by loading dictionaries for the locale and the current settings values.
final DictionaryFacilitatorForSuggest oldDictionaryFacilitator = *
(oldSuggest == null) ? null : oldSuggest.mDictionaryFacilitator; * @param locale the locale
// Creates new dictionary facilitator for the new locale. */
private void resetSuggestForLocale(final Locale locale) {
final DictionaryFacilitatorForSuggest dictionaryFacilitator = final DictionaryFacilitatorForSuggest dictionaryFacilitator =
new DictionaryFacilitatorForSuggest(this /* context */, locale, settingsValues,
this /* DictionaryInitializationListener */, oldDictionaryFacilitator);
final Suggest newSuggest = new Suggest(locale, dictionaryFacilitator);
if (settingsValues.mCorrectionEnabled) {
newSuggest.setAutoCorrectionThreshold(settingsValues.mAutoCorrectionThreshold);
}
replaceSuggest(newSuggest);
}
/* package private */ void resetSuggestMainDict() {
final DictionaryFacilitatorForSuggest oldDictionaryFacilitator =
mInputLogic.mSuggest.mDictionaryFacilitator; mInputLogic.mSuggest.mDictionaryFacilitator;
final DictionaryFacilitatorForSuggest dictionaryFacilitator = final SettingsValues settingsValues = mSettings.getCurrent();
new DictionaryFacilitatorForSuggest(this /* listener */, oldDictionaryFacilitator); dictionaryFacilitator.resetDictionaries(this /* context */, locale,
replaceSuggest(new Suggest(mInputLogic.mSuggest /* oldSuggest */, dictionaryFacilitator)); settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts,
false /* forceReloadMainDictionary */, this);
if (settingsValues.mCorrectionEnabled) {
mInputLogic.mSuggest.setAutoCorrectionThreshold(
settingsValues.mAutoCorrectionThreshold);
}
} }
private void replaceSuggest(final Suggest newSuggest) { /**
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { * Reset suggest by loading the main dictionary of the current locale.
ResearchLogger.getInstance().initDictionary(newSuggest.mDictionaryFacilitator); */
} /* package private */ void resetSuggestMainDict() {
mInputLogic.replaceSuggest(newSuggest); final DictionaryFacilitatorForSuggest dictionaryFacilitator =
refreshPersonalizationDictionarySession(); mInputLogic.mSuggest.mDictionaryFacilitator;
final SettingsValues settingsValues = mSettings.getCurrent();
dictionaryFacilitator.resetDictionaries(this /* context */,
dictionaryFacilitator.getLocale(), settingsValues.mUseContactsDict,
settingsValues.mUsePersonalizedDicts, true /* forceReloadMainDictionary */, this);
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
final Suggest suggest = mInputLogic.mSuggest; mInputLogic.mSuggest.mDictionaryFacilitator.closeDictionaries();
if (suggest != null) {
suggest.close();
mInputLogic.mSuggest = null;
}
mSettings.onDestroy(); mSettings.onDestroy();
unregisterReceiver(mReceiver); unregisterReceiver(mReceiver);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@ -802,10 +779,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Note: the following does a round-trip IPC on the main thread: be careful // Note: the following does a round-trip IPC on the main thread: be careful
final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
Suggest suggest = mInputLogic.mSuggest; final Suggest suggest = mInputLogic.mSuggest;
if (null != suggest && null != currentLocale && !currentLocale.equals(suggest.mLocale)) { if (null != currentLocale && !currentLocale.equals(suggest.getLocale())) {
initSuggest(); // TODO: Do this automatically.
suggest = mInputLogic.mSuggest; resetSuggest();
} }
// TODO[IL]: Can the following be moved to InputLogic#startInput? // TODO[IL]: Can the following be moved to InputLogic#startInput?
@ -833,13 +810,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (isDifferentTextField || if (isDifferentTextField ||
!currentSettingsValues.hasSameOrientation(getResources().getConfiguration())) { !currentSettingsValues.hasSameOrientation(getResources().getConfiguration())) {
loadSettings(); loadSettings();
suggest = mInputLogic.mSuggest;
} }
if (isDifferentTextField) { if (isDifferentTextField) {
mainKeyboardView.closing(); mainKeyboardView.closing();
currentSettingsValues = mSettings.getCurrent(); currentSettingsValues = mSettings.getCurrent();
if (suggest != null && currentSettingsValues.mCorrectionEnabled) { if (currentSettingsValues.mCorrectionEnabled) {
suggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold); suggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold);
} }
@ -865,8 +841,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.cancelUpdateSuggestionStrip(); mHandler.cancelUpdateSuggestionStrip();
mainKeyboardView.setMainDictionaryAvailability(null != suggest mainKeyboardView.setMainDictionaryAvailability(
? suggest.mDictionaryFacilitator.hasMainDictionary() : false); suggest.mDictionaryFacilitator.hasInitializedMainDictionary());
mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn, mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn,
currentSettingsValues.mKeyPreviewPopupDismissDelay); currentSettingsValues.mKeyPreviewPopupDismissDelay);
mainKeyboardView.setSlidingKeyInputPreviewEnabled( mainKeyboardView.setSlidingKeyInputPreviewEnabled(
@ -1407,8 +1383,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void getSuggestedWords(final int sessionId, final int sequenceNumber, public void getSuggestedWords(final int sessionId, final int sequenceNumber,
final OnGetSuggestedWordsCallback callback) { final OnGetSuggestedWordsCallback callback) {
final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
final Suggest suggest = mInputLogic.mSuggest; if (keyboard == null) {
if (keyboard == null || suggest == null) {
callback.onGetSuggestedWords(SuggestedWords.EMPTY); callback.onGetSuggestedWords(SuggestedWords.EMPTY);
return; return;
} }
@ -1437,7 +1412,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} }
} }
} }
suggest.getSuggestedWords(mInputLogic.mWordComposer, mInputLogic.mSuggest.getSuggestedWords(mInputLogic.mWordComposer,
mInputLogic.mWordComposer.getPreviousWordForSuggestion(), mInputLogic.mWordComposer.getPreviousWordForSuggestion(),
keyboard.getProximityInfo(), currentSettings.mBlockPotentiallyOffensive, keyboard.getProximityInfo(), currentSettings.mBlockPotentiallyOffensive,
currentSettings.mCorrectionEnabled, additionalFeaturesOptions, sessionId, currentSettings.mCorrectionEnabled, additionalFeaturesOptions, sessionId,
@ -1722,12 +1697,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// 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 replaceDictionariesForTest(final Locale locale) { /* package for test */ void replaceDictionariesForTest(final Locale locale) {
final DictionaryFacilitatorForSuggest oldDictionaryFacilitator = final SettingsValues settingsValues = mSettings.getCurrent();
mInputLogic.mSuggest.mDictionaryFacilitator; mInputLogic.mSuggest.mDictionaryFacilitator.resetDictionaries(this, locale,
final DictionaryFacilitatorForSuggest dictionaryFacilitator = settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts,
new DictionaryFacilitatorForSuggest(this, locale, mSettings.getCurrent(), false /* forceReloadMainDictionary */, this /* listener */);
this /* listener */, oldDictionaryFacilitator);
replaceSuggest(new Suggest(locale, dictionaryFacilitator));
} }
// DO NOT USE THIS for any other purpose than testing. // DO NOT USE THIS for any other purpose than testing.
@ -1738,8 +1711,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} }
public void dumpDictionaryForDebug(final String dictName) { public void dumpDictionaryForDebug(final String dictName) {
if (mInputLogic.mSuggest == null) { final DictionaryFacilitatorForSuggest dictionaryFacilitator =
initSuggest(); mInputLogic.mSuggest.mDictionaryFacilitator;
if (dictionaryFacilitator.getLocale() == null) {
resetSuggest();
} }
mInputLogic.mSuggest.mDictionaryFacilitator.dumpDictionaryForDebug(dictName); mInputLogic.mSuggest.mDictionaryFacilitator.dumpDictionaryForDebug(dictName);
} }

View File

@ -23,12 +23,11 @@ import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.utils.AutoCorrectionUtils; import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
import com.android.inputmethod.latin.utils.BoundedTreeSet;
import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.StringUtils; import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.SuggestionResults;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.Locale; import java.util.Locale;
/** /**
@ -53,29 +52,16 @@ public final class Suggest {
private static final int SUPPRESS_SUGGEST_THRESHOLD = -2000000000; private static final int SUPPRESS_SUGGEST_THRESHOLD = -2000000000;
private static final boolean DBG = LatinImeLogger.sDBG; private static final boolean DBG = LatinImeLogger.sDBG;
public final DictionaryFacilitatorForSuggest mDictionaryFacilitator; public final DictionaryFacilitatorForSuggest mDictionaryFacilitator =
new DictionaryFacilitatorForSuggest();
private float mAutoCorrectionThreshold; private float mAutoCorrectionThreshold;
// Locale used for upper- and title-casing words public Locale getLocale() {
public final Locale mLocale; return mDictionaryFacilitator.getLocale();
// TODO: Move dictionaryFacilitator constructing logics from LatinIME to Suggest.
public Suggest(final Locale locale,
final DictionaryFacilitatorForSuggest dictionaryFacilitator) {
mLocale = locale;
mDictionaryFacilitator = dictionaryFacilitator;
} }
// Creates instance with new dictionary facilitator. public void setAutoCorrectionThreshold(final float threshold) {
public Suggest(final Suggest oldSuggst,
final DictionaryFacilitatorForSuggest dictionaryFacilitator) {
mLocale = oldSuggst.mLocale;
mAutoCorrectionThreshold = oldSuggst.mAutoCorrectionThreshold;
mDictionaryFacilitator = dictionaryFacilitator;
}
public void setAutoCorrectionThreshold(float threshold) {
mAutoCorrectionThreshold = threshold; mAutoCorrectionThreshold = threshold;
} }
@ -108,9 +94,6 @@ public final class Suggest {
final int[] additionalFeaturesOptions, final int sequenceNumber, final int[] additionalFeaturesOptions, final int sequenceNumber,
final OnGetSuggestedWordsCallback callback) { final OnGetSuggestedWordsCallback callback) {
final int trailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount(); final int trailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
SuggestedWords.MAX_SUGGESTIONS);
final String typedWord = wordComposer.getTypedWord(); final String typedWord = wordComposer.getTypedWord();
final String consideredWord = trailingSingleQuotesCount > 0 final String consideredWord = trailingSingleQuotesCount > 0
? typedWord.substring(0, typedWord.length() - trailingSingleQuotesCount) ? typedWord.substring(0, typedWord.length() - trailingSingleQuotesCount)
@ -132,20 +115,20 @@ public final class Suggest {
} else { } else {
rawSuggestions = null; rawSuggestions = null;
} }
mDictionaryFacilitator.getSuggestions(wordComposerForLookup, prevWordForBigram, final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
proximityInfo, blockOffensiveWords, additionalFeaturesOptions, SESSION_TYPING, wordComposerForLookup, prevWordForBigram, proximityInfo, blockOffensiveWords,
suggestionsSet, rawSuggestions); additionalFeaturesOptions, SESSION_TYPING, rawSuggestions);
final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized(); final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
final boolean isAllUpperCase = wordComposer.isAllUpperCase(); final boolean isAllUpperCase = wordComposer.isAllUpperCase();
final String firstSuggestion; final String firstSuggestion;
final String whitelistedWord; final String whitelistedWord;
if (suggestionsSet.isEmpty()) { if (suggestionResults.isEmpty()) {
whitelistedWord = firstSuggestion = null; whitelistedWord = firstSuggestion = null;
} else { } else {
final SuggestedWordInfo firstSuggestedWordInfo = getTransformedSuggestedWordInfo( final SuggestedWordInfo firstSuggestedWordInfo = getTransformedSuggestedWordInfo(
suggestionsSet.first(), mLocale, isAllUpperCase, isFirstCharCapitalized, suggestionResults.first(), suggestionResults.mLocale, isAllUpperCase,
trailingSingleQuotesCount); isFirstCharCapitalized, trailingSingleQuotesCount);
firstSuggestion = firstSuggestedWordInfo.mWord; firstSuggestion = firstSuggestedWordInfo.mWord;
if (SuggestedWordInfo.KIND_WHITELIST != firstSuggestedWordInfo.mKind) { if (SuggestedWordInfo.KIND_WHITELIST != firstSuggestedWordInfo.mKind) {
whitelistedWord = null; whitelistedWord = null;
@ -175,10 +158,10 @@ public final class Suggest {
// the current settings. It may also be useful to know, when the setting is off, whether // the current settings. It may also be useful to know, when the setting is off, whether
// the word *would* have been auto-corrected. // the word *would* have been auto-corrected.
if (!isCorrectionEnabled || !allowsToBeAutoCorrected || isPrediction if (!isCorrectionEnabled || !allowsToBeAutoCorrected || isPrediction
|| suggestionsSet.isEmpty() || wordComposer.hasDigits() || suggestionResults.isEmpty() || wordComposer.hasDigits()
|| wordComposer.isMostlyCaps() || wordComposer.isResumed() || wordComposer.isMostlyCaps() || wordComposer.isResumed()
|| !mDictionaryFacilitator.hasMainDictionary() || !mDictionaryFacilitator.hasInitializedMainDictionary()
|| SuggestedWordInfo.KIND_SHORTCUT == suggestionsSet.first().mKind) { || SuggestedWordInfo.KIND_SHORTCUT == suggestionResults.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
// their language, and it will unexpectedly auto-correct. For example, if the user // their language, and it will unexpectedly auto-correct. For example, if the user
@ -190,17 +173,17 @@ public final class Suggest {
hasAutoCorrection = false; hasAutoCorrection = false;
} else { } else {
hasAutoCorrection = AutoCorrectionUtils.suggestionExceedsAutoCorrectionThreshold( hasAutoCorrection = AutoCorrectionUtils.suggestionExceedsAutoCorrectionThreshold(
suggestionsSet.first(), consideredWord, mAutoCorrectionThreshold); suggestionResults.first(), consideredWord, mAutoCorrectionThreshold);
} }
final ArrayList<SuggestedWordInfo> suggestionsContainer = final ArrayList<SuggestedWordInfo> suggestionsContainer =
CollectionUtils.newArrayList(suggestionsSet); CollectionUtils.newArrayList(suggestionResults);
final int suggestionsCount = suggestionsContainer.size(); final int suggestionsCount = suggestionsContainer.size();
if (isFirstCharCapitalized || isAllUpperCase || 0 != trailingSingleQuotesCount) { if (isFirstCharCapitalized || isAllUpperCase || 0 != trailingSingleQuotesCount) {
for (int i = 0; i < suggestionsCount; ++i) { for (int i = 0; i < suggestionsCount; ++i) {
final SuggestedWordInfo wordInfo = suggestionsContainer.get(i); final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo( final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
wordInfo, mLocale, isAllUpperCase, isFirstCharCapitalized, wordInfo, suggestionResults.mLocale, isAllUpperCase, isFirstCharCapitalized,
trailingSingleQuotesCount); trailingSingleQuotesCount);
suggestionsContainer.set(i, transformedWordInfo); suggestionsContainer.set(i, transformedWordInfo);
} }
@ -244,23 +227,21 @@ public final class Suggest {
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions, final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
final int sessionId, final int sequenceNumber, final int sessionId, final int sequenceNumber,
final OnGetSuggestedWordsCallback callback) { final OnGetSuggestedWordsCallback callback) {
final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
SuggestedWords.MAX_SUGGESTIONS);
final ArrayList<SuggestedWordInfo> rawSuggestions; final ArrayList<SuggestedWordInfo> rawSuggestions;
if (ProductionFlag.INCLUDE_RAW_SUGGESTIONS) { if (ProductionFlag.INCLUDE_RAW_SUGGESTIONS) {
rawSuggestions = CollectionUtils.newArrayList(); rawSuggestions = CollectionUtils.newArrayList();
} else { } else {
rawSuggestions = null; rawSuggestions = null;
} }
mDictionaryFacilitator.getSuggestions(wordComposer, prevWordForBigram, proximityInfo, final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
blockOffensiveWords, additionalFeaturesOptions, sessionId, suggestionsSet, wordComposer, prevWordForBigram, proximityInfo, blockOffensiveWords,
rawSuggestions); additionalFeaturesOptions, sessionId, rawSuggestions);
for (SuggestedWordInfo wordInfo : suggestionsSet) { for (SuggestedWordInfo wordInfo : suggestionResults) {
LatinImeLogger.onAddSuggestedWord(wordInfo.mWord, wordInfo.mSourceDict.mDictType); LatinImeLogger.onAddSuggestedWord(wordInfo.mWord, wordInfo.mSourceDict.mDictType);
} }
final ArrayList<SuggestedWordInfo> suggestionsContainer = final ArrayList<SuggestedWordInfo> suggestionsContainer =
CollectionUtils.newArrayList(suggestionsSet); CollectionUtils.newArrayList(suggestionResults);
final int suggestionsCount = suggestionsContainer.size(); final int suggestionsCount = suggestionsContainer.size();
final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock(); final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock();
final boolean isAllUpperCase = wordComposer.isAllUpperCase(); final boolean isAllUpperCase = wordComposer.isAllUpperCase();
@ -268,7 +249,7 @@ public final class Suggest {
for (int i = 0; i < suggestionsCount; ++i) { for (int i = 0; i < suggestionsCount; ++i) {
final SuggestedWordInfo wordInfo = suggestionsContainer.get(i); final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo( final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
wordInfo, mLocale, isAllUpperCase, isFirstCharCapitalized, wordInfo, suggestionResults.mLocale, isAllUpperCase, isFirstCharCapitalized,
0 /* trailingSingleQuotesCount */); 0 /* trailingSingleQuotesCount */);
suggestionsContainer.set(i, transformedWordInfo); suggestionsContainer.set(i, transformedWordInfo);
} }
@ -326,22 +307,6 @@ public final class Suggest {
return suggestionsList; return suggestionsList;
} }
private static final class SuggestedWordInfoComparator
implements Comparator<SuggestedWordInfo> {
// This comparator ranks the word info with the higher frequency first. That's because
// that's the order we want our elements in.
@Override
public int compare(final SuggestedWordInfo o1, final SuggestedWordInfo o2) {
if (o1.mScore > o2.mScore) return -1;
if (o1.mScore < o2.mScore) return 1;
if (o1.mCodePointCount < o2.mCodePointCount) return -1;
if (o1.mCodePointCount > o2.mCodePointCount) return 1;
return o1.mWord.compareTo(o2.mWord);
}
}
private static final SuggestedWordInfoComparator sSuggestedWordInfoComparator =
new SuggestedWordInfoComparator();
/* package for test */ static SuggestedWordInfo getTransformedSuggestedWordInfo( /* package for test */ static SuggestedWordInfo getTransformedSuggestedWordInfo(
final SuggestedWordInfo wordInfo, final Locale locale, final boolean isAllUpperCase, final SuggestedWordInfo wordInfo, final Locale locale, final boolean isAllUpperCase,
final boolean isFirstCharCapitalized, final int trailingSingleQuotesCount) { final boolean isFirstCharCapitalized, final int trailingSingleQuotesCount) {
@ -365,8 +330,4 @@ public final class Suggest {
wordInfo.mSourceDict, wordInfo.mIndexOfTouchPointOfSecondWord, wordInfo.mSourceDict, wordInfo.mIndexOfTouchPointOfSecondWord,
wordInfo.mAutoCommitFirstWordConfidence); wordInfo.mAutoCommitFirstWordConfidence);
} }
public void close() {
mDictionaryFacilitator.close();
}
} }

View File

@ -32,6 +32,7 @@ import com.android.inputmethod.event.InputTransaction;
import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.KeyboardSwitcher;
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.InputPointers; import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.LastComposedWord; import com.android.inputmethod.latin.LastComposedWord;
import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.LatinIME;
@ -77,8 +78,7 @@ public final class InputLogic {
private int mSpaceState; private int mSpaceState;
// Never null // Never null
public SuggestedWords mSuggestedWords = SuggestedWords.EMPTY; public SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
// TODO: mSuggest should be touched by a single thread. public final Suggest mSuggest = new Suggest();
public volatile Suggest mSuggest;
public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD; public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
public final WordComposer mWordComposer; public final WordComposer mWordComposer;
@ -106,15 +106,6 @@ public final class InputLogic {
mInputLogicHandler = InputLogicHandler.NULL_HANDLER; mInputLogicHandler = InputLogicHandler.NULL_HANDLER;
} }
// Replace the old Suggest with the passed Suggest and close it.
public void replaceSuggest(final Suggest newSuggest) {
final Suggest oldSuggest = mSuggest;
mSuggest = newSuggest;
if (oldSuggest != null) {
oldSuggest.close();
}
}
/** /**
* Initializes the input logic for input in an editor. * Initializes the input logic for input in an editor.
* *
@ -283,23 +274,18 @@ public final class InputLogic {
// We should show the "Touch again to save" hint if the user pressed the first entry // We should show the "Touch again to save" hint if the user pressed the first entry
// AND it's in none of our current dictionaries (main, user or otherwise). // AND it's in none of our current dictionaries (main, user or otherwise).
// Please note that if mSuggest is null, it means that everything is off: suggestion final DictionaryFacilitatorForSuggest dictionaryFacilitator =
// and correction, so we shouldn't try to show the hint mSuggest.mDictionaryFacilitator;
final Suggest suggest = mSuggest;
final boolean showingAddToDictionaryHint = final boolean showingAddToDictionaryHint =
(SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind (SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind
|| SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind) || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind)
&& suggest != null && !dictionaryFacilitator.isValidWord(suggestion, true /* ignoreCase */);
// If the suggestion is not in the dictionary, the hint should be shown.
&& !suggest.mDictionaryFacilitator.isValidWord(suggestion,
true /* ignoreCase */);
if (settingsValues.mIsInternal) { if (settingsValues.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 if (showingAddToDictionaryHint && dictionaryFacilitator.isUserDictionaryEnabled()) {
&& suggest.mDictionaryFacilitator.isUserDictionaryEnabled()) {
mSuggestionStripViewAccessor.showAddToDictionaryHint(suggestion); mSuggestionStripViewAccessor.showAddToDictionaryHint(suggestion);
} else { } else {
// If we're not showing the "Touch again to save", then update the suggestion strip. // If we're not showing the "Touch again to save", then update the suggestion strip.
@ -1231,20 +1217,17 @@ public final class InputLogic {
if (!settingsValues.mCorrectionEnabled) return; if (!settingsValues.mCorrectionEnabled) return;
if (TextUtils.isEmpty(suggestion)) return; if (TextUtils.isEmpty(suggestion)) return;
final Suggest suggest = mSuggest;
if (suggest == null) return;
final boolean wasAutoCapitalized = final boolean wasAutoCapitalized =
mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps(); mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps();
final int timeStampInSeconds = (int)TimeUnit.MILLISECONDS.toSeconds( final int timeStampInSeconds = (int)TimeUnit.MILLISECONDS.toSeconds(
System.currentTimeMillis()); System.currentTimeMillis());
suggest.mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized, prevWord, mSuggest.mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized, prevWord,
timeStampInSeconds); timeStampInSeconds);
} }
public void performUpdateSuggestionStripSync(final SettingsValues settingsValues) { public void performUpdateSuggestionStripSync(final SettingsValues settingsValues) {
// Check if we have a suggestion engine attached. // Check if we have a suggestion engine attached.
if (mSuggest == null || !settingsValues.isSuggestionsRequested()) { if (!settingsValues.isSuggestionsRequested()) {
if (mWordComposer.isComposingWord()) { if (mWordComposer.isComposingWord()) {
Log.w(TAG, "Called updateSuggestionsOrPredictions but suggestions were not " Log.w(TAG, "Called updateSuggestionsOrPredictions but suggestions were not "
+ "requested!"); + "requested!");
@ -1446,10 +1429,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) { mSuggest.mDictionaryFacilitator.cancelAddingUserHistory(
mSuggest.mDictionaryFacilitator.cancelAddingUserHistory( previousWord, committedWordString);
previousWord, committedWordString);
}
} }
final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString; final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString;
final SpannableString textToCommit = new SpannableString(stringToCommit); final SpannableString textToCommit = new SpannableString(stringToCommit);

View File

@ -1,49 +0,0 @@
/*
* Copyright (C) 2012 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.utils;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import java.util.Collection;
import java.util.Comparator;
import java.util.TreeSet;
/**
* A TreeSet that is bounded in size and throws everything that's smaller than its limit
*/
public final class BoundedTreeSet extends TreeSet<SuggestedWordInfo> {
private final int mCapacity;
public BoundedTreeSet(final Comparator<SuggestedWordInfo> comparator, final int capacity) {
super(comparator);
mCapacity = capacity;
}
@Override
public boolean add(final SuggestedWordInfo e) {
if (size() < mCapacity) return super.add(e);
if (comparator().compare(e, last()) > 0) return false;
super.add(e);
pollLast(); // removes the last element
return true;
}
@Override
public boolean addAll(final Collection<? extends SuggestedWordInfo> e) {
if (null == e) return false;
return super.addAll(e);
}
}

View File

@ -119,7 +119,7 @@ public final class LanguageModelParam {
private static LanguageModelParam detectWhetherVaildWordOrNotAndGetLanguageModelParam( private static LanguageModelParam detectWhetherVaildWordOrNotAndGetLanguageModelParam(
final String prevWord, final String targetWord, final int timestamp, final String prevWord, final String targetWord, final int timestamp,
final DictionaryFacilitatorForSuggest dictionaryFacilitator) { final DictionaryFacilitatorForSuggest dictionaryFacilitator) {
final Locale locale = dictionaryFacilitator.mLocale; final Locale locale = dictionaryFacilitator.getLocale();
if (!dictionaryFacilitator.isValidWord(targetWord, true /* ignoreCase */)) { if (!dictionaryFacilitator.isValidWord(targetWord, true /* ignoreCase */)) {
// OOV word. // OOV word.
return createAndGetLanguageModelParamOfWord(prevWord, targetWord, timestamp, return createAndGetLanguageModelParamOfWord(prevWord, targetWord, timestamp,

View File

@ -0,0 +1,76 @@
/*
* Copyright (C) 2014 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.utils;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import java.util.Collection;
import java.util.Comparator;
import java.util.Locale;
import java.util.TreeSet;
/**
* A TreeSet of SuggestedWordInfo that is bounded in size and throws everything that's smaller
* than its limit
*/
public final class SuggestionResults extends TreeSet<SuggestedWordInfo> {
public final Locale mLocale;
private final int mCapacity;
public SuggestionResults(final Locale locale, final int capacity) {
this(locale, sSuggestedWordInfoComparator, capacity);
}
public SuggestionResults(final Locale locale, final Comparator<SuggestedWordInfo> comparator,
final int capacity) {
super(comparator);
mLocale = locale;
mCapacity = capacity;
}
@Override
public boolean add(final SuggestedWordInfo e) {
if (size() < mCapacity) return super.add(e);
if (comparator().compare(e, last()) > 0) return false;
super.add(e);
pollLast(); // removes the last element
return true;
}
@Override
public boolean addAll(final Collection<? extends SuggestedWordInfo> e) {
if (null == e) return false;
return super.addAll(e);
}
private static final class SuggestedWordInfoComparator
implements Comparator<SuggestedWordInfo> {
// This comparator ranks the word info with the higher frequency first. That's because
// that's the order we want our elements in.
@Override
public int compare(final SuggestedWordInfo o1, final SuggestedWordInfo o2) {
if (o1.mScore > o2.mScore) return -1;
if (o1.mScore < o2.mScore) return 1;
if (o1.mCodePointCount < o2.mCodePointCount) return -1;
if (o1.mCodePointCount > o2.mCodePointCount) return 1;
return o1.mWord.compareTo(o2.mWord);
}
}
private static final SuggestedWordInfoComparator sSuggestedWordInfoComparator =
new SuggestedWordInfoComparator();
}

View File

@ -155,8 +155,9 @@ public abstract class MainLogBuffer extends FixedLogBuffer {
} }
// Reload the dictionary in case it has changed (e.g., because the user has changed // Reload the dictionary in case it has changed (e.g., because the user has changed
// languages). // languages).
if ((mDictionaryFacilitator == null || !mDictionaryFacilitator.hasMainDictionary()) if ((mDictionaryFacilitator == null
&& mDictionaryForTesting == null) { || !mDictionaryFacilitator.hasInitializedMainDictionary())
&& mDictionaryForTesting == null) {
// Main dictionary is unavailable. Since we cannot check it, we cannot tell if a // Main dictionary is unavailable. Since we cannot check it, we cannot tell if a
// word is out-of-vocabulary or not. Therefore, we must judge the entire buffer // word is out-of-vocabulary or not. Therefore, we must judge the entire buffer
// contents to potentially pose a privacy risk. // contents to potentially pose a privacy risk.