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