1ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev/*
283e9a29c3379e9b5a304cffe5ddcbd1188cfa677Jatin Matani7 * Copyright (C) 2013 The Android Open Source Project
3ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev *
4ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev * Licensed under the Apache License, Version 2.0 (the "License");
5ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev * you may not use this file except in compliance with the License.
6ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev * You may obtain a copy of the License at
7ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev *
8ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev *      http://www.apache.org/licenses/LICENSE-2.0
9ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev *
10ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev * Unless required by applicable law or agreed to in writing, software
11ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev * distributed under the License is distributed on an "AS IS" BASIS,
12ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev * See the License for the specific language governing permissions and
14ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev * limitations under the License.
15ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev */
16ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
17ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevpackage com.android.inputmethod.latin;
18ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
19604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheikimport android.Manifest;
20ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport android.content.Context;
21ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport android.text.TextUtils;
22ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport android.util.Log;
23459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovicimport android.util.LruCache;
24ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
25ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport com.android.inputmethod.annotations.UsedForTesting;
26487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanevimport com.android.inputmethod.keyboard.Keyboard;
27ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport com.android.inputmethod.latin.NgramContext.WordInfo;
28ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
292b8d763c65b2482fcdc7efe301907ac18133fa42Martin Paraskevovimport com.android.inputmethod.latin.common.ComposedData;
30ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport com.android.inputmethod.latin.common.Constants;
310e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovicimport com.android.inputmethod.latin.common.StringUtils;
32604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheikimport com.android.inputmethod.latin.permissions.PermissionsUtil;
33ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport com.android.inputmethod.latin.personalization.UserHistoryDictionary;
34ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
35ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport com.android.inputmethod.latin.utils.ExecutorUtils;
36ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport com.android.inputmethod.latin.utils.SuggestionResults;
37ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
38ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport java.io.File;
39ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport java.lang.reflect.InvocationTargetException;
40ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport java.lang.reflect.Method;
41ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport java.util.ArrayList;
42ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport java.util.Collections;
43ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport java.util.HashMap;
44ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport java.util.HashSet;
4572278d30478335b914b2a9758fda0131bde9c70eDan Zivkovicimport java.util.List;
46ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport java.util.Locale;
47ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport java.util.Map;
48ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport java.util.concurrent.ConcurrentHashMap;
49ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport java.util.concurrent.CountDownLatch;
50ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport java.util.concurrent.TimeUnit;
51ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
52ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport javax.annotation.Nonnull;
53ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevimport javax.annotation.Nullable;
54ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
55ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev/**
56ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev * Facilitates interaction with different kinds of dictionaries. Provides APIs
57ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev * to instantiate and select the correct dictionaries (based on language or account),
58ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev * update entries and fetch suggestions.
59ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev *
60ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev * Currently AndroidSpellCheckerService and LatinIME both use DictionaryFacilitator as
61ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev * a client for interacting with dictionaries.
62ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev */
63ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanevpublic class DictionaryFacilitatorImpl implements DictionaryFacilitator {
64ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    // TODO: Consolidate dictionaries in native code.
65ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    public static final String TAG = DictionaryFacilitatorImpl.class.getSimpleName();
66ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
67ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    // HACK: This threshold is being used when adding a capitalized entry in the User History
68ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    // dictionary.
69ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140;
70ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
71107fb4c476779df16be23e245547253978c197acDan Zivkovic    private DictionaryGroup mDictionaryGroup = new DictionaryGroup();
72ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    private volatile CountDownLatch mLatchForWaitingLoadingMainDictionaries = new CountDownLatch(0);
73ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    // To synchronize assigning mDictionaryGroup to ensure closing dictionaries.
74ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    private final Object mLock = new Object();
75ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
76ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    public static final Map<String, Class<? extends ExpandableBinaryDictionary>>
77ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            DICT_TYPE_TO_CLASS = new HashMap<>();
78ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
79ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    static {
80ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER_HISTORY, UserHistoryDictionary.class);
81ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER, UserBinaryDictionary.class);
82ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTACTS, ContactsBinaryDictionary.class);
83ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
84ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
85ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    private static final String DICT_FACTORY_METHOD_NAME = "getDictionary";
86ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    private static final Class<?>[] DICT_FACTORY_METHOD_ARG_TYPES =
87ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            new Class[] { Context.class, Locale.class, File.class, String.class, String.class };
88ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
89459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic    private LruCache<String, Boolean> mValidSpellingWordReadCache;
90459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic    private LruCache<String, Boolean> mValidSpellingWordWriteCache;
91459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic
92459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic    @Override
93459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic    public void setValidSpellingWordReadCache(final LruCache<String, Boolean> cache) {
94459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic        mValidSpellingWordReadCache = cache;
95459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic    }
96459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic
97459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic    @Override
98459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic    public void setValidSpellingWordWriteCache(final LruCache<String, Boolean> cache) {
99459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic        mValidSpellingWordWriteCache = cache;
100459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic    }
101459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic
102107fb4c476779df16be23e245547253978c197acDan Zivkovic    @Override
103107fb4c476779df16be23e245547253978c197acDan Zivkovic    public boolean isForLocale(final Locale locale) {
104107fb4c476779df16be23e245547253978c197acDan Zivkovic        return locale != null && locale.equals(mDictionaryGroup.mLocale);
105ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
106ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
107ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    /**
108ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev     * Returns whether this facilitator is exactly for this account.
109ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev     *
110ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev     * @param account the account to test against.
111ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev     */
112ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    public boolean isForAccount(@Nullable final String account) {
113107fb4c476779df16be23e245547253978c197acDan Zivkovic        return TextUtils.equals(mDictionaryGroup.mAccount, account);
114ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
115ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
116ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    /**
117ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev     * A group of dictionaries that work together for a single language.
118ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev     */
119ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    private static class DictionaryGroup {
120ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        // TODO: Add null analysis annotations.
121ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        // TODO: Run evaluation to determine a reasonable value for these constants. The current
122ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        // values are ad-hoc and chosen without any particular care or methodology.
123ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        public static final float WEIGHT_FOR_MOST_PROBABLE_LANGUAGE = 1.0f;
124ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        public static final float WEIGHT_FOR_GESTURING_IN_NOT_MOST_PROBABLE_LANGUAGE = 0.95f;
125ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        public static final float WEIGHT_FOR_TYPING_IN_NOT_MOST_PROBABLE_LANGUAGE = 0.6f;
126ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
127ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        /**
128ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev         * The locale associated with the dictionary group.
129ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev         */
130ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        @Nullable public final Locale mLocale;
131ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
132ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        /**
133ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev         * The user account associated with the dictionary group.
134ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev         */
135ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        @Nullable public final String mAccount;
136ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
137ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        @Nullable private Dictionary mMainDict;
138ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        // Confidence that the most probable language is actually the language the user is
139ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        // typing in. For now, this is simply the number of times a word from this language
140ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        // has been committed in a row.
141ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        private int mConfidence = 0;
142ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
143ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        public float mWeightForTypingInLocale = WEIGHT_FOR_MOST_PROBABLE_LANGUAGE;
144ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        public float mWeightForGesturingInLocale = WEIGHT_FOR_MOST_PROBABLE_LANGUAGE;
145ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        public final ConcurrentHashMap<String, ExpandableBinaryDictionary> mSubDictMap =
146ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                new ConcurrentHashMap<>();
147ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
148ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        public DictionaryGroup() {
149ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            this(null /* locale */, null /* mainDict */, null /* account */,
150ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                    Collections.<String, ExpandableBinaryDictionary>emptyMap() /* subDicts */);
151ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
152ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
153ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        public DictionaryGroup(@Nullable final Locale locale,
154ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                @Nullable final Dictionary mainDict,
155ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                @Nullable final String account,
156ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                final Map<String, ExpandableBinaryDictionary> subDicts) {
157ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            mLocale = locale;
158ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            mAccount = account;
159ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            // The main dictionary can be asynchronously loaded.
160ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            setMainDict(mainDict);
161ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            for (final Map.Entry<String, ExpandableBinaryDictionary> entry : subDicts.entrySet()) {
162ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                setSubDict(entry.getKey(), entry.getValue());
163ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            }
164ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
165ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
166ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        private void setSubDict(final String dictType, final ExpandableBinaryDictionary dict) {
167ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            if (dict != null) {
168ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                mSubDictMap.put(dictType, dict);
169ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            }
170ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
171ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
172ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        public void setMainDict(final Dictionary mainDict) {
173ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            // Close old dictionary if exists. Main dictionary can be assigned multiple times.
174ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final Dictionary oldDict = mMainDict;
175ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            mMainDict = mainDict;
176ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            if (oldDict != null && mainDict != oldDict) {
177ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                oldDict.close();
178ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            }
179ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
180ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
181ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        public Dictionary getDict(final String dictType) {
182ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            if (Dictionary.TYPE_MAIN.equals(dictType)) {
183ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                return mMainDict;
184ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            }
185ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            return getSubDict(dictType);
186ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
187ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
188ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        public ExpandableBinaryDictionary getSubDict(final String dictType) {
189ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            return mSubDictMap.get(dictType);
190ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
191ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
192ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        public boolean hasDict(final String dictType, @Nullable final String account) {
193ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            if (Dictionary.TYPE_MAIN.equals(dictType)) {
194ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                return mMainDict != null;
195ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            }
196ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            if (Dictionary.TYPE_USER_HISTORY.equals(dictType) &&
197ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                    !TextUtils.equals(account, mAccount)) {
198ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                // If the dictionary type is user history, & if the account doesn't match,
199ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                // return immediately. If the account matches, continue looking it up in the
200ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                // sub dictionary map.
201ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                return false;
202ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            }
203ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            return mSubDictMap.containsKey(dictType);
204ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
205ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
206ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        public void closeDict(final String dictType) {
207ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final Dictionary dict;
208ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            if (Dictionary.TYPE_MAIN.equals(dictType)) {
209ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                dict = mMainDict;
210ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            } else {
211ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                dict = mSubDictMap.remove(dictType);
212ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            }
213ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            if (dict != null) {
214ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                dict.close();
215ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            }
216ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
217ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
218ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
219ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    public DictionaryFacilitatorImpl() {
220ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
221ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
222755b3d882f9d74ceaec7b59fe618dff3ad54f304Dan Zivkovic    @Override
223755b3d882f9d74ceaec7b59fe618dff3ad54f304Dan Zivkovic    public void onStartInput() {
224755b3d882f9d74ceaec7b59fe618dff3ad54f304Dan Zivkovic    }
225755b3d882f9d74ceaec7b59fe618dff3ad54f304Dan Zivkovic
226755b3d882f9d74ceaec7b59fe618dff3ad54f304Dan Zivkovic    @Override
2279bad1ac33f65dc3454a689b88560988b77a00a87Dan Zivkovic    public void onFinishInput(Context context) {
228755b3d882f9d74ceaec7b59fe618dff3ad54f304Dan Zivkovic    }
229755b3d882f9d74ceaec7b59fe618dff3ad54f304Dan Zivkovic
230755b3d882f9d74ceaec7b59fe618dff3ad54f304Dan Zivkovic    @Override
231ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    public boolean isActive() {
232107fb4c476779df16be23e245547253978c197acDan Zivkovic        return mDictionaryGroup.mLocale != null;
233ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
234ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
235107fb4c476779df16be23e245547253978c197acDan Zivkovic    @Override
236107fb4c476779df16be23e245547253978c197acDan Zivkovic    public Locale getLocale() {
237107fb4c476779df16be23e245547253978c197acDan Zivkovic        return mDictionaryGroup.mLocale;
238ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
239ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
240f22571e2d9756294fa2fa332a395c06010f54d92Dan Zivkovic    @Override
241f22571e2d9756294fa2fa332a395c06010f54d92Dan Zivkovic    public boolean usesContacts() {
242f22571e2d9756294fa2fa332a395c06010f54d92Dan Zivkovic        return mDictionaryGroup.getSubDict(Dictionary.TYPE_CONTACTS) != null;
243f22571e2d9756294fa2fa332a395c06010f54d92Dan Zivkovic    }
244f22571e2d9756294fa2fa332a395c06010f54d92Dan Zivkovic
245f22571e2d9756294fa2fa332a395c06010f54d92Dan Zivkovic    @Override
246f22571e2d9756294fa2fa332a395c06010f54d92Dan Zivkovic    public String getAccount() {
247f22571e2d9756294fa2fa332a395c06010f54d92Dan Zivkovic        return null;
248f22571e2d9756294fa2fa332a395c06010f54d92Dan Zivkovic    }
249f22571e2d9756294fa2fa332a395c06010f54d92Dan Zivkovic
250ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    @Nullable
251ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    private static ExpandableBinaryDictionary getSubDict(final String dictType,
252ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final Context context, final Locale locale, final File dictFile,
253ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final String dictNamePrefix, @Nullable final String account) {
254ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        final Class<? extends ExpandableBinaryDictionary> dictClass =
255ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                DICT_TYPE_TO_CLASS.get(dictType);
256ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        if (dictClass == null) {
257ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            return null;
258ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
259ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        try {
260ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final Method factoryMethod = dictClass.getMethod(DICT_FACTORY_METHOD_NAME,
261ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                    DICT_FACTORY_METHOD_ARG_TYPES);
262ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final Object dict = factoryMethod.invoke(null /* obj */,
263ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                    new Object[] { context, locale, dictFile, dictNamePrefix, account });
264ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            return (ExpandableBinaryDictionary) dict;
265ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        } catch (final NoSuchMethodException | SecurityException | IllegalAccessException
266ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                | IllegalArgumentException | InvocationTargetException e) {
267ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            Log.e(TAG, "Cannot create dictionary: " + dictType, e);
268ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            return null;
269ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
270ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
271ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
272ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    @Nullable
273107fb4c476779df16be23e245547253978c197acDan Zivkovic    static DictionaryGroup findDictionaryGroupWithLocale(final DictionaryGroup dictionaryGroup,
274ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final Locale locale) {
275107fb4c476779df16be23e245547253978c197acDan Zivkovic        return locale.equals(dictionaryGroup.mLocale) ? dictionaryGroup : null;
276ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
277ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
278107fb4c476779df16be23e245547253978c197acDan Zivkovic    @Override
279d6a8adcb044dd8b73a1c96776a835b411a978b46Dan Zivkovic    public void resetDictionaries(
280d6a8adcb044dd8b73a1c96776a835b411a978b46Dan Zivkovic            final Context context,
281107fb4c476779df16be23e245547253978c197acDan Zivkovic            final Locale newLocale,
282ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final boolean useContactsDict,
283ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final boolean usePersonalizedDicts,
284ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final boolean forceReloadMainDictionary,
285d6a8adcb044dd8b73a1c96776a835b411a978b46Dan Zivkovic            @Nullable final String account,
286ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final String dictNamePrefix,
287d6a8adcb044dd8b73a1c96776a835b411a978b46Dan Zivkovic            @Nullable final DictionaryInitializationListener listener) {
288ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        final HashMap<Locale, ArrayList<String>> existingDictionariesToCleanup = new HashMap<>();
289ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        // TODO: Make subDictTypesToUse configurable by resource or a static final list.
290ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        final HashSet<String> subDictTypesToUse = new HashSet<>();
291ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        subDictTypesToUse.add(Dictionary.TYPE_USER);
292604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik
293604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik        // Do not use contacts dictionary if we do not have permissions to read contacts.
294604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik        final boolean contactsPermissionGranted = PermissionsUtil.checkAllPermissionsGranted(
295604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik                context, Manifest.permission.READ_CONTACTS);
296604158669b407a40cd0f23538fad4dce5d738f24Mohammadinamul Sheik        if (useContactsDict && contactsPermissionGranted) {
297ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            subDictTypesToUse.add(Dictionary.TYPE_CONTACTS);
298ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
299ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        if (usePersonalizedDicts) {
300ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            subDictTypesToUse.add(Dictionary.TYPE_USER_HISTORY);
301ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
302ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
303ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        // Gather all dictionaries. We'll remove them from the list to clean up later.
304107fb4c476779df16be23e245547253978c197acDan Zivkovic        final ArrayList<String> dictTypeForLocale = new ArrayList<>();
305107fb4c476779df16be23e245547253978c197acDan Zivkovic        existingDictionariesToCleanup.put(newLocale, dictTypeForLocale);
306107fb4c476779df16be23e245547253978c197acDan Zivkovic        final DictionaryGroup currentDictionaryGroupForLocale =
307107fb4c476779df16be23e245547253978c197acDan Zivkovic                findDictionaryGroupWithLocale(mDictionaryGroup, newLocale);
308107fb4c476779df16be23e245547253978c197acDan Zivkovic        if (currentDictionaryGroupForLocale != null) {
30923d19626f3edd142eab2c58e41e40fdfc27b8b2bDan Zivkovic            for (final String dictType : DYNAMIC_DICTIONARY_TYPES) {
310ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                if (currentDictionaryGroupForLocale.hasDict(dictType, account)) {
311ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                    dictTypeForLocale.add(dictType);
312ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                }
313ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            }
314ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            if (currentDictionaryGroupForLocale.hasDict(Dictionary.TYPE_MAIN, account)) {
315ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                dictTypeForLocale.add(Dictionary.TYPE_MAIN);
316ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            }
317ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
318ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
319107fb4c476779df16be23e245547253978c197acDan Zivkovic        final DictionaryGroup dictionaryGroupForLocale =
320107fb4c476779df16be23e245547253978c197acDan Zivkovic                findDictionaryGroupWithLocale(mDictionaryGroup, newLocale);
321107fb4c476779df16be23e245547253978c197acDan Zivkovic        final ArrayList<String> dictTypesToCleanupForLocale =
322107fb4c476779df16be23e245547253978c197acDan Zivkovic                existingDictionariesToCleanup.get(newLocale);
323107fb4c476779df16be23e245547253978c197acDan Zivkovic        final boolean noExistingDictsForThisLocale = (null == dictionaryGroupForLocale);
324ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
325107fb4c476779df16be23e245547253978c197acDan Zivkovic        final Dictionary mainDict;
326107fb4c476779df16be23e245547253978c197acDan Zivkovic        if (forceReloadMainDictionary || noExistingDictsForThisLocale
327107fb4c476779df16be23e245547253978c197acDan Zivkovic                || !dictionaryGroupForLocale.hasDict(Dictionary.TYPE_MAIN, account)) {
328107fb4c476779df16be23e245547253978c197acDan Zivkovic            mainDict = null;
329107fb4c476779df16be23e245547253978c197acDan Zivkovic        } else {
330107fb4c476779df16be23e245547253978c197acDan Zivkovic            mainDict = dictionaryGroupForLocale.getDict(Dictionary.TYPE_MAIN);
331107fb4c476779df16be23e245547253978c197acDan Zivkovic            dictTypesToCleanupForLocale.remove(Dictionary.TYPE_MAIN);
332107fb4c476779df16be23e245547253978c197acDan Zivkovic        }
333ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
334107fb4c476779df16be23e245547253978c197acDan Zivkovic        final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
335107fb4c476779df16be23e245547253978c197acDan Zivkovic        for (final String subDictType : subDictTypesToUse) {
336107fb4c476779df16be23e245547253978c197acDan Zivkovic            final ExpandableBinaryDictionary subDict;
337107fb4c476779df16be23e245547253978c197acDan Zivkovic            if (noExistingDictsForThisLocale
338107fb4c476779df16be23e245547253978c197acDan Zivkovic                    || !dictionaryGroupForLocale.hasDict(subDictType, account)) {
339107fb4c476779df16be23e245547253978c197acDan Zivkovic                // Create a new dictionary.
340107fb4c476779df16be23e245547253978c197acDan Zivkovic                subDict = getSubDict(subDictType, context, newLocale, null /* dictFile */,
341107fb4c476779df16be23e245547253978c197acDan Zivkovic                        dictNamePrefix, account);
342107fb4c476779df16be23e245547253978c197acDan Zivkovic            } else {
343107fb4c476779df16be23e245547253978c197acDan Zivkovic                // Reuse the existing dictionary, and don't close it at the end
344107fb4c476779df16be23e245547253978c197acDan Zivkovic                subDict = dictionaryGroupForLocale.getSubDict(subDictType);
345107fb4c476779df16be23e245547253978c197acDan Zivkovic                dictTypesToCleanupForLocale.remove(subDictType);
346ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            }
347107fb4c476779df16be23e245547253978c197acDan Zivkovic            subDicts.put(subDictType, subDict);
348ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
349107fb4c476779df16be23e245547253978c197acDan Zivkovic        DictionaryGroup newDictionaryGroup =
350107fb4c476779df16be23e245547253978c197acDan Zivkovic                new DictionaryGroup(newLocale, mainDict, account, subDicts);
351ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
352ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        // Replace Dictionaries.
353107fb4c476779df16be23e245547253978c197acDan Zivkovic        final DictionaryGroup oldDictionaryGroup;
354ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        synchronized (mLock) {
355107fb4c476779df16be23e245547253978c197acDan Zivkovic            oldDictionaryGroup = mDictionaryGroup;
356107fb4c476779df16be23e245547253978c197acDan Zivkovic            mDictionaryGroup = newDictionaryGroup;
357ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            if (hasAtLeastOneUninitializedMainDictionary()) {
358107fb4c476779df16be23e245547253978c197acDan Zivkovic                asyncReloadUninitializedMainDictionaries(context, newLocale, listener);
359ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            }
360ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
361ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        if (listener != null) {
362ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            listener.onUpdateMainDictionaryAvailability(hasAtLeastOneInitializedMainDictionary());
363ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
364ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
365ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        // Clean up old dictionaries.
366ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        for (final Locale localeToCleanUp : existingDictionariesToCleanup.keySet()) {
367ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final ArrayList<String> dictTypesToCleanUp =
368ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                    existingDictionariesToCleanup.get(localeToCleanUp);
369ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final DictionaryGroup dictionarySetToCleanup =
370107fb4c476779df16be23e245547253978c197acDan Zivkovic                    findDictionaryGroupWithLocale(oldDictionaryGroup, localeToCleanUp);
371ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            for (final String dictType : dictTypesToCleanUp) {
372ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                dictionarySetToCleanup.closeDict(dictType);
373ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            }
374ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
375459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic
376459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic        if (mValidSpellingWordWriteCache != null) {
377459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic            mValidSpellingWordWriteCache.evictAll();
378459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic        }
379ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
380ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
381ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    private void asyncReloadUninitializedMainDictionaries(final Context context,
382107fb4c476779df16be23e245547253978c197acDan Zivkovic            final Locale locale, final DictionaryInitializationListener listener) {
383ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        final CountDownLatch latchForWaitingLoadingMainDictionary = new CountDownLatch(1);
384ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        mLatchForWaitingLoadingMainDictionaries = latchForWaitingLoadingMainDictionary;
385eaa710d4aaac75ff2b7e29608d004fe7662b392eDan Zivkovic        ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute(new Runnable() {
386ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            @Override
387ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            public void run() {
388ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                doReloadUninitializedMainDictionaries(
389107fb4c476779df16be23e245547253978c197acDan Zivkovic                        context, locale, listener, latchForWaitingLoadingMainDictionary);
390ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            }
391ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        });
392ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
393ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
394107fb4c476779df16be23e245547253978c197acDan Zivkovic    void doReloadUninitializedMainDictionaries(final Context context, final Locale locale,
395ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final DictionaryInitializationListener listener,
396ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final CountDownLatch latchForWaitingLoadingMainDictionary) {
397107fb4c476779df16be23e245547253978c197acDan Zivkovic        final DictionaryGroup dictionaryGroup =
398107fb4c476779df16be23e245547253978c197acDan Zivkovic                findDictionaryGroupWithLocale(mDictionaryGroup, locale);
399107fb4c476779df16be23e245547253978c197acDan Zivkovic        if (null == dictionaryGroup) {
400107fb4c476779df16be23e245547253978c197acDan Zivkovic            // This should never happen, but better safe than crashy
401107fb4c476779df16be23e245547253978c197acDan Zivkovic            Log.w(TAG, "Expected a dictionary group for " + locale + " but none found");
402107fb4c476779df16be23e245547253978c197acDan Zivkovic            return;
403107fb4c476779df16be23e245547253978c197acDan Zivkovic        }
404107fb4c476779df16be23e245547253978c197acDan Zivkovic        final Dictionary mainDict =
405107fb4c476779df16be23e245547253978c197acDan Zivkovic                DictionaryFactory.createMainDictionaryFromManager(context, locale);
406107fb4c476779df16be23e245547253978c197acDan Zivkovic        synchronized (mLock) {
407107fb4c476779df16be23e245547253978c197acDan Zivkovic            if (locale.equals(dictionaryGroup.mLocale)) {
408107fb4c476779df16be23e245547253978c197acDan Zivkovic                dictionaryGroup.setMainDict(mainDict);
409107fb4c476779df16be23e245547253978c197acDan Zivkovic            } else {
410107fb4c476779df16be23e245547253978c197acDan Zivkovic                // Dictionary facilitator has been reset for another locale.
411107fb4c476779df16be23e245547253978c197acDan Zivkovic                mainDict.close();
412ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            }
413ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
414ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        if (listener != null) {
415107fb4c476779df16be23e245547253978c197acDan Zivkovic            listener.onUpdateMainDictionaryAvailability(hasAtLeastOneInitializedMainDictionary());
416ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
417ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        latchForWaitingLoadingMainDictionary.countDown();
418ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
419ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
420ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    @UsedForTesting
421107fb4c476779df16be23e245547253978c197acDan Zivkovic    public void resetDictionariesForTesting(final Context context, final Locale locale,
422ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final ArrayList<String> dictionaryTypes, final HashMap<String, File> dictionaryFiles,
423ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final Map<String, Map<String, String>> additionalDictAttributes,
424ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            @Nullable final String account) {
425ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        Dictionary mainDictionary = null;
426ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
427ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
428107fb4c476779df16be23e245547253978c197acDan Zivkovic        for (final String dictType : dictionaryTypes) {
429107fb4c476779df16be23e245547253978c197acDan Zivkovic            if (dictType.equals(Dictionary.TYPE_MAIN)) {
430107fb4c476779df16be23e245547253978c197acDan Zivkovic                mainDictionary = DictionaryFactory.createMainDictionaryFromManager(context,
431107fb4c476779df16be23e245547253978c197acDan Zivkovic                        locale);
432107fb4c476779df16be23e245547253978c197acDan Zivkovic            } else {
433107fb4c476779df16be23e245547253978c197acDan Zivkovic                final File dictFile = dictionaryFiles.get(dictType);
434107fb4c476779df16be23e245547253978c197acDan Zivkovic                final ExpandableBinaryDictionary dict = getSubDict(
435107fb4c476779df16be23e245547253978c197acDan Zivkovic                        dictType, context, locale, dictFile, "" /* dictNamePrefix */, account);
436107fb4c476779df16be23e245547253978c197acDan Zivkovic                if (additionalDictAttributes.containsKey(dictType)) {
437107fb4c476779df16be23e245547253978c197acDan Zivkovic                    dict.clearAndFlushDictionaryWithAdditionalAttributes(
438107fb4c476779df16be23e245547253978c197acDan Zivkovic                            additionalDictAttributes.get(dictType));
439ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                }
440107fb4c476779df16be23e245547253978c197acDan Zivkovic                if (dict == null) {
441107fb4c476779df16be23e245547253978c197acDan Zivkovic                    throw new RuntimeException("Unknown dictionary type: " + dictType);
442107fb4c476779df16be23e245547253978c197acDan Zivkovic                }
443107fb4c476779df16be23e245547253978c197acDan Zivkovic                dict.reloadDictionaryIfRequired();
444107fb4c476779df16be23e245547253978c197acDan Zivkovic                dict.waitAllTasksForTests();
445107fb4c476779df16be23e245547253978c197acDan Zivkovic                subDicts.put(dictType, dict);
446ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            }
447ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
448107fb4c476779df16be23e245547253978c197acDan Zivkovic        mDictionaryGroup = new DictionaryGroup(locale, mainDictionary, account, subDicts);
449ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
450ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
451ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    public void closeDictionaries() {
452107fb4c476779df16be23e245547253978c197acDan Zivkovic        final DictionaryGroup dictionaryGroupToClose;
453ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        synchronized (mLock) {
454107fb4c476779df16be23e245547253978c197acDan Zivkovic            dictionaryGroupToClose = mDictionaryGroup;
455107fb4c476779df16be23e245547253978c197acDan Zivkovic            mDictionaryGroup = new DictionaryGroup();
456ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
457107fb4c476779df16be23e245547253978c197acDan Zivkovic        for (final String dictType : ALL_DICTIONARY_TYPES) {
458107fb4c476779df16be23e245547253978c197acDan Zivkovic            dictionaryGroupToClose.closeDict(dictType);
459ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
460ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
461ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
462ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    @UsedForTesting
463ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    public ExpandableBinaryDictionary getSubDictForTesting(final String dictName) {
464107fb4c476779df16be23e245547253978c197acDan Zivkovic        return mDictionaryGroup.getSubDict(dictName);
465ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
466ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
467ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    // The main dictionaries are loaded asynchronously.  Don't cache the return value
468ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    // of these methods.
469ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    public boolean hasAtLeastOneInitializedMainDictionary() {
470107fb4c476779df16be23e245547253978c197acDan Zivkovic        final Dictionary mainDict = mDictionaryGroup.getDict(Dictionary.TYPE_MAIN);
471107fb4c476779df16be23e245547253978c197acDan Zivkovic        if (mainDict != null && mainDict.isInitialized()) {
472107fb4c476779df16be23e245547253978c197acDan Zivkovic            return true;
473ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
474ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        return false;
475ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
476ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
477ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    public boolean hasAtLeastOneUninitializedMainDictionary() {
478107fb4c476779df16be23e245547253978c197acDan Zivkovic        final Dictionary mainDict = mDictionaryGroup.getDict(Dictionary.TYPE_MAIN);
479107fb4c476779df16be23e245547253978c197acDan Zivkovic        if (mainDict == null || !mainDict.isInitialized()) {
480107fb4c476779df16be23e245547253978c197acDan Zivkovic            return true;
481ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
482ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        return false;
483ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
484ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
485ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    public void waitForLoadingMainDictionaries(final long timeout, final TimeUnit unit)
486ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            throws InterruptedException {
487ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        mLatchForWaitingLoadingMainDictionaries.await(timeout, unit);
488ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
489ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
490ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    @UsedForTesting
491ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit)
492ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            throws InterruptedException {
493ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        waitForLoadingMainDictionaries(timeout, unit);
494107fb4c476779df16be23e245547253978c197acDan Zivkovic        for (final ExpandableBinaryDictionary dict : mDictionaryGroup.mSubDictMap.values()) {
495107fb4c476779df16be23e245547253978c197acDan Zivkovic            dict.waitAllTasksForTests();
496ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
497ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
498ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
499ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
50094b8523566a6155f83abe8ba191a3522f7280ce1Jatin Matani            @Nonnull final NgramContext ngramContext, final long timeStampInSeconds,
501ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final boolean blockPotentiallyOffensive) {
502459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic        // Update the spelling cache before learning. Words that are not yet added to user history
503459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic        // and appear in no other language model are not considered valid.
504459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic        putWordIntoValidSpellingWordCache("addToUserHistory", suggestion);
505459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic
506ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        final String[] words = suggestion.split(Constants.WORD_SEPARATOR);
507ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        NgramContext ngramContextForCurrentWord = ngramContext;
508ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        for (int i = 0; i < words.length; i++) {
509ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final String currentWord = words[i];
510ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final boolean wasCurrentWordAutoCapitalized = (i == 0) ? wasAutoCapitalized : false;
511107fb4c476779df16be23e245547253978c197acDan Zivkovic            addWordToUserHistory(mDictionaryGroup, ngramContextForCurrentWord, currentWord,
512107fb4c476779df16be23e245547253978c197acDan Zivkovic                    wasCurrentWordAutoCapitalized, (int) timeStampInSeconds,
513107fb4c476779df16be23e245547253978c197acDan Zivkovic                    blockPotentiallyOffensive);
514ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            ngramContextForCurrentWord =
515ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                    ngramContextForCurrentWord.getNextNgramContext(new WordInfo(currentWord));
516ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
517ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
518ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
5190e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovic    private void putWordIntoValidSpellingWordCache(
5200e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovic            @Nonnull final String caller,
5210e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovic            @Nonnull final String originalWord) {
5220e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovic        if (mValidSpellingWordWriteCache == null) {
5230e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovic            return;
5240e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovic        }
5250e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovic
5260e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovic        final String lowerCaseWord = originalWord.toLowerCase(getLocale());
5270e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovic        final boolean lowerCaseValid = isValidSpellingWord(lowerCaseWord);
5280e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovic        mValidSpellingWordWriteCache.put(lowerCaseWord, lowerCaseValid);
5290e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovic
5300e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovic        final String capitalWord =
5310e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovic                StringUtils.capitalizeFirstAndDowncaseRest(originalWord, getLocale());
5320e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovic        final boolean capitalValid;
5330e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovic        if (lowerCaseValid) {
5340e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovic            // The lower case form of the word is valid, so the upper case must be valid.
5350e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovic            capitalValid = true;
5360e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovic        } else {
5370e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovic            capitalValid = isValidSpellingWord(capitalWord);
5380e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovic        }
5390e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovic        mValidSpellingWordWriteCache.put(capitalWord, capitalValid);
540459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic    }
541459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic
542ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    private void addWordToUserHistory(final DictionaryGroup dictionaryGroup,
543ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final NgramContext ngramContext, final String word, final boolean wasAutoCapitalized,
544ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final int timeStampInSeconds, final boolean blockPotentiallyOffensive) {
545ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        final ExpandableBinaryDictionary userHistoryDictionary =
546ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                dictionaryGroup.getSubDict(Dictionary.TYPE_USER_HISTORY);
547107fb4c476779df16be23e245547253978c197acDan Zivkovic        if (userHistoryDictionary == null || !isForLocale(userHistoryDictionary.mLocale)) {
548ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            return;
549ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
550ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        final int maxFreq = getFrequency(word);
551ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        if (maxFreq == 0 && blockPotentiallyOffensive) {
552ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            return;
553ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
554ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        final String lowerCasedWord = word.toLowerCase(dictionaryGroup.mLocale);
555ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        final String secondWord;
556ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        if (wasAutoCapitalized) {
557c0eb57124fd295ceb85c3350de3189c40594ee96Dan Zivkovic            if (isValidSuggestionWord(word) && !isValidSuggestionWord(lowerCasedWord)) {
558ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                // If the word was auto-capitalized and exists only as a capitalized word in the
559ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                // dictionary, then we must not downcase it before registering it. For example,
560ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                // the name of the contacts in start-of-sentence position would come here with the
561ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                // wasAutoCapitalized flag: if we downcase it, we'd register a lower-case version
562ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                // of that contact's name which would end up popping in suggestions.
563ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                secondWord = word;
564ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            } else {
565ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                // If however the word is not in the dictionary, or exists as a lower-case word
566ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                // only, then we consider that was a lower-case word that had been auto-capitalized.
567ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                secondWord = lowerCasedWord;
568ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            }
569ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        } else {
570ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            // HACK: We'd like to avoid adding the capitalized form of common words to the User
571ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            // History dictionary in order to avoid suggesting them until the dictionary
572ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            // consolidation is done.
573ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            // TODO: Remove this hack when ready.
574ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            final int lowerCaseFreqInMainDict = dictionaryGroup.hasDict(Dictionary.TYPE_MAIN,
575ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                    null /* account */) ?
576ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                    dictionaryGroup.getDict(Dictionary.TYPE_MAIN).getFrequency(lowerCasedWord) :
577ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                    Dictionary.NOT_A_PROBABILITY;
578ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            if (maxFreq < lowerCaseFreqInMainDict
579ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                    && lowerCaseFreqInMainDict >= CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT) {
580ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                // Use lower cased word as the word can be a distracter of the popular word.
581ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                secondWord = lowerCasedWord;
582ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            } else {
583ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                secondWord = word;
584ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            }
585ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
586ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        // We demote unrecognized words (frequency < 0, below) by specifying them as "invalid".
587ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        // We don't add words with 0-frequency (assuming they would be profanity etc.).
588ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        final boolean isValid = maxFreq > 0;
589ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        UserHistoryDictionary.addToDictionary(userHistoryDictionary, ngramContext, secondWord,
590644a709a5fec65c3ac1c96f18af397458fac7658Dan Zivkovic                isValid, timeStampInSeconds);
591ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
592ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
593ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    private void removeWord(final String dictName, final String word) {
594107fb4c476779df16be23e245547253978c197acDan Zivkovic        final ExpandableBinaryDictionary dictionary = mDictionaryGroup.getSubDict(dictName);
595ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        if (dictionary != null) {
596ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            dictionary.removeUnigramEntryDynamically(word);
597ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
598ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
599ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
600edea47ff2e901d02b381bf88aa6cb35c4b5ee811Tom Ouyang    @Override
601edea47ff2e901d02b381bf88aa6cb35c4b5ee811Tom Ouyang    public void unlearnFromUserHistory(final String word,
60294b8523566a6155f83abe8ba191a3522f7280ce1Jatin Matani            @Nonnull final NgramContext ngramContext, final long timeStampInSeconds,
603edea47ff2e901d02b381bf88aa6cb35c4b5ee811Tom Ouyang            final int eventType) {
604edea47ff2e901d02b381bf88aa6cb35c4b5ee811Tom Ouyang        // TODO: Decide whether or not to remove the word on EVENT_BACKSPACE.
605edea47ff2e901d02b381bf88aa6cb35c4b5ee811Tom Ouyang        if (eventType != Constants.EVENT_BACKSPACE) {
606edea47ff2e901d02b381bf88aa6cb35c4b5ee811Tom Ouyang            removeWord(Dictionary.TYPE_USER_HISTORY, word);
607edea47ff2e901d02b381bf88aa6cb35c4b5ee811Tom Ouyang        }
608459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic
609459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic        // Update the spelling cache after unlearning. Words that are removed from user history
610459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic        // and appear in no other language model are not considered valid.
611459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic        putWordIntoValidSpellingWordCache("unlearnFromUserHistory", word.toLowerCase());
612ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
613ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
614ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    // TODO: Revise the way to fusion suggestion results.
615b00c054125d9f2aa31c2147920cc52cbf2a45cccMohammadinamul Sheik    @Override
61638b9bffa3f60785f30d7976879ddf936f747d7ddDan Zivkovic    @Nonnull public SuggestionResults getSuggestionResults(ComposedData composedData,
617487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanev            NgramContext ngramContext, @Nonnull final Keyboard keyboard,
618b00c054125d9f2aa31c2147920cc52cbf2a45cccMohammadinamul Sheik            SettingsValuesForSuggestion settingsValuesForSuggestion, int sessionId,
619487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanev            int inputStyle) {
620487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanev        long proximityInfoHandle = keyboard.getProximityInfo().getNativeProximityInfo();
621ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        final SuggestionResults suggestionResults = new SuggestionResults(
6225551302d275e3f54da9d86bcea633556ad12db8eDan Zivkovic                SuggestedWords.MAX_SUGGESTIONS, ngramContext.isBeginningOfSentenceContext(),
6235551302d275e3f54da9d86bcea633556ad12db8eDan Zivkovic                false /* firstSuggestionExceedsConfidenceThreshold */);
624ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        final float[] weightOfLangModelVsSpatialModel =
625ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev                new float[] { Dictionary.NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL };
626541ef56e057eb7d81eae6e294ce9eb364f825867Dan Zivkovic        for (final String dictType : ALL_DICTIONARY_TYPES) {
627107fb4c476779df16be23e245547253978c197acDan Zivkovic            final Dictionary dictionary = mDictionaryGroup.getDict(dictType);
628107fb4c476779df16be23e245547253978c197acDan Zivkovic            if (null == dictionary) continue;
629107fb4c476779df16be23e245547253978c197acDan Zivkovic            final float weightForLocale = composedData.mIsBatchMode
630107fb4c476779df16be23e245547253978c197acDan Zivkovic                    ? mDictionaryGroup.mWeightForGesturingInLocale
631107fb4c476779df16be23e245547253978c197acDan Zivkovic                    : mDictionaryGroup.mWeightForTypingInLocale;
632107fb4c476779df16be23e245547253978c197acDan Zivkovic            final ArrayList<SuggestedWordInfo> dictionarySuggestions =
633107fb4c476779df16be23e245547253978c197acDan Zivkovic                    dictionary.getSuggestions(composedData, ngramContext,
634107fb4c476779df16be23e245547253978c197acDan Zivkovic                            proximityInfoHandle, settingsValuesForSuggestion, sessionId,
635107fb4c476779df16be23e245547253978c197acDan Zivkovic                            weightForLocale, weightOfLangModelVsSpatialModel);
636107fb4c476779df16be23e245547253978c197acDan Zivkovic            if (null == dictionarySuggestions) continue;
637107fb4c476779df16be23e245547253978c197acDan Zivkovic            suggestionResults.addAll(dictionarySuggestions);
638107fb4c476779df16be23e245547253978c197acDan Zivkovic            if (null != suggestionResults.mRawSuggestions) {
639107fb4c476779df16be23e245547253978c197acDan Zivkovic                suggestionResults.mRawSuggestions.addAll(dictionarySuggestions);
640ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            }
641ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
642ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        return suggestionResults;
643ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
644ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
645c0eb57124fd295ceb85c3350de3189c40594ee96Dan Zivkovic    public boolean isValidSpellingWord(final String word) {
646459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic        if (mValidSpellingWordReadCache != null) {
6470e15ff6b48adfe5d2ff8ac988314f3d5eaaa43deDan Zivkovic            final Boolean cachedValue = mValidSpellingWordReadCache.get(word);
648459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic            if (cachedValue != null) {
649459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic                return cachedValue;
650459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic            }
651459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic        }
652459b4f353e6138b644c1f06de68e93532ee0d856Dan Zivkovic
653541ef56e057eb7d81eae6e294ce9eb364f825867Dan Zivkovic        return isValidWord(word, ALL_DICTIONARY_TYPES);
654c0eb57124fd295ceb85c3350de3189c40594ee96Dan Zivkovic    }
655c0eb57124fd295ceb85c3350de3189c40594ee96Dan Zivkovic
656c0eb57124fd295ceb85c3350de3189c40594ee96Dan Zivkovic    public boolean isValidSuggestionWord(final String word) {
657541ef56e057eb7d81eae6e294ce9eb364f825867Dan Zivkovic        return isValidWord(word, ALL_DICTIONARY_TYPES);
658c0eb57124fd295ceb85c3350de3189c40594ee96Dan Zivkovic    }
659c0eb57124fd295ceb85c3350de3189c40594ee96Dan Zivkovic
660c0eb57124fd295ceb85c3350de3189c40594ee96Dan Zivkovic    private boolean isValidWord(final String word, final String[] dictionariesToCheck) {
661ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        if (TextUtils.isEmpty(word)) {
662ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            return false;
663ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
664107fb4c476779df16be23e245547253978c197acDan Zivkovic        if (mDictionaryGroup.mLocale == null) {
665107fb4c476779df16be23e245547253978c197acDan Zivkovic            return false;
666107fb4c476779df16be23e245547253978c197acDan Zivkovic        }
667107fb4c476779df16be23e245547253978c197acDan Zivkovic        for (final String dictType : dictionariesToCheck) {
668107fb4c476779df16be23e245547253978c197acDan Zivkovic            final Dictionary dictionary = mDictionaryGroup.getDict(dictType);
669107fb4c476779df16be23e245547253978c197acDan Zivkovic            // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
670107fb4c476779df16be23e245547253978c197acDan Zivkovic            // would be immutable once it's finished initializing, but concretely a null test is
671107fb4c476779df16be23e245547253978c197acDan Zivkovic            // probably good enough for the time being.
672107fb4c476779df16be23e245547253978c197acDan Zivkovic            if (null == dictionary) continue;
673107fb4c476779df16be23e245547253978c197acDan Zivkovic            if (dictionary.isValidWord(word)) {
674107fb4c476779df16be23e245547253978c197acDan Zivkovic                return true;
675ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            }
676ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
677ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        return false;
678ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
679ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
6809a289da4e6ea40417422a540e821069d5d6e4a82Dan Zivkovic    private int getFrequency(final String word) {
681ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        if (TextUtils.isEmpty(word)) {
682ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            return Dictionary.NOT_A_PROBABILITY;
683ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
684ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        int maxFreq = Dictionary.NOT_A_PROBABILITY;
685107fb4c476779df16be23e245547253978c197acDan Zivkovic        for (final String dictType : ALL_DICTIONARY_TYPES) {
686107fb4c476779df16be23e245547253978c197acDan Zivkovic            final Dictionary dictionary = mDictionaryGroup.getDict(dictType);
687107fb4c476779df16be23e245547253978c197acDan Zivkovic            if (dictionary == null) continue;
688107fb4c476779df16be23e245547253978c197acDan Zivkovic            final int tempFreq = dictionary.getFrequency(word);
689107fb4c476779df16be23e245547253978c197acDan Zivkovic            if (tempFreq >= maxFreq) {
690107fb4c476779df16be23e245547253978c197acDan Zivkovic                maxFreq = tempFreq;
691ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev            }
692ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
693ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        return maxFreq;
694ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
695ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
696f22571e2d9756294fa2fa332a395c06010f54d92Dan Zivkovic    private boolean clearSubDictionary(final String dictName) {
697107fb4c476779df16be23e245547253978c197acDan Zivkovic        final ExpandableBinaryDictionary dictionary = mDictionaryGroup.getSubDict(dictName);
698f22571e2d9756294fa2fa332a395c06010f54d92Dan Zivkovic        if (dictionary == null) {
699f22571e2d9756294fa2fa332a395c06010f54d92Dan Zivkovic            return false;
700ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
701f22571e2d9756294fa2fa332a395c06010f54d92Dan Zivkovic        dictionary.clear();
702f22571e2d9756294fa2fa332a395c06010f54d92Dan Zivkovic        return true;
703ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
704ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
70583e9a29c3379e9b5a304cffe5ddcbd1188cfa677Jatin Matani    @Override
706f22571e2d9756294fa2fa332a395c06010f54d92Dan Zivkovic    public boolean clearUserHistoryDictionary(final Context context) {
707f22571e2d9756294fa2fa332a395c06010f54d92Dan Zivkovic        return clearSubDictionary(Dictionary.TYPE_USER_HISTORY);
708ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
709ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
71083e9a29c3379e9b5a304cffe5ddcbd1188cfa677Jatin Matani    @Override
711ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    public void dumpDictionaryForDebug(final String dictName) {
712107fb4c476779df16be23e245547253978c197acDan Zivkovic        final ExpandableBinaryDictionary dictToDump = mDictionaryGroup.getSubDict(dictName);
713107fb4c476779df16be23e245547253978c197acDan Zivkovic        if (dictToDump == null) {
714107fb4c476779df16be23e245547253978c197acDan Zivkovic            Log.e(TAG, "Cannot dump " + dictName + ". "
715107fb4c476779df16be23e245547253978c197acDan Zivkovic                    + "The dictionary is not being used for suggestion or cannot be dumped.");
716107fb4c476779df16be23e245547253978c197acDan Zivkovic            return;
717ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
718107fb4c476779df16be23e245547253978c197acDan Zivkovic        dictToDump.dumpAllWordsForDebug();
719ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
720ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev
72183e9a29c3379e9b5a304cffe5ddcbd1188cfa677Jatin Matani    @Override
72272278d30478335b914b2a9758fda0131bde9c70eDan Zivkovic    @Nonnull public List<DictionaryStats> getDictionaryStats(final Context context) {
72372278d30478335b914b2a9758fda0131bde9c70eDan Zivkovic        final ArrayList<DictionaryStats> statsOfEnabledSubDicts = new ArrayList<>();
724107fb4c476779df16be23e245547253978c197acDan Zivkovic        for (final String dictType : DYNAMIC_DICTIONARY_TYPES) {
725107fb4c476779df16be23e245547253978c197acDan Zivkovic            final ExpandableBinaryDictionary dictionary = mDictionaryGroup.getSubDict(dictType);
726107fb4c476779df16be23e245547253978c197acDan Zivkovic            if (dictionary == null) continue;
72772278d30478335b914b2a9758fda0131bde9c70eDan Zivkovic            statsOfEnabledSubDicts.add(dictionary.getDictionaryStats());
728ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        }
729ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev        return statsOfEnabledSubDicts;
730ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev    }
731c809dc26a1e5667de6568ae1ef8c64269f884a4fChieu Nguyen
732c809dc26a1e5667de6568ae1ef8c64269f884a4fChieu Nguyen    @Override
733c809dc26a1e5667de6568ae1ef8c64269f884a4fChieu Nguyen    public String dump(final Context context) {
734c809dc26a1e5667de6568ae1ef8c64269f884a4fChieu Nguyen        return "";
735c809dc26a1e5667de6568ae1ef8c64269f884a4fChieu Nguyen    }
736ab6f3b36d0303bc4cc1ad0fbbc72a64ca2df0eb2Mario Tanev}
737