1022c1cc20379767966f4915e2dea65fc0b67c0d8satok/* 2022c1cc20379767966f4915e2dea65fc0b67c0d8satok * Copyright (C) 2011 The Android Open Source Project 3022c1cc20379767966f4915e2dea65fc0b67c0d8satok * 48aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * Licensed under the Apache License, Version 2.0 (the "License"); 58aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * you may not use this file except in compliance with the License. 68aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * You may obtain a copy of the License at 7022c1cc20379767966f4915e2dea65fc0b67c0d8satok * 88aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * http://www.apache.org/licenses/LICENSE-2.0 9022c1cc20379767966f4915e2dea65fc0b67c0d8satok * 10022c1cc20379767966f4915e2dea65fc0b67c0d8satok * Unless required by applicable law or agreed to in writing, software 118aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * distributed under the License is distributed on an "AS IS" BASIS, 128aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 138aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * See the License for the specific language governing permissions and 148aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * limitations under the License. 15022c1cc20379767966f4915e2dea65fc0b67c0d8satok */ 16022c1cc20379767966f4915e2dea65fc0b67c0d8satok 17022c1cc20379767966f4915e2dea65fc0b67c0d8satokpackage com.android.inputmethod.latin.spellcheck; 18022c1cc20379767966f4915e2dea65fc0b67c0d8satok 19c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalardimport android.content.Intent; 20db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalardimport android.content.SharedPreferences; 21db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalardimport android.preference.PreferenceManager; 22022c1cc20379767966f4915e2dea65fc0b67c0d8satokimport android.service.textservice.SpellCheckerService; 23204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaokaimport android.text.InputType; 24a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalardimport android.util.Log; 25204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaokaimport android.view.inputmethod.EditorInfo; 26204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaokaimport android.view.inputmethod.InputMethodSubtype; 27022c1cc20379767966f4915e2dea65fc0b67c0d8satokimport android.view.textservice.SuggestionsInfo; 28022c1cc20379767966f4915e2dea65fc0b67c0d8satok 29244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataokaimport com.android.inputmethod.keyboard.KeyboardLayoutSet; 30673cebf9e97289b3b0cd343ff7193dff69684a48Jean Chalardimport com.android.inputmethod.latin.BinaryDictionary; 3167fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalardimport com.android.inputmethod.latin.ContactsBinaryDictionary; 323234123fba901243990972158d023a5d1c273316Jean Chalardimport com.android.inputmethod.latin.Dictionary; 33150bad6fd4b401177c480acf5640b4db0f821886Jean Chalardimport com.android.inputmethod.latin.DictionaryCollection; 343234123fba901243990972158d023a5d1c273316Jean Chalardimport com.android.inputmethod.latin.DictionaryFactory; 3559b501a05078e5a9de7cdace19c51ca693076a17Jean Chalardimport com.android.inputmethod.latin.R; 3618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyangimport com.android.inputmethod.latin.SynchronouslyLoadedContactsBinaryDictionary; 37f6adff6227a15af105dbf39c57213a24bf16780bTom Ouyangimport com.android.inputmethod.latin.SynchronouslyLoadedUserBinaryDictionary; 3867fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalardimport com.android.inputmethod.latin.UserBinaryDictionary; 39204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaokaimport com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; 40e28eba5074664d5716b8e58b8d0a235746b261ebKen Wakasaimport com.android.inputmethod.latin.utils.CollectionUtils; 41e28eba5074664d5716b8e58b8d0a235746b261ebKen Wakasaimport com.android.inputmethod.latin.utils.LocaleUtils; 42e28eba5074664d5716b8e58b8d0a235746b261ebKen Wakasaimport com.android.inputmethod.latin.utils.StringUtils; 433234123fba901243990972158d023a5d1c273316Jean Chalard 44db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalardimport java.lang.ref.WeakReference; 456b166a193398554694cb680f704c2ffc23d03a0eJean Chalardimport java.util.ArrayList; 46f098fbbef324df034cc04de04d9b5fe6657238c7Jean Chalardimport java.util.Arrays; 473234123fba901243990972158d023a5d1c273316Jean Chalardimport java.util.Collections; 48cc8c8b99bd0463f5977dea82f5e2379ea1dd4e73Tadashi G. Takaokaimport java.util.HashSet; 49db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalardimport java.util.Iterator; 503234123fba901243990972158d023a5d1c273316Jean Chalardimport java.util.Locale; 513234123fba901243990972158d023a5d1c273316Jean Chalardimport java.util.Map; 523234123fba901243990972158d023a5d1c273316Jean Chalardimport java.util.TreeMap; 533234123fba901243990972158d023a5d1c273316Jean Chalard 54022c1cc20379767966f4915e2dea65fc0b67c0d8satok/** 55022c1cc20379767966f4915e2dea65fc0b67c0d8satok * Service for spell checking, using LatinIME's dictionaries and mechanisms. 56022c1cc20379767966f4915e2dea65fc0b67c0d8satok */ 57a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaokapublic final class AndroidSpellCheckerService extends SpellCheckerService 58db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard implements SharedPreferences.OnSharedPreferenceChangeListener { 59a90992e56244a914195daba3a2dd8a0e66e63384satok private static final String TAG = AndroidSpellCheckerService.class.getSimpleName(); 60a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard private static final boolean DBG = false; 61a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard private static final int POOL_SIZE = 2; 623234123fba901243990972158d023a5d1c273316Jean Chalard 63db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard public static final String PREF_USE_CONTACTS_KEY = "pref_spellcheck_use_contacts"; 64db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard 65204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka private static final int SPELLCHECKER_DUMMY_KEYBOARD_WIDTH = 480; 66204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka private static final int SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT = 368; 67204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka 686b166a193398554694cb680f704c2ffc23d03a0eJean Chalard private final static String[] EMPTY_STRING_ARRAY = new String[0]; 695f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka private Map<String, DictionaryPool> mDictionaryPools = CollectionUtils.newSynchronizedTreeMap(); 7067fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard private Map<String, UserBinaryDictionary> mUserDictionaries = 715f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka CollectionUtils.newSynchronizedTreeMap(); 7267fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard private ContactsBinaryDictionary mContactsDictionary; 733234123fba901243990972158d023a5d1c273316Jean Chalard 74a409f009fa410019ad10b1134ff57393443eba33Jean Chalard // The threshold for a suggestion to be considered "recommended". 750028ed3627ff4f37a62a80f3b2c857e373cd5090satok private float mRecommendedThreshold; 76db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard // Whether to use the contacts dictionary 77db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard private boolean mUseContactsDictionary; 78db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard private final Object mUseContactsLock = new Object(); 79db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard 80db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard private final HashSet<WeakReference<DictionaryCollection>> mDictionaryCollectionsList = 815f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka CollectionUtils.newHashSet(); 8259b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard 831830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard public static final int SCRIPT_LATIN = 0; 841830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard public static final int SCRIPT_CYRILLIC = 1; 85d8590857bdff7f30a93af07aef0362d9f7460a5aJean Chalard public static final int SCRIPT_GREEK = 2; 8684ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka public static final String SINGLE_QUOTE = "\u0027"; 8784ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka public static final String APOSTROPHE = "\u2019"; 881830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard private static final TreeMap<String, Integer> mLanguageToScript; 891830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard static { 901830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard // List of the supported languages and their associated script. We won't check 911830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard // words written in another script than the selected script, because we know we 921830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard // don't have those in our dictionary so we will underline everything and we 93b1f3c24c6326ad63b4fcad4014c20161984e40efJean Chalard // will never have any suggestions, so it makes no sense checking them, and this 94b1f3c24c6326ad63b4fcad4014c20161984e40efJean Chalard // is done in {@link #shouldFilterOut}. Also, the script is used to choose which 95b1f3c24c6326ad63b4fcad4014c20161984e40efJean Chalard // proximity to pass to the dictionary descent algorithm. 96b1f3c24c6326ad63b4fcad4014c20161984e40efJean Chalard // IMPORTANT: this only contains languages - do not write countries in there. 97b1f3c24c6326ad63b4fcad4014c20161984e40efJean Chalard // Only the language is searched from the map. 985f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka mLanguageToScript = CollectionUtils.newTreeMap(); 991830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard mLanguageToScript.put("cs", SCRIPT_LATIN); 100d8590857bdff7f30a93af07aef0362d9f7460a5aJean Chalard mLanguageToScript.put("da", SCRIPT_LATIN); 101d8590857bdff7f30a93af07aef0362d9f7460a5aJean Chalard mLanguageToScript.put("de", SCRIPT_LATIN); 102d8590857bdff7f30a93af07aef0362d9f7460a5aJean Chalard mLanguageToScript.put("el", SCRIPT_GREEK); 103d8590857bdff7f30a93af07aef0362d9f7460a5aJean Chalard mLanguageToScript.put("en", SCRIPT_LATIN); 1041830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard mLanguageToScript.put("es", SCRIPT_LATIN); 105d8590857bdff7f30a93af07aef0362d9f7460a5aJean Chalard mLanguageToScript.put("fi", SCRIPT_LATIN); 106d8590857bdff7f30a93af07aef0362d9f7460a5aJean Chalard mLanguageToScript.put("fr", SCRIPT_LATIN); 107d527a15ec44089930dd23c9e20b8672024a4555bJean Chalard mLanguageToScript.put("hr", SCRIPT_LATIN); 108d8590857bdff7f30a93af07aef0362d9f7460a5aJean Chalard mLanguageToScript.put("it", SCRIPT_LATIN); 109d8590857bdff7f30a93af07aef0362d9f7460a5aJean Chalard mLanguageToScript.put("lt", SCRIPT_LATIN); 110d8590857bdff7f30a93af07aef0362d9f7460a5aJean Chalard mLanguageToScript.put("lv", SCRIPT_LATIN); 111d8590857bdff7f30a93af07aef0362d9f7460a5aJean Chalard mLanguageToScript.put("nb", SCRIPT_LATIN); 112d8590857bdff7f30a93af07aef0362d9f7460a5aJean Chalard mLanguageToScript.put("nl", SCRIPT_LATIN); 113b1f3c24c6326ad63b4fcad4014c20161984e40efJean Chalard mLanguageToScript.put("pt", SCRIPT_LATIN); 114d8590857bdff7f30a93af07aef0362d9f7460a5aJean Chalard mLanguageToScript.put("sl", SCRIPT_LATIN); 1151830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard mLanguageToScript.put("ru", SCRIPT_CYRILLIC); 1161830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard } 1171830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard 11859b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard @Override public void onCreate() { 11959b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard super.onCreate(); 120a409f009fa410019ad10b1134ff57393443eba33Jean Chalard mRecommendedThreshold = 1210028ed3627ff4f37a62a80f3b2c857e373cd5090satok Float.parseFloat(getString(R.string.spellchecker_recommended_threshold_value)); 122db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 123db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard prefs.registerOnSharedPreferenceChangeListener(this); 124db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard onSharedPreferenceChanged(prefs, PREF_USE_CONTACTS_KEY); 125db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 126db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard 12784ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka public static int getScriptFromLocale(final Locale locale) { 1281830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard final Integer script = mLanguageToScript.get(locale.getLanguage()); 1291830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard if (null == script) { 1301830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard throw new RuntimeException("We have been called with an unsupported language: \"" 1311830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard + locale.getLanguage() + "\". Framework bug?"); 1321830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard } 1331830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard return script; 1341830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard } 1351830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard 136244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka private static String getKeyboardLayoutNameForScript(final int script) { 137244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka switch (script) { 138244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka case AndroidSpellCheckerService.SCRIPT_LATIN: 139244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka return "qwerty"; 140244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka case AndroidSpellCheckerService.SCRIPT_CYRILLIC: 141244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka return "east_slavic"; 142244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka case AndroidSpellCheckerService.SCRIPT_GREEK: 143244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka return "greek"; 144244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka default: 145244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka throw new RuntimeException("Wrong script supplied: " + script); 146244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka } 147244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka } 148244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka 149db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard @Override 150db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { 151db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (!PREF_USE_CONTACTS_KEY.equals(key)) return; 152db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard synchronized(mUseContactsLock) { 153db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard mUseContactsDictionary = prefs.getBoolean(PREF_USE_CONTACTS_KEY, true); 154db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (mUseContactsDictionary) { 155db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard startUsingContactsDictionaryLocked(); 156db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } else { 157db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard stopUsingContactsDictionaryLocked(); 158db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 159db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 160db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 161db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard 162db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard private void startUsingContactsDictionaryLocked() { 163db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (null == mContactsDictionary) { 16467fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard // TODO: use the right locale for each session 16567fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard mContactsDictionary = 16667fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard new SynchronouslyLoadedContactsBinaryDictionary(this, Locale.getDefault()); 167db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 168db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard final Iterator<WeakReference<DictionaryCollection>> iterator = 169db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard mDictionaryCollectionsList.iterator(); 170db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard while (iterator.hasNext()) { 171db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard final WeakReference<DictionaryCollection> dictRef = iterator.next(); 172db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard final DictionaryCollection dict = dictRef.get(); 173db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (null == dict) { 174db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard iterator.remove(); 175db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } else { 176db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard dict.addDictionary(mContactsDictionary); 177db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 178db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 179db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 180db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard 181db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard private void stopUsingContactsDictionaryLocked() { 182db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (null == mContactsDictionary) return; 18318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang final Dictionary contactsDict = mContactsDictionary; 18418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang // TODO: revert to the concrete type when USE_BINARY_CONTACTS_DICTIONARY is no longer needed 185db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard mContactsDictionary = null; 186db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard final Iterator<WeakReference<DictionaryCollection>> iterator = 187db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard mDictionaryCollectionsList.iterator(); 188db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard while (iterator.hasNext()) { 189db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard final WeakReference<DictionaryCollection> dictRef = iterator.next(); 190db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard final DictionaryCollection dict = dictRef.get(); 191db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (null == dict) { 192db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard iterator.remove(); 193db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } else { 194db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard dict.removeDictionary(contactsDict); 195db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 196db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 197db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard contactsDict.close(); 19859b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard } 19959b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard 2005bcf8ee66ceb38675a6b70fefcb574978e0fae92satok @Override 2015bcf8ee66ceb38675a6b70fefcb574978e0fae92satok public Session createSession() { 20237b19ffe6c9d8335cc0e1c1c50f5b08c778b287cSatoshi Kataoka // Should not refer to AndroidSpellCheckerSession directly considering 20337b19ffe6c9d8335cc0e1c1c50f5b08c778b287cSatoshi Kataoka // that AndroidSpellCheckerSession may be overlaid. 20437b19ffe6c9d8335cc0e1c1c50f5b08c778b287cSatoshi Kataoka return AndroidSpellCheckerSessionFactory.newInstance(this); 2055bcf8ee66ceb38675a6b70fefcb574978e0fae92satok } 2065bcf8ee66ceb38675a6b70fefcb574978e0fae92satok 207df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard /** 208df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard * Returns an empty SuggestionsInfo with flags signaling the word is not in the dictionary. 209df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard * @param reportAsTypo whether this should include the flag LOOKS_LIKE_TYPO, for red underline. 210df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard * @return the empty SuggestionsInfo with the appropriate flags set. 211df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard */ 212df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard public static SuggestionsInfo getNotInDictEmptySuggestions(final boolean reportAsTypo) { 213df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard return new SuggestionsInfo(reportAsTypo ? SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO : 0, 214df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard EMPTY_STRING_ARRAY); 215cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard } 216cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard 217df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard /** 218df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard * Returns an empty suggestionInfo with flags signaling the word is in the dictionary. 219df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard * @return the empty SuggestionsInfo with the appropriate flags set. 220df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard */ 22184ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka public static SuggestionsInfo getInDictEmptySuggestions() { 222cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard return new SuggestionsInfo(SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY, 223cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard EMPTY_STRING_ARRAY); 224cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard } 225cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard 22684ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka public SuggestionsGatherer newSuggestionsGatherer(final String text, int maxLength) { 227d5781eef628c2cd4ac38029040746daa4679d637Satoshi Kataoka return new SuggestionsGatherer(text, mRecommendedThreshold, maxLength); 22884ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka } 22984ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka 23061e7ec658710eca3fd03af39b52b4a87eabcdd4cJean Chalard // TODO: remove this class and replace it by storage local to the session. 231a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaoka public static final class SuggestionsGatherer { 232a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaoka public static final class Result { 23359b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard public final String[] mSuggestions; 234a409f009fa410019ad10b1134ff57393443eba33Jean Chalard public final boolean mHasRecommendedSuggestions; 235a409f009fa410019ad10b1134ff57393443eba33Jean Chalard public Result(final String[] gatheredSuggestions, 236a409f009fa410019ad10b1134ff57393443eba33Jean Chalard final boolean hasRecommendedSuggestions) { 23759b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard mSuggestions = gatheredSuggestions; 238a409f009fa410019ad10b1134ff57393443eba33Jean Chalard mHasRecommendedSuggestions = hasRecommendedSuggestions; 23959b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard } 24059b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard } 24159b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard 242bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka private final ArrayList<String> mSuggestions; 2433234123fba901243990972158d023a5d1c273316Jean Chalard private final int[] mScores; 24485782abaf178f6aafa1f8999123ff540f04c17bcJean Chalard private final String mOriginalText; 2450028ed3627ff4f37a62a80f3b2c857e373cd5090satok private final float mRecommendedThreshold; 2463234123fba901243990972158d023a5d1c273316Jean Chalard private final int mMaxLength; 2473234123fba901243990972158d023a5d1c273316Jean Chalard private int mLength = 0; 24859b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard 24959b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard // The two following attributes are only ever filled if the requested max length 25059b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard // is 0 (or less, which is treated the same). 25159b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard private String mBestSuggestion = null; 25259b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard private int mBestScore = Integer.MIN_VALUE; // As small as possible 2533234123fba901243990972158d023a5d1c273316Jean Chalard 254d5781eef628c2cd4ac38029040746daa4679d637Satoshi Kataoka SuggestionsGatherer(final String originalText, final float recommendedThreshold, 255d5781eef628c2cd4ac38029040746daa4679d637Satoshi Kataoka final int maxLength) { 25685782abaf178f6aafa1f8999123ff540f04c17bcJean Chalard mOriginalText = originalText; 257a409f009fa410019ad10b1134ff57393443eba33Jean Chalard mRecommendedThreshold = recommendedThreshold; 2583234123fba901243990972158d023a5d1c273316Jean Chalard mMaxLength = maxLength; 2595f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka mSuggestions = CollectionUtils.newArrayList(maxLength + 1); 2603234123fba901243990972158d023a5d1c273316Jean Chalard mScores = new int[mMaxLength]; 2613234123fba901243990972158d023a5d1c273316Jean Chalard } 2623234123fba901243990972158d023a5d1c273316Jean Chalard 2630a7944653105f257d99e9db2d90b2bfc932ee765Jean Chalard synchronized public boolean addWord(char[] word, int[] spaceIndices, int wordOffset, 2640a7944653105f257d99e9db2d90b2bfc932ee765Jean Chalard int wordLength, int score) { 265672635493e1dc2baf9fd4a94e73c5b06d0450e7eKen Wakasa final int positionIndex = Arrays.binarySearch(mScores, 0, mLength, score); 2663234123fba901243990972158d023a5d1c273316Jean Chalard // binarySearch returns the index if the element exists, and -<insertion index> - 1 2673234123fba901243990972158d023a5d1c273316Jean Chalard // if it doesn't. See documentation for binarySearch. 2683234123fba901243990972158d023a5d1c273316Jean Chalard final int insertIndex = positionIndex >= 0 ? positionIndex : -positionIndex - 1; 2693234123fba901243990972158d023a5d1c273316Jean Chalard 2704609c02f9e61370557fee675c67263160fbf7feeJean Chalard if (insertIndex == 0 && mLength >= mMaxLength) { 2714609c02f9e61370557fee675c67263160fbf7feeJean Chalard // In the future, we may want to keep track of the best suggestion score even if 2724609c02f9e61370557fee675c67263160fbf7feeJean Chalard // we are asked for 0 suggestions. In this case, we can use the following 2734609c02f9e61370557fee675c67263160fbf7feeJean Chalard // (tested) code to keep it: 2744609c02f9e61370557fee675c67263160fbf7feeJean Chalard // If the maxLength is 0 (should never be less, but if it is, it's treated as 0) 2754609c02f9e61370557fee675c67263160fbf7feeJean Chalard // then we need to keep track of the best suggestion in mBestScore and 2764609c02f9e61370557fee675c67263160fbf7feeJean Chalard // mBestSuggestion. This is so that we know whether the best suggestion makes 2774609c02f9e61370557fee675c67263160fbf7feeJean Chalard // the score cutoff, since we need to know that to return a meaningful 2784609c02f9e61370557fee675c67263160fbf7feeJean Chalard // looksLikeTypo. 2794609c02f9e61370557fee675c67263160fbf7feeJean Chalard // if (0 >= mMaxLength) { 2804609c02f9e61370557fee675c67263160fbf7feeJean Chalard // if (score > mBestScore) { 2814609c02f9e61370557fee675c67263160fbf7feeJean Chalard // mBestScore = score; 2824609c02f9e61370557fee675c67263160fbf7feeJean Chalard // mBestSuggestion = new String(word, wordOffset, wordLength); 2834609c02f9e61370557fee675c67263160fbf7feeJean Chalard // } 2844609c02f9e61370557fee675c67263160fbf7feeJean Chalard // } 2854609c02f9e61370557fee675c67263160fbf7feeJean Chalard return true; 2864609c02f9e61370557fee675c67263160fbf7feeJean Chalard } 287c53661f152f2d676f8cec656cbdd93adfa7fc908Jean Chalard if (insertIndex >= mMaxLength) { 288c53661f152f2d676f8cec656cbdd93adfa7fc908Jean Chalard // We found a suggestion, but its score is too weak to be kept considering 289c53661f152f2d676f8cec656cbdd93adfa7fc908Jean Chalard // the suggestion limit. 290c53661f152f2d676f8cec656cbdd93adfa7fc908Jean Chalard return true; 291c53661f152f2d676f8cec656cbdd93adfa7fc908Jean Chalard } 2924609c02f9e61370557fee675c67263160fbf7feeJean Chalard 2934609c02f9e61370557fee675c67263160fbf7feeJean Chalard final String wordString = new String(word, wordOffset, wordLength); 2943234123fba901243990972158d023a5d1c273316Jean Chalard if (mLength < mMaxLength) { 2953234123fba901243990972158d023a5d1c273316Jean Chalard final int copyLen = mLength - insertIndex; 2963234123fba901243990972158d023a5d1c273316Jean Chalard ++mLength; 2973234123fba901243990972158d023a5d1c273316Jean Chalard System.arraycopy(mScores, insertIndex, mScores, insertIndex + 1, copyLen); 2984609c02f9e61370557fee675c67263160fbf7feeJean Chalard mSuggestions.add(insertIndex, wordString); 2993234123fba901243990972158d023a5d1c273316Jean Chalard } else { 3003234123fba901243990972158d023a5d1c273316Jean Chalard System.arraycopy(mScores, 1, mScores, 0, insertIndex); 3014609c02f9e61370557fee675c67263160fbf7feeJean Chalard mSuggestions.add(insertIndex, wordString); 3026b166a193398554694cb680f704c2ffc23d03a0eJean Chalard mSuggestions.remove(0); 3033234123fba901243990972158d023a5d1c273316Jean Chalard } 3043234123fba901243990972158d023a5d1c273316Jean Chalard mScores[insertIndex] = score; 3053234123fba901243990972158d023a5d1c273316Jean Chalard 3063234123fba901243990972158d023a5d1c273316Jean Chalard return true; 3073234123fba901243990972158d023a5d1c273316Jean Chalard } 3083234123fba901243990972158d023a5d1c273316Jean Chalard 30985782abaf178f6aafa1f8999123ff540f04c17bcJean Chalard public Result getResults(final int capitalizeType, final Locale locale) { 31059b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard final String[] gatheredSuggestions; 311a409f009fa410019ad10b1134ff57393443eba33Jean Chalard final boolean hasRecommendedSuggestions; 31259b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard if (0 == mLength) { 313f1b464da31365c112877a35dff849daee1dbb88aJean Chalard // TODO: the comment below describes what is intended, but in the practice 314f1b464da31365c112877a35dff849daee1dbb88aJean Chalard // mBestSuggestion is only ever set to null so it doesn't work. Fix this. 31559b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard // Either we found no suggestions, or we found some BUT the max length was 0. 31659b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard // If we found some mBestSuggestion will not be null. If it is null, then 31759b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard // we found none, regardless of the max length. 31859b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard if (null == mBestSuggestion) { 31959b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard gatheredSuggestions = null; 320a409f009fa410019ad10b1134ff57393443eba33Jean Chalard hasRecommendedSuggestions = false; 32159b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard } else { 32259b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard gatheredSuggestions = EMPTY_STRING_ARRAY; 3230028ed3627ff4f37a62a80f3b2c857e373cd5090satok final float normalizedScore = BinaryDictionary.calcNormalizedScore( 324be0cf72253f15bff6abdeaa79f60a56f06ab7b86satok mOriginalText, mBestSuggestion, mBestScore); 325a409f009fa410019ad10b1134ff57393443eba33Jean Chalard hasRecommendedSuggestions = (normalizedScore > mRecommendedThreshold); 32659b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard } 32759b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard } else { 32859b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard if (DBG) { 32959b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard if (mLength != mSuggestions.size()) { 33059b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard Log.e(TAG, "Suggestion size is not the same as stored mLength"); 33159b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard } 332af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard for (int i = mLength - 1; i >= 0; --i) { 333af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard Log.i(TAG, "" + mScores[i] + " " + mSuggestions.get(i)); 334af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard } 3356b166a193398554694cb680f704c2ffc23d03a0eJean Chalard } 33659b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard Collections.reverse(mSuggestions); 337cc8c8b99bd0463f5977dea82f5e2379ea1dd4e73Tadashi G. Takaoka StringUtils.removeDupes(mSuggestions); 3387d3836d63a2eb4b79c4ad93cdae4f1f61cdb518eJean Chalard if (StringUtils.CAPITALIZE_ALL == capitalizeType) { 339f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard for (int i = 0; i < mSuggestions.size(); ++i) { 340f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard // get(i) returns a CharSequence which is actually a String so .toString() 341f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard // should return the same object. 342f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard mSuggestions.set(i, mSuggestions.get(i).toString().toUpperCase(locale)); 343f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard } 3447d3836d63a2eb4b79c4ad93cdae4f1f61cdb518eJean Chalard } else if (StringUtils.CAPITALIZE_FIRST == capitalizeType) { 345f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard for (int i = 0; i < mSuggestions.size(); ++i) { 346f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard // Likewise 34799b93d17d53c2d587c45373831b327f7851ec0a8Jean Chalard mSuggestions.set(i, StringUtils.capitalizeFirstCodePoint( 3483bf57a5624679a20db26df912077a53b9f90ad36Tadashi G. Takaoka mSuggestions.get(i).toString(), locale)); 349f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard } 350f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard } 35159b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard // This returns a String[], while toArray() returns an Object[] which cannot be cast 35259b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard // into a String[]. 35359b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard gatheredSuggestions = mSuggestions.toArray(EMPTY_STRING_ARRAY); 35459b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard 355af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard final int bestScore = mScores[mLength - 1]; 356bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka final String bestSuggestion = mSuggestions.get(0); 3570028ed3627ff4f37a62a80f3b2c857e373cd5090satok final float normalizedScore = 358be0cf72253f15bff6abdeaa79f60a56f06ab7b86satok BinaryDictionary.calcNormalizedScore( 359be0cf72253f15bff6abdeaa79f60a56f06ab7b86satok mOriginalText, bestSuggestion.toString(), bestScore); 360a409f009fa410019ad10b1134ff57393443eba33Jean Chalard hasRecommendedSuggestions = (normalizedScore > mRecommendedThreshold); 361af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard if (DBG) { 362af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard Log.i(TAG, "Best suggestion : " + bestSuggestion + ", score " + bestScore); 3634609c02f9e61370557fee675c67263160fbf7feeJean Chalard Log.i(TAG, "Normalized score = " + normalizedScore 364a409f009fa410019ad10b1134ff57393443eba33Jean Chalard + " (threshold " + mRecommendedThreshold 365a409f009fa410019ad10b1134ff57393443eba33Jean Chalard + ") => hasRecommendedSuggestions = " + hasRecommendedSuggestions); 366af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard } 3673234123fba901243990972158d023a5d1c273316Jean Chalard } 368a409f009fa410019ad10b1134ff57393443eba33Jean Chalard return new Result(gatheredSuggestions, hasRecommendedSuggestions); 3693234123fba901243990972158d023a5d1c273316Jean Chalard } 3703234123fba901243990972158d023a5d1c273316Jean Chalard } 3713234123fba901243990972158d023a5d1c273316Jean Chalard 372c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard @Override 373c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard public boolean onUnbind(final Intent intent) { 3748403611960cd0b2a40b77275c536e8088c098830Jean Chalard closeAllDictionaries(); 3758403611960cd0b2a40b77275c536e8088c098830Jean Chalard return false; 3768403611960cd0b2a40b77275c536e8088c098830Jean Chalard } 3778403611960cd0b2a40b77275c536e8088c098830Jean Chalard 3788403611960cd0b2a40b77275c536e8088c098830Jean Chalard private void closeAllDictionaries() { 379c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard final Map<String, DictionaryPool> oldPools = mDictionaryPools; 3805f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka mDictionaryPools = CollectionUtils.newSynchronizedTreeMap(); 38167fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard final Map<String, UserBinaryDictionary> oldUserDictionaries = mUserDictionaries; 3825f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka mUserDictionaries = CollectionUtils.newSynchronizedTreeMap(); 383769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard new Thread("spellchecker_close_dicts") { 384769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard @Override 385769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard public void run() { 386769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard for (DictionaryPool pool : oldPools.values()) { 387769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard pool.close(); 388769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard } 389769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard for (Dictionary dict : oldUserDictionaries.values()) { 390769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard dict.close(); 391769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard } 392769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard synchronized (mUseContactsLock) { 393769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard if (null != mContactsDictionary) { 394769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard // The synchronously loaded contacts dictionary should have been in one 395769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard // or several pools, but it is shielded against multiple closing and it's 396769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard // safe to call it several times. 39767fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard final ContactsBinaryDictionary dictToClose = mContactsDictionary; 398769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard // TODO: revert to the concrete type when USE_BINARY_CONTACTS_DICTIONARY 399769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard // is no longer needed 400769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard mContactsDictionary = null; 401769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard dictToClose.close(); 402769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard } 403769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard } 404db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 405769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard }.start(); 406c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard } 407c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard 40884ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka public DictionaryPool getDictionaryPool(final String locale) { 409a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard DictionaryPool pool = mDictionaryPools.get(locale); 410a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard if (null == pool) { 411ef35cb631c45c8b106fe7ed9e0d1178c3e5fb963Jean Chalard final Locale localeObject = LocaleUtils.constructLocaleFromString(locale); 412a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard pool = new DictionaryPool(POOL_SIZE, this, localeObject); 413a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard mDictionaryPools.put(locale, pool); 4143234123fba901243990972158d023a5d1c273316Jean Chalard } 415a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard return pool; 416a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard } 417a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard 418244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka public DictAndKeyboard createDictAndKeyboard(final Locale locale) { 4191830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard final int script = getScriptFromLocale(locale); 420244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka final String keyboardLayoutName = getKeyboardLayoutNameForScript(script); 421204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka final InputMethodSubtype subtype = AdditionalSubtypeUtils.createAdditionalSubtype( 422204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka locale.toString(), keyboardLayoutName, null); 423204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka final KeyboardLayoutSet keyboardLayoutSet = createKeyboardSetForSpellChecker(subtype); 424244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka 425150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard final DictionaryCollection dictionaryCollection = 426f0e12a969974987f1b97929886c6ebe6a685c538Jean Chalard DictionaryFactory.createMainDictionaryFromManager(this, locale, 42724aee9100e92dc4c06cdb54487a4922420fa8660Jean Chalard true /* useFullEditDistance */); 428150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard final String localeStr = locale.toString(); 42967fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard UserBinaryDictionary userDictionary = mUserDictionaries.get(localeStr); 430fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard if (null == userDictionary) { 43167fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard userDictionary = new SynchronouslyLoadedUserBinaryDictionary(this, localeStr, true); 432fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard mUserDictionaries.put(localeStr, userDictionary); 433fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard } 434fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard dictionaryCollection.addDictionary(userDictionary); 43518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang synchronized (mUseContactsLock) { 436db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (mUseContactsDictionary) { 437db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (null == mContactsDictionary) { 43867fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard // TODO: use the right locale. We can't do it right now because the 43967fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard // spell checker is reusing the contacts dictionary across sessions 44067fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard // without regard for their locale, so we need to fix that first. 44167fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard mContactsDictionary = new SynchronouslyLoadedContactsBinaryDictionary(this, 44267fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard Locale.getDefault()); 443db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 444db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 445db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard dictionaryCollection.addDictionary(mContactsDictionary); 446db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard mDictionaryCollectionsList.add( 447db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard new WeakReference<DictionaryCollection>(dictionaryCollection)); 4482e3c6da8688a907024d4d8e0f2db3e0ed4fab8dbJean Chalard } 449244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka return new DictAndKeyboard(dictionaryCollection, keyboardLayoutSet); 4503234123fba901243990972158d023a5d1c273316Jean Chalard } 451204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka 452204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka private KeyboardLayoutSet createKeyboardSetForSpellChecker(final InputMethodSubtype subtype) { 453204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka final EditorInfo editorInfo = new EditorInfo(); 454204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka editorInfo.inputType = InputType.TYPE_CLASS_TEXT; 455204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(this, editorInfo); 456204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka builder.setKeyboardGeometry( 457204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka SPELLCHECKER_DUMMY_KEYBOARD_WIDTH, SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT); 458204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka builder.setSubtype(subtype); 459204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka builder.setIsSpellChecker(true /* isSpellChecker */); 460204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka builder.disableTouchPositionCorrectionData(); 461204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka return builder.build(); 462204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka } 463022c1cc20379767966f4915e2dea65fc0b67c0d8satok} 464