DictionaryFacilitator.java revision 26b3d1de7ec8d94d982aab0ba54590de3462274c
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,
472                new DistracterFilterCheckingIsInDictionary(
473                        mDistracterFilter, userHistoryDictionary));
474    }
475
476    private void removeWord(final String dictName, final String word) {
477        final ExpandableBinaryDictionary dictionary = mDictionaries.getSubDict(dictName);
478        if (dictionary != null) {
479            dictionary.removeUnigramEntryDynamically(word);
480        }
481    }
482
483    public void removeWordFromPersonalizedDicts(final String word) {
484        removeWord(Dictionary.TYPE_USER_HISTORY, word);
485        removeWord(Dictionary.TYPE_PERSONALIZATION, word);
486        removeWord(Dictionary.TYPE_CONTEXTUAL, word);
487    }
488
489    // TODO: Revise the way to fusion suggestion results.
490    public SuggestionResults getSuggestionResults(final WordComposer composer,
491            final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
492            final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId) {
493        final Dictionaries dictionaries = mDictionaries;
494        final SuggestionResults suggestionResults = new SuggestionResults(
495                dictionaries.mLocale, SuggestedWords.MAX_SUGGESTIONS,
496                prevWordsInfo.mPrevWordsInfo[0].mIsBeginningOfSentence);
497        final float[] languageWeight = new float[] { Dictionary.NOT_A_LANGUAGE_WEIGHT };
498        for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
499            final Dictionary dictionary = dictionaries.getDict(dictType);
500            if (null == dictionary) continue;
501            final ArrayList<SuggestedWordInfo> dictionarySuggestions =
502                    dictionary.getSuggestions(composer, prevWordsInfo, proximityInfo,
503                            settingsValuesForSuggestion, sessionId, languageWeight);
504            if (null == dictionarySuggestions) continue;
505            suggestionResults.addAll(dictionarySuggestions);
506            if (null != suggestionResults.mRawSuggestions) {
507                suggestionResults.mRawSuggestions.addAll(dictionarySuggestions);
508            }
509        }
510        return suggestionResults;
511    }
512
513    public boolean isValidWord(final String word, final boolean ignoreCase) {
514        if (TextUtils.isEmpty(word)) {
515            return false;
516        }
517        final Dictionaries dictionaries = mDictionaries;
518        if (dictionaries.mLocale == null) {
519            return false;
520        }
521        final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale);
522        for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
523            final Dictionary dictionary = dictionaries.getDict(dictType);
524            // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
525            // would be immutable once it's finished initializing, but concretely a null test is
526            // probably good enough for the time being.
527            if (null == dictionary) continue;
528            if (dictionary.isValidWord(word)
529                    || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
530                return true;
531            }
532        }
533        return false;
534    }
535
536    private int getFrequencyInternal(final String word,
537            final boolean isGettingMaxFrequencyOfExactMatches) {
538        if (TextUtils.isEmpty(word)) {
539            return Dictionary.NOT_A_PROBABILITY;
540        }
541        int maxFreq = Dictionary.NOT_A_PROBABILITY;
542        final Dictionaries dictionaries = mDictionaries;
543        for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
544            final Dictionary dictionary = dictionaries.getDict(dictType);
545            if (dictionary == null) continue;
546            final int tempFreq;
547            if (isGettingMaxFrequencyOfExactMatches) {
548                tempFreq = dictionary.getMaxFrequencyOfExactMatches(word);
549            } else {
550                tempFreq = dictionary.getFrequency(word);
551            }
552            if (tempFreq >= maxFreq) {
553                maxFreq = tempFreq;
554            }
555        }
556        return maxFreq;
557    }
558
559    public int getFrequency(final String word) {
560        return getFrequencyInternal(word, false /* isGettingMaxFrequencyOfExactMatches */);
561    }
562
563    public int getMaxFrequencyOfExactMatches(final String word) {
564        return getFrequencyInternal(word, true /* isGettingMaxFrequencyOfExactMatches */);
565    }
566
567    private void clearSubDictionary(final String dictName) {
568        final ExpandableBinaryDictionary dictionary = mDictionaries.getSubDict(dictName);
569        if (dictionary != null) {
570            dictionary.clear();
571        }
572    }
573
574    public void clearUserHistoryDictionary() {
575        clearSubDictionary(Dictionary.TYPE_USER_HISTORY);
576    }
577
578    // This method gets called only when the IME receives a notification to remove the
579    // personalization dictionary.
580    public void clearPersonalizationDictionary() {
581        clearSubDictionary(Dictionary.TYPE_PERSONALIZATION);
582    }
583
584    public void clearContextualDictionary() {
585        clearSubDictionary(Dictionary.TYPE_CONTEXTUAL);
586    }
587
588    public void addEntriesToPersonalizationDictionary(
589            final PersonalizationDataChunk personalizationDataChunk,
590            final SpacingAndPunctuations spacingAndPunctuations,
591            final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) {
592        final ExpandableBinaryDictionary personalizationDict =
593                mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION);
594        if (personalizationDict == null) {
595            if (callback != null) {
596                callback.onFinished();
597            }
598            return;
599        }
600        final ArrayList<LanguageModelParam> languageModelParams =
601                LanguageModelParam.createLanguageModelParamsFrom(
602                        personalizationDataChunk.mTokens,
603                        personalizationDataChunk.mTimestampInSeconds,
604                        this /* dictionaryFacilitator */, spacingAndPunctuations,
605                        new DistracterFilterCheckingIsInDictionary(
606                                mDistracterFilter, personalizationDict));
607        if (languageModelParams == null || languageModelParams.isEmpty()) {
608            if (callback != null) {
609                callback.onFinished();
610            }
611            return;
612        }
613        personalizationDict.addMultipleDictionaryEntriesDynamically(languageModelParams, callback);
614    }
615
616    public void addPhraseToContextualDictionary(final String[] phrase, final int probability,
617            final int bigramProbabilityForWords, final int bigramProbabilityForPhrases) {
618        final ExpandableBinaryDictionary contextualDict =
619                mDictionaries.getSubDict(Dictionary.TYPE_CONTEXTUAL);
620        if (contextualDict == null) {
621            return;
622        }
623        PrevWordsInfo prevWordsInfo = PrevWordsInfo.BEGINNING_OF_SENTENCE;
624        for (int i = 0; i < phrase.length; i++) {
625            final String[] subPhrase = Arrays.copyOfRange(phrase, i /* start */, phrase.length);
626            final String subPhraseStr = TextUtils.join(Constants.WORD_SEPARATOR, subPhrase);
627            contextualDict.addUnigramEntryWithCheckingDistracter(
628                    subPhraseStr, probability, null /* shortcutTarget */,
629                    Dictionary.NOT_A_PROBABILITY /* shortcutFreq */,
630                    false /* isNotAWord */, false /* isBlacklisted */,
631                    BinaryDictionary.NOT_A_VALID_TIMESTAMP,
632                    DistracterFilter.EMPTY_DISTRACTER_FILTER);
633            contextualDict.addNgramEntry(prevWordsInfo, subPhraseStr,
634                    bigramProbabilityForPhrases, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
635
636            if (i < phrase.length - 1) {
637                contextualDict.addUnigramEntryWithCheckingDistracter(
638                        phrase[i], probability, null /* shortcutTarget */,
639                        Dictionary.NOT_A_PROBABILITY /* shortcutFreq */,
640                        false /* isNotAWord */, false /* isBlacklisted */,
641                        BinaryDictionary.NOT_A_VALID_TIMESTAMP,
642                        DistracterFilter.EMPTY_DISTRACTER_FILTER);
643                contextualDict.addNgramEntry(prevWordsInfo, phrase[i],
644                        bigramProbabilityForWords, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
645            }
646            prevWordsInfo =
647                    prevWordsInfo.getNextPrevWordsInfo(new PrevWordsInfo.WordInfo(phrase[i]));
648        }
649    }
650
651    public void dumpDictionaryForDebug(final String dictName) {
652        final ExpandableBinaryDictionary dictToDump = mDictionaries.getSubDict(dictName);
653        if (dictToDump == null) {
654            Log.e(TAG, "Cannot dump " + dictName + ". "
655                    + "The dictionary is not being used for suggestion or cannot be dumped.");
656            return;
657        }
658        dictToDump.dumpAllWordsForDebug();
659    }
660}
661