SpellCheckerSession.java revision 0dc1f648a09b46c45190ba1ce7daecf7fada4347
1988323c57bd25a58f05dfa492d9b9c8ab62c5153satok/*
2988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * Copyright (C) 2011 The Android Open Source Project
3988323c57bd25a58f05dfa492d9b9c8ab62c5153satok *
4988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * Licensed under the Apache License, Version 2.0 (the "License");
5988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * you may not use this file except in compliance with the License.
6988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * You may obtain a copy of the License at
7988323c57bd25a58f05dfa492d9b9c8ab62c5153satok *
8988323c57bd25a58f05dfa492d9b9c8ab62c5153satok *      http://www.apache.org/licenses/LICENSE-2.0
9988323c57bd25a58f05dfa492d9b9c8ab62c5153satok *
10988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * Unless required by applicable law or agreed to in writing, software
11988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * distributed under the License is distributed on an "AS IS" BASIS,
12988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * See the License for the specific language governing permissions and
14988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * limitations under the License.
15988323c57bd25a58f05dfa492d9b9c8ab62c5153satok */
16988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
17aafd955fa8f5c31c511763c0f826b6d7acf15b9csatokpackage android.view.textservice;
18988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
19988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport com.android.internal.textservice.ISpellCheckerSession;
20988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport com.android.internal.textservice.ISpellCheckerSessionListener;
21988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport com.android.internal.textservice.ITextServicesManager;
22988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport com.android.internal.textservice.ITextServicesSessionListener;
23988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
24988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.os.Handler;
25988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.os.Message;
26988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.os.RemoteException;
27988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.util.Log;
281bedd99761e3d2acdac947d641e7fee5db556141satokimport android.view.textservice.SpellCheckerInfo;
29988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.view.textservice.SuggestionsInfo;
30988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.view.textservice.TextInfo;
31988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
32988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport java.util.LinkedList;
33988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport java.util.Queue;
34988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
35988323c57bd25a58f05dfa492d9b9c8ab62c5153satok/**
36988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * The SpellCheckerSession interface provides the per client functionality of SpellCheckerService.
3744b75030931d9c65c9e495a86d11d71da59b4429satok *
3844b75030931d9c65c9e495a86d11d71da59b4429satok *
3944b75030931d9c65c9e495a86d11d71da59b4429satok * <a name="Applications"></a>
4044b75030931d9c65c9e495a86d11d71da59b4429satok * <h3>Applications</h3>
4144b75030931d9c65c9e495a86d11d71da59b4429satok *
4244b75030931d9c65c9e495a86d11d71da59b4429satok * <p>In most cases, applications that are using the standard
4344b75030931d9c65c9e495a86d11d71da59b4429satok * {@link android.widget.TextView} or its subclasses will have little they need
4444b75030931d9c65c9e495a86d11d71da59b4429satok * to do to work well with spell checker services.  The main things you need to
4544b75030931d9c65c9e495a86d11d71da59b4429satok * be aware of are:</p>
4644b75030931d9c65c9e495a86d11d71da59b4429satok *
4744b75030931d9c65c9e495a86d11d71da59b4429satok * <ul>
4844b75030931d9c65c9e495a86d11d71da59b4429satok * <li> Properly set the {@link android.R.attr#inputType} in your editable
4944b75030931d9c65c9e495a86d11d71da59b4429satok * text views, so that the spell checker will have enough context to help the
5044b75030931d9c65c9e495a86d11d71da59b4429satok * user in editing text in them.
5144b75030931d9c65c9e495a86d11d71da59b4429satok * </ul>
5244b75030931d9c65c9e495a86d11d71da59b4429satok *
5344b75030931d9c65c9e495a86d11d71da59b4429satok * <p>For the rare people amongst us writing client applications that use the spell checker service
5444b75030931d9c65c9e495a86d11d71da59b4429satok * directly, you will need to use {@link #getSuggestions(TextInfo, int)} or
5544b75030931d9c65c9e495a86d11d71da59b4429satok * {@link #getSuggestions(TextInfo[], int, boolean)} for obtaining results from the spell checker
5644b75030931d9c65c9e495a86d11d71da59b4429satok * service by yourself.</p>
5744b75030931d9c65c9e495a86d11d71da59b4429satok *
5844b75030931d9c65c9e495a86d11d71da59b4429satok * <h3>Security</h3>
5944b75030931d9c65c9e495a86d11d71da59b4429satok *
6044b75030931d9c65c9e495a86d11d71da59b4429satok * <p>There are a lot of security issues associated with spell checkers,
6144b75030931d9c65c9e495a86d11d71da59b4429satok * since they could monitor all the text being sent to them
6244b75030931d9c65c9e495a86d11d71da59b4429satok * through, for instance, {@link android.widget.TextView}.
6344b75030931d9c65c9e495a86d11d71da59b4429satok * The Android spell checker framework also allows
6444b75030931d9c65c9e495a86d11d71da59b4429satok * arbitrary third party spell checkers, so care must be taken to restrict their
6544b75030931d9c65c9e495a86d11d71da59b4429satok * selection and interactions.</p>
6644b75030931d9c65c9e495a86d11d71da59b4429satok *
6744b75030931d9c65c9e495a86d11d71da59b4429satok * <p>Here are some key points about the security architecture behind the
6844b75030931d9c65c9e495a86d11d71da59b4429satok * spell checker framework:</p>
6944b75030931d9c65c9e495a86d11d71da59b4429satok *
7044b75030931d9c65c9e495a86d11d71da59b4429satok * <ul>
7144b75030931d9c65c9e495a86d11d71da59b4429satok * <li>Only the system is allowed to directly access a spell checker framework's
7244b75030931d9c65c9e495a86d11d71da59b4429satok * {@link android.service.textservice.SpellCheckerService} interface, via the
7344b75030931d9c65c9e495a86d11d71da59b4429satok * {@link android.Manifest.permission#BIND_TEXT_SERVICE} permission.  This is
7444b75030931d9c65c9e495a86d11d71da59b4429satok * enforced in the system by not binding to a spell checker service that does
7544b75030931d9c65c9e495a86d11d71da59b4429satok * not require this permission.
7644b75030931d9c65c9e495a86d11d71da59b4429satok *
7744b75030931d9c65c9e495a86d11d71da59b4429satok * <li>The user must explicitly enable a new spell checker in settings before
7844b75030931d9c65c9e495a86d11d71da59b4429satok * they can be enabled, to confirm with the system that they know about it
7944b75030931d9c65c9e495a86d11d71da59b4429satok * and want to make it available for use.
8044b75030931d9c65c9e495a86d11d71da59b4429satok * </ul>
8144b75030931d9c65c9e495a86d11d71da59b4429satok *
82988323c57bd25a58f05dfa492d9b9c8ab62c5153satok */
83988323c57bd25a58f05dfa492d9b9c8ab62c5153satokpublic class SpellCheckerSession {
84988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private static final String TAG = SpellCheckerSession.class.getSimpleName();
85988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private static final boolean DBG = false;
86aafd955fa8f5c31c511763c0f826b6d7acf15b9csatok    /**
87aafd955fa8f5c31c511763c0f826b6d7acf15b9csatok     * Name under which a SpellChecker service component publishes information about itself.
88aafd955fa8f5c31c511763c0f826b6d7acf15b9csatok     * This meta-data must reference an XML resource.
89aafd955fa8f5c31c511763c0f826b6d7acf15b9csatok     **/
90aafd955fa8f5c31c511763c0f826b6d7acf15b9csatok    public static final String SERVICE_META_DATA = "android.view.textservice.scs";
910dc1f648a09b46c45190ba1ce7daecf7fada4347satok    private static final String SUPPORT_SENTENCE_SPELL_CHECK = "SupportSentenceSpellCheck";
92aafd955fa8f5c31c511763c0f826b6d7acf15b9csatok
93988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
94988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private static final int MSG_ON_GET_SUGGESTION_MULTIPLE = 1;
950dc1f648a09b46c45190ba1ce7daecf7fada4347satok    private static final int MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE = 2;
96988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
97988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private final InternalListener mInternalListener;
98988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private final ITextServicesManager mTextServicesManager;
991bedd99761e3d2acdac947d641e7fee5db556141satok    private final SpellCheckerInfo mSpellCheckerInfo;
100988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private final SpellCheckerSessionListenerImpl mSpellCheckerSessionListenerImpl;
1010dc1f648a09b46c45190ba1ce7daecf7fada4347satok    private final SpellCheckerSubtype mSubtype;
102988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
103988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private boolean mIsUsed;
104988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private SpellCheckerSessionListener mSpellCheckerSessionListener;
105988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
106988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    /** Handler that will execute the main tasks */
107988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private final Handler mHandler = new Handler() {
108988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        @Override
109988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void handleMessage(Message msg) {
110988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            switch (msg.what) {
111988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                case MSG_ON_GET_SUGGESTION_MULTIPLE:
112988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    handleOnGetSuggestionsMultiple((SuggestionsInfo[]) msg.obj);
113988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    break;
1140dc1f648a09b46c45190ba1ce7daecf7fada4347satok                case MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE:
1150dc1f648a09b46c45190ba1ce7daecf7fada4347satok                    handleOnGetSuggestionsMultipleForSentence((SuggestionsInfo[]) msg.obj);
1160dc1f648a09b46c45190ba1ce7daecf7fada4347satok                    break;
117988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
118988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
119988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    };
120988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
121988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    /**
122988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * Constructor
123988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * @hide
124988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     */
1251bedd99761e3d2acdac947d641e7fee5db556141satok    public SpellCheckerSession(
1260dc1f648a09b46c45190ba1ce7daecf7fada4347satok            SpellCheckerInfo info, ITextServicesManager tsm, SpellCheckerSessionListener listener,
1270dc1f648a09b46c45190ba1ce7daecf7fada4347satok            SpellCheckerSubtype subtype) {
1281bedd99761e3d2acdac947d641e7fee5db556141satok        if (info == null || listener == null || tsm == null) {
129988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            throw new NullPointerException();
130988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
1311bedd99761e3d2acdac947d641e7fee5db556141satok        mSpellCheckerInfo = info;
132988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        mSpellCheckerSessionListenerImpl = new SpellCheckerSessionListenerImpl(mHandler);
133a80838d9d63fcc9a83a9e7c99884e5b50316d4f0Jean Chalard        mInternalListener = new InternalListener(mSpellCheckerSessionListenerImpl);
134988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        mTextServicesManager = tsm;
135988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        mIsUsed = true;
136988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        mSpellCheckerSessionListener = listener;
1370dc1f648a09b46c45190ba1ce7daecf7fada4347satok        mSubtype = subtype;
138988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
139988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
140988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    /**
141988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * @return true if the connection to a text service of this session is disconnected and not
142988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * alive.
143988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     */
144988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    public boolean isSessionDisconnected() {
145988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        return mSpellCheckerSessionListenerImpl.isDisconnected();
146988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
147988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
148988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    /**
1491bedd99761e3d2acdac947d641e7fee5db556141satok     * Get the spell checker service info this spell checker session has.
1501bedd99761e3d2acdac947d641e7fee5db556141satok     * @return SpellCheckerInfo for the specified locale.
1511bedd99761e3d2acdac947d641e7fee5db556141satok     */
1521bedd99761e3d2acdac947d641e7fee5db556141satok    public SpellCheckerInfo getSpellChecker() {
1531bedd99761e3d2acdac947d641e7fee5db556141satok        return mSpellCheckerInfo;
1541bedd99761e3d2acdac947d641e7fee5db556141satok    }
1551bedd99761e3d2acdac947d641e7fee5db556141satok
1561bedd99761e3d2acdac947d641e7fee5db556141satok    /**
157b4aff97c85e730857893742f73a082f6b8d139casatok     * Cancel pending and running spell check tasks
158b4aff97c85e730857893742f73a082f6b8d139casatok     */
159b4aff97c85e730857893742f73a082f6b8d139casatok    public void cancel() {
160b4aff97c85e730857893742f73a082f6b8d139casatok        mSpellCheckerSessionListenerImpl.cancel();
161b4aff97c85e730857893742f73a082f6b8d139casatok    }
162b4aff97c85e730857893742f73a082f6b8d139casatok
163b4aff97c85e730857893742f73a082f6b8d139casatok    /**
164988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * Finish this session and allow TextServicesManagerService to disconnect the bound spell
165988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * checker.
166988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     */
167988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    public void close() {
168988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        mIsUsed = false;
169988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        try {
17074061ff90453c79ddbde468f630a41425da07710satok            mSpellCheckerSessionListenerImpl.close();
171988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mTextServicesManager.finishSpellCheckerService(mSpellCheckerSessionListenerImpl);
172988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        } catch (RemoteException e) {
173988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            // do nothing
174988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
175988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
176988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
177988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    /**
1780dc1f648a09b46c45190ba1ce7daecf7fada4347satok     * @hide
1790dc1f648a09b46c45190ba1ce7daecf7fada4347satok     */
1800dc1f648a09b46c45190ba1ce7daecf7fada4347satok    public void getSuggestionsForSentence(TextInfo textInfo, int suggestionsLimit) {
1810dc1f648a09b46c45190ba1ce7daecf7fada4347satok        mSpellCheckerSessionListenerImpl.getSuggestionsMultipleForSentence(
1820dc1f648a09b46c45190ba1ce7daecf7fada4347satok                new TextInfo[] {textInfo}, suggestionsLimit);
1830dc1f648a09b46c45190ba1ce7daecf7fada4347satok    }
1840dc1f648a09b46c45190ba1ce7daecf7fada4347satok
1850dc1f648a09b46c45190ba1ce7daecf7fada4347satok    /**
186988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * Get candidate strings for a substring of the specified text.
187988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * @param textInfo text metadata for a spell checker
188988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * @param suggestionsLimit the number of limit of suggestions returned
189988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     */
190988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    public void getSuggestions(TextInfo textInfo, int suggestionsLimit) {
191988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        getSuggestions(new TextInfo[] {textInfo}, suggestionsLimit, false);
192988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
193988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
194988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    /**
195988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * A batch process of getSuggestions
196988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * @param textInfos an array of text metadata for a spell checker
197988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * @param suggestionsLimit the number of limit of suggestions returned
198988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * @param sequentialWords true if textInfos can be treated as sequential words.
199988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     */
200988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    public void getSuggestions(
201988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) {
2026be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok        if (DBG) {
2036be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok            Log.w(TAG, "getSuggestions from " + mSpellCheckerInfo.getId());
2046be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok        }
205988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        // TODO: Handle multiple words suggestions by using WordBreakIterator
206988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        mSpellCheckerSessionListenerImpl.getSuggestionsMultiple(
207988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                textInfos, suggestionsLimit, sequentialWords);
208988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
209988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
210988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private void handleOnGetSuggestionsMultiple(SuggestionsInfo[] suggestionInfos) {
211988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        mSpellCheckerSessionListener.onGetSuggestions(suggestionInfos);
212988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
213988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
2140dc1f648a09b46c45190ba1ce7daecf7fada4347satok    private void handleOnGetSuggestionsMultipleForSentence(SuggestionsInfo[] suggestionInfos) {
2150dc1f648a09b46c45190ba1ce7daecf7fada4347satok        mSpellCheckerSessionListener.onGetSuggestionsForSentence(suggestionInfos);
2160dc1f648a09b46c45190ba1ce7daecf7fada4347satok    }
2170dc1f648a09b46c45190ba1ce7daecf7fada4347satok
218988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private static class SpellCheckerSessionListenerImpl extends ISpellCheckerSessionListener.Stub {
219988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private static final int TASK_CANCEL = 1;
220988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private static final int TASK_GET_SUGGESTIONS_MULTIPLE = 2;
22174061ff90453c79ddbde468f630a41425da07710satok        private static final int TASK_CLOSE = 3;
2220dc1f648a09b46c45190ba1ce7daecf7fada4347satok        private static final int TASK_GET_SUGGESTIONS_MULTIPLE_FOR_SENTENCE = 4;
223988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private final Queue<SpellCheckerParams> mPendingTasks =
224988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                new LinkedList<SpellCheckerParams>();
225060677f4686a93d92117d7d472e754423a368bdbsatok        private Handler mHandler;
226988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
227988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private boolean mOpened;
228988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private ISpellCheckerSession mISpellCheckerSession;
229988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
230988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public SpellCheckerSessionListenerImpl(Handler handler) {
231988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mOpened = false;
232988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mHandler = handler;
233988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
234988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
235988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private static class SpellCheckerParams {
236988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            public final int mWhat;
237988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            public final TextInfo[] mTextInfos;
238988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            public final int mSuggestionsLimit;
239988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            public final boolean mSequentialWords;
240988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            public SpellCheckerParams(int what, TextInfo[] textInfos, int suggestionsLimit,
241988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    boolean sequentialWords) {
242988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                mWhat = what;
243988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                mTextInfos = textInfos;
244988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                mSuggestionsLimit = suggestionsLimit;
245988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                mSequentialWords = sequentialWords;
246988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
247988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
248988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
249988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private void processTask(SpellCheckerParams scp) {
250988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            switch (scp.mWhat) {
251988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                case TASK_CANCEL:
252988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    processCancel();
253988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    break;
254988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                case TASK_GET_SUGGESTIONS_MULTIPLE:
255988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    processGetSuggestionsMultiple(scp);
256988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    break;
25774061ff90453c79ddbde468f630a41425da07710satok                case TASK_CLOSE:
25874061ff90453c79ddbde468f630a41425da07710satok                    processClose();
25974061ff90453c79ddbde468f630a41425da07710satok                    break;
2600dc1f648a09b46c45190ba1ce7daecf7fada4347satok                case TASK_GET_SUGGESTIONS_MULTIPLE_FOR_SENTENCE:
2610dc1f648a09b46c45190ba1ce7daecf7fada4347satok                    processGetSuggestionsMultipleForSentence(scp);
2620dc1f648a09b46c45190ba1ce7daecf7fada4347satok                    break;
263988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
264988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
265988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
266988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public synchronized void onServiceConnected(ISpellCheckerSession session) {
267988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mISpellCheckerSession = session;
268988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mOpened = true;
269988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (DBG)
270988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                Log.d(TAG, "onServiceConnected - Success");
271988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            while (!mPendingTasks.isEmpty()) {
272988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                processTask(mPendingTasks.poll());
273988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
274988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
275988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
276b4aff97c85e730857893742f73a082f6b8d139casatok        public void cancel() {
277b4aff97c85e730857893742f73a082f6b8d139casatok            if (DBG) {
278b4aff97c85e730857893742f73a082f6b8d139casatok                Log.w(TAG, "cancel");
279b4aff97c85e730857893742f73a082f6b8d139casatok            }
280b4aff97c85e730857893742f73a082f6b8d139casatok            processOrEnqueueTask(new SpellCheckerParams(TASK_CANCEL, null, 0, false));
281b4aff97c85e730857893742f73a082f6b8d139casatok        }
282b4aff97c85e730857893742f73a082f6b8d139casatok
283988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void getSuggestionsMultiple(
284988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) {
2856be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok            if (DBG) {
2866be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok                Log.w(TAG, "getSuggestionsMultiple");
2876be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok            }
288988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            processOrEnqueueTask(
289988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    new SpellCheckerParams(TASK_GET_SUGGESTIONS_MULTIPLE, textInfos,
290988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                            suggestionsLimit, sequentialWords));
291988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
292988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
2930dc1f648a09b46c45190ba1ce7daecf7fada4347satok        public void getSuggestionsMultipleForSentence(TextInfo[] textInfos, int suggestionsLimit) {
2940dc1f648a09b46c45190ba1ce7daecf7fada4347satok            if (DBG) {
2950dc1f648a09b46c45190ba1ce7daecf7fada4347satok                Log.w(TAG, "getSuggestionsMultipleForSentence");
2960dc1f648a09b46c45190ba1ce7daecf7fada4347satok            }
2970dc1f648a09b46c45190ba1ce7daecf7fada4347satok            processOrEnqueueTask(
2980dc1f648a09b46c45190ba1ce7daecf7fada4347satok                    new SpellCheckerParams(TASK_GET_SUGGESTIONS_MULTIPLE_FOR_SENTENCE,
2990dc1f648a09b46c45190ba1ce7daecf7fada4347satok                            textInfos, suggestionsLimit, false));
3000dc1f648a09b46c45190ba1ce7daecf7fada4347satok        }
3010dc1f648a09b46c45190ba1ce7daecf7fada4347satok
30274061ff90453c79ddbde468f630a41425da07710satok        public void close() {
30374061ff90453c79ddbde468f630a41425da07710satok            if (DBG) {
30474061ff90453c79ddbde468f630a41425da07710satok                Log.w(TAG, "close");
30574061ff90453c79ddbde468f630a41425da07710satok            }
30674061ff90453c79ddbde468f630a41425da07710satok            processOrEnqueueTask(new SpellCheckerParams(TASK_CLOSE, null, 0, false));
30774061ff90453c79ddbde468f630a41425da07710satok        }
30874061ff90453c79ddbde468f630a41425da07710satok
309988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public boolean isDisconnected() {
310988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            return mOpened && mISpellCheckerSession == null;
311988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
312988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
313988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public boolean checkOpenConnection() {
314988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (mISpellCheckerSession != null) {
315988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                return true;
316988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
317988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            Log.e(TAG, "not connected to the spellchecker service.");
318988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            return false;
319988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
320988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
321988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private void processOrEnqueueTask(SpellCheckerParams scp) {
3226be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok            if (DBG) {
3236be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok                Log.d(TAG, "process or enqueue task: " + mISpellCheckerSession);
3246be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok            }
325b4aff97c85e730857893742f73a082f6b8d139casatok            SpellCheckerParams closeTask = null;
326988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (mISpellCheckerSession == null) {
327b4aff97c85e730857893742f73a082f6b8d139casatok                if (scp.mWhat == TASK_CANCEL) {
328b4aff97c85e730857893742f73a082f6b8d139casatok                    while (!mPendingTasks.isEmpty()) {
329b4aff97c85e730857893742f73a082f6b8d139casatok                        final SpellCheckerParams tmp = mPendingTasks.poll();
330b4aff97c85e730857893742f73a082f6b8d139casatok                        if (tmp.mWhat == TASK_CLOSE) {
331b4aff97c85e730857893742f73a082f6b8d139casatok                            // Only one close task should be processed, while we need to remove all
332b4aff97c85e730857893742f73a082f6b8d139casatok                            // close tasks from the queue
333b4aff97c85e730857893742f73a082f6b8d139casatok                            closeTask = tmp;
334b4aff97c85e730857893742f73a082f6b8d139casatok                        }
335b4aff97c85e730857893742f73a082f6b8d139casatok                    }
336b4aff97c85e730857893742f73a082f6b8d139casatok                }
337988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                mPendingTasks.offer(scp);
338b4aff97c85e730857893742f73a082f6b8d139casatok                if (closeTask != null) {
339b4aff97c85e730857893742f73a082f6b8d139casatok                    mPendingTasks.offer(closeTask);
340b4aff97c85e730857893742f73a082f6b8d139casatok                }
341988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            } else {
342988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                processTask(scp);
343988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
344988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
345988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
346988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private void processCancel() {
347988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (!checkOpenConnection()) {
348988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                return;
349988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
3506be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok            if (DBG) {
3516be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok                Log.w(TAG, "Cancel spell checker tasks.");
3526be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok            }
353988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            try {
3545357806980269d846a15c845a6fcc0384fb18860satok                mISpellCheckerSession.onCancel();
355988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            } catch (RemoteException e) {
356988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                Log.e(TAG, "Failed to cancel " + e);
357988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
358988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
359988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
36074061ff90453c79ddbde468f630a41425da07710satok        private void processClose() {
36174061ff90453c79ddbde468f630a41425da07710satok            if (!checkOpenConnection()) {
36274061ff90453c79ddbde468f630a41425da07710satok                return;
36374061ff90453c79ddbde468f630a41425da07710satok            }
36474061ff90453c79ddbde468f630a41425da07710satok            if (DBG) {
36574061ff90453c79ddbde468f630a41425da07710satok                Log.w(TAG, "Close spell checker tasks.");
36674061ff90453c79ddbde468f630a41425da07710satok            }
36774061ff90453c79ddbde468f630a41425da07710satok            try {
36874061ff90453c79ddbde468f630a41425da07710satok                mISpellCheckerSession.onClose();
36974061ff90453c79ddbde468f630a41425da07710satok                mISpellCheckerSession = null;
370060677f4686a93d92117d7d472e754423a368bdbsatok                mHandler = null;
37174061ff90453c79ddbde468f630a41425da07710satok            } catch (RemoteException e) {
37274061ff90453c79ddbde468f630a41425da07710satok                Log.e(TAG, "Failed to close " + e);
37374061ff90453c79ddbde468f630a41425da07710satok            }
37474061ff90453c79ddbde468f630a41425da07710satok        }
37574061ff90453c79ddbde468f630a41425da07710satok
376988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private void processGetSuggestionsMultiple(SpellCheckerParams scp) {
377988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (!checkOpenConnection()) {
378988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                return;
379988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
3806be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok            if (DBG) {
3816be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok                Log.w(TAG, "Get suggestions from the spell checker.");
3826be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok            }
383988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            try {
3845357806980269d846a15c845a6fcc0384fb18860satok                mISpellCheckerSession.onGetSuggestionsMultiple(
385988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                        scp.mTextInfos, scp.mSuggestionsLimit, scp.mSequentialWords);
386988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            } catch (RemoteException e) {
387988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                Log.e(TAG, "Failed to get suggestions " + e);
388988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
389988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
390988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
3910dc1f648a09b46c45190ba1ce7daecf7fada4347satok        private void processGetSuggestionsMultipleForSentence(SpellCheckerParams scp) {
3920dc1f648a09b46c45190ba1ce7daecf7fada4347satok            if (!checkOpenConnection()) {
3930dc1f648a09b46c45190ba1ce7daecf7fada4347satok                return;
3940dc1f648a09b46c45190ba1ce7daecf7fada4347satok            }
3950dc1f648a09b46c45190ba1ce7daecf7fada4347satok            if (DBG) {
3960dc1f648a09b46c45190ba1ce7daecf7fada4347satok                Log.w(TAG, "Get suggestions from the spell checker.");
3970dc1f648a09b46c45190ba1ce7daecf7fada4347satok            }
3980dc1f648a09b46c45190ba1ce7daecf7fada4347satok            if (scp.mTextInfos.length != 1) {
3990dc1f648a09b46c45190ba1ce7daecf7fada4347satok                throw new IllegalArgumentException();
4000dc1f648a09b46c45190ba1ce7daecf7fada4347satok            }
4010dc1f648a09b46c45190ba1ce7daecf7fada4347satok            try {
4020dc1f648a09b46c45190ba1ce7daecf7fada4347satok                mISpellCheckerSession.onGetSuggestionsMultipleForSentence(
4030dc1f648a09b46c45190ba1ce7daecf7fada4347satok                        scp.mTextInfos, scp.mSuggestionsLimit);
4040dc1f648a09b46c45190ba1ce7daecf7fada4347satok            } catch (RemoteException e) {
4050dc1f648a09b46c45190ba1ce7daecf7fada4347satok                Log.e(TAG, "Failed to get suggestions " + e);
4060dc1f648a09b46c45190ba1ce7daecf7fada4347satok            }
4070dc1f648a09b46c45190ba1ce7daecf7fada4347satok        }
4080dc1f648a09b46c45190ba1ce7daecf7fada4347satok
409988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        @Override
410988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void onGetSuggestions(SuggestionsInfo[] results) {
411988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mHandler.sendMessage(Message.obtain(mHandler, MSG_ON_GET_SUGGESTION_MULTIPLE, results));
412988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
4130dc1f648a09b46c45190ba1ce7daecf7fada4347satok
4140dc1f648a09b46c45190ba1ce7daecf7fada4347satok        @Override
4150dc1f648a09b46c45190ba1ce7daecf7fada4347satok        public void onGetSuggestionsForSentence(SuggestionsInfo[] results) {
4160dc1f648a09b46c45190ba1ce7daecf7fada4347satok            mHandler.sendMessage(
4170dc1f648a09b46c45190ba1ce7daecf7fada4347satok                    Message.obtain(mHandler, MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE, results));
4180dc1f648a09b46c45190ba1ce7daecf7fada4347satok        }
419988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
420988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
421988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    /**
422988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * Callback for getting results from text services
423988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     */
424988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    public interface SpellCheckerSessionListener {
425988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        /**
426988323c57bd25a58f05dfa492d9b9c8ab62c5153satok         * Callback for "getSuggestions"
427988323c57bd25a58f05dfa492d9b9c8ab62c5153satok         * @param results an array of results of getSuggestions
428988323c57bd25a58f05dfa492d9b9c8ab62c5153satok         */
429988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void onGetSuggestions(SuggestionsInfo[] results);
4300dc1f648a09b46c45190ba1ce7daecf7fada4347satok        /**
4310dc1f648a09b46c45190ba1ce7daecf7fada4347satok         * @hide
4320dc1f648a09b46c45190ba1ce7daecf7fada4347satok         */
4330dc1f648a09b46c45190ba1ce7daecf7fada4347satok        public void onGetSuggestionsForSentence(SuggestionsInfo[] results);
434988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
435988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
436a80838d9d63fcc9a83a9e7c99884e5b50316d4f0Jean Chalard    private static class InternalListener extends ITextServicesSessionListener.Stub {
437a80838d9d63fcc9a83a9e7c99884e5b50316d4f0Jean Chalard        private final SpellCheckerSessionListenerImpl mParentSpellCheckerSessionListenerImpl;
438a80838d9d63fcc9a83a9e7c99884e5b50316d4f0Jean Chalard
439a80838d9d63fcc9a83a9e7c99884e5b50316d4f0Jean Chalard        public InternalListener(SpellCheckerSessionListenerImpl spellCheckerSessionListenerImpl) {
440a80838d9d63fcc9a83a9e7c99884e5b50316d4f0Jean Chalard            mParentSpellCheckerSessionListenerImpl = spellCheckerSessionListenerImpl;
441a80838d9d63fcc9a83a9e7c99884e5b50316d4f0Jean Chalard        }
442a80838d9d63fcc9a83a9e7c99884e5b50316d4f0Jean Chalard
443988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        @Override
444988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void onServiceConnected(ISpellCheckerSession session) {
4456be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok            if (DBG) {
4466be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok                Log.w(TAG, "SpellCheckerSession connected.");
4476be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok            }
448a80838d9d63fcc9a83a9e7c99884e5b50316d4f0Jean Chalard            mParentSpellCheckerSessionListenerImpl.onServiceConnected(session);
449988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
450988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
451988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
452988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    @Override
453988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    protected void finalize() throws Throwable {
454988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        super.finalize();
455988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        if (mIsUsed) {
456988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            Log.e(TAG, "SpellCheckerSession was not finished properly." +
457988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    "You should call finishShession() when you finished to use a spell checker.");
458988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            close();
459988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
460988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
461988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
462988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    /**
463988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * @hide
464988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     */
465988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    public ITextServicesSessionListener getTextServicesSessionListener() {
466988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        return mInternalListener;
467988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
468988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
469988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    /**
470988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * @hide
471988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     */
472988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    public ISpellCheckerSessionListener getSpellCheckerSessionListener() {
473988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        return mSpellCheckerSessionListenerImpl;
474988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
4750dc1f648a09b46c45190ba1ce7daecf7fada4347satok
4760dc1f648a09b46c45190ba1ce7daecf7fada4347satok    /**
4770dc1f648a09b46c45190ba1ce7daecf7fada4347satok     * @hide
4780dc1f648a09b46c45190ba1ce7daecf7fada4347satok     */
4790dc1f648a09b46c45190ba1ce7daecf7fada4347satok    public boolean isSentenceSpellCheckSupported() {
4800dc1f648a09b46c45190ba1ce7daecf7fada4347satok        return mSubtype.containsExtraValueKey(SUPPORT_SENTENCE_SPELL_CHECK);
4810dc1f648a09b46c45190ba1ce7daecf7fada4347satok    }
482988323c57bd25a58f05dfa492d9b9c8ab62c5153satok}
483