AndroidSpellCheckerService.java revision d527a15ec44089930dd23c9e20b8672024a4555b
1022c1cc20379767966f4915e2dea65fc0b67c0d8satok/* 2022c1cc20379767966f4915e2dea65fc0b67c0d8satok * Copyright (C) 2011 The Android Open Source Project 3022c1cc20379767966f4915e2dea65fc0b67c0d8satok * 4022c1cc20379767966f4915e2dea65fc0b67c0d8satok * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5022c1cc20379767966f4915e2dea65fc0b67c0d8satok * use this file except in compliance with the License. You may obtain a copy of 6022c1cc20379767966f4915e2dea65fc0b67c0d8satok * the License at 7022c1cc20379767966f4915e2dea65fc0b67c0d8satok * 8022c1cc20379767966f4915e2dea65fc0b67c0d8satok * http://www.apache.org/licenses/LICENSE-2.0 9022c1cc20379767966f4915e2dea65fc0b67c0d8satok * 10022c1cc20379767966f4915e2dea65fc0b67c0d8satok * Unless required by applicable law or agreed to in writing, software 11022c1cc20379767966f4915e2dea65fc0b67c0d8satok * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12022c1cc20379767966f4915e2dea65fc0b67c0d8satok * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13022c1cc20379767966f4915e2dea65fc0b67c0d8satok * License for the specific language governing permissions and limitations under 14022c1cc20379767966f4915e2dea65fc0b67c0d8satok * 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; 23ab72a97d7ce44230a0c824797d1675a5ca354a56Tadashi G. Takaokaimport android.text.TextUtils; 24a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalardimport android.util.Log; 2574a84febc76d1ec6c0b6d8afbf50349da9b38d74satokimport android.util.LruCache; 265434f46481c6331c3f107e6940cb49ba9dd5ea4dsatokimport android.view.textservice.SentenceSuggestionsInfo; 27022c1cc20379767966f4915e2dea65fc0b67c0d8satokimport android.view.textservice.SuggestionsInfo; 28022c1cc20379767966f4915e2dea65fc0b67c0d8satokimport android.view.textservice.TextInfo; 29022c1cc20379767966f4915e2dea65fc0b67c0d8satok 309260422423819ed6942f11c03960d5764e97c262Ken Wakasaimport com.android.inputmethod.compat.SuggestionsInfoCompatUtils; 313234123fba901243990972158d023a5d1c273316Jean Chalardimport com.android.inputmethod.keyboard.ProximityInfo; 32673cebf9e97289b3b0cd343ff7193dff69684a48Jean Chalardimport com.android.inputmethod.latin.BinaryDictionary; 333234123fba901243990972158d023a5d1c273316Jean Chalardimport com.android.inputmethod.latin.Dictionary; 343234123fba901243990972158d023a5d1c273316Jean Chalardimport com.android.inputmethod.latin.Dictionary.WordCallback; 35150bad6fd4b401177c480acf5640b4db0f821886Jean Chalardimport com.android.inputmethod.latin.DictionaryCollection; 363234123fba901243990972158d023a5d1c273316Jean Chalardimport com.android.inputmethod.latin.DictionaryFactory; 3718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyangimport com.android.inputmethod.latin.LatinIME; 38ef35cb631c45c8b106fe7ed9e0d1178c3e5fb963Jean Chalardimport com.android.inputmethod.latin.LocaleUtils; 3959b501a05078e5a9de7cdace19c51ca693076a17Jean Chalardimport com.android.inputmethod.latin.R; 40cc8c8b99bd0463f5977dea82f5e2379ea1dd4e73Tadashi G. Takaokaimport com.android.inputmethod.latin.StringUtils; 4118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyangimport com.android.inputmethod.latin.SynchronouslyLoadedContactsBinaryDictionary; 422e3c6da8688a907024d4d8e0f2db3e0ed4fab8dbJean Chalardimport com.android.inputmethod.latin.SynchronouslyLoadedContactsDictionary; 43f6adff6227a15af105dbf39c57213a24bf16780bTom Ouyangimport com.android.inputmethod.latin.SynchronouslyLoadedUserBinaryDictionary; 44f019d505d7da97c03c321eef02c4879c4e0448f6Jean Chalardimport com.android.inputmethod.latin.SynchronouslyLoadedUserDictionary; 45fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalardimport com.android.inputmethod.latin.WhitelistDictionary; 463234123fba901243990972158d023a5d1c273316Jean Chalardimport com.android.inputmethod.latin.WordComposer; 473234123fba901243990972158d023a5d1c273316Jean Chalard 48db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalardimport java.lang.ref.WeakReference; 496b166a193398554694cb680f704c2ffc23d03a0eJean Chalardimport java.util.ArrayList; 50f098fbbef324df034cc04de04d9b5fe6657238c7Jean Chalardimport java.util.Arrays; 513234123fba901243990972158d023a5d1c273316Jean Chalardimport java.util.Collections; 52cc8c8b99bd0463f5977dea82f5e2379ea1dd4e73Tadashi G. Takaokaimport java.util.HashSet; 53db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalardimport java.util.Iterator; 543234123fba901243990972158d023a5d1c273316Jean Chalardimport java.util.Locale; 553234123fba901243990972158d023a5d1c273316Jean Chalardimport java.util.Map; 563234123fba901243990972158d023a5d1c273316Jean Chalardimport java.util.TreeMap; 573234123fba901243990972158d023a5d1c273316Jean Chalard 58022c1cc20379767966f4915e2dea65fc0b67c0d8satok/** 59022c1cc20379767966f4915e2dea65fc0b67c0d8satok * Service for spell checking, using LatinIME's dictionaries and mechanisms. 60022c1cc20379767966f4915e2dea65fc0b67c0d8satok */ 61db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalardpublic class AndroidSpellCheckerService extends SpellCheckerService 62db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard implements SharedPreferences.OnSharedPreferenceChangeListener { 63a90992e56244a914195daba3a2dd8a0e66e63384satok private static final String TAG = AndroidSpellCheckerService.class.getSimpleName(); 64a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard private static final boolean DBG = false; 65a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard private static final int POOL_SIZE = 2; 663234123fba901243990972158d023a5d1c273316Jean Chalard 67db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard public static final String PREF_USE_CONTACTS_KEY = "pref_spellcheck_use_contacts"; 68db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard 69f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard private static final int CAPITALIZE_NONE = 0; // No caps, or mixed case 70f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard private static final int CAPITALIZE_FIRST = 1; // First only 71f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard private static final int CAPITALIZE_ALL = 2; // All caps 72f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard 736b166a193398554694cb680f704c2ffc23d03a0eJean Chalard private final static String[] EMPTY_STRING_ARRAY = new String[0]; 74c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard private Map<String, DictionaryPool> mDictionaryPools = 75a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard Collections.synchronizedMap(new TreeMap<String, DictionaryPool>()); 76150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard private Map<String, Dictionary> mUserDictionaries = 77150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard Collections.synchronizedMap(new TreeMap<String, Dictionary>()); 78fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard private Map<String, Dictionary> mWhitelistDictionaries = 79fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard Collections.synchronizedMap(new TreeMap<String, Dictionary>()); 8018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang private Dictionary mContactsDictionary; 813234123fba901243990972158d023a5d1c273316Jean Chalard 824609c02f9e61370557fee675c67263160fbf7feeJean Chalard // The threshold for a candidate to be offered as a suggestion. 830028ed3627ff4f37a62a80f3b2c857e373cd5090satok private float mSuggestionThreshold; 84a409f009fa410019ad10b1134ff57393443eba33Jean Chalard // The threshold for a suggestion to be considered "recommended". 850028ed3627ff4f37a62a80f3b2c857e373cd5090satok private float mRecommendedThreshold; 86db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard // Whether to use the contacts dictionary 87db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard private boolean mUseContactsDictionary; 88db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard private final Object mUseContactsLock = new Object(); 89db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard 90db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard private final HashSet<WeakReference<DictionaryCollection>> mDictionaryCollectionsList = 91db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard new HashSet<WeakReference<DictionaryCollection>>(); 9259b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard 931830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard public static final int SCRIPT_LATIN = 0; 941830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard public static final int SCRIPT_CYRILLIC = 1; 95e58f3af8a7bf852c3b100de1bd85d95d13e0e15esatok private static final String SINGLE_QUOTE = "\u0027"; 96e58f3af8a7bf852c3b100de1bd85d95d13e0e15esatok private static final String APOSTROPHE = "\u2019"; 971830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard private static final TreeMap<String, Integer> mLanguageToScript; 981830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard static { 991830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard // List of the supported languages and their associated script. We won't check 1001830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard // words written in another script than the selected script, because we know we 1011830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard // don't have those in our dictionary so we will underline everything and we 1021830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard // will never have any suggestions, so it makes no sense checking them. 1031830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard mLanguageToScript = new TreeMap<String, Integer>(); 1041830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard mLanguageToScript.put("en", SCRIPT_LATIN); 105d527a15ec44089930dd23c9e20b8672024a4555bJean Chalard mLanguageToScript.put("en_US", SCRIPT_LATIN); 106d527a15ec44089930dd23c9e20b8672024a4555bJean Chalard mLanguageToScript.put("en_GB", SCRIPT_LATIN); 1071830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard mLanguageToScript.put("fr", SCRIPT_LATIN); 1081830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard mLanguageToScript.put("de", SCRIPT_LATIN); 1091830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard mLanguageToScript.put("nl", SCRIPT_LATIN); 1101830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard mLanguageToScript.put("cs", SCRIPT_LATIN); 1111830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard mLanguageToScript.put("es", SCRIPT_LATIN); 1121830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard mLanguageToScript.put("it", SCRIPT_LATIN); 113d527a15ec44089930dd23c9e20b8672024a4555bJean Chalard mLanguageToScript.put("hr", SCRIPT_LATIN); 114d527a15ec44089930dd23c9e20b8672024a4555bJean Chalard mLanguageToScript.put("pt_BR", SCRIPT_LATIN); 1151830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard mLanguageToScript.put("ru", SCRIPT_CYRILLIC); 116d527a15ec44089930dd23c9e20b8672024a4555bJean Chalard // TODO: Make a persian proximity, and activate the Farsi subtype. 117d527a15ec44089930dd23c9e20b8672024a4555bJean Chalard // mLanguageToScript.put("fa", SCRIPT_PERSIAN); 1181830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard } 1191830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard 12059b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard @Override public void onCreate() { 12159b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard super.onCreate(); 1224609c02f9e61370557fee675c67263160fbf7feeJean Chalard mSuggestionThreshold = 1230028ed3627ff4f37a62a80f3b2c857e373cd5090satok Float.parseFloat(getString(R.string.spellchecker_suggestion_threshold_value)); 124a409f009fa410019ad10b1134ff57393443eba33Jean Chalard mRecommendedThreshold = 1250028ed3627ff4f37a62a80f3b2c857e373cd5090satok Float.parseFloat(getString(R.string.spellchecker_recommended_threshold_value)); 126db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 127db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard prefs.registerOnSharedPreferenceChangeListener(this); 128db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard onSharedPreferenceChanged(prefs, PREF_USE_CONTACTS_KEY); 129db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 130db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard 1311830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard private static int getScriptFromLocale(final Locale locale) { 1321830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard final Integer script = mLanguageToScript.get(locale.getLanguage()); 1331830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard if (null == script) { 1341830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard throw new RuntimeException("We have been called with an unsupported language: \"" 1351830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard + locale.getLanguage() + "\". Framework bug?"); 1361830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard } 1371830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard return script; 1381830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard } 1391830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard 140db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard @Override 141db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { 142db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (!PREF_USE_CONTACTS_KEY.equals(key)) return; 143db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard synchronized(mUseContactsLock) { 144db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard mUseContactsDictionary = prefs.getBoolean(PREF_USE_CONTACTS_KEY, true); 145db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (mUseContactsDictionary) { 146db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard startUsingContactsDictionaryLocked(); 147db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } else { 148db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard stopUsingContactsDictionaryLocked(); 149db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 150db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 151db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 152db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard 153db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard private void startUsingContactsDictionaryLocked() { 154db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (null == mContactsDictionary) { 155db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard mContactsDictionary = new SynchronouslyLoadedContactsDictionary(this); 156db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 157db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard final Iterator<WeakReference<DictionaryCollection>> iterator = 158db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard mDictionaryCollectionsList.iterator(); 159db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard while (iterator.hasNext()) { 160db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard final WeakReference<DictionaryCollection> dictRef = iterator.next(); 161db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard final DictionaryCollection dict = dictRef.get(); 162db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (null == dict) { 163db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard iterator.remove(); 164db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } else { 165db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard dict.addDictionary(mContactsDictionary); 166db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 167db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 168db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 169db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard 170db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard private void stopUsingContactsDictionaryLocked() { 171db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (null == mContactsDictionary) return; 17218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang final Dictionary contactsDict = mContactsDictionary; 17318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang // TODO: revert to the concrete type when USE_BINARY_CONTACTS_DICTIONARY is no longer needed 174db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard mContactsDictionary = null; 175db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard final Iterator<WeakReference<DictionaryCollection>> iterator = 176db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard mDictionaryCollectionsList.iterator(); 177db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard while (iterator.hasNext()) { 178db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard final WeakReference<DictionaryCollection> dictRef = iterator.next(); 179db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard final DictionaryCollection dict = dictRef.get(); 180db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (null == dict) { 181db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard iterator.remove(); 182db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } else { 183db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard dict.removeDictionary(contactsDict); 184db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 185db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 186db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard contactsDict.close(); 18759b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard } 18859b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard 1895bcf8ee66ceb38675a6b70fefcb574978e0fae92satok @Override 1905bcf8ee66ceb38675a6b70fefcb574978e0fae92satok public Session createSession() { 19159b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard return new AndroidSpellCheckerSession(this); 1925bcf8ee66ceb38675a6b70fefcb574978e0fae92satok } 1935bcf8ee66ceb38675a6b70fefcb574978e0fae92satok 194cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard private static SuggestionsInfo getNotInDictEmptySuggestions() { 195cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard return new SuggestionsInfo(0, EMPTY_STRING_ARRAY); 196cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard } 197cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard 198cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard private static SuggestionsInfo getInDictEmptySuggestions() { 199cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard return new SuggestionsInfo(SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY, 200cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard EMPTY_STRING_ARRAY); 201cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard } 202cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard 2033234123fba901243990972158d023a5d1c273316Jean Chalard private static class SuggestionsGatherer implements WordCallback { 20459b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard public static class Result { 20559b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard public final String[] mSuggestions; 206a409f009fa410019ad10b1134ff57393443eba33Jean Chalard public final boolean mHasRecommendedSuggestions; 207a409f009fa410019ad10b1134ff57393443eba33Jean Chalard public Result(final String[] gatheredSuggestions, 208a409f009fa410019ad10b1134ff57393443eba33Jean Chalard final boolean hasRecommendedSuggestions) { 20959b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard mSuggestions = gatheredSuggestions; 210a409f009fa410019ad10b1134ff57393443eba33Jean Chalard mHasRecommendedSuggestions = hasRecommendedSuggestions; 21159b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard } 21259b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard } 21359b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard 2146b166a193398554694cb680f704c2ffc23d03a0eJean Chalard private final ArrayList<CharSequence> mSuggestions; 2153234123fba901243990972158d023a5d1c273316Jean Chalard private final int[] mScores; 21685782abaf178f6aafa1f8999123ff540f04c17bcJean Chalard private final String mOriginalText; 2170028ed3627ff4f37a62a80f3b2c857e373cd5090satok private final float mSuggestionThreshold; 2180028ed3627ff4f37a62a80f3b2c857e373cd5090satok private final float mRecommendedThreshold; 2193234123fba901243990972158d023a5d1c273316Jean Chalard private final int mMaxLength; 2203234123fba901243990972158d023a5d1c273316Jean Chalard private int mLength = 0; 22159b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard 22259b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard // The two following attributes are only ever filled if the requested max length 22359b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard // is 0 (or less, which is treated the same). 22459b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard private String mBestSuggestion = null; 22559b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard private int mBestScore = Integer.MIN_VALUE; // As small as possible 2263234123fba901243990972158d023a5d1c273316Jean Chalard 2270028ed3627ff4f37a62a80f3b2c857e373cd5090satok SuggestionsGatherer(final String originalText, final float suggestionThreshold, 2280028ed3627ff4f37a62a80f3b2c857e373cd5090satok final float recommendedThreshold, final int maxLength) { 22985782abaf178f6aafa1f8999123ff540f04c17bcJean Chalard mOriginalText = originalText; 2304609c02f9e61370557fee675c67263160fbf7feeJean Chalard mSuggestionThreshold = suggestionThreshold; 231a409f009fa410019ad10b1134ff57393443eba33Jean Chalard mRecommendedThreshold = recommendedThreshold; 2323234123fba901243990972158d023a5d1c273316Jean Chalard mMaxLength = maxLength; 2336b166a193398554694cb680f704c2ffc23d03a0eJean Chalard mSuggestions = new ArrayList<CharSequence>(maxLength + 1); 2343234123fba901243990972158d023a5d1c273316Jean Chalard mScores = new int[mMaxLength]; 2353234123fba901243990972158d023a5d1c273316Jean Chalard } 2363234123fba901243990972158d023a5d1c273316Jean Chalard 2373234123fba901243990972158d023a5d1c273316Jean Chalard @Override 2383234123fba901243990972158d023a5d1c273316Jean Chalard synchronized public boolean addWord(char[] word, int wordOffset, int wordLength, int score, 2396e082cb30dbe1a8cc314b474dc1377b85fdb25c2Jean Chalard int dicTypeId, int dataType) { 240672635493e1dc2baf9fd4a94e73c5b06d0450e7eKen Wakasa final int positionIndex = Arrays.binarySearch(mScores, 0, mLength, score); 2413234123fba901243990972158d023a5d1c273316Jean Chalard // binarySearch returns the index if the element exists, and -<insertion index> - 1 2423234123fba901243990972158d023a5d1c273316Jean Chalard // if it doesn't. See documentation for binarySearch. 2433234123fba901243990972158d023a5d1c273316Jean Chalard final int insertIndex = positionIndex >= 0 ? positionIndex : -positionIndex - 1; 2443234123fba901243990972158d023a5d1c273316Jean Chalard 2454609c02f9e61370557fee675c67263160fbf7feeJean Chalard if (insertIndex == 0 && mLength >= mMaxLength) { 2464609c02f9e61370557fee675c67263160fbf7feeJean Chalard // In the future, we may want to keep track of the best suggestion score even if 2474609c02f9e61370557fee675c67263160fbf7feeJean Chalard // we are asked for 0 suggestions. In this case, we can use the following 2484609c02f9e61370557fee675c67263160fbf7feeJean Chalard // (tested) code to keep it: 2494609c02f9e61370557fee675c67263160fbf7feeJean Chalard // If the maxLength is 0 (should never be less, but if it is, it's treated as 0) 2504609c02f9e61370557fee675c67263160fbf7feeJean Chalard // then we need to keep track of the best suggestion in mBestScore and 2514609c02f9e61370557fee675c67263160fbf7feeJean Chalard // mBestSuggestion. This is so that we know whether the best suggestion makes 2524609c02f9e61370557fee675c67263160fbf7feeJean Chalard // the score cutoff, since we need to know that to return a meaningful 2534609c02f9e61370557fee675c67263160fbf7feeJean Chalard // looksLikeTypo. 2544609c02f9e61370557fee675c67263160fbf7feeJean Chalard // if (0 >= mMaxLength) { 2554609c02f9e61370557fee675c67263160fbf7feeJean Chalard // if (score > mBestScore) { 2564609c02f9e61370557fee675c67263160fbf7feeJean Chalard // mBestScore = score; 2574609c02f9e61370557fee675c67263160fbf7feeJean Chalard // mBestSuggestion = new String(word, wordOffset, wordLength); 2584609c02f9e61370557fee675c67263160fbf7feeJean Chalard // } 2594609c02f9e61370557fee675c67263160fbf7feeJean Chalard // } 2604609c02f9e61370557fee675c67263160fbf7feeJean Chalard return true; 2614609c02f9e61370557fee675c67263160fbf7feeJean Chalard } 262c53661f152f2d676f8cec656cbdd93adfa7fc908Jean Chalard if (insertIndex >= mMaxLength) { 263c53661f152f2d676f8cec656cbdd93adfa7fc908Jean Chalard // We found a suggestion, but its score is too weak to be kept considering 264c53661f152f2d676f8cec656cbdd93adfa7fc908Jean Chalard // the suggestion limit. 265c53661f152f2d676f8cec656cbdd93adfa7fc908Jean Chalard return true; 266c53661f152f2d676f8cec656cbdd93adfa7fc908Jean Chalard } 2674609c02f9e61370557fee675c67263160fbf7feeJean Chalard 2684609c02f9e61370557fee675c67263160fbf7feeJean Chalard // Compute the normalized score and skip this word if it's normalized score does not 2694609c02f9e61370557fee675c67263160fbf7feeJean Chalard // make the threshold. 2704609c02f9e61370557fee675c67263160fbf7feeJean Chalard final String wordString = new String(word, wordOffset, wordLength); 2710028ed3627ff4f37a62a80f3b2c857e373cd5090satok final float normalizedScore = 272be0cf72253f15bff6abdeaa79f60a56f06ab7b86satok BinaryDictionary.calcNormalizedScore(mOriginalText, wordString, score); 2734609c02f9e61370557fee675c67263160fbf7feeJean Chalard if (normalizedScore < mSuggestionThreshold) { 2744609c02f9e61370557fee675c67263160fbf7feeJean Chalard if (DBG) Log.i(TAG, wordString + " does not make the score threshold"); 2754609c02f9e61370557fee675c67263160fbf7feeJean Chalard return true; 2764609c02f9e61370557fee675c67263160fbf7feeJean Chalard } 2774609c02f9e61370557fee675c67263160fbf7feeJean Chalard 2783234123fba901243990972158d023a5d1c273316Jean Chalard if (mLength < mMaxLength) { 2793234123fba901243990972158d023a5d1c273316Jean Chalard final int copyLen = mLength - insertIndex; 2803234123fba901243990972158d023a5d1c273316Jean Chalard ++mLength; 2813234123fba901243990972158d023a5d1c273316Jean Chalard System.arraycopy(mScores, insertIndex, mScores, insertIndex + 1, copyLen); 2824609c02f9e61370557fee675c67263160fbf7feeJean Chalard mSuggestions.add(insertIndex, wordString); 2833234123fba901243990972158d023a5d1c273316Jean Chalard } else { 2843234123fba901243990972158d023a5d1c273316Jean Chalard System.arraycopy(mScores, 1, mScores, 0, insertIndex); 2854609c02f9e61370557fee675c67263160fbf7feeJean Chalard mSuggestions.add(insertIndex, wordString); 2866b166a193398554694cb680f704c2ffc23d03a0eJean Chalard mSuggestions.remove(0); 2873234123fba901243990972158d023a5d1c273316Jean Chalard } 2883234123fba901243990972158d023a5d1c273316Jean Chalard mScores[insertIndex] = score; 2893234123fba901243990972158d023a5d1c273316Jean Chalard 2903234123fba901243990972158d023a5d1c273316Jean Chalard return true; 2913234123fba901243990972158d023a5d1c273316Jean Chalard } 2923234123fba901243990972158d023a5d1c273316Jean Chalard 29385782abaf178f6aafa1f8999123ff540f04c17bcJean Chalard public Result getResults(final int capitalizeType, final Locale locale) { 29459b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard final String[] gatheredSuggestions; 295a409f009fa410019ad10b1134ff57393443eba33Jean Chalard final boolean hasRecommendedSuggestions; 29659b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard if (0 == mLength) { 29759b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard // Either we found no suggestions, or we found some BUT the max length was 0. 29859b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard // If we found some mBestSuggestion will not be null. If it is null, then 29959b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard // we found none, regardless of the max length. 30059b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard if (null == mBestSuggestion) { 30159b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard gatheredSuggestions = null; 302a409f009fa410019ad10b1134ff57393443eba33Jean Chalard hasRecommendedSuggestions = false; 30359b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard } else { 30459b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard gatheredSuggestions = EMPTY_STRING_ARRAY; 3050028ed3627ff4f37a62a80f3b2c857e373cd5090satok final float normalizedScore = BinaryDictionary.calcNormalizedScore( 306be0cf72253f15bff6abdeaa79f60a56f06ab7b86satok mOriginalText, mBestSuggestion, mBestScore); 307a409f009fa410019ad10b1134ff57393443eba33Jean Chalard hasRecommendedSuggestions = (normalizedScore > mRecommendedThreshold); 30859b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard } 30959b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard } else { 31059b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard if (DBG) { 31159b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard if (mLength != mSuggestions.size()) { 31259b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard Log.e(TAG, "Suggestion size is not the same as stored mLength"); 31359b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard } 314af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard for (int i = mLength - 1; i >= 0; --i) { 315af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard Log.i(TAG, "" + mScores[i] + " " + mSuggestions.get(i)); 316af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard } 3176b166a193398554694cb680f704c2ffc23d03a0eJean Chalard } 31859b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard Collections.reverse(mSuggestions); 319cc8c8b99bd0463f5977dea82f5e2379ea1dd4e73Tadashi G. Takaoka StringUtils.removeDupes(mSuggestions); 320f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard if (CAPITALIZE_ALL == capitalizeType) { 321f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard for (int i = 0; i < mSuggestions.size(); ++i) { 322f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard // get(i) returns a CharSequence which is actually a String so .toString() 323f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard // should return the same object. 324f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard mSuggestions.set(i, mSuggestions.get(i).toString().toUpperCase(locale)); 325f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard } 326f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard } else if (CAPITALIZE_FIRST == capitalizeType) { 327f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard for (int i = 0; i < mSuggestions.size(); ++i) { 328f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard // Likewise 32911d9ee742f8ff3fb31b0e3beb32ee4870c63d8e3Tadashi G. Takaoka mSuggestions.set(i, StringUtils.toTitleCase( 3303bf57a5624679a20db26df912077a53b9f90ad36Tadashi G. Takaoka mSuggestions.get(i).toString(), locale)); 331f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard } 332f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard } 33359b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard // This returns a String[], while toArray() returns an Object[] which cannot be cast 33459b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard // into a String[]. 33559b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard gatheredSuggestions = mSuggestions.toArray(EMPTY_STRING_ARRAY); 33659b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard 337af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard final int bestScore = mScores[mLength - 1]; 33859b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard final CharSequence bestSuggestion = mSuggestions.get(0); 3390028ed3627ff4f37a62a80f3b2c857e373cd5090satok final float normalizedScore = 340be0cf72253f15bff6abdeaa79f60a56f06ab7b86satok BinaryDictionary.calcNormalizedScore( 341be0cf72253f15bff6abdeaa79f60a56f06ab7b86satok mOriginalText, bestSuggestion.toString(), bestScore); 342a409f009fa410019ad10b1134ff57393443eba33Jean Chalard hasRecommendedSuggestions = (normalizedScore > mRecommendedThreshold); 343af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard if (DBG) { 344af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard Log.i(TAG, "Best suggestion : " + bestSuggestion + ", score " + bestScore); 3454609c02f9e61370557fee675c67263160fbf7feeJean Chalard Log.i(TAG, "Normalized score = " + normalizedScore 346a409f009fa410019ad10b1134ff57393443eba33Jean Chalard + " (threshold " + mRecommendedThreshold 347a409f009fa410019ad10b1134ff57393443eba33Jean Chalard + ") => hasRecommendedSuggestions = " + hasRecommendedSuggestions); 348af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard } 3493234123fba901243990972158d023a5d1c273316Jean Chalard } 350a409f009fa410019ad10b1134ff57393443eba33Jean Chalard return new Result(gatheredSuggestions, hasRecommendedSuggestions); 3513234123fba901243990972158d023a5d1c273316Jean Chalard } 3523234123fba901243990972158d023a5d1c273316Jean Chalard } 3533234123fba901243990972158d023a5d1c273316Jean Chalard 354c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard @Override 355c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard public boolean onUnbind(final Intent intent) { 3568403611960cd0b2a40b77275c536e8088c098830Jean Chalard closeAllDictionaries(); 3578403611960cd0b2a40b77275c536e8088c098830Jean Chalard return false; 3588403611960cd0b2a40b77275c536e8088c098830Jean Chalard } 3598403611960cd0b2a40b77275c536e8088c098830Jean Chalard 3608403611960cd0b2a40b77275c536e8088c098830Jean Chalard private void closeAllDictionaries() { 361c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard final Map<String, DictionaryPool> oldPools = mDictionaryPools; 362c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard mDictionaryPools = Collections.synchronizedMap(new TreeMap<String, DictionaryPool>()); 363150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard final Map<String, Dictionary> oldUserDictionaries = mUserDictionaries; 364150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard mUserDictionaries = Collections.synchronizedMap(new TreeMap<String, Dictionary>()); 365fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard final Map<String, Dictionary> oldWhitelistDictionaries = mWhitelistDictionaries; 366fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard mWhitelistDictionaries = Collections.synchronizedMap(new TreeMap<String, Dictionary>()); 367c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard for (DictionaryPool pool : oldPools.values()) { 368c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard pool.close(); 369c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard } 370150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard for (Dictionary dict : oldUserDictionaries.values()) { 371150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard dict.close(); 372150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard } 373fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard for (Dictionary dict : oldWhitelistDictionaries.values()) { 374fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard dict.close(); 375fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard } 37618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang synchronized (mUseContactsLock) { 377db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (null != mContactsDictionary) { 378db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard // The synchronously loaded contacts dictionary should have been in one 379db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard // or several pools, but it is shielded against multiple closing and it's 380db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard // safe to call it several times. 38118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang final Dictionary dictToClose = mContactsDictionary; 38218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang // TODO: revert to the concrete type when USE_BINARY_CONTACTS_DICTIONARY is no 38318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang // longer needed 384db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard mContactsDictionary = null; 385db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard dictToClose.close(); 386db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 3872e3c6da8688a907024d4d8e0f2db3e0ed4fab8dbJean Chalard } 388c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard } 389c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard 390a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard private DictionaryPool getDictionaryPool(final String locale) { 391a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard DictionaryPool pool = mDictionaryPools.get(locale); 392a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard if (null == pool) { 393ef35cb631c45c8b106fe7ed9e0d1178c3e5fb963Jean Chalard final Locale localeObject = LocaleUtils.constructLocaleFromString(locale); 394a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard pool = new DictionaryPool(POOL_SIZE, this, localeObject); 395a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard mDictionaryPools.put(locale, pool); 3963234123fba901243990972158d023a5d1c273316Jean Chalard } 397a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard return pool; 398a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard } 399a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard 400a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard public DictAndProximity createDictAndProximity(final Locale locale) { 4011830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard final int script = getScriptFromLocale(locale); 4021830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard final ProximityInfo proximityInfo = ProximityInfo.createSpellCheckerProximityInfo( 40388794b24c0928e3bbea59999fce47c78c028863dKen Wakasa SpellCheckerProximityInfo.getProximityForScript(script), 40488794b24c0928e3bbea59999fce47c78c028863dKen Wakasa SpellCheckerProximityInfo.ROW_SIZE, 40588794b24c0928e3bbea59999fce47c78c028863dKen Wakasa SpellCheckerProximityInfo.PROXIMITY_GRID_WIDTH, 40688794b24c0928e3bbea59999fce47c78c028863dKen Wakasa SpellCheckerProximityInfo.PROXIMITY_GRID_HEIGHT); 407150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard final DictionaryCollection dictionaryCollection = 408f0e12a969974987f1b97929886c6ebe6a685c538Jean Chalard DictionaryFactory.createMainDictionaryFromManager(this, locale, 40924aee9100e92dc4c06cdb54487a4922420fa8660Jean Chalard true /* useFullEditDistance */); 410150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard final String localeStr = locale.toString(); 411fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard Dictionary userDictionary = mUserDictionaries.get(localeStr); 412fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard if (null == userDictionary) { 413f6adff6227a15af105dbf39c57213a24bf16780bTom Ouyang if (LatinIME.USE_BINARY_USER_DICTIONARY) { 414f6adff6227a15af105dbf39c57213a24bf16780bTom Ouyang userDictionary = new SynchronouslyLoadedUserBinaryDictionary(this, localeStr, true); 415f6adff6227a15af105dbf39c57213a24bf16780bTom Ouyang } else { 416f6adff6227a15af105dbf39c57213a24bf16780bTom Ouyang userDictionary = new SynchronouslyLoadedUserDictionary(this, localeStr, true); 417f6adff6227a15af105dbf39c57213a24bf16780bTom Ouyang } 418fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard mUserDictionaries.put(localeStr, userDictionary); 419fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard } 420fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard dictionaryCollection.addDictionary(userDictionary); 421fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard Dictionary whitelistDictionary = mWhitelistDictionaries.get(localeStr); 422fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard if (null == whitelistDictionary) { 423fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard whitelistDictionary = new WhitelistDictionary(this, locale); 424fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard mWhitelistDictionaries.put(localeStr, whitelistDictionary); 425150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard } 426fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard dictionaryCollection.addDictionary(whitelistDictionary); 42718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang synchronized (mUseContactsLock) { 428db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (mUseContactsDictionary) { 429db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (null == mContactsDictionary) { 43018222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang // TODO: revert to the concrete type when USE_BINARY_CONTACTS_DICTIONARY is no 43118222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang // longer needed 43218222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang if (LatinIME.USE_BINARY_CONTACTS_DICTIONARY) { 43318222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang mContactsDictionary = new SynchronouslyLoadedContactsBinaryDictionary(this); 43418222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang } else { 43518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang mContactsDictionary = new SynchronouslyLoadedContactsDictionary(this); 43618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang } 437db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 438db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 439db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard dictionaryCollection.addDictionary(mContactsDictionary); 440db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard mDictionaryCollectionsList.add( 441db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard new WeakReference<DictionaryCollection>(dictionaryCollection)); 4422e3c6da8688a907024d4d8e0f2db3e0ed4fab8dbJean Chalard } 443150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard return new DictAndProximity(dictionaryCollection, proximityInfo); 4443234123fba901243990972158d023a5d1c273316Jean Chalard } 4453234123fba901243990972158d023a5d1c273316Jean Chalard 446f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard // This method assumes the text is not empty or null. 447f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard private static int getCapitalizationType(String text) { 448f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard // If the first char is not uppercase, then the word is either all lower case, 449f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard // and in either case we return CAPITALIZE_NONE. 450f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard if (!Character.isUpperCase(text.codePointAt(0))) return CAPITALIZE_NONE; 4519242a2bcf8a6b07bb045a8356711bed1493c251eJean Chalard final int len = text.length(); 452f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard int capsCount = 1; 4539242a2bcf8a6b07bb045a8356711bed1493c251eJean Chalard for (int i = 1; i < len; i = text.offsetByCodePoints(i, 1)) { 454f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard if (1 != capsCount && i != capsCount) break; 455f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard if (Character.isUpperCase(text.codePointAt(i))) ++capsCount; 456f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard } 457f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard // We know the first char is upper case. So we want to test if either everything 458f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard // else is lower case, or if everything else is upper case. If the string is 459f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard // exactly one char long, then we will arrive here with capsCount 1, and this is 460f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard // correct, too. 461f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard if (1 == capsCount) return CAPITALIZE_FIRST; 462f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard return (len == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE); 463f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard } 464f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard 46559b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard private static class AndroidSpellCheckerSession extends Session { 466a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard // Immutable, but need the locale which is not available in the constructor yet 46759b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard private DictionaryPool mDictionaryPool; 4685d4c5692f11958064ba7c0de5715f30c96175400Jean Chalard // Likewise 46959b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard private Locale mLocale; 470bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard // Cache this for performance 471bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard private int mScript; // One of SCRIPT_LATIN or SCRIPT_CYRILLIC for now. 47259b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard 47359b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard private final AndroidSpellCheckerService mService; 47459b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard 47574a84febc76d1ec6c0b6d8afbf50349da9b38d74satok private final SuggestionsCache mSuggestionsCache = new SuggestionsCache(); 47674a84febc76d1ec6c0b6d8afbf50349da9b38d74satok 47774a84febc76d1ec6c0b6d8afbf50349da9b38d74satok private static class SuggestionsParams { 47874a84febc76d1ec6c0b6d8afbf50349da9b38d74satok public final String[] mSuggestions; 47974a84febc76d1ec6c0b6d8afbf50349da9b38d74satok public final int mFlags; 48074a84febc76d1ec6c0b6d8afbf50349da9b38d74satok public SuggestionsParams(String[] suggestions, int flags) { 48174a84febc76d1ec6c0b6d8afbf50349da9b38d74satok mSuggestions = suggestions; 48274a84febc76d1ec6c0b6d8afbf50349da9b38d74satok mFlags = flags; 48374a84febc76d1ec6c0b6d8afbf50349da9b38d74satok } 48474a84febc76d1ec6c0b6d8afbf50349da9b38d74satok } 48574a84febc76d1ec6c0b6d8afbf50349da9b38d74satok 48674a84febc76d1ec6c0b6d8afbf50349da9b38d74satok private static class SuggestionsCache { 48774a84febc76d1ec6c0b6d8afbf50349da9b38d74satok private static final int MAX_CACHE_SIZE = 50; 48874a84febc76d1ec6c0b6d8afbf50349da9b38d74satok // TODO: support bigram 48974a84febc76d1ec6c0b6d8afbf50349da9b38d74satok private final LruCache<String, SuggestionsParams> mUnigramSuggestionsInfoCache = 49074a84febc76d1ec6c0b6d8afbf50349da9b38d74satok new LruCache<String, SuggestionsParams>(MAX_CACHE_SIZE); 49174a84febc76d1ec6c0b6d8afbf50349da9b38d74satok 49274a84febc76d1ec6c0b6d8afbf50349da9b38d74satok public SuggestionsParams getSuggestionsFromCache(String query) { 49374a84febc76d1ec6c0b6d8afbf50349da9b38d74satok return mUnigramSuggestionsInfoCache.get(query); 49474a84febc76d1ec6c0b6d8afbf50349da9b38d74satok } 49574a84febc76d1ec6c0b6d8afbf50349da9b38d74satok 49674a84febc76d1ec6c0b6d8afbf50349da9b38d74satok public void putSuggestionsToCache(String query, String[] suggestions, int flags) { 49774a84febc76d1ec6c0b6d8afbf50349da9b38d74satok if (suggestions == null || TextUtils.isEmpty(query)) { 49874a84febc76d1ec6c0b6d8afbf50349da9b38d74satok return; 49974a84febc76d1ec6c0b6d8afbf50349da9b38d74satok } 50074a84febc76d1ec6c0b6d8afbf50349da9b38d74satok mUnigramSuggestionsInfoCache.put(query, new SuggestionsParams(suggestions, flags)); 50174a84febc76d1ec6c0b6d8afbf50349da9b38d74satok } 5025434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok 5035434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok public void remove(String key) { 5045434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok mUnigramSuggestionsInfoCache.remove(key); 5055434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok } 50674a84febc76d1ec6c0b6d8afbf50349da9b38d74satok } 50774a84febc76d1ec6c0b6d8afbf50349da9b38d74satok 50859b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard AndroidSpellCheckerSession(final AndroidSpellCheckerService service) { 50959b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard mService = service; 51059b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard } 511a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard 5125bcf8ee66ceb38675a6b70fefcb574978e0fae92satok @Override 5135bcf8ee66ceb38675a6b70fefcb574978e0fae92satok public void onCreate() { 5145d4c5692f11958064ba7c0de5715f30c96175400Jean Chalard final String localeString = getLocale(); 51559b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard mDictionaryPool = mService.getDictionaryPool(localeString); 516ef35cb631c45c8b106fe7ed9e0d1178c3e5fb963Jean Chalard mLocale = LocaleUtils.constructLocaleFromString(localeString); 5171830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard mScript = getScriptFromLocale(mLocale); 518a90992e56244a914195daba3a2dd8a0e66e63384satok } 5193234123fba901243990972158d023a5d1c273316Jean Chalard 52072479ea3636a7f9379ff40ae673fc67255abab6dJean Chalard /* 52172479ea3636a7f9379ff40ae673fc67255abab6dJean Chalard * Returns whether the code point is a letter that makes sense for the specified 52272479ea3636a7f9379ff40ae673fc67255abab6dJean Chalard * locale for this spell checker. 52372479ea3636a7f9379ff40ae673fc67255abab6dJean Chalard * The dictionaries supported by Latin IME are described in res/xml/spellchecker.xml 524bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard * and is limited to EFIGS languages and Russian. 525bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard * Hence at the moment this explicitly tests for Cyrillic characters or Latin characters 526bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard * as appropriate, and explicitly excludes CJK, Arabic and Hebrew characters. 52772479ea3636a7f9379ff40ae673fc67255abab6dJean Chalard */ 52872479ea3636a7f9379ff40ae673fc67255abab6dJean Chalard private static boolean isLetterCheckableByLanguage(final int codePoint, 529bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard final int script) { 530bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard switch (script) { 531bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard case SCRIPT_LATIN: 532bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard // Our supported latin script dictionaries (EFIGS) at the moment only include 533bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard // characters in the C0, C1, Latin Extended A and B, IPA extensions unicode 534bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard // blocks. As it happens, those are back-to-back in the code range 0x40 to 0x2AF, 535bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard // so the below is a very efficient way to test for it. As for the 0-0x3F, it's 536bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard // excluded from isLetter anyway. 537bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard return codePoint <= 0x2AF && Character.isLetter(codePoint); 538bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard case SCRIPT_CYRILLIC: 539bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard // All Cyrillic characters are in the 400~52F block. There are some in the upper 540bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard // Unicode range, but they are archaic characters that are not used in modern 541bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard // russian and are not used by our dictionary. 542bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard return codePoint >= 0x400 && codePoint <= 0x52F && Character.isLetter(codePoint); 543bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard default: 544bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard // Should never come here 545bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard throw new RuntimeException("Impossible value of script: " + script); 546bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard } 54772479ea3636a7f9379ff40ae673fc67255abab6dJean Chalard } 54872479ea3636a7f9379ff40ae673fc67255abab6dJean Chalard 54988fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard /** 55088fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard * Finds out whether a particular string should be filtered out of spell checking. 55188fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard * 552bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard * This will loosely match URLs, numbers, symbols. To avoid always underlining words that 553bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard * we know we will never recognize, this accepts a script identifier that should be one 554bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard * of the SCRIPT_* constants defined above, to rule out quickly characters from very 555bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard * different languages. 55688fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard * 55788fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard * @param text the string to evaluate. 558bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard * @param script the identifier for the script this spell checker recognizes 55988fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard * @return true if we should filter this text out, false otherwise 56088fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard */ 561bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard private static boolean shouldFilterOut(final String text, final int script) { 56288fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard if (TextUtils.isEmpty(text) || text.length() <= 1) return true; 56388fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard 56488fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard // TODO: check if an equivalent processing can't be done more quickly with a 56588fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard // compiled regexp. 56688fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard // Filter by first letter 56788fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard final int firstCodePoint = text.codePointAt(0); 56888fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard // Filter out words that don't start with a letter or an apostrophe 569bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard if (!isLetterCheckableByLanguage(firstCodePoint, script) 57088fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard && '\'' != firstCodePoint) return true; 57188fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard 57288fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard // Filter contents 57388fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard final int length = text.length(); 57488fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard int letterCount = 0; 5759242a2bcf8a6b07bb045a8356711bed1493c251eJean Chalard for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) { 57688fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard final int codePoint = text.codePointAt(i); 57788fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard // Any word containing a '@' is probably an e-mail address 57888fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard // Any word containing a '/' is probably either an ad-hoc combination of two 57988fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard // words or a URI - in either case we don't want to spell check that 5809242a2bcf8a6b07bb045a8356711bed1493c251eJean Chalard if ('@' == codePoint || '/' == codePoint) return true; 581bb2b30fc7ff31182d314e4db9baf1913bf08522dJean Chalard if (isLetterCheckableByLanguage(codePoint, script)) ++letterCount; 58288fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard } 58388fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard // Guestimate heuristic: perform spell checking if at least 3/4 of the characters 58488fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard // in this word are letters 58588fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard return (letterCount * 4 < length * 3); 58688fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard } 58788fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard 5885434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok private SentenceSuggestionsInfo fixWronglyInvalidatedWordWithSingleQuote( 5895434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok TextInfo ti, SentenceSuggestionsInfo ssi) { 5905434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final String typedText = ti.getText(); 5915434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok if (!typedText.contains(SINGLE_QUOTE)) { 5925434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok return null; 5935434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok } 5945434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final int N = ssi.getSuggestionsCount(); 5955434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final ArrayList<Integer> additionalOffsets = new ArrayList<Integer>(); 5965434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final ArrayList<Integer> additionalLengths = new ArrayList<Integer>(); 5975434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final ArrayList<SuggestionsInfo> additionalSuggestionsInfos = 5985434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok new ArrayList<SuggestionsInfo>(); 5995434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok for (int i = 0; i < N; ++i) { 6005434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final SuggestionsInfo si = ssi.getSuggestionsInfoAt(i); 6015434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final int flags = si.getSuggestionsAttributes(); 6025434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok if ((flags & SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY) == 0) { 6035434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok continue; 6045434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok } 6055434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final int offset = ssi.getOffsetAt(i); 6065434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final int length = ssi.getLengthAt(i); 6075434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final String subText = typedText.substring(offset, offset + length); 6085434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok if (!subText.contains(SINGLE_QUOTE)) { 6095434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok continue; 6105434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok } 6115434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final String[] splitTexts = subText.split(SINGLE_QUOTE, -1); 6125434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok if (splitTexts == null || splitTexts.length <= 1) { 6135434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok continue; 6145434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok } 6155434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final int splitNum = splitTexts.length; 6165434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok for (int j = 0; j < splitNum; ++j) { 6175434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final String splitText = splitTexts[j]; 6185434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok if (TextUtils.isEmpty(splitText)) { 6195434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok continue; 6205434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok } 6215434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok if (mSuggestionsCache.getSuggestionsFromCache(splitText) == null) { 6225434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok continue; 6235434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok } 6245434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final int newLength = splitText.length(); 6255434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok // Neither RESULT_ATTR_IN_THE_DICTIONARY nor RESULT_ATTR_LOOKS_LIKE_TYPO 6265434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final int newFlags = 0; 6275434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final SuggestionsInfo newSi = new SuggestionsInfo(newFlags, EMPTY_STRING_ARRAY); 6285434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok newSi.setCookieAndSequence(si.getCookie(), si.getSequence()); 6295434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok if (DBG) { 6305434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok Log.d(TAG, "Override and remove old span over: " 6315434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok + splitText + ", " + offset + "," + newLength); 6325434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok } 6335434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok additionalOffsets.add(offset); 6345434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok additionalLengths.add(newLength); 6355434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok additionalSuggestionsInfos.add(newSi); 6365434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok } 6375434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok } 6385434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final int additionalSize = additionalOffsets.size(); 6395434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok if (additionalSize <= 0) { 6405434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok return null; 6415434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok } 6425434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final int suggestionsSize = N + additionalSize; 6435434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final int[] newOffsets = new int[suggestionsSize]; 6445434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final int[] newLengths = new int[suggestionsSize]; 6455434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final SuggestionsInfo[] newSuggestionsInfos = new SuggestionsInfo[suggestionsSize]; 6465434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok int i; 6475434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok for (i = 0; i < N; ++i) { 6485434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok newOffsets[i] = ssi.getOffsetAt(i); 6495434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok newLengths[i] = ssi.getLengthAt(i); 6505434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok newSuggestionsInfos[i] = ssi.getSuggestionsInfoAt(i); 6515434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok } 6525434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok for (; i < suggestionsSize; ++i) { 6535434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok newOffsets[i] = additionalOffsets.get(i - N); 6545434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok newLengths[i] = additionalLengths.get(i - N); 6555434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok newSuggestionsInfos[i] = additionalSuggestionsInfos.get(i - N); 6565434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok } 6575434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok return new SentenceSuggestionsInfo(newSuggestionsInfos, newOffsets, newLengths); 6585434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok } 6595434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok 6605434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok @Override 6615434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok public SentenceSuggestionsInfo[] onGetSentenceSuggestionsMultiple( 6625434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok TextInfo[] textInfos, int suggestionsLimit) { 6635434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final SentenceSuggestionsInfo[] retval = super.onGetSentenceSuggestionsMultiple( 6645434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok textInfos, suggestionsLimit); 6655434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok if (retval == null || retval.length != textInfos.length) { 6665434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok return retval; 6675434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok } 6685434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok for (int i = 0; i < retval.length; ++i) { 6695434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok final SentenceSuggestionsInfo tempSsi = 6705434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok fixWronglyInvalidatedWordWithSingleQuote(textInfos[i], retval[i]); 6715434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok if (tempSsi != null) { 6725434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok retval[i] = tempSsi; 6735434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok } 6745434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok } 6755434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok return retval; 6765434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok } 6775434f46481c6331c3f107e6940cb49ba9dd5ea4dsatok 678315d731d8f11929b6202020475a477024067c1f1satok @Override 679315d731d8f11929b6202020475a477024067c1f1satok public SuggestionsInfo[] onGetSuggestionsMultiple(TextInfo[] textInfos, 680315d731d8f11929b6202020475a477024067c1f1satok int suggestionsLimit, boolean sequentialWords) { 681315d731d8f11929b6202020475a477024067c1f1satok final int length = textInfos.length; 682315d731d8f11929b6202020475a477024067c1f1satok final SuggestionsInfo[] retval = new SuggestionsInfo[length]; 683315d731d8f11929b6202020475a477024067c1f1satok for (int i = 0; i < length; ++i) { 684315d731d8f11929b6202020475a477024067c1f1satok final String prevWord; 685315d731d8f11929b6202020475a477024067c1f1satok if (sequentialWords && i > 0) { 686315d731d8f11929b6202020475a477024067c1f1satok final String prevWordCandidate = textInfos[i - 1].getText(); 687315d731d8f11929b6202020475a477024067c1f1satok // Note that an empty string would be used to indicate the initial word 688315d731d8f11929b6202020475a477024067c1f1satok // in the future. 689315d731d8f11929b6202020475a477024067c1f1satok prevWord = TextUtils.isEmpty(prevWordCandidate) ? null : prevWordCandidate; 690315d731d8f11929b6202020475a477024067c1f1satok } else { 691315d731d8f11929b6202020475a477024067c1f1satok prevWord = null; 692315d731d8f11929b6202020475a477024067c1f1satok } 693315d731d8f11929b6202020475a477024067c1f1satok retval[i] = onGetSuggestions(textInfos[i], prevWord, suggestionsLimit); 694315d731d8f11929b6202020475a477024067c1f1satok retval[i].setCookieAndSequence( 695315d731d8f11929b6202020475a477024067c1f1satok textInfos[i].getCookie(), textInfos[i].getSequence()); 696315d731d8f11929b6202020475a477024067c1f1satok } 697315d731d8f11929b6202020475a477024067c1f1satok return retval; 698315d731d8f11929b6202020475a477024067c1f1satok } 699315d731d8f11929b6202020475a477024067c1f1satok 7005bcf8ee66ceb38675a6b70fefcb574978e0fae92satok // Note : this must be reentrant 7015bcf8ee66ceb38675a6b70fefcb574978e0fae92satok /** 7025bcf8ee66ceb38675a6b70fefcb574978e0fae92satok * Gets a list of suggestions for a specific string. This returns a list of possible 70370b9c5d9913b676f21fe29f795bdb25324509205Jean Chalard * corrections for the text passed as an argument. It may split or group words, and 7045bcf8ee66ceb38675a6b70fefcb574978e0fae92satok * even perform grammatical analysis. 7055bcf8ee66ceb38675a6b70fefcb574978e0fae92satok */ 7065bcf8ee66ceb38675a6b70fefcb574978e0fae92satok @Override 7075bcf8ee66ceb38675a6b70fefcb574978e0fae92satok public SuggestionsInfo onGetSuggestions(final TextInfo textInfo, 7085bcf8ee66ceb38675a6b70fefcb574978e0fae92satok final int suggestionsLimit) { 709315d731d8f11929b6202020475a477024067c1f1satok return onGetSuggestions(textInfo, null, suggestionsLimit); 710315d731d8f11929b6202020475a477024067c1f1satok } 711315d731d8f11929b6202020475a477024067c1f1satok 712315d731d8f11929b6202020475a477024067c1f1satok private SuggestionsInfo onGetSuggestions( 713315d731d8f11929b6202020475a477024067c1f1satok final TextInfo textInfo, final String prevWord, final int suggestionsLimit) { 714a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard try { 715e58f3af8a7bf852c3b100de1bd85d95d13e0e15esatok final String inText = textInfo.getText(); 71674a84febc76d1ec6c0b6d8afbf50349da9b38d74satok final SuggestionsParams cachedSuggestionsParams = 717e58f3af8a7bf852c3b100de1bd85d95d13e0e15esatok mSuggestionsCache.getSuggestionsFromCache(inText); 71874a84febc76d1ec6c0b6d8afbf50349da9b38d74satok if (cachedSuggestionsParams != null) { 71974a84febc76d1ec6c0b6d8afbf50349da9b38d74satok if (DBG) { 720e58f3af8a7bf852c3b100de1bd85d95d13e0e15esatok Log.d(TAG, "Cache hit: " + inText + ", " + cachedSuggestionsParams.mFlags); 72174a84febc76d1ec6c0b6d8afbf50349da9b38d74satok } 72274a84febc76d1ec6c0b6d8afbf50349da9b38d74satok return new SuggestionsInfo( 72374a84febc76d1ec6c0b6d8afbf50349da9b38d74satok cachedSuggestionsParams.mFlags, cachedSuggestionsParams.mSuggestions); 72474a84febc76d1ec6c0b6d8afbf50349da9b38d74satok } 725199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard 726e58f3af8a7bf852c3b100de1bd85d95d13e0e15esatok if (shouldFilterOut(inText, mScript)) { 727a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard DictAndProximity dictInfo = null; 728a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard try { 729a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard dictInfo = mDictionaryPool.takeOrGetNull(); 730cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard if (null == dictInfo) return getNotInDictEmptySuggestions(); 731e58f3af8a7bf852c3b100de1bd85d95d13e0e15esatok return dictInfo.mDictionary.isValidWord(inText) ? 732e58f3af8a7bf852c3b100de1bd85d95d13e0e15esatok getInDictEmptySuggestions() : getNotInDictEmptySuggestions(); 733a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard } finally { 734a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard if (null != dictInfo) { 735a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard if (!mDictionaryPool.offer(dictInfo)) { 736a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard Log.e(TAG, "Can't re-insert a dictionary into its pool"); 737a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard } 738a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard } 739a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard } 740e897e4d3422c8d9d8b6f051376cc2ba16e4d5945Jean Chalard } 741e58f3af8a7bf852c3b100de1bd85d95d13e0e15esatok final String text = inText.replaceAll(APOSTROPHE, SINGLE_QUOTE); 742199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard 743647db70fec321d9847f6568cc7bd2b3bd6671322Jean Chalard // TODO: Don't gather suggestions if the limit is <= 0 unless necessary 7444609c02f9e61370557fee675c67263160fbf7feeJean Chalard final SuggestionsGatherer suggestionsGatherer = new SuggestionsGatherer(text, 745a409f009fa410019ad10b1134ff57393443eba33Jean Chalard mService.mSuggestionThreshold, mService.mRecommendedThreshold, 746a409f009fa410019ad10b1134ff57393443eba33Jean Chalard suggestionsLimit); 747199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard final WordComposer composer = new WordComposer(); 748199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard final int length = text.length(); 7499242a2bcf8a6b07bb045a8356711bed1493c251eJean Chalard for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) { 750081616cd2f472295449268cecb570771b969cba3Jean Chalard final int codePoint = text.codePointAt(i); 751081616cd2f472295449268cecb570771b969cba3Jean Chalard // The getXYForCodePointAndScript method returns (Y << 16) + X 752081616cd2f472295449268cecb570771b969cba3Jean Chalard final int xy = SpellCheckerProximityInfo.getXYForCodePointAndScript( 753081616cd2f472295449268cecb570771b969cba3Jean Chalard codePoint, mScript); 754b0b89c87f60a8b6515d830ff5b36866fc64b7a26Jean Chalard if (SpellCheckerProximityInfo.NOT_A_COORDINATE_PAIR == xy) { 755b0b89c87f60a8b6515d830ff5b36866fc64b7a26Jean Chalard composer.add(codePoint, WordComposer.NOT_A_COORDINATE, 756b0b89c87f60a8b6515d830ff5b36866fc64b7a26Jean Chalard WordComposer.NOT_A_COORDINATE, null); 757b0b89c87f60a8b6515d830ff5b36866fc64b7a26Jean Chalard } else { 758b0b89c87f60a8b6515d830ff5b36866fc64b7a26Jean Chalard composer.add(codePoint, xy & 0xFFFF, xy >> 16, null); 759b0b89c87f60a8b6515d830ff5b36866fc64b7a26Jean Chalard } 7605d4c5692f11958064ba7c0de5715f30c96175400Jean Chalard } 761199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard 762199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard final int capitalizeType = getCapitalizationType(text); 763199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard boolean isInDict = true; 764a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard DictAndProximity dictInfo = null; 765a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard try { 766a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard dictInfo = mDictionaryPool.takeOrGetNull(); 767cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard if (null == dictInfo) return getNotInDictEmptySuggestions(); 768315d731d8f11929b6202020475a477024067c1f1satok dictInfo.mDictionary.getWords(composer, prevWord, suggestionsGatherer, 769a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard dictInfo.mProximityInfo); 770a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard isInDict = dictInfo.mDictionary.isValidWord(text); 771a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard if (!isInDict && CAPITALIZE_NONE != capitalizeType) { 772a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard // We want to test the word again if it's all caps or first caps only. 773a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard // If it's fully down, we already tested it, if it's mixed case, we don't 774a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard // want to test a lowercase version of it. 775a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard isInDict = dictInfo.mDictionary.isValidWord(text.toLowerCase(mLocale)); 776a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard } 777a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard } finally { 778a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard if (null != dictInfo) { 779a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard if (!mDictionaryPool.offer(dictInfo)) { 780a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard Log.e(TAG, "Can't re-insert a dictionary into its pool"); 781a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard } 782a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard } 783c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard } 7845bcf8ee66ceb38675a6b70fefcb574978e0fae92satok 78585782abaf178f6aafa1f8999123ff540f04c17bcJean Chalard final SuggestionsGatherer.Result result = suggestionsGatherer.getResults( 78685782abaf178f6aafa1f8999123ff540f04c17bcJean Chalard capitalizeType, mLocale); 787199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard 788199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard if (DBG) { 789199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard Log.i(TAG, "Spell checking results for " + text + " with suggestion limit " 790199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard + suggestionsLimit); 791647db70fec321d9847f6568cc7bd2b3bd6671322Jean Chalard Log.i(TAG, "IsInDict = " + isInDict); 792647db70fec321d9847f6568cc7bd2b3bd6671322Jean Chalard Log.i(TAG, "LooksLikeTypo = " + (!isInDict)); 793a409f009fa410019ad10b1134ff57393443eba33Jean Chalard Log.i(TAG, "HasRecommendedSuggestions = " + result.mHasRecommendedSuggestions); 79451075d145a85d1acaff08c02f4d6b10b175eaa36Jean Chalard if (null != result.mSuggestions) { 79551075d145a85d1acaff08c02f4d6b10b175eaa36Jean Chalard for (String suggestion : result.mSuggestions) { 79651075d145a85d1acaff08c02f4d6b10b175eaa36Jean Chalard Log.i(TAG, suggestion); 79751075d145a85d1acaff08c02f4d6b10b175eaa36Jean Chalard } 798199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard } 799199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard } 800a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard 801199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard final int flags = 802647db70fec321d9847f6568cc7bd2b3bd6671322Jean Chalard (isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY 803a409f009fa410019ad10b1134ff57393443eba33Jean Chalard : SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO) 804a409f009fa410019ad10b1134ff57393443eba33Jean Chalard | (result.mHasRecommendedSuggestions 8059260422423819ed6942f11c03960d5764e97c262Ken Wakasa ? SuggestionsInfoCompatUtils 8069260422423819ed6942f11c03960d5764e97c262Ken Wakasa .getValueOf_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS() 807a409f009fa410019ad10b1134ff57393443eba33Jean Chalard : 0); 80874a84febc76d1ec6c0b6d8afbf50349da9b38d74satok final SuggestionsInfo retval = new SuggestionsInfo(flags, result.mSuggestions); 80974a84febc76d1ec6c0b6d8afbf50349da9b38d74satok mSuggestionsCache.putSuggestionsToCache(text, result.mSuggestions, flags); 81074a84febc76d1ec6c0b6d8afbf50349da9b38d74satok return retval; 811199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard } catch (RuntimeException e) { 812199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard // Don't kill the keyboard if there is a bug in the spell checker 813199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard if (DBG) { 814199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard throw e; 815199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard } else { 816199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard Log.e(TAG, "Exception while spellcheking: " + e); 817cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard return getNotInDictEmptySuggestions(); 818af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard } 819af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard } 8205bcf8ee66ceb38675a6b70fefcb574978e0fae92satok } 821022c1cc20379767966f4915e2dea65fc0b67c0d8satok } 822022c1cc20379767966f4915e2dea65fc0b67c0d8satok} 823