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