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;
24204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaokaimport android.view.inputmethod.EditorInfo;
25204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaokaimport android.view.inputmethod.InputMethodSubtype;
26022c1cc20379767966f4915e2dea65fc0b67c0d8satokimport android.view.textservice.SuggestionsInfo;
27022c1cc20379767966f4915e2dea65fc0b67c0d8satok
289e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagiimport com.android.inputmethod.keyboard.Keyboard;
299e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagiimport com.android.inputmethod.keyboard.KeyboardId;
30244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataokaimport com.android.inputmethod.keyboard.KeyboardLayoutSet;
319e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagiimport com.android.inputmethod.latin.DictionaryFacilitator;
32d267764d5abf12f95d09d1ec8f02548d992ca612Keisuke Kuroyanagiimport com.android.inputmethod.latin.DictionaryFacilitatorLruCache;
33bb0eca57054758ef17b032d2654c1fc5f6b32101Keisuke Kuroyanagiimport com.android.inputmethod.latin.NgramContext;
3459b501a05078e5a9de7cdace19c51ca693076a17Jean Chalardimport com.android.inputmethod.latin.R;
3585ddfe1317a4475269e53f62c2338c335e02e839Jean Chalardimport com.android.inputmethod.latin.RichInputMethodSubtype;
36b00c054125d9f2aa31c2147920cc52cbf2a45cccMohammadinamul Sheikimport com.android.inputmethod.latin.SuggestedWords;
372b8d763c65b2482fcdc7efe301907ac18133fa42Martin Paraskevovimport com.android.inputmethod.latin.common.ComposedData;
389e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagiimport com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
39204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaokaimport com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
40289299bf66de5fb0c8a378f2366c0760da27077bJean Chalardimport com.android.inputmethod.latin.utils.ScriptUtils;
419e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagiimport com.android.inputmethod.latin.utils.SuggestionResults;
423234123fba901243990972158d023a5d1c273316Jean Chalard
433234123fba901243990972158d023a5d1c273316Jean Chalardimport java.util.Locale;
449e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagiimport java.util.concurrent.ConcurrentHashMap;
459e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagiimport java.util.concurrent.ConcurrentLinkedQueue;
469e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagiimport java.util.concurrent.Semaphore;
473234123fba901243990972158d023a5d1c273316Jean Chalard
48487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanevimport javax.annotation.Nonnull;
49487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanev
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 {
558aa310aa5a1b8e726e78c57361d496a82c569bf6Mario Tanev    private static final String TAG = AndroidSpellCheckerService.class.getSimpleName();
568aa310aa5a1b8e726e78c57361d496a82c569bf6Mario Tanev    private static final boolean DEBUG = false;
578aa310aa5a1b8e726e78c57361d496a82c569bf6Mario Tanev
58db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard    public static final String PREF_USE_CONTACTS_KEY = "pref_spellcheck_use_contacts";
59db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard
60204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka    private static final int SPELLCHECKER_DUMMY_KEYBOARD_WIDTH = 480;
616cc318bd6a2c5993cce128d75fe753c3686331c1Keisuke Kuroyanagi    private static final int SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT = 301;
62204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka
639e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    private static final String DICTIONARY_NAME_PREFIX = "spellcheck_";
649e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi
659e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    private static final String[] EMPTY_STRING_ARRAY = new String[0];
669e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi
679e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    private final int MAX_NUM_OF_THREADS_READ_DICTIONARY = 2;
689e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    private final Semaphore mSemaphore = new Semaphore(MAX_NUM_OF_THREADS_READ_DICTIONARY,
699e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            true /* fair */);
709e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    // TODO: Make each spell checker session has its own session id.
719e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    private final ConcurrentLinkedQueue<Integer> mSessionIdPool = new ConcurrentLinkedQueue<>();
729e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi
73d267764d5abf12f95d09d1ec8f02548d992ca612Keisuke Kuroyanagi    private final DictionaryFacilitatorLruCache mDictionaryFacilitatorCache =
74d6a8adcb044dd8b73a1c96776a835b411a978b46Dan Zivkovic            new DictionaryFacilitatorLruCache(this /* context */, DICTIONARY_NAME_PREFIX);
759e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    private final ConcurrentHashMap<Locale, Keyboard> mKeyboardCache = new ConcurrentHashMap<>();
763234123fba901243990972158d023a5d1c273316Jean Chalard
77a409f009fa410019ad10b1134ff57393443eba33Jean Chalard    // The threshold for a suggestion to be considered "recommended".
780028ed3627ff4f37a62a80f3b2c857e373cd5090satok    private float mRecommendedThreshold;
799e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    // TODO: make a spell checker option to block offensive words or not
809e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    private final SettingsValuesForSuggestion mSettingsValuesForSuggestion =
814e0af43673936c336fff298bc9aeee1d75c3bc92Chieu Nguyen            new SettingsValuesForSuggestion(true /* blockPotentiallyOffensive */);
8259b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard
8384ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka    public static final String SINGLE_QUOTE = "\u0027";
8484ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka    public static final String APOSTROPHE = "\u2019";
851830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard
869e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    public AndroidSpellCheckerService() {
879e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        super();
889e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        for (int i = 0; i < MAX_NUM_OF_THREADS_READ_DICTIONARY; i++) {
899e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            mSessionIdPool.add(i);
909e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        }
919e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    }
929e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi
9326fb83c481034cb9dbc9504e60fde40c6b213e97Dan Zivkovic    @Override
9426fb83c481034cb9dbc9504e60fde40c6b213e97Dan Zivkovic    public void onCreate() {
9559b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard        super.onCreate();
9687eb7ac29c51ba4c341cb663cdbbc5ea74595f2dDan Zivkovic        mRecommendedThreshold = Float.parseFloat(
9787eb7ac29c51ba4c341cb663cdbbc5ea74595f2dDan Zivkovic                getString(R.string.spellchecker_recommended_threshold_value));
98db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
99db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard        prefs.registerOnSharedPreferenceChangeListener(this);
100db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard        onSharedPreferenceChanged(prefs, PREF_USE_CONTACTS_KEY);
101db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard    }
102db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard
1031467fa0c2642e05aef6f588eef0f704b6da3aee2Keisuke Kuroyanagi    public float getRecommendedThreshold() {
1041467fa0c2642e05aef6f588eef0f704b6da3aee2Keisuke Kuroyanagi        return mRecommendedThreshold;
1051467fa0c2642e05aef6f588eef0f704b6da3aee2Keisuke Kuroyanagi    }
1061467fa0c2642e05aef6f588eef0f704b6da3aee2Keisuke Kuroyanagi
10723a7998edde3b25d4dc7a14b8a653ccd325d2405Dan Zivkovic    private static String getKeyboardLayoutNameForLocale(final Locale locale) {
10823a7998edde3b25d4dc7a14b8a653ccd325d2405Dan Zivkovic        // See b/19963288.
10923a7998edde3b25d4dc7a14b8a653ccd325d2405Dan Zivkovic        if (locale.getLanguage().equals("sr")) {
11023a7998edde3b25d4dc7a14b8a653ccd325d2405Dan Zivkovic            return "south_slavic";
11123a7998edde3b25d4dc7a14b8a653ccd325d2405Dan Zivkovic        }
11223a7998edde3b25d4dc7a14b8a653ccd325d2405Dan Zivkovic        final int script = ScriptUtils.getScriptFromSpellCheckerLocale(locale);
113244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka        switch (script) {
114289299bf66de5fb0c8a378f2366c0760da27077bJean Chalard        case ScriptUtils.SCRIPT_LATIN:
115244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka            return "qwerty";
116289299bf66de5fb0c8a378f2366c0760da27077bJean Chalard        case ScriptUtils.SCRIPT_CYRILLIC:
117244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka            return "east_slavic";
118289299bf66de5fb0c8a378f2366c0760da27077bJean Chalard        case ScriptUtils.SCRIPT_GREEK:
119244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka            return "greek";
12016c262abe57a1cc481930391e800c35867f3d0b4Chieu Nguyen        case ScriptUtils.SCRIPT_HEBREW:
12116c262abe57a1cc481930391e800c35867f3d0b4Chieu Nguyen            return "hebrew";
122244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka        default:
123244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka            throw new RuntimeException("Wrong script supplied: " + script);
124244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka        }
125244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka    }
126244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka
127db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard    @Override
128db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard    public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
129db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard        if (!PREF_USE_CONTACTS_KEY.equals(key)) return;
13029aa3df3dadeb5829a70652a24b0756e2c9e45caDan Zivkovic        final boolean useContactsDictionary = prefs.getBoolean(PREF_USE_CONTACTS_KEY, true);
13129aa3df3dadeb5829a70652a24b0756e2c9e45caDan Zivkovic        mDictionaryFacilitatorCache.setUseContactsDictionary(useContactsDictionary);
13259b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard    }
13359b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard
1345bcf8ee66ceb38675a6b70fefcb574978e0fae92satok    @Override
1355bcf8ee66ceb38675a6b70fefcb574978e0fae92satok    public Session createSession() {
13637b19ffe6c9d8335cc0e1c1c50f5b08c778b287cSatoshi Kataoka        // Should not refer to AndroidSpellCheckerSession directly considering
13737b19ffe6c9d8335cc0e1c1c50f5b08c778b287cSatoshi Kataoka        // that AndroidSpellCheckerSession may be overlaid.
13837b19ffe6c9d8335cc0e1c1c50f5b08c778b287cSatoshi Kataoka        return AndroidSpellCheckerSessionFactory.newInstance(this);
1395bcf8ee66ceb38675a6b70fefcb574978e0fae92satok    }
1405bcf8ee66ceb38675a6b70fefcb574978e0fae92satok
141df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard    /**
142df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard     * Returns an empty SuggestionsInfo with flags signaling the word is not in the dictionary.
143df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard     * @param reportAsTypo whether this should include the flag LOOKS_LIKE_TYPO, for red underline.
144df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard     * @return the empty SuggestionsInfo with the appropriate flags set.
145df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard     */
146df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard    public static SuggestionsInfo getNotInDictEmptySuggestions(final boolean reportAsTypo) {
147df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard        return new SuggestionsInfo(reportAsTypo ? SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO : 0,
148df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard                EMPTY_STRING_ARRAY);
149cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard    }
150cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard
151df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard    /**
152df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard     * Returns an empty suggestionInfo with flags signaling the word is in the dictionary.
153df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard     * @return the empty SuggestionsInfo with the appropriate flags set.
154df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard     */
15584ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka    public static SuggestionsInfo getInDictEmptySuggestions() {
156cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard        return new SuggestionsInfo(SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY,
157cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard                EMPTY_STRING_ARRAY);
158cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard    }
159cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard
1609e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    public boolean isValidWord(final Locale locale, final String word) {
1619e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        mSemaphore.acquireUninterruptibly();
1629e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        try {
1639e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            DictionaryFacilitator dictionaryFacilitatorForLocale =
164d267764d5abf12f95d09d1ec8f02548d992ca612Keisuke Kuroyanagi                    mDictionaryFacilitatorCache.get(locale);
165c0eb57124fd295ceb85c3350de3189c40594ee96Dan Zivkovic            return dictionaryFacilitatorForLocale.isValidSpellingWord(word);
1669e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        } finally {
1679e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            mSemaphore.release();
1689e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        }
1698403611960cd0b2a40b77275c536e8088c098830Jean Chalard    }
1708403611960cd0b2a40b77275c536e8088c098830Jean Chalard
1712b8d763c65b2482fcdc7efe301907ac18133fa42Martin Paraskevov    public SuggestionResults getSuggestionResults(final Locale locale,
1722b8d763c65b2482fcdc7efe301907ac18133fa42Martin Paraskevov            final ComposedData composedData, final NgramContext ngramContext,
173487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanev            @Nonnull final Keyboard keyboard) {
1749e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        Integer sessionId = null;
1759e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        mSemaphore.acquireUninterruptibly();
1769e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        try {
1779e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            sessionId = mSessionIdPool.poll();
1789e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            DictionaryFacilitator dictionaryFacilitatorForLocale =
179d267764d5abf12f95d09d1ec8f02548d992ca612Keisuke Kuroyanagi                    mDictionaryFacilitatorCache.get(locale);
1802b8d763c65b2482fcdc7efe301907ac18133fa42Martin Paraskevov            return dictionaryFacilitatorForLocale.getSuggestionResults(composedData, ngramContext,
181487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanev                    keyboard, mSettingsValuesForSuggestion,
182487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanev                    sessionId, SuggestedWords.INPUT_STYLE_TYPING);
1839e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        } finally {
1849e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            if (sessionId != null) {
1859e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi                mSessionIdPool.add(sessionId);
1869e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            }
1879e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            mSemaphore.release();
1889e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        }
1899e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    }
1909e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi
1919e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    public boolean hasMainDictionaryForLocale(final Locale locale) {
1929e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        mSemaphore.acquireUninterruptibly();
1939e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        try {
1949e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            final DictionaryFacilitator dictionaryFacilitator =
195d267764d5abf12f95d09d1ec8f02548d992ca612Keisuke Kuroyanagi                    mDictionaryFacilitatorCache.get(locale);
1968cd53266229895a3e0c6618e3765d57fc5d0b392Jean Chalard            return dictionaryFacilitator.hasAtLeastOneInitializedMainDictionary();
1979e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        } finally {
1989e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            mSemaphore.release();
1999e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        }
2009e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    }
2019e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi
2029e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    @Override
2039e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    public boolean onUnbind(final Intent intent) {
2049e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        mSemaphore.acquireUninterruptibly(MAX_NUM_OF_THREADS_READ_DICTIONARY);
2059e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        try {
20629aa3df3dadeb5829a70652a24b0756e2c9e45caDan Zivkovic            mDictionaryFacilitatorCache.closeDictionaries();
2079e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        } finally {
2089e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            mSemaphore.release(MAX_NUM_OF_THREADS_READ_DICTIONARY);
2099e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        }
2109e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        mKeyboardCache.clear();
2119e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        return false;
212c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard    }
213c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard
2149e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    public Keyboard getKeyboardForLocale(final Locale locale) {
2159e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        Keyboard keyboard = mKeyboardCache.get(locale);
2169e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        if (keyboard == null) {
2179e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            keyboard = createKeyboardForLocale(locale);
2189e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            if (keyboard != null) {
2199e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi                mKeyboardCache.put(locale, keyboard);
2209e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            }
2213234123fba901243990972158d023a5d1c273316Jean Chalard        }
2229e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        return keyboard;
223a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard    }
224a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard
2259e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    private Keyboard createKeyboardForLocale(final Locale locale) {
22623a7998edde3b25d4dc7a14b8a653ccd325d2405Dan Zivkovic        final String keyboardLayoutName = getKeyboardLayoutNameForLocale(locale);
2273895d7f8dc2e4999947f61220b86fa148f433413Yohei Yukawa        final InputMethodSubtype subtype = AdditionalSubtypeUtils.createDummyAdditionalSubtype(
2283895d7f8dc2e4999947f61220b86fa148f433413Yohei Yukawa                locale.toString(), keyboardLayoutName);
229204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka        final KeyboardLayoutSet keyboardLayoutSet = createKeyboardSetForSpellChecker(subtype);
2309e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        return keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
2313234123fba901243990972158d023a5d1c273316Jean Chalard    }
232204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka
233204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka    private KeyboardLayoutSet createKeyboardSetForSpellChecker(final InputMethodSubtype subtype) {
234204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka        final EditorInfo editorInfo = new EditorInfo();
235204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka        editorInfo.inputType = InputType.TYPE_CLASS_TEXT;
236204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka        final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(this, editorInfo);
237204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka        builder.setKeyboardGeometry(
238204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka                SPELLCHECKER_DUMMY_KEYBOARD_WIDTH, SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT);
239b86ca76cea9aedf47a81f9272fb59897de3bbbe7Dan Zivkovic        builder.setSubtype(RichInputMethodSubtype.getRichInputMethodSubtype(subtype));
240204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka        builder.setIsSpellChecker(true /* isSpellChecker */);
241204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka        builder.disableTouchPositionCorrectionData();
242204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka        return builder.build();
243204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka    }
244022c1cc20379767966f4915e2dea65fc0b67c0d8satok}
245