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