AndroidSpellCheckerService.java revision ecab6aff5908bfd5b34670d2e2bb3696627fa47c
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.keyboard.ProximityInfo;
329e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagiimport com.android.inputmethod.latin.DictionaryFacilitator;
33d267764d5abf12f95d09d1ec8f02548d992ca612Keisuke Kuroyanagiimport com.android.inputmethod.latin.DictionaryFacilitatorLruCache;
34bb0eca57054758ef17b032d2654c1fc5f6b32101Keisuke Kuroyanagiimport com.android.inputmethod.latin.NgramContext;
3559b501a05078e5a9de7cdace19c51ca693076a17Jean Chalardimport com.android.inputmethod.latin.R;
3685ddfe1317a4475269e53f62c2338c335e02e839Jean Chalardimport com.android.inputmethod.latin.RichInputMethodSubtype;
379e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagiimport com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
38204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaokaimport com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
39289299bf66de5fb0c8a378f2366c0760da27077bJean Chalardimport com.android.inputmethod.latin.utils.ScriptUtils;
409e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagiimport com.android.inputmethod.latin.utils.SuggestionResults;
419e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagiimport com.android.inputmethod.latin.WordComposer;
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
48022c1cc20379767966f4915e2dea65fc0b67c0d8satok/**
49022c1cc20379767966f4915e2dea65fc0b67c0d8satok * Service for spell checking, using LatinIME's dictionaries and mechanisms.
50022c1cc20379767966f4915e2dea65fc0b67c0d8satok */
51a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaokapublic final class AndroidSpellCheckerService extends SpellCheckerService
52db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard        implements SharedPreferences.OnSharedPreferenceChangeListener {
53db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard    public static final String PREF_USE_CONTACTS_KEY = "pref_spellcheck_use_contacts";
54db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard
55204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka    private static final int SPELLCHECKER_DUMMY_KEYBOARD_WIDTH = 480;
566cc318bd6a2c5993cce128d75fe753c3686331c1Keisuke Kuroyanagi    private static final int SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT = 301;
57204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka
589e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    private static final String DICTIONARY_NAME_PREFIX = "spellcheck_";
599e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi
609e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    private static final String[] EMPTY_STRING_ARRAY = new String[0];
619e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi
629e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    private final int MAX_NUM_OF_THREADS_READ_DICTIONARY = 2;
639e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    private final Semaphore mSemaphore = new Semaphore(MAX_NUM_OF_THREADS_READ_DICTIONARY,
649e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            true /* fair */);
659e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    // TODO: Make each spell checker session has its own session id.
669e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    private final ConcurrentLinkedQueue<Integer> mSessionIdPool = new ConcurrentLinkedQueue<>();
679e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi
689e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    private static final int MAX_DICTIONARY_FACILITATOR_COUNT = 3;
69d267764d5abf12f95d09d1ec8f02548d992ca612Keisuke Kuroyanagi    private final DictionaryFacilitatorLruCache mDictionaryFacilitatorCache =
70d267764d5abf12f95d09d1ec8f02548d992ca612Keisuke Kuroyanagi            new DictionaryFacilitatorLruCache(this /* context */, MAX_DICTIONARY_FACILITATOR_COUNT,
71d267764d5abf12f95d09d1ec8f02548d992ca612Keisuke Kuroyanagi                    DICTIONARY_NAME_PREFIX);
729e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    private final ConcurrentHashMap<Locale, Keyboard> mKeyboardCache = new ConcurrentHashMap<>();
733234123fba901243990972158d023a5d1c273316Jean Chalard
74a409f009fa410019ad10b1134ff57393443eba33Jean Chalard    // The threshold for a suggestion to be considered "recommended".
750028ed3627ff4f37a62a80f3b2c857e373cd5090satok    private float mRecommendedThreshold;
769e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    // TODO: make a spell checker option to block offensive words or not
779e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    private final SettingsValuesForSuggestion mSettingsValuesForSuggestion =
789e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            new SettingsValuesForSuggestion(true /* blockPotentiallyOffensive */,
799e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi                    true /* spaceAwareGestureEnabled */,
809e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi                    null /* additionalFeaturesSettingValues */);
8159b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard
8284ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka    public static final String SINGLE_QUOTE = "\u0027";
8384ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka    public static final String APOSTROPHE = "\u2019";
841830cd1dc8259aa57175f1cf2a3d8797a7a35935Jean Chalard
859e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    public AndroidSpellCheckerService() {
869e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        super();
879e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        for (int i = 0; i < MAX_NUM_OF_THREADS_READ_DICTIONARY; i++) {
889e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            mSessionIdPool.add(i);
899e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        }
909e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    }
919e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi
9259b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard    @Override public void onCreate() {
9359b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard        super.onCreate();
94a409f009fa410019ad10b1134ff57393443eba33Jean Chalard        mRecommendedThreshold =
950028ed3627ff4f37a62a80f3b2c857e373cd5090satok                Float.parseFloat(getString(R.string.spellchecker_recommended_threshold_value));
96db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
97db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard        prefs.registerOnSharedPreferenceChangeListener(this);
98db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard        onSharedPreferenceChanged(prefs, PREF_USE_CONTACTS_KEY);
99db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard    }
100db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard
1011467fa0c2642e05aef6f588eef0f704b6da3aee2Keisuke Kuroyanagi    public float getRecommendedThreshold() {
1021467fa0c2642e05aef6f588eef0f704b6da3aee2Keisuke Kuroyanagi        return mRecommendedThreshold;
1031467fa0c2642e05aef6f588eef0f704b6da3aee2Keisuke Kuroyanagi    }
1041467fa0c2642e05aef6f588eef0f704b6da3aee2Keisuke Kuroyanagi
105244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka    private static String getKeyboardLayoutNameForScript(final int script) {
106244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka        switch (script) {
107289299bf66de5fb0c8a378f2366c0760da27077bJean Chalard        case ScriptUtils.SCRIPT_LATIN:
108244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka            return "qwerty";
109289299bf66de5fb0c8a378f2366c0760da27077bJean Chalard        case ScriptUtils.SCRIPT_CYRILLIC:
110244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka            return "east_slavic";
111289299bf66de5fb0c8a378f2366c0760da27077bJean Chalard        case ScriptUtils.SCRIPT_GREEK:
112244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka            return "greek";
113244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka        default:
114244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka            throw new RuntimeException("Wrong script supplied: " + script);
115244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka        }
116244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka    }
117244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka
118db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard    @Override
119db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard    public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
120db5aedb5a5eea5224e5a732b689c97eead2e35f4Jean Chalard        if (!PREF_USE_CONTACTS_KEY.equals(key)) return;
1219e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            final boolean useContactsDictionary = prefs.getBoolean(PREF_USE_CONTACTS_KEY, true);
122d267764d5abf12f95d09d1ec8f02548d992ca612Keisuke Kuroyanagi            mDictionaryFacilitatorCache.setUseContactsDictionary(useContactsDictionary);
12359b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard    }
12459b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard
1255bcf8ee66ceb38675a6b70fefcb574978e0fae92satok    @Override
1265bcf8ee66ceb38675a6b70fefcb574978e0fae92satok    public Session createSession() {
12737b19ffe6c9d8335cc0e1c1c50f5b08c778b287cSatoshi Kataoka        // Should not refer to AndroidSpellCheckerSession directly considering
12837b19ffe6c9d8335cc0e1c1c50f5b08c778b287cSatoshi Kataoka        // that AndroidSpellCheckerSession may be overlaid.
12937b19ffe6c9d8335cc0e1c1c50f5b08c778b287cSatoshi Kataoka        return AndroidSpellCheckerSessionFactory.newInstance(this);
1305bcf8ee66ceb38675a6b70fefcb574978e0fae92satok    }
1315bcf8ee66ceb38675a6b70fefcb574978e0fae92satok
132df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard    /**
133df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard     * Returns an empty SuggestionsInfo with flags signaling the word is not in the dictionary.
134df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard     * @param reportAsTypo whether this should include the flag LOOKS_LIKE_TYPO, for red underline.
135df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard     * @return the empty SuggestionsInfo with the appropriate flags set.
136df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard     */
137df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard    public static SuggestionsInfo getNotInDictEmptySuggestions(final boolean reportAsTypo) {
138df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard        return new SuggestionsInfo(reportAsTypo ? SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO : 0,
139df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard                EMPTY_STRING_ARRAY);
140cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard    }
141cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard
142df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard    /**
143df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard     * Returns an empty suggestionInfo with flags signaling the word is in the dictionary.
144df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard     * @return the empty SuggestionsInfo with the appropriate flags set.
145df33982fce6312203ed7446926f31ed92a8ae1caJean Chalard     */
14684ed0966417d93b07c4da2b295244b160d223ce9Satoshi Kataoka    public static SuggestionsInfo getInDictEmptySuggestions() {
147cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard        return new SuggestionsInfo(SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY,
148cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard                EMPTY_STRING_ARRAY);
149cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard    }
150cba1af9c5626a2cb1e611735deb72db72d02c4c1Jean Chalard
1519e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    public boolean isValidWord(final Locale locale, final String word) {
1529e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        mSemaphore.acquireUninterruptibly();
1539e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        try {
1549e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            DictionaryFacilitator dictionaryFacilitatorForLocale =
155d267764d5abf12f95d09d1ec8f02548d992ca612Keisuke Kuroyanagi                    mDictionaryFacilitatorCache.get(locale);
1569e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            return dictionaryFacilitatorForLocale.isValidWord(word, false /* igroreCase */);
1579e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        } finally {
1589e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            mSemaphore.release();
1599e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        }
1608403611960cd0b2a40b77275c536e8088c098830Jean Chalard    }
1618403611960cd0b2a40b77275c536e8088c098830Jean Chalard
1629e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    public SuggestionResults getSuggestionResults(final Locale locale, final WordComposer composer,
163bb0eca57054758ef17b032d2654c1fc5f6b32101Keisuke Kuroyanagi            final NgramContext ngramContext, final ProximityInfo proximityInfo) {
1649e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        Integer sessionId = null;
1659e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        mSemaphore.acquireUninterruptibly();
1669e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        try {
1679e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            sessionId = mSessionIdPool.poll();
1689e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            DictionaryFacilitator dictionaryFacilitatorForLocale =
169d267764d5abf12f95d09d1ec8f02548d992ca612Keisuke Kuroyanagi                    mDictionaryFacilitatorCache.get(locale);
170bb0eca57054758ef17b032d2654c1fc5f6b32101Keisuke Kuroyanagi            return dictionaryFacilitatorForLocale.getSuggestionResults(composer, ngramContext,
171ecab6aff5908bfd5b34670d2e2bb3696627fa47cJean Chalard                    proximityInfo.getNativeProximityInfo(), mSettingsValuesForSuggestion,
172ecab6aff5908bfd5b34670d2e2bb3696627fa47cJean Chalard                    sessionId);
1739e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        } finally {
1749e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            if (sessionId != null) {
1759e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi                mSessionIdPool.add(sessionId);
1769e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            }
1779e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            mSemaphore.release();
1789e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        }
1799e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    }
1809e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi
1819e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    public boolean hasMainDictionaryForLocale(final Locale locale) {
1829e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        mSemaphore.acquireUninterruptibly();
1839e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        try {
1849e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            final DictionaryFacilitator dictionaryFacilitator =
185d267764d5abf12f95d09d1ec8f02548d992ca612Keisuke Kuroyanagi                    mDictionaryFacilitatorCache.get(locale);
1868cd53266229895a3e0c6618e3765d57fc5d0b392Jean Chalard            return dictionaryFacilitator.hasAtLeastOneInitializedMainDictionary();
1879e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        } finally {
1889e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            mSemaphore.release();
1899e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        }
1909e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    }
1919e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi
1929e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    @Override
1939e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    public boolean onUnbind(final Intent intent) {
1949e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        mSemaphore.acquireUninterruptibly(MAX_NUM_OF_THREADS_READ_DICTIONARY);
1959e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        try {
1969e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            mDictionaryFacilitatorCache.evictAll();
1979e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        } finally {
1989e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            mSemaphore.release(MAX_NUM_OF_THREADS_READ_DICTIONARY);
1999e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        }
2009e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        mKeyboardCache.clear();
2019e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        return false;
202c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard    }
203c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard
2049e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    public Keyboard getKeyboardForLocale(final Locale locale) {
2059e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        Keyboard keyboard = mKeyboardCache.get(locale);
2069e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        if (keyboard == null) {
2079e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            keyboard = createKeyboardForLocale(locale);
2089e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            if (keyboard != null) {
2099e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi                mKeyboardCache.put(locale, keyboard);
2109e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi            }
2113234123fba901243990972158d023a5d1c273316Jean Chalard        }
2129e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        return keyboard;
213a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard    }
214a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard
2159e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi    private Keyboard createKeyboardForLocale(final Locale locale) {
2160dab3171d442a4d0acc87cc0019bfcbd4ea4123fJean Chalard        final int script = ScriptUtils.getScriptFromSpellCheckerLocale(locale);
217244a24e3685f3fc1d0cbfaf375ad137f917740c2Satoshi Kataoka        final String keyboardLayoutName = getKeyboardLayoutNameForScript(script);
2183895d7f8dc2e4999947f61220b86fa148f433413Yohei Yukawa        final InputMethodSubtype subtype = AdditionalSubtypeUtils.createDummyAdditionalSubtype(
2193895d7f8dc2e4999947f61220b86fa148f433413Yohei Yukawa                locale.toString(), keyboardLayoutName);
220204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka        final KeyboardLayoutSet keyboardLayoutSet = createKeyboardSetForSpellChecker(subtype);
2219e76304d6004c43c3149bc2df460af2a00b18a4fKeisuke Kuroyanagi        return keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
2223234123fba901243990972158d023a5d1c273316Jean Chalard    }
223204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka
224204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka    private KeyboardLayoutSet createKeyboardSetForSpellChecker(final InputMethodSubtype subtype) {
225204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka        final EditorInfo editorInfo = new EditorInfo();
226204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka        editorInfo.inputType = InputType.TYPE_CLASS_TEXT;
227204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka        final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(this, editorInfo);
228204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka        builder.setKeyboardGeometry(
229204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka                SPELLCHECKER_DUMMY_KEYBOARD_WIDTH, SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT);
23085ddfe1317a4475269e53f62c2338c335e02e839Jean Chalard        builder.setSubtype(new RichInputMethodSubtype(subtype));
231204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka        builder.setIsSpellChecker(true /* isSpellChecker */);
232204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka        builder.disableTouchPositionCorrectionData();
233204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka        return builder.build();
234204e7b140171a0a8b954cf508da139e93c3b2b2cTadashi G. Takaoka    }
235022c1cc20379767966f4915e2dea65fc0b67c0d8satok}
236