[ML7] Have multiple DictionaryGroup instances in facilitator

This is the central change of multilingual input.

Bug: 11230254
Change-Id: Id8b68fb101e837e8cf182ab4bc1e55e4da5cc49d
main
Jean Chalard 2014-09-11 18:51:31 +09:00
parent 97b465044f
commit 8cd5326622
7 changed files with 187 additions and 111 deletions

View File

@ -61,9 +61,9 @@ public class DictionaryFacilitator {
// dictionary.
private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140;
private DictionaryGroup mDictionaryGroup = new DictionaryGroup();
private DictionaryGroup[] mDictionaryGroups = new DictionaryGroup[] { new DictionaryGroup() };
private boolean mIsUserDictEnabled = false;
private volatile CountDownLatch mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0);
private volatile CountDownLatch mLatchForWaitingLoadingMainDictionaries = new CountDownLatch(0);
// To synchronize assigning mDictionaryGroup to ensure closing dictionaries.
private final Object mLock = new Object();
private final DistracterFilter mDistracterFilter;
@ -193,8 +193,9 @@ public class DictionaryFacilitator {
mPersonalizationHelper.setIsMonolingualUser(isMonolingualUser);
}
// TODO: remove this, replace with version returning multiple locales
public Locale getLocale() {
return mDictionaryGroup.mLocale;
return mDictionaryGroups[0].mLocale;
}
private static ExpandableBinaryDictionary getSubDict(final String dictType,
@ -226,6 +227,21 @@ public class DictionaryFacilitator {
usePersonalizedDicts, forceReloadMainDictionary, listener, "" /* dictNamePrefix */);
}
private DictionaryGroup findDictionaryGroupWithLocale(final DictionaryGroup[] dictionaryGroups,
final Locale locale) {
for (int i = 0; i < dictionaryGroups.length; ++i) {
if (locale.equals(dictionaryGroups[i].mLocale)) {
return dictionaryGroups[i];
}
}
return null;
}
private DictionaryGroup getDictionaryGroupForActiveLanguage() {
// TODO: implement this
return mDictionaryGroups[0];
}
public void resetDictionariesWithDictNamePrefix(final Context context,
final Locale newLocaleToUse,
final boolean useContactsDict, final boolean usePersonalizedDicts,
@ -252,7 +268,7 @@ public class DictionaryFacilitator {
final ArrayList<String> dictsForLocale = new ArrayList<>();
existingDictsToCleanup.put(newLocale, dictsForLocale);
final DictionaryGroup currentDictionaryGroupForLocale =
newLocale.equals(mDictionaryGroup.mLocale) ? mDictionaryGroup : null;
findDictionaryGroupWithLocale(mDictionaryGroups, newLocale);
if (null == currentDictionaryGroupForLocale) {
continue;
}
@ -266,10 +282,11 @@ public class DictionaryFacilitator {
}
}
final HashMap<Locale, DictionaryGroup> newDictionaryGroups = new HashMap<>();
for (final Locale newLocale : newLocales) {
final DictionaryGroup[] newDictionaryGroups = new DictionaryGroup[newLocales.length];
for (int i = 0; i < newLocales.length; ++i) {
final Locale newLocale = newLocales[i];
final DictionaryGroup dictionaryGroupForLocale =
newLocale.equals(mDictionaryGroup.mLocale) ? mDictionaryGroup : null;
findDictionaryGroupWithLocale(mDictionaryGroups, newLocale);
final ArrayList<String> dictsToCleanupForLocale = existingDictsToCleanup.get(newLocale);
final boolean noExistingDictsForThisLocale = (null == dictionaryGroupForLocale);
@ -297,30 +314,29 @@ public class DictionaryFacilitator {
}
subDicts.put(subDictType, subDict);
}
newDictionaryGroups.put(newLocale, new DictionaryGroup(newLocale, mainDict, subDicts));
newDictionaryGroups[i] = new DictionaryGroup(newLocale, mainDict, subDicts);
}
// Replace Dictionaries.
// TODO: use multiple locales.
final DictionaryGroup newDictionaryGroup = newDictionaryGroups.get(newLocaleToUse);
final DictionaryGroup oldDictionaryGroup;
final DictionaryGroup[] oldDictionaryGroups;
synchronized (mLock) {
oldDictionaryGroup = mDictionaryGroup;
mDictionaryGroup = newDictionaryGroup;
oldDictionaryGroups = mDictionaryGroups;
mDictionaryGroups = newDictionaryGroups;
mIsUserDictEnabled = UserBinaryDictionary.isEnabled(context);
if (null == newDictionaryGroup.getDict(Dictionary.TYPE_MAIN)) {
if (hasAtLeastOneUninitializedMainDictionary()) {
asyncReloadUninitializedMainDictionaries(context, newLocales, listener);
}
}
if (listener != null) {
listener.onUpdateMainDictionaryAvailability(hasInitializedMainDictionary());
listener.onUpdateMainDictionaryAvailability(hasAtLeastOneInitializedMainDictionary());
}
// Clean up old dictionaries.
for (final Locale localeToCleanUp : existingDictsToCleanup.keySet()) {
final ArrayList<String> dictTypesToCleanUp =
existingDictsToCleanup.get(localeToCleanUp);
final DictionaryGroup dictionarySetToCleanup = oldDictionaryGroup;
final DictionaryGroup dictionarySetToCleanup =
findDictionaryGroupWithLocale(oldDictionaryGroups, localeToCleanUp);
for (final String dictType : dictTypesToCleanUp) {
dictionarySetToCleanup.closeDict(dictType);
}
@ -330,12 +346,18 @@ public class DictionaryFacilitator {
private void asyncReloadUninitializedMainDictionaries(final Context context,
final Locale[] locales, final DictionaryInitializationListener listener) {
final CountDownLatch latchForWaitingLoadingMainDictionary = new CountDownLatch(1);
mLatchForWaitingLoadingMainDictionary = latchForWaitingLoadingMainDictionary;
mLatchForWaitingLoadingMainDictionaries = latchForWaitingLoadingMainDictionary;
ExecutorUtils.getExecutor("InitializeBinaryDictionary").execute(new Runnable() {
@Override
public void run() {
for (final Locale locale : locales) {
final DictionaryGroup dictionaryGroup = mDictionaryGroup;
final DictionaryGroup dictionaryGroup =
findDictionaryGroupWithLocale(mDictionaryGroups, locale);
if (null == dictionaryGroup) {
// This should never happen, but better safe than crashy
Log.w(TAG, "Expected a dictionary group for " + locale + " but none found");
continue;
}
final Dictionary mainDict =
DictionaryFactory.createMainDictionaryFromManager(context, locale);
synchronized (mLock) {
@ -348,7 +370,8 @@ public class DictionaryFacilitator {
}
}
if (listener != null) {
listener.onUpdateMainDictionaryAvailability(hasInitializedMainDictionary());
listener.onUpdateMainDictionaryAvailability(
hasAtLeastOneInitializedMainDictionary());
}
latchForWaitingLoadingMainDictionary.countDown();
}
@ -381,18 +404,21 @@ public class DictionaryFacilitator {
subDicts.put(dictType, dict);
}
}
mDictionaryGroup = new DictionaryGroup(locale, mainDictionary, subDicts);
mDictionaryGroups = new DictionaryGroup[] {
new DictionaryGroup(locale, mainDictionary, subDicts) };
}
public void closeDictionaries() {
final DictionaryGroup dictionaryGroup;
final DictionaryGroup[] dictionaryGroups;
synchronized (mLock) {
dictionaryGroup = mDictionaryGroup;
mDictionaryGroup = new DictionaryGroup();
dictionaryGroups = mDictionaryGroups;
mDictionaryGroups = new DictionaryGroup[] { new DictionaryGroup() };
}
for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
dictionaryGroup.closeDict(dictType);
}
}
mDistracterFilter.close();
if (mPersonalizationHelper != null) {
mPersonalizationHelper.close();
@ -401,42 +427,73 @@ public class DictionaryFacilitator {
@UsedForTesting
public ExpandableBinaryDictionary getSubDictForTesting(final String dictName) {
return mDictionaryGroup.getSubDict(dictName);
return mDictionaryGroups[0].getSubDict(dictName);
}
// The main dictionary could have been loaded asynchronously. Don't cache the return value
// of this method.
public boolean hasInitializedMainDictionary() {
final Dictionary mainDict = mDictionaryGroup.getDict(Dictionary.TYPE_MAIN);
return mainDict != null && mainDict.isInitialized();
// The main dictionaries are loaded asynchronously. Don't cache the return value
// of these methods.
public boolean hasAtLeastOneInitializedMainDictionary() {
final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
final Dictionary mainDict = dictionaryGroup.getDict(Dictionary.TYPE_MAIN);
if (mainDict != null && mainDict.isInitialized()) {
return true;
}
}
return false;
}
public boolean hasAtLeastOneUninitializedMainDictionary() {
final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
final Dictionary mainDict = dictionaryGroup.getDict(Dictionary.TYPE_MAIN);
if (mainDict == null || !mainDict.isInitialized()) {
return true;
}
}
return false;
}
public boolean hasPersonalizationDictionary() {
return mDictionaryGroup.hasDict(Dictionary.TYPE_PERSONALIZATION);
final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
if (dictionaryGroup.hasDict(Dictionary.TYPE_PERSONALIZATION)) {
return true;
}
}
return false;
}
public void flushPersonalizationDictionary() {
final HashSet<ExpandableBinaryDictionary> personalizationDictsUsedForSuggestion =
new HashSet<>();
final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
final ExpandableBinaryDictionary personalizationDictUsedForSuggestion =
mDictionaryGroup.getSubDict(Dictionary.TYPE_PERSONALIZATION);
dictionaryGroup.getSubDict(Dictionary.TYPE_PERSONALIZATION);
personalizationDictsUsedForSuggestion.add(personalizationDictUsedForSuggestion);
}
mPersonalizationHelper.flushPersonalizationDictionariesToUpdate(
personalizationDictUsedForSuggestion);
personalizationDictsUsedForSuggestion);
mDistracterFilter.close();
}
public void waitForLoadingMainDictionary(final long timeout, final TimeUnit unit)
public void waitForLoadingMainDictionaries(final long timeout, final TimeUnit unit)
throws InterruptedException {
mLatchForWaitingLoadingMainDictionary.await(timeout, unit);
mLatchForWaitingLoadingMainDictionaries.await(timeout, unit);
}
@UsedForTesting
public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit)
throws InterruptedException {
waitForLoadingMainDictionary(timeout, unit);
final Map<String, ExpandableBinaryDictionary> dictMap = mDictionaryGroup.mSubDictMap;
for (final ExpandableBinaryDictionary dict : dictMap.values()) {
waitForLoadingMainDictionaries(timeout, unit);
final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
for (final ExpandableBinaryDictionary dict : dictionaryGroup.mSubDictMap.values()) {
dict.waitAllTasksForTests();
}
}
}
public boolean isUserDictionaryEnabled() {
return mIsUserDictEnabled;
@ -453,7 +510,7 @@ public class DictionaryFacilitator {
public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
final PrevWordsInfo prevWordsInfo, final int timeStampInSeconds,
final boolean blockPotentiallyOffensive) {
final DictionaryGroup dictionaryGroup = mDictionaryGroup;
final DictionaryGroup dictionaryGroup = getDictionaryGroupForActiveLanguage();
final String[] words = suggestion.split(Constants.WORD_SEPARATOR);
PrevWordsInfo prevWordsInfoForCurrentWord = prevWordsInfo;
for (int i = 0; i < words.length; i++) {
@ -520,7 +577,8 @@ public class DictionaryFacilitator {
}
private void removeWord(final String dictName, final String word) {
final ExpandableBinaryDictionary dictionary = mDictionaryGroup.getSubDict(dictName);
final ExpandableBinaryDictionary dictionary =
getDictionaryGroupForActiveLanguage().getSubDict(dictName);
if (dictionary != null) {
dictionary.removeUnigramEntryDynamically(word);
}
@ -536,10 +594,11 @@ public class DictionaryFacilitator {
public SuggestionResults getSuggestionResults(final WordComposer composer,
final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId) {
final DictionaryGroup dictionaryGroup = mDictionaryGroup;
final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
final SuggestionResults suggestionResults =
new SuggestionResults(SuggestedWords.MAX_SUGGESTIONS);
final float[] languageWeight = new float[] { Dictionary.NOT_A_LANGUAGE_WEIGHT };
for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
final Dictionary dictionary = dictionaryGroup.getDict(dictType);
if (null == dictionary) continue;
@ -552,6 +611,7 @@ public class DictionaryFacilitator {
suggestionResults.mRawSuggestions.addAll(dictionarySuggestions);
}
}
}
return suggestionResults;
}
@ -559,9 +619,10 @@ public class DictionaryFacilitator {
if (TextUtils.isEmpty(word)) {
return false;
}
final DictionaryGroup dictionaryGroup = mDictionaryGroup;
final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
if (dictionaryGroup.mLocale == null) {
return false;
continue;
}
final String lowerCasedWord = word.toLowerCase(dictionaryGroup.mLocale);
for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
@ -575,6 +636,7 @@ public class DictionaryFacilitator {
return true;
}
}
}
return false;
}
@ -584,7 +646,8 @@ public class DictionaryFacilitator {
return Dictionary.NOT_A_PROBABILITY;
}
int maxFreq = Dictionary.NOT_A_PROBABILITY;
final DictionaryGroup dictionaryGroup = mDictionaryGroup;
final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
final Dictionary dictionary = dictionaryGroup.getDict(dictType);
if (dictionary == null) continue;
@ -598,6 +661,7 @@ public class DictionaryFacilitator {
maxFreq = tempFreq;
}
}
}
return maxFreq;
}
@ -610,11 +674,14 @@ public class DictionaryFacilitator {
}
private void clearSubDictionary(final String dictName) {
final ExpandableBinaryDictionary dictionary = mDictionaryGroup.getSubDict(dictName);
final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
final ExpandableBinaryDictionary dictionary = dictionaryGroup.getSubDict(dictName);
if (dictionary != null) {
dictionary.clear();
}
}
}
public void clearUserHistoryDictionary() {
clearSubDictionary(Dictionary.TYPE_USER_HISTORY);
@ -641,8 +708,10 @@ public class DictionaryFacilitator {
public void addPhraseToContextualDictionary(final String[] phrase, final int probability,
final int bigramProbabilityForWords, final int bigramProbabilityForPhrases) {
// TODO: we're inserting the phrase into the dictionary for the active language. Rethink
// this a bit from a theoretical point of view.
final ExpandableBinaryDictionary contextualDict =
mDictionaryGroup.getSubDict(Dictionary.TYPE_CONTEXTUAL);
getDictionaryGroupForActiveLanguage().getSubDict(Dictionary.TYPE_CONTEXTUAL);
if (contextualDict == null) {
return;
}
@ -675,7 +744,9 @@ public class DictionaryFacilitator {
}
public void dumpDictionaryForDebug(final String dictName) {
final ExpandableBinaryDictionary dictToDump = mDictionaryGroup.getSubDict(dictName);
final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
final ExpandableBinaryDictionary dictToDump = dictionaryGroup.getSubDict(dictName);
if (dictToDump == null) {
Log.e(TAG, "Cannot dump " + dictName + ". "
+ "The dictionary is not being used for suggestion or cannot be dumped.");
@ -683,15 +754,18 @@ public class DictionaryFacilitator {
}
dictToDump.dumpAllWordsForDebug();
}
}
public ArrayList<Pair<String, DictionaryStats>> getStatsOfEnabledSubDicts() {
final ArrayList<Pair<String, DictionaryStats>> statsOfEnabledSubDicts = new ArrayList<>();
final DictionaryGroup dictionaryGroup = mDictionaryGroup;
final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
for (final String dictType : SUB_DICT_TYPES) {
final ExpandableBinaryDictionary dictionary = dictionaryGroup.getSubDict(dictType);
if (dictionary == null) continue;
statsOfEnabledSubDicts.add(new Pair<>(dictType, dictionary.getDictionaryStats()));
}
}
return statsOfEnabledSubDicts;
}
}

View File

@ -84,7 +84,7 @@ public class DictionaryFacilitatorLruCache {
private void waitForLoadingMainDictionary(final DictionaryFacilitator dictionaryFacilitator) {
for (int i = 0; i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT; i++) {
try {
dictionaryFacilitator.waitForLoadingMainDictionary(
dictionaryFacilitator.waitForLoadingMainDictionaries(
WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
return;
} catch (final InterruptedException e) {

View File

@ -156,23 +156,25 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
private void asyncExecuteTaskWithWriteLock(final Runnable task) {
asyncExecuteTaskWithLock(mLock.writeLock(), task);
asyncExecuteTaskWithLock(mLock.writeLock(), mDictName /* executorName */, task);
}
private void asyncExecuteTaskWithLock(final Lock lock, final Runnable task) {
asyncPreCheckAndExecuteTaskWithLock(lock, null /* preCheckTask */, task);
private void asyncExecuteTaskWithLock(final Lock lock, final String executorName,
final Runnable task) {
asyncPreCheckAndExecuteTaskWithLock(lock, null /* preCheckTask */, executorName, task);
}
private void asyncPreCheckAndExecuteTaskWithWriteLock(
final Callable<Boolean> preCheckTask, final Runnable task) {
asyncPreCheckAndExecuteTaskWithLock(mLock.writeLock(), preCheckTask, task);
asyncPreCheckAndExecuteTaskWithLock(mLock.writeLock(), preCheckTask,
mDictName /* executorName */, task);
}
// Execute task with lock when the result of preCheckTask is true or preCheckTask is null.
private void asyncPreCheckAndExecuteTaskWithLock(final Lock lock,
final Callable<Boolean> preCheckTask, final Runnable task) {
ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
final Callable<Boolean> preCheckTask, final String executorName, final Runnable task) {
ExecutorUtils.getExecutor(executorName).execute(new Runnable() {
@Override
public void run() {
if (preCheckTask != null) {
@ -676,10 +678,10 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
public void dumpAllWordsForDebug() {
reloadDictionaryIfRequired();
asyncExecuteTaskWithLock(mLock.readLock(), new Runnable() {
asyncExecuteTaskWithLock(mLock.readLock(), "dumpAllWordsForDebug", new Runnable() {
@Override
public void run() {
Log.d(TAG, "Dump dictionary: " + mDictName);
Log.d(TAG, "Dump dictionary: " + mDictName + " for " + mLocale);
try {
final DictionaryHeader header = mBinaryDictionary.getHeader();
Log.d(TAG, "Format version: " + mBinaryDictionary.getFormatVersion());

View File

@ -998,7 +998,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.cancelUpdateSuggestionStrip();
mainKeyboardView.setMainDictionaryAvailability(
mDictionaryFacilitator.hasInitializedMainDictionary());
mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary());
mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn,
currentSettingsValues.mKeyPreviewPopupDismissDelay);
mainKeyboardView.setSlidingKeyInputPreviewEnabled(

View File

@ -88,17 +88,17 @@ public class PersonalizationHelperForDictionaryFacilitator {
/**
* Flush personalization dictionaries to dictionary files. Close dictionaries after writing
* files except the dictionary that is used for generating suggestions.
* files except the dictionaries that is used for generating suggestions.
*
* @param personalizationDictUsedForSuggestion the personalization dictionary used for
* @param personalizationDictsUsedForSuggestion the personalization dictionaries used for
* generating suggestions that won't be closed.
*/
public void flushPersonalizationDictionariesToUpdate(
final ExpandableBinaryDictionary personalizationDictUsedForSuggestion) {
final HashSet<ExpandableBinaryDictionary> personalizationDictsUsedForSuggestion) {
for (final ExpandableBinaryDictionary personalizationDict :
mPersonalizationDictsToUpdate.values()) {
personalizationDict.asyncFlushBinaryDictionary();
if (personalizationDict != personalizationDictUsedForSuggestion) {
if (!personalizationDictsUsedForSuggestion.contains(personalizationDict)) {
// Close if the dictionary is not being used for suggestion.
personalizationDict.close();
}

View File

@ -157,7 +157,7 @@ public final class Suggest {
if (!isCorrectionEnabled || !allowsToBeAutoCorrected || resultsArePredictions
|| suggestionResults.isEmpty() || wordComposer.hasDigits()
|| wordComposer.isMostlyCaps() || wordComposer.isResumed()
|| !mDictionaryFacilitator.hasInitializedMainDictionary()
|| !mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary()
|| suggestionResults.first().isKindOf(SuggestedWordInfo.KIND_SHORTCUT)) {
// 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

View File

@ -185,7 +185,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
try {
final DictionaryFacilitator dictionaryFacilitator =
mDictionaryFacilitatorCache.get(locale);
return dictionaryFacilitator.hasInitializedMainDictionary();
return dictionaryFacilitator.hasAtLeastOneInitializedMainDictionary();
} finally {
mSemaphore.release();
}