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; 23a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalardimport android.util.Log; 24022c1cc20379767966f4915e2dea65fc0b67c0d8satokimport android.view.textservice.SuggestionsInfo; 25022c1cc20379767966f4915e2dea65fc0b67c0d8satok 263234123fba901243990972158d023a5d1c273316Jean Chalardimport com.android.inputmethod.keyboard.ProximityInfo; 27673cebf9e97289b3b0cd343ff7193dff69684a48Jean Chalardimport com.android.inputmethod.latin.BinaryDictionary; 285f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaokaimport com.android.inputmethod.latin.CollectionUtils; 2967fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalardimport com.android.inputmethod.latin.ContactsBinaryDictionary; 303234123fba901243990972158d023a5d1c273316Jean Chalardimport com.android.inputmethod.latin.Dictionary; 31150bad6fd4b401177c480acf5640b4db0f821886Jean Chalardimport com.android.inputmethod.latin.DictionaryCollection; 323234123fba901243990972158d023a5d1c273316Jean Chalardimport com.android.inputmethod.latin.DictionaryFactory; 33ef35cb631c45c8b106fe7ed9e0d1178c3e5fb963Jean Chalardimport com.android.inputmethod.latin.LocaleUtils; 3459b501a05078e5a9de7cdace19c51ca693076a17Jean Chalardimport com.android.inputmethod.latin.R; 35cc8c8b99bd0463f5977dea82f5e2379ea1dd4e73Tadashi G. Takaokaimport com.android.inputmethod.latin.StringUtils; 3618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyangimport com.android.inputmethod.latin.SynchronouslyLoadedContactsBinaryDictionary; 37f6adff6227a15af105dbf39c57213a24bf16780bTom Ouyangimport com.android.inputmethod.latin.SynchronouslyLoadedUserBinaryDictionary; 3867fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalardimport com.android.inputmethod.latin.UserBinaryDictionary; 393234123fba901243990972158d023a5d1c273316Jean Chalard 40db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalardimport java.lang.ref.WeakReference; 416b166a193398554694cb680f704c2ffc23d03a0eJean Chalardimport java.util.ArrayList; 42f098fbbef324df034cc04de04d9b5fe6657238c7Jean Chalardimport java.util.Arrays; 433234123fba901243990972158d023a5d1c273316Jean Chalardimport java.util.Collections; 44cc8c8b99bd0463f5977dea82f5e2379ea1dd4e73Tadashi G. Takaokaimport java.util.HashSet; 45db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalardimport java.util.Iterator; 463234123fba901243990972158d023a5d1c273316Jean Chalardimport java.util.Locale; 473234123fba901243990972158d023a5d1c273316Jean Chalardimport java.util.Map; 483234123fba901243990972158d023a5d1c273316Jean Chalardimport java.util.TreeMap; 493234123fba901243990972158d023a5d1c273316Jean Chalard 50022c1cc20379767966f4915e2dea65fc0b67c0d8satok/** 51022c1cc20379767966f4915e2dea65fc0b67c0d8satok * Service for spell checking, using LatinIME's dictionaries and mechanisms. 52022c1cc20379767966f4915e2dea65fc0b67c0d8satok */ 53a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaokapublic final class AndroidSpellCheckerService extends SpellCheckerService 54db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard implements SharedPreferences.OnSharedPreferenceChangeListener { 55a90992e56244a914195daba3a2dd8a0e66e63384satok private static final String TAG = AndroidSpellCheckerService.class.getSimpleName(); 56a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard private static final boolean DBG = false; 57a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard private static final int POOL_SIZE = 2; 583234123fba901243990972158d023a5d1c273316Jean Chalard 59db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard public static final String PREF_USE_CONTACTS_KEY = "pref_spellcheck_use_contacts"; 60db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard 6184ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka public static final int CAPITALIZE_NONE = 0; // No caps, or mixed case 6284ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka public static final int CAPITALIZE_FIRST = 1; // First only 6384ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka public static final int CAPITALIZE_ALL = 2; // All caps 64f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard 656b166a193398554694cb680f704c2ffc23d03a0eJean Chalard private final static String[] EMPTY_STRING_ARRAY = new String[0]; 665f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka private Map<String, DictionaryPool> mDictionaryPools = CollectionUtils.newSynchronizedTreeMap(); 6767fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard private Map<String, UserBinaryDictionary> mUserDictionaries = 685f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka CollectionUtils.newSynchronizedTreeMap(); 6967fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard private ContactsBinaryDictionary mContactsDictionary; 703234123fba901243990972158d023a5d1c273316Jean Chalard 714609c02f9e61370557fee675c67263160fbf7feeJean Chalard // The threshold for a candidate to be offered as a suggestion. 720028ed3627ff4f37a62a80f3b2c857e373cd5090satok private float mSuggestionThreshold; 73a409f009fa410019ad10b1134ff57393443eba33Jean Chalard // The threshold for a suggestion to be considered "recommended". 740028ed3627ff4f37a62a80f3b2c857e373cd5090satok private float mRecommendedThreshold; 75db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard // Whether to use the contacts dictionary 76db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard private boolean mUseContactsDictionary; 77db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard private final Object mUseContactsLock = new Object(); 78db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard 79db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard private final HashSet<WeakReference<DictionaryCollection>> mDictionaryCollectionsList = 805f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka CollectionUtils.newHashSet(); 8159b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard 821830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard public static final int SCRIPT_LATIN = 0; 831830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard public static final int SCRIPT_CYRILLIC = 1; 8484ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka public static final String SINGLE_QUOTE = "\u0027"; 8584ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka public static final String APOSTROPHE = "\u2019"; 861830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard private static final TreeMap<String, Integer> mLanguageToScript; 871830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard static { 881830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard // List of the supported languages and their associated script. We won't check 891830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard // words written in another script than the selected script, because we know we 901830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard // don't have those in our dictionary so we will underline everything and we 91b1f3c24c6326ad63b4fcad4014c20161984e40efJean Chalard // will never have any suggestions, so it makes no sense checking them, and this 92b1f3c24c6326ad63b4fcad4014c20161984e40efJean Chalard // is done in {@link #shouldFilterOut}. Also, the script is used to choose which 93b1f3c24c6326ad63b4fcad4014c20161984e40efJean Chalard // proximity to pass to the dictionary descent algorithm. 94b1f3c24c6326ad63b4fcad4014c20161984e40efJean Chalard // IMPORTANT: this only contains languages - do not write countries in there. 95b1f3c24c6326ad63b4fcad4014c20161984e40efJean Chalard // Only the language is searched from the map. 965f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka mLanguageToScript = CollectionUtils.newTreeMap(); 971830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard mLanguageToScript.put("en", SCRIPT_LATIN); 981830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard mLanguageToScript.put("fr", SCRIPT_LATIN); 991830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard mLanguageToScript.put("de", SCRIPT_LATIN); 1001830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard mLanguageToScript.put("nl", SCRIPT_LATIN); 1011830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard mLanguageToScript.put("cs", SCRIPT_LATIN); 1021830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard mLanguageToScript.put("es", SCRIPT_LATIN); 1031830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard mLanguageToScript.put("it", SCRIPT_LATIN); 104d527a15ec44089930dd23c9e20b8672024a4555bJean Chalard mLanguageToScript.put("hr", SCRIPT_LATIN); 105b1f3c24c6326ad63b4fcad4014c20161984e40efJean Chalard mLanguageToScript.put("pt", SCRIPT_LATIN); 1061830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard mLanguageToScript.put("ru", SCRIPT_CYRILLIC); 107d527a15ec44089930dd23c9e20b8672024a4555bJean Chalard // TODO: Make a persian proximity, and activate the Farsi subtype. 108d527a15ec44089930dd23c9e20b8672024a4555bJean Chalard // mLanguageToScript.put("fa", SCRIPT_PERSIAN); 1091830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard } 1101830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard 11159b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard @Override public void onCreate() { 11259b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard super.onCreate(); 1134609c02f9e61370557fee675c67263160fbf7feeJean Chalard mSuggestionThreshold = 1140028ed3627ff4f37a62a80f3b2c857e373cd5090satok Float.parseFloat(getString(R.string.spellchecker_suggestion_threshold_value)); 115a409f009fa410019ad10b1134ff57393443eba33Jean Chalard mRecommendedThreshold = 1160028ed3627ff4f37a62a80f3b2c857e373cd5090satok Float.parseFloat(getString(R.string.spellchecker_recommended_threshold_value)); 117db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 118db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard prefs.registerOnSharedPreferenceChangeListener(this); 119db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard onSharedPreferenceChanged(prefs, PREF_USE_CONTACTS_KEY); 120db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 121db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard 12284ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka public static int getScriptFromLocale(final Locale locale) { 1231830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard final Integer script = mLanguageToScript.get(locale.getLanguage()); 1241830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard if (null == script) { 1251830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard throw new RuntimeException("We have been called with an unsupported language: \"" 1261830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard + locale.getLanguage() + "\". Framework bug?"); 1271830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard } 1281830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard return script; 1291830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard } 1301830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard 131db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard @Override 132db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { 133db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (!PREF_USE_CONTACTS_KEY.equals(key)) return; 134db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard synchronized(mUseContactsLock) { 135db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard mUseContactsDictionary = prefs.getBoolean(PREF_USE_CONTACTS_KEY, true); 136db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (mUseContactsDictionary) { 137db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard startUsingContactsDictionaryLocked(); 138db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } else { 139db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard stopUsingContactsDictionaryLocked(); 140db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 141db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 142db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 143db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard 144db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard private void startUsingContactsDictionaryLocked() { 145db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (null == mContactsDictionary) { 14667fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard // TODO: use the right locale for each session 14767fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard mContactsDictionary = 14867fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard new SynchronouslyLoadedContactsBinaryDictionary(this, Locale.getDefault()); 149db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 150db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard final Iterator<WeakReference<DictionaryCollection>> iterator = 151db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard mDictionaryCollectionsList.iterator(); 152db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard while (iterator.hasNext()) { 153db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard final WeakReference<DictionaryCollection> dictRef = iterator.next(); 154db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard final DictionaryCollection dict = dictRef.get(); 155db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (null == dict) { 156db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard iterator.remove(); 157db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } else { 158db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard dict.addDictionary(mContactsDictionary); 159db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 160db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 161db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 162db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard 163db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard private void stopUsingContactsDictionaryLocked() { 164db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (null == mContactsDictionary) return; 16518222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang final Dictionary contactsDict = mContactsDictionary; 16618222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang // TODO: revert to the concrete type when USE_BINARY_CONTACTS_DICTIONARY is no longer needed 167db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard mContactsDictionary = null; 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.removeDictionary(contactsDict); 177db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 178db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 179db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard contactsDict.close(); 18059b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard } 18159b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard 1825bcf8ee66ceb38675a6b70fefcb574978e0fae92satok @Override 1835bcf8ee66ceb38675a6b70fefcb574978e0fae92satok public Session createSession() { 18437b19ffe6c9d8335cc0e1c1c50f5b08c778b287cSatoshi Kataoka // Should not refer to AndroidSpellCheckerSession directly considering 18537b19ffe6c9d8335cc0e1c1c50f5b08c778b287cSatoshi Kataoka // that AndroidSpellCheckerSession may be overlaid. 18637b19ffe6c9d8335cc0e1c1c50f5b08c778b287cSatoshi Kataoka return AndroidSpellCheckerSessionFactory.newInstance(this); 1875bcf8ee66ceb38675a6b70fefcb574978e0fae92satok } 1885bcf8ee66ceb38675a6b70fefcb574978e0fae92satok 18984ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka public static SuggestionsInfo getNotInDictEmptySuggestions() { 190cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard return new SuggestionsInfo(0, EMPTY_STRING_ARRAY); 191cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard } 192cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard 19384ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka public static SuggestionsInfo getInDictEmptySuggestions() { 194cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard return new SuggestionsInfo(SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY, 195cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard EMPTY_STRING_ARRAY); 196cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard } 197cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard 19884ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka public SuggestionsGatherer newSuggestionsGatherer(final String text, int maxLength) { 19984ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka return new SuggestionsGatherer( 20084ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka text, mSuggestionThreshold, mRecommendedThreshold, maxLength); 20184ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka } 20284ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka 20361e7ec658710eca3fd03af39b52b4a87eabcdd4cJean Chalard // TODO: remove this class and replace it by storage local to the session. 204a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaoka public static final class SuggestionsGatherer { 205a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaoka public static final class Result { 20659b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard public final String[] mSuggestions; 207a409f009fa410019ad10b1134ff57393443eba33Jean Chalard public final boolean mHasRecommendedSuggestions; 208a409f009fa410019ad10b1134ff57393443eba33Jean Chalard public Result(final String[] gatheredSuggestions, 209a409f009fa410019ad10b1134ff57393443eba33Jean Chalard final boolean hasRecommendedSuggestions) { 21059b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard mSuggestions = gatheredSuggestions; 211a409f009fa410019ad10b1134ff57393443eba33Jean Chalard mHasRecommendedSuggestions = hasRecommendedSuggestions; 21259b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard } 21359b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard } 21459b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard 2156b166a193398554694cb680f704c2ffc23d03a0eJean Chalard private final ArrayList<CharSequence> mSuggestions; 2163234123fba901243990972158d023a5d1c273316Jean Chalard private final int[] mScores; 21785782abaf178f6aafa1f8999123ff540f04c17bcJean Chalard private final String mOriginalText; 2180028ed3627ff4f37a62a80f3b2c857e373cd5090satok private final float mSuggestionThreshold; 2190028ed3627ff4f37a62a80f3b2c857e373cd5090satok private final float mRecommendedThreshold; 2203234123fba901243990972158d023a5d1c273316Jean Chalard private final int mMaxLength; 2213234123fba901243990972158d023a5d1c273316Jean Chalard private int mLength = 0; 22259b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard 22359b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard // The two following attributes are only ever filled if the requested max length 22459b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard // is 0 (or less, which is treated the same). 22559b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard private String mBestSuggestion = null; 22659b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard private int mBestScore = Integer.MIN_VALUE; // As small as possible 2273234123fba901243990972158d023a5d1c273316Jean Chalard 2280028ed3627ff4f37a62a80f3b2c857e373cd5090satok SuggestionsGatherer(final String originalText, final float suggestionThreshold, 2290028ed3627ff4f37a62a80f3b2c857e373cd5090satok final float recommendedThreshold, final int maxLength) { 23085782abaf178f6aafa1f8999123ff540f04c17bcJean Chalard mOriginalText = originalText; 2314609c02f9e61370557fee675c67263160fbf7feeJean Chalard mSuggestionThreshold = suggestionThreshold; 232a409f009fa410019ad10b1134ff57393443eba33Jean Chalard mRecommendedThreshold = recommendedThreshold; 2333234123fba901243990972158d023a5d1c273316Jean Chalard mMaxLength = maxLength; 2345f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka mSuggestions = CollectionUtils.newArrayList(maxLength + 1); 2353234123fba901243990972158d023a5d1c273316Jean Chalard mScores = new int[mMaxLength]; 2363234123fba901243990972158d023a5d1c273316Jean Chalard } 2373234123fba901243990972158d023a5d1c273316Jean Chalard 2380a7944653105f257d99e9db2d90b2bfc932ee765Jean Chalard synchronized public boolean addWord(char[] word, int[] spaceIndices, int wordOffset, 2390a7944653105f257d99e9db2d90b2bfc932ee765Jean Chalard int wordLength, int score) { 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; 3625f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka mDictionaryPools = CollectionUtils.newSynchronizedTreeMap(); 36367fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard final Map<String, UserBinaryDictionary> oldUserDictionaries = mUserDictionaries; 3645f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka mUserDictionaries = CollectionUtils.newSynchronizedTreeMap(); 365769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard new Thread("spellchecker_close_dicts") { 366769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard @Override 367769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard public void run() { 368769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard for (DictionaryPool pool : oldPools.values()) { 369769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard pool.close(); 370769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard } 371769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard for (Dictionary dict : oldUserDictionaries.values()) { 372769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard dict.close(); 373769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard } 374769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard synchronized (mUseContactsLock) { 375769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard if (null != mContactsDictionary) { 376769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard // The synchronously loaded contacts dictionary should have been in one 377769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard // or several pools, but it is shielded against multiple closing and it's 378769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard // safe to call it several times. 37967fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard final ContactsBinaryDictionary dictToClose = mContactsDictionary; 380769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard // TODO: revert to the concrete type when USE_BINARY_CONTACTS_DICTIONARY 381769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard // is no longer needed 382769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard mContactsDictionary = null; 383769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard dictToClose.close(); 384769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard } 385769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard } 386db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 387769cecf7e79dc6e2a98e527bdb9943bef9a42396Jean Chalard }.start(); 388c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard } 389c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard 39084ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka public 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(); 41167fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard UserBinaryDictionary userDictionary = mUserDictionaries.get(localeStr); 412fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard if (null == userDictionary) { 41367fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard userDictionary = new SynchronouslyLoadedUserBinaryDictionary(this, localeStr, true); 414fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard mUserDictionaries.put(localeStr, userDictionary); 415fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard } 416fee149abe0358ff0efcebff3d0b60d8be83af437Jean Chalard dictionaryCollection.addDictionary(userDictionary); 41718222f8c863e509538857b1fafca9c696fae2f55Tom Ouyang synchronized (mUseContactsLock) { 418db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (mUseContactsDictionary) { 419db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard if (null == mContactsDictionary) { 42067fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard // TODO: use the right locale. We can't do it right now because the 42167fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard // spell checker is reusing the contacts dictionary across sessions 42267fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard // without regard for their locale, so we need to fix that first. 42367fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard mContactsDictionary = new SynchronouslyLoadedContactsBinaryDictionary(this, 42467fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard Locale.getDefault()); 425db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 426db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard } 427db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard dictionaryCollection.addDictionary(mContactsDictionary); 428db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard mDictionaryCollectionsList.add( 429db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard new WeakReference<DictionaryCollection>(dictionaryCollection)); 4302e3c6da8688a907024d4d8e0f2db3e0ed4fab8dbJean Chalard } 431150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard return new DictAndProximity(dictionaryCollection, proximityInfo); 4323234123fba901243990972158d023a5d1c273316Jean Chalard } 4333234123fba901243990972158d023a5d1c273316Jean Chalard 434f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard // This method assumes the text is not empty or null. 43584ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka public static int getCapitalizationType(String text) { 436f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard // If the first char is not uppercase, then the word is either all lower case, 437f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard // and in either case we return CAPITALIZE_NONE. 438f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard if (!Character.isUpperCase(text.codePointAt(0))) return CAPITALIZE_NONE; 4399242a2bcf8a6b07bb045a8356711bed1493c251eJean Chalard final int len = text.length(); 440f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard int capsCount = 1; 4419242a2bcf8a6b07bb045a8356711bed1493c251eJean Chalard for (int i = 1; i < len; i = text.offsetByCodePoints(i, 1)) { 442f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard if (1 != capsCount && i != capsCount) break; 443f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard if (Character.isUpperCase(text.codePointAt(i))) ++capsCount; 444f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard } 445f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard // We know the first char is upper case. So we want to test if either everything 446f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard // else is lower case, or if everything else is upper case. If the string is 447f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard // exactly one char long, then we will arrive here with capsCount 1, and this is 448f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard // correct, too. 449f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard if (1 == capsCount) return CAPITALIZE_FIRST; 450f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard return (len == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE); 451f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard } 452022c1cc20379767966f4915e2dea65fc0b67c0d8satok} 453