DictionaryFacilitator.java revision b8a9479b57007edb5cb12c628797f89a8164f596
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.inputmethod.latin;
18
19import android.content.Context;
20import android.text.TextUtils;
21import android.util.Log;
22import android.view.inputmethod.InputMethodSubtype;
23
24import com.android.inputmethod.annotations.UsedForTesting;
25import com.android.inputmethod.keyboard.ProximityInfo;
26import com.android.inputmethod.latin.PrevWordsInfo.WordInfo;
27import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
28import com.android.inputmethod.latin.personalization.ContextualDictionary;
29import com.android.inputmethod.latin.personalization.PersonalizationDataChunk;
30import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
31import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
32import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
33import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
34import com.android.inputmethod.latin.utils.DistracterFilter;
35import com.android.inputmethod.latin.utils.DistracterFilterCheckingIsInDictionary;
36import com.android.inputmethod.latin.utils.ExecutorUtils;
37import com.android.inputmethod.latin.utils.LanguageModelParam;
38import com.android.inputmethod.latin.utils.SuggestionResults;
39
40import java.io.File;
41import java.lang.reflect.InvocationTargetException;
42import java.lang.reflect.Method;
43import java.util.ArrayList;
44import java.util.Arrays;
45import java.util.HashMap;
46import java.util.HashSet;
47import java.util.List;
48import java.util.Locale;
49import java.util.Map;
50import java.util.concurrent.ConcurrentHashMap;
51import java.util.concurrent.CountDownLatch;
52import java.util.concurrent.TimeUnit;
53
54// TODO: Consolidate dictionaries in native code.
55public class DictionaryFacilitator {
56    public static final String TAG = DictionaryFacilitator.class.getSimpleName();
57
58    // HACK: This threshold is being used when adding a capitalized entry in the User History
59    // dictionary.
60    private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140;
61
62    private Dictionaries mDictionaries = new Dictionaries();
63    private boolean mIsUserDictEnabled = false;
64    private volatile CountDownLatch mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0);
65    // To synchronize assigning mDictionaries to ensure closing dictionaries.
66    private final Object mLock = new Object();
67    private final DistracterFilter mDistracterFilter;
68
69    private static final String[] DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS =
70            new String[] {
71                Dictionary.TYPE_MAIN,
72                Dictionary.TYPE_USER_HISTORY,
73                Dictionary.TYPE_PERSONALIZATION,
74                Dictionary.TYPE_USER,
75                Dictionary.TYPE_CONTACTS,
76                Dictionary.TYPE_CONTEXTUAL
77            };
78
79    public static final Map<String, Class<? extends ExpandableBinaryDictionary>>
80            DICT_TYPE_TO_CLASS = new HashMap<>();
81
82    static {
83        DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER_HISTORY, UserHistoryDictionary.class);
84        DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_PERSONALIZATION, PersonalizationDictionary.class);
85        DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER, UserBinaryDictionary.class);
86        DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTACTS, ContactsBinaryDictionary.class);
87        DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTEXTUAL, ContextualDictionary.class);
88    }
89
90    private static final String DICT_FACTORY_METHOD_NAME = "getDictionary";
91    private static final Class<?>[] DICT_FACTORY_METHOD_ARG_TYPES =
92            new Class[] { Context.class, Locale.class, File.class, String.class };
93
94    private static final String[] SUB_DICT_TYPES =
95            Arrays.copyOfRange(DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS, 1 /* start */,
96                    DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS.length);
97
98    /**
99     * Class contains dictionaries for a locale.
100     */
101    private static class Dictionaries {
102        public final Locale mLocale;
103        private Dictionary mMainDict;
104        public final ConcurrentHashMap<String, ExpandableBinaryDictionary> mSubDictMap =
105                new ConcurrentHashMap<>();
106
107        public Dictionaries() {
108            mLocale = null;
109        }
110
111        public Dictionaries(final Locale locale, final Dictionary mainDict,
112                final Map<String, ExpandableBinaryDictionary> subDicts) {
113            mLocale = locale;
114            // Main dictionary can be asynchronously loaded.
115            setMainDict(mainDict);
116            for (final Map.Entry<String, ExpandableBinaryDictionary> entry : subDicts.entrySet()) {
117                setSubDict(entry.getKey(), entry.getValue());
118            }
119        }
120
121        private void setSubDict(final String dictType, final ExpandableBinaryDictionary dict) {
122            if (dict != null) {
123                mSubDictMap.put(dictType, dict);
124            }
125        }
126
127        public void setMainDict(final Dictionary mainDict) {
128            // Close old dictionary if exists. Main dictionary can be assigned multiple times.
129            final Dictionary oldDict = mMainDict;
130            mMainDict = mainDict;
131            if (oldDict != null && mainDict != oldDict) {
132                oldDict.close();
133            }
134        }
135
136        public Dictionary getDict(final String dictType) {
137            if (Dictionary.TYPE_MAIN.equals(dictType)) {
138                return mMainDict;
139            } else {
140                return getSubDict(dictType);
141            }
142        }
143
144        public ExpandableBinaryDictionary getSubDict(final String dictType) {
145            return mSubDictMap.get(dictType);
146        }
147
148        public boolean hasDict(final String dictType) {
149            if (Dictionary.TYPE_MAIN.equals(dictType)) {
150                return mMainDict != null;
151            } else {
152                return mSubDictMap.containsKey(dictType);
153            }
154        }
155
156        public void closeDict(final String dictType) {
157            final Dictionary dict;
158            if (Dictionary.TYPE_MAIN.equals(dictType)) {
159                dict = mMainDict;
160            } else {
161                dict = mSubDictMap.remove(dictType);
162            }
163            if (dict != null) {
164                dict.close();
165            }
166        }
167    }
168
169    public interface DictionaryInitializationListener {
170        public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable);
171    }
172
173    public DictionaryFacilitator() {
174        mDistracterFilter = DistracterFilter.EMPTY_DISTRACTER_FILTER;
175    }
176
177    public DictionaryFacilitator(final DistracterFilter distracterFilter) {
178        mDistracterFilter = distracterFilter;
179    }
180
181    public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) {
182        mDistracterFilter.updateEnabledSubtypes(enabledSubtypes);
183    }
184
185    public Locale getLocale() {
186        return mDictionaries.mLocale;
187    }
188
189    private static ExpandableBinaryDictionary getSubDict(final String dictType,
190            final Context context, final Locale locale, final File dictFile,
191            final String dictNamePrefix) {
192        final Class<? extends ExpandableBinaryDictionary> dictClass =
193                DICT_TYPE_TO_CLASS.get(dictType);
194        if (dictClass == null) {
195            return null;
196        }
197        try {
198            final Method factoryMethod = dictClass.getMethod(DICT_FACTORY_METHOD_NAME,
199                    DICT_FACTORY_METHOD_ARG_TYPES);
200            final Object dict = factoryMethod.invoke(null /* obj */,
201                    new Object[] { context, locale, dictFile, dictNamePrefix });
202            return (ExpandableBinaryDictionary) dict;
203        } catch (final NoSuchMethodException | SecurityException | IllegalAccessException
204                | IllegalArgumentException | InvocationTargetException e) {
205            Log.e(TAG, "Cannot create dictionary: " + dictType, e);
206            return null;
207        }
208    }
209
210    public void resetDictionaries(final Context context, final Locale newLocale,
211            final boolean useContactsDict, final boolean usePersonalizedDicts,
212            final boolean forceReloadMainDictionary,
213            final DictionaryInitializationListener listener) {
214        resetDictionariesWithDictNamePrefix(context, newLocale, useContactsDict,
215                usePersonalizedDicts, forceReloadMainDictionary, listener, "" /* dictNamePrefix */);
216    }
217
218    public void resetDictionariesWithDictNamePrefix(final Context context, final Locale newLocale,
219            final boolean useContactsDict, final boolean usePersonalizedDicts,
220            final boolean forceReloadMainDictionary,
221            final DictionaryInitializationListener listener,
222            final String dictNamePrefix) {
223        final boolean localeHasBeenChanged = !newLocale.equals(mDictionaries.mLocale);
224        // We always try to have the main dictionary. Other dictionaries can be unused.
225        final boolean reloadMainDictionary = localeHasBeenChanged || forceReloadMainDictionary;
226        // TODO: Make subDictTypesToUse configurable by resource or a static final list.
227        final HashSet<String> subDictTypesToUse = new HashSet<>();
228        if (useContactsDict) {
229            subDictTypesToUse.add(Dictionary.TYPE_CONTACTS);
230        }
231        subDictTypesToUse.add(Dictionary.TYPE_USER);
232        if (usePersonalizedDicts) {
233            subDictTypesToUse.add(Dictionary.TYPE_USER_HISTORY);
234            subDictTypesToUse.add(Dictionary.TYPE_PERSONALIZATION);
235            subDictTypesToUse.add(Dictionary.TYPE_CONTEXTUAL);
236        }
237
238        final Dictionary newMainDict;
239        if (reloadMainDictionary) {
240            // The main dictionary will be asynchronously loaded.
241            newMainDict = null;
242        } else {
243            newMainDict = mDictionaries.getDict(Dictionary.TYPE_MAIN);
244        }
245
246        final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
247        for (final String dictType : SUB_DICT_TYPES) {
248            if (!subDictTypesToUse.contains(dictType)) {
249                // This dictionary will not be used.
250                continue;
251            }
252            final ExpandableBinaryDictionary dict;
253            if (!localeHasBeenChanged && mDictionaries.hasDict(dictType)) {
254                // Continue to use current dictionary.
255                dict = mDictionaries.getSubDict(dictType);
256            } else {
257                // Start to use new dictionary.
258                dict = getSubDict(dictType, context, newLocale, null /* dictFile */,
259                        dictNamePrefix);
260            }
261            subDicts.put(dictType, dict);
262        }
263
264        // Replace Dictionaries.
265        final Dictionaries newDictionaries = new Dictionaries(newLocale, newMainDict, subDicts);
266        final Dictionaries oldDictionaries;
267        synchronized (mLock) {
268            oldDictionaries = mDictionaries;
269            mDictionaries = newDictionaries;
270            mIsUserDictEnabled = UserBinaryDictionary.isEnabled(context);
271            if (reloadMainDictionary) {
272                asyncReloadMainDictionary(context, newLocale, listener);
273            }
274        }
275        if (listener != null) {
276            listener.onUpdateMainDictionaryAvailability(hasInitializedMainDictionary());
277        }
278        // Clean up old dictionaries.
279        if (reloadMainDictionary) {
280            oldDictionaries.closeDict(Dictionary.TYPE_MAIN);
281        }
282        for (final String dictType : SUB_DICT_TYPES) {
283            if (localeHasBeenChanged || !subDictTypesToUse.contains(dictType)) {
284                oldDictionaries.closeDict(dictType);
285            }
286        }
287        oldDictionaries.mSubDictMap.clear();
288    }
289
290    private void asyncReloadMainDictionary(final Context context, final Locale locale,
291            final DictionaryInitializationListener listener) {
292        final CountDownLatch latchForWaitingLoadingMainDictionary = new CountDownLatch(1);
293        mLatchForWaitingLoadingMainDictionary = latchForWaitingLoadingMainDictionary;
294        ExecutorUtils.getExecutor("InitializeBinaryDictionary").execute(new Runnable() {
295            @Override
296            public void run() {
297                final Dictionary mainDict =
298                        DictionaryFactory.createMainDictionaryFromManager(context, locale);
299                synchronized (mLock) {
300                    if (locale.equals(mDictionaries.mLocale)) {
301                        mDictionaries.setMainDict(mainDict);
302                    } else {
303                        // Dictionary facilitator has been reset for another locale.
304                        mainDict.close();
305                    }
306                }
307                if (listener != null) {
308                    listener.onUpdateMainDictionaryAvailability(hasInitializedMainDictionary());
309                }
310                latchForWaitingLoadingMainDictionary.countDown();
311            }
312        });
313    }
314
315    @UsedForTesting
316    public void resetDictionariesForTesting(final Context context, final Locale locale,
317            final ArrayList<String> dictionaryTypes, final HashMap<String, File> dictionaryFiles,
318            final Map<String, Map<String, String>> additionalDictAttributes) {
319        Dictionary mainDictionary = null;
320        final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
321
322        for (final String dictType : dictionaryTypes) {
323            if (dictType.equals(Dictionary.TYPE_MAIN)) {
324                mainDictionary = DictionaryFactory.createMainDictionaryFromManager(context, locale);
325            } else {
326                final File dictFile = dictionaryFiles.get(dictType);
327                final ExpandableBinaryDictionary dict = getSubDict(
328                        dictType, context, locale, dictFile, "" /* dictNamePrefix */);
329                if (additionalDictAttributes.containsKey(dictType)) {
330                    dict.clearAndFlushDictionaryWithAdditionalAttributes(
331                            additionalDictAttributes.get(dictType));
332                }
333                if (dict == null) {
334                    throw new RuntimeException("Unknown dictionary type: " + dictType);
335                }
336                dict.reloadDictionaryIfRequired();
337                dict.waitAllTasksForTests();
338                subDicts.put(dictType, dict);
339            }
340        }
341        mDictionaries = new Dictionaries(locale, mainDictionary, subDicts);
342    }
343
344    public void closeDictionaries() {
345        final Dictionaries dictionaries;
346        synchronized (mLock) {
347            dictionaries = mDictionaries;
348            mDictionaries = new Dictionaries();
349        }
350        for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
351            dictionaries.closeDict(dictType);
352        }
353        mDistracterFilter.close();
354    }
355
356    @UsedForTesting
357    public ExpandableBinaryDictionary getSubDictForTesting(final String dictName) {
358        return mDictionaries.getSubDict(dictName);
359    }
360
361    // The main dictionary could have been loaded asynchronously.  Don't cache the return value
362    // of this method.
363    public boolean hasInitializedMainDictionary() {
364        final Dictionary mainDict = mDictionaries.getDict(Dictionary.TYPE_MAIN);
365        return mainDict != null && mainDict.isInitialized();
366    }
367
368    public boolean hasPersonalizationDictionary() {
369        return mDictionaries.hasDict(Dictionary.TYPE_PERSONALIZATION);
370    }
371
372    public void flushPersonalizationDictionary() {
373        final ExpandableBinaryDictionary personalizationDict =
374                mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION);
375        if (personalizationDict != null) {
376            personalizationDict.asyncFlushBinaryDictionary();
377        }
378    }
379
380    public void waitForLoadingMainDictionary(final long timeout, final TimeUnit unit)
381            throws InterruptedException {
382        mLatchForWaitingLoadingMainDictionary.await(timeout, unit);
383    }
384
385    @UsedForTesting
386    public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit)
387            throws InterruptedException {
388        waitForLoadingMainDictionary(timeout, unit);
389        final Map<String, ExpandableBinaryDictionary> dictMap = mDictionaries.mSubDictMap;
390        for (final ExpandableBinaryDictionary dict : dictMap.values()) {
391            dict.waitAllTasksForTests();
392        }
393    }
394
395    public boolean isUserDictionaryEnabled() {
396        return mIsUserDictEnabled;
397    }
398
399    public void addWordToUserDictionary(final Context context, final String word) {
400        final Locale locale = getLocale();
401        if (locale == null) {
402            return;
403        }
404        UserBinaryDictionary.addWordToUserDictionary(context, locale, word);
405    }
406
407    public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
408            final PrevWordsInfo prevWordsInfo, final int timeStampInSeconds,
409            final boolean blockPotentiallyOffensive) {
410        final Dictionaries dictionaries = mDictionaries;
411        final String[] words = suggestion.split(Constants.WORD_SEPARATOR);
412        PrevWordsInfo prevWordsInfoForCurrentWord = prevWordsInfo;
413        for (int i = 0; i < words.length; i++) {
414            final String currentWord = words[i];
415            final boolean wasCurrentWordAutoCapitalized = (i == 0) ? wasAutoCapitalized : false;
416            addWordToUserHistory(dictionaries, prevWordsInfoForCurrentWord, currentWord,
417                    wasCurrentWordAutoCapitalized, timeStampInSeconds, blockPotentiallyOffensive);
418            prevWordsInfoForCurrentWord =
419                    prevWordsInfoForCurrentWord.getNextPrevWordsInfo(new WordInfo(currentWord));
420        }
421    }
422
423    private void addWordToUserHistory(final Dictionaries dictionaries,
424            final PrevWordsInfo prevWordsInfo, final String word, final boolean wasAutoCapitalized,
425            final int timeStampInSeconds, final boolean blockPotentiallyOffensive) {
426        final ExpandableBinaryDictionary userHistoryDictionary =
427                dictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
428        if (userHistoryDictionary == null) {
429            return;
430        }
431        final int maxFreq = getFrequency(word);
432        if (maxFreq == 0 && blockPotentiallyOffensive) {
433            return;
434        }
435        final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale);
436        final String secondWord;
437        if (wasAutoCapitalized) {
438            if (isValidWord(word, false /* ignoreCase */)
439                    && !isValidWord(lowerCasedWord, false /* ignoreCase */)) {
440                // If the word was auto-capitalized and exists only as a capitalized word in the
441                // dictionary, then we must not downcase it before registering it. For example,
442                // the name of the contacts in start-of-sentence position would come here with the
443                // wasAutoCapitalized flag: if we downcase it, we'd register a lower-case version
444                // of that contact's name which would end up popping in suggestions.
445                secondWord = word;
446            } else {
447                // If however the word is not in the dictionary, or exists as a lower-case word
448                // only, then we consider that was a lower-case word that had been auto-capitalized.
449                secondWord = lowerCasedWord;
450            }
451        } else {
452            // HACK: We'd like to avoid adding the capitalized form of common words to the User
453            // History dictionary in order to avoid suggesting them until the dictionary
454            // consolidation is done.
455            // TODO: Remove this hack when ready.
456            final int lowerCaseFreqInMainDict = dictionaries.hasDict(Dictionary.TYPE_MAIN) ?
457                    dictionaries.getDict(Dictionary.TYPE_MAIN).getFrequency(lowerCasedWord) :
458                            Dictionary.NOT_A_PROBABILITY;
459            if (maxFreq < lowerCaseFreqInMainDict
460                    && lowerCaseFreqInMainDict >= CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT) {
461                // Use lower cased word as the word can be a distracter of the popular word.
462                secondWord = lowerCasedWord;
463            } else {
464                secondWord = word;
465            }
466        }
467        // We demote unrecognized words (frequency < 0, below) by specifying them as "invalid".
468        // We don't add words with 0-frequency (assuming they would be profanity etc.).
469        final boolean isValid = maxFreq > 0;
470        UserHistoryDictionary.addToDictionary(userHistoryDictionary, prevWordsInfo, secondWord,
471                isValid, timeStampInSeconds, mDistracterFilter);
472    }
473
474    private void removeWord(final String dictName, final String word) {
475        final ExpandableBinaryDictionary dictionary = mDictionaries.getSubDict(dictName);
476        if (dictionary != null) {
477            dictionary.removeUnigramEntryDynamically(word);
478        }
479    }
480
481    public void removeWordFromPersonalizedDicts(final String word) {
482        removeWord(Dictionary.TYPE_USER_HISTORY, word);
483        removeWord(Dictionary.TYPE_PERSONALIZATION, word);
484        removeWord(Dictionary.TYPE_CONTEXTUAL, word);
485    }
486
487    // TODO: Revise the way to fusion suggestion results.
488    public SuggestionResults getSuggestionResults(final WordComposer composer,
489            final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
490            final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId) {
491        final Dictionaries dictionaries = mDictionaries;
492        final SuggestionResults suggestionResults =
493                new SuggestionResults(dictionaries.mLocale, SuggestedWords.MAX_SUGGESTIONS);
494        final float[] languageWeight = new float[] { Dictionary.NOT_A_LANGUAGE_WEIGHT };
495        for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
496            final Dictionary dictionary = dictionaries.getDict(dictType);
497            if (null == dictionary) continue;
498            final ArrayList<SuggestedWordInfo> dictionarySuggestions =
499                    dictionary.getSuggestions(composer, prevWordsInfo, proximityInfo,
500                            settingsValuesForSuggestion, sessionId, languageWeight);
501            if (null == dictionarySuggestions) continue;
502            suggestionResults.addAll(dictionarySuggestions);
503            if (null != suggestionResults.mRawSuggestions) {
504                suggestionResults.mRawSuggestions.addAll(dictionarySuggestions);
505            }
506        }
507        return suggestionResults;
508    }
509
510    public boolean isValidWord(final String word, final boolean ignoreCase) {
511        if (TextUtils.isEmpty(word)) {
512            return false;
513        }
514        final Dictionaries dictionaries = mDictionaries;
515        if (dictionaries.mLocale == null) {
516            return false;
517        }
518        final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale);
519        for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
520            final Dictionary dictionary = dictionaries.getDict(dictType);
521            // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
522            // would be immutable once it's finished initializing, but concretely a null test is
523            // probably good enough for the time being.
524            if (null == dictionary) continue;
525            if (dictionary.isValidWord(word)
526                    || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
527                return true;
528            }
529        }
530        return false;
531    }
532
533    private int getFrequencyInternal(final String word,
534            final boolean isGettingMaxFrequencyOfExactMatches) {
535        if (TextUtils.isEmpty(word)) {
536            return Dictionary.NOT_A_PROBABILITY;
537        }
538        int maxFreq = Dictionary.NOT_A_PROBABILITY;
539        final Dictionaries dictionaries = mDictionaries;
540        for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
541            final Dictionary dictionary = dictionaries.getDict(dictType);
542            if (dictionary == null) continue;
543            final int tempFreq;
544            if (isGettingMaxFrequencyOfExactMatches) {
545                tempFreq = dictionary.getMaxFrequencyOfExactMatches(word);
546            } else {
547                tempFreq = dictionary.getFrequency(word);
548            }
549            if (tempFreq >= maxFreq) {
550                maxFreq = tempFreq;
551            }
552        }
553        return maxFreq;
554    }
555
556    public int getFrequency(final String word) {
557        return getFrequencyInternal(word, false /* isGettingMaxFrequencyOfExactMatches */);
558    }
559
560    public int getMaxFrequencyOfExactMatches(final String word) {
561        return getFrequencyInternal(word, true /* isGettingMaxFrequencyOfExactMatches */);
562    }
563
564    private void clearSubDictionary(final String dictName) {
565        final ExpandableBinaryDictionary dictionary = mDictionaries.getSubDict(dictName);
566        if (dictionary != null) {
567            dictionary.clear();
568        }
569    }
570
571    public void clearUserHistoryDictionary() {
572        clearSubDictionary(Dictionary.TYPE_USER_HISTORY);
573    }
574
575    // This method gets called only when the IME receives a notification to remove the
576    // personalization dictionary.
577    public void clearPersonalizationDictionary() {
578        clearSubDictionary(Dictionary.TYPE_PERSONALIZATION);
579    }
580
581    public void clearContextualDictionary() {
582        clearSubDictionary(Dictionary.TYPE_CONTEXTUAL);
583    }
584
585    public void addEntriesToPersonalizationDictionary(
586            final PersonalizationDataChunk personalizationDataChunk,
587            final SpacingAndPunctuations spacingAndPunctuations,
588            final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) {
589        final ExpandableBinaryDictionary personalizationDict =
590                mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION);
591        if (personalizationDict == null) {
592            if (callback != null) {
593                callback.onFinished();
594            }
595            return;
596        }
597        final ArrayList<LanguageModelParam> languageModelParams =
598                LanguageModelParam.createLanguageModelParamsFrom(
599                        personalizationDataChunk.mTokens,
600                        personalizationDataChunk.mTimestampInSeconds,
601                        this /* dictionaryFacilitator */, spacingAndPunctuations,
602                        new DistracterFilterCheckingIsInDictionary(
603                                mDistracterFilter, personalizationDict));
604        if (languageModelParams == null || languageModelParams.isEmpty()) {
605            if (callback != null) {
606                callback.onFinished();
607            }
608            return;
609        }
610        personalizationDict.addMultipleDictionaryEntriesDynamically(languageModelParams, callback);
611    }
612
613    public void addPhraseToContextualDictionary(final String[] phrase, final int probability,
614            final int bigramProbabilityForWords, final int bigramProbabilityForPhrases) {
615        final ExpandableBinaryDictionary contextualDict =
616                mDictionaries.getSubDict(Dictionary.TYPE_CONTEXTUAL);
617        if (contextualDict == null) {
618            return;
619        }
620        PrevWordsInfo prevWordsInfo = PrevWordsInfo.BEGINNING_OF_SENTENCE;
621        for (int i = 0; i < phrase.length; i++) {
622            final String[] subPhrase = Arrays.copyOfRange(phrase, i /* start */, phrase.length);
623            final String subPhraseStr = TextUtils.join(Constants.WORD_SEPARATOR, subPhrase);
624            contextualDict.addUnigramEntryWithCheckingDistracter(
625                    subPhraseStr, probability, null /* shortcutTarget */,
626                    Dictionary.NOT_A_PROBABILITY /* shortcutFreq */,
627                    false /* isNotAWord */, false /* isBlacklisted */,
628                    BinaryDictionary.NOT_A_VALID_TIMESTAMP,
629                    DistracterFilter.EMPTY_DISTRACTER_FILTER);
630            contextualDict.addNgramEntry(prevWordsInfo, subPhraseStr,
631                    bigramProbabilityForPhrases, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
632
633            if (i < phrase.length - 1) {
634                contextualDict.addUnigramEntryWithCheckingDistracter(
635                        phrase[i], probability, null /* shortcutTarget */,
636                        Dictionary.NOT_A_PROBABILITY /* shortcutFreq */,
637                        false /* isNotAWord */, false /* isBlacklisted */,
638                        BinaryDictionary.NOT_A_VALID_TIMESTAMP,
639                        DistracterFilter.EMPTY_DISTRACTER_FILTER);
640                contextualDict.addNgramEntry(prevWordsInfo, phrase[i],
641                        bigramProbabilityForWords, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
642            }
643            prevWordsInfo =
644                    prevWordsInfo.getNextPrevWordsInfo(new PrevWordsInfo.WordInfo(phrase[i]));
645        }
646    }
647
648    public void dumpDictionaryForDebug(final String dictName) {
649        final ExpandableBinaryDictionary dictToDump = mDictionaries.getSubDict(dictName);
650        if (dictToDump == null) {
651            Log.e(TAG, "Cannot dump " + dictName + ". "
652                    + "The dictionary is not being used for suggestion or cannot be dumped.");
653            return;
654        }
655        dictToDump.dumpAllWordsForDebug();
656    }
657}
658