SpellCheckerSession.java revision b4aff97c85e730857893742f73a082f6b8d139ca
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";
91aafd955fa8f5c31c511763c0f826b6d7acf15b9csatok
92988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
93988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private static final int MSG_ON_GET_SUGGESTION_MULTIPLE = 1;
94988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
95988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private final InternalListener mInternalListener;
96988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private final ITextServicesManager mTextServicesManager;
971bedd99761e3d2acdac947d641e7fee5db556141satok    private final SpellCheckerInfo mSpellCheckerInfo;
98988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private final SpellCheckerSessionListenerImpl mSpellCheckerSessionListenerImpl;
99988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
100988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private boolean mIsUsed;
101988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private SpellCheckerSessionListener mSpellCheckerSessionListener;
102988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
103988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    /** Handler that will execute the main tasks */
104988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private final Handler mHandler = new Handler() {
105988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        @Override
106988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void handleMessage(Message msg) {
107988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            switch (msg.what) {
108988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                case MSG_ON_GET_SUGGESTION_MULTIPLE:
109988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    handleOnGetSuggestionsMultiple((SuggestionsInfo[]) msg.obj);
110988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    break;
111988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
112988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
113988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    };
114988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
115988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    /**
116988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * Constructor
117988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * @hide
118988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     */
1191bedd99761e3d2acdac947d641e7fee5db556141satok    public SpellCheckerSession(
1201bedd99761e3d2acdac947d641e7fee5db556141satok            SpellCheckerInfo info, ITextServicesManager tsm, SpellCheckerSessionListener listener) {
1211bedd99761e3d2acdac947d641e7fee5db556141satok        if (info == null || listener == null || tsm == null) {
122988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            throw new NullPointerException();
123988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
1241bedd99761e3d2acdac947d641e7fee5db556141satok        mSpellCheckerInfo = info;
125988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        mSpellCheckerSessionListenerImpl = new SpellCheckerSessionListenerImpl(mHandler);
126a80838d9d63fcc9a83a9e7c99884e5b50316d4f0Jean Chalard        mInternalListener = new InternalListener(mSpellCheckerSessionListenerImpl);
127988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        mTextServicesManager = tsm;
128988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        mIsUsed = true;
129988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        mSpellCheckerSessionListener = listener;
130988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
131988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
132988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    /**
133988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * @return true if the connection to a text service of this session is disconnected and not
134988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * alive.
135988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     */
136988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    public boolean isSessionDisconnected() {
137988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        return mSpellCheckerSessionListenerImpl.isDisconnected();
138988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
139988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
140988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    /**
1411bedd99761e3d2acdac947d641e7fee5db556141satok     * Get the spell checker service info this spell checker session has.
1421bedd99761e3d2acdac947d641e7fee5db556141satok     * @return SpellCheckerInfo for the specified locale.
1431bedd99761e3d2acdac947d641e7fee5db556141satok     */
1441bedd99761e3d2acdac947d641e7fee5db556141satok    public SpellCheckerInfo getSpellChecker() {
1451bedd99761e3d2acdac947d641e7fee5db556141satok        return mSpellCheckerInfo;
1461bedd99761e3d2acdac947d641e7fee5db556141satok    }
1471bedd99761e3d2acdac947d641e7fee5db556141satok
1481bedd99761e3d2acdac947d641e7fee5db556141satok    /**
149b4aff97c85e730857893742f73a082f6b8d139casatok     * Cancel pending and running spell check tasks
150b4aff97c85e730857893742f73a082f6b8d139casatok     */
151b4aff97c85e730857893742f73a082f6b8d139casatok    public void cancel() {
152b4aff97c85e730857893742f73a082f6b8d139casatok        mSpellCheckerSessionListenerImpl.cancel();
153b4aff97c85e730857893742f73a082f6b8d139casatok    }
154b4aff97c85e730857893742f73a082f6b8d139casatok
155b4aff97c85e730857893742f73a082f6b8d139casatok    /**
156988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * Finish this session and allow TextServicesManagerService to disconnect the bound spell
157988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * checker.
158988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     */
159988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    public void close() {
160988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        mIsUsed = false;
161988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        try {
16274061ff90453c79ddbde468f630a41425da07710satok            mSpellCheckerSessionListenerImpl.close();
163988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mTextServicesManager.finishSpellCheckerService(mSpellCheckerSessionListenerImpl);
164988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        } catch (RemoteException e) {
165988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            // do nothing
166988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
167988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
168988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
169988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    /**
170988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * Get candidate strings for a substring of the specified text.
171988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * @param textInfo text metadata for a spell checker
172988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * @param suggestionsLimit the number of limit of suggestions returned
173988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     */
174988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    public void getSuggestions(TextInfo textInfo, int suggestionsLimit) {
175988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        getSuggestions(new TextInfo[] {textInfo}, suggestionsLimit, false);
176988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
177988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
178988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    /**
179988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * A batch process of getSuggestions
180988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * @param textInfos an array of text metadata for a spell checker
181988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * @param suggestionsLimit the number of limit of suggestions returned
182988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * @param sequentialWords true if textInfos can be treated as sequential words.
183988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     */
184988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    public void getSuggestions(
185988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) {
1866be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok        if (DBG) {
1876be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok            Log.w(TAG, "getSuggestions from " + mSpellCheckerInfo.getId());
1886be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok        }
189988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        // TODO: Handle multiple words suggestions by using WordBreakIterator
190988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        mSpellCheckerSessionListenerImpl.getSuggestionsMultiple(
191988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                textInfos, suggestionsLimit, sequentialWords);
192988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
193988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
194988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private void handleOnGetSuggestionsMultiple(SuggestionsInfo[] suggestionInfos) {
195988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        mSpellCheckerSessionListener.onGetSuggestions(suggestionInfos);
196988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
197988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
198988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private static class SpellCheckerSessionListenerImpl extends ISpellCheckerSessionListener.Stub {
199988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private static final int TASK_CANCEL = 1;
200988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private static final int TASK_GET_SUGGESTIONS_MULTIPLE = 2;
20174061ff90453c79ddbde468f630a41425da07710satok        private static final int TASK_CLOSE = 3;
202988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private final Queue<SpellCheckerParams> mPendingTasks =
203988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                new LinkedList<SpellCheckerParams>();
204988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private final Handler mHandler;
205988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
206988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private boolean mOpened;
207988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private ISpellCheckerSession mISpellCheckerSession;
208988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
209988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public SpellCheckerSessionListenerImpl(Handler handler) {
210988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mOpened = false;
211988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mHandler = handler;
212988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
213988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
214988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private static class SpellCheckerParams {
215988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            public final int mWhat;
216988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            public final TextInfo[] mTextInfos;
217988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            public final int mSuggestionsLimit;
218988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            public final boolean mSequentialWords;
219988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            public SpellCheckerParams(int what, TextInfo[] textInfos, int suggestionsLimit,
220988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    boolean sequentialWords) {
221988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                mWhat = what;
222988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                mTextInfos = textInfos;
223988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                mSuggestionsLimit = suggestionsLimit;
224988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                mSequentialWords = sequentialWords;
225988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
226988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
227988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
228988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private void processTask(SpellCheckerParams scp) {
229988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            switch (scp.mWhat) {
230988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                case TASK_CANCEL:
231988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    processCancel();
232988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    break;
233988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                case TASK_GET_SUGGESTIONS_MULTIPLE:
234988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    processGetSuggestionsMultiple(scp);
235988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    break;
23674061ff90453c79ddbde468f630a41425da07710satok                case TASK_CLOSE:
23774061ff90453c79ddbde468f630a41425da07710satok                    processClose();
23874061ff90453c79ddbde468f630a41425da07710satok                    break;
239988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
240988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
241988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
242988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public synchronized void onServiceConnected(ISpellCheckerSession session) {
243988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mISpellCheckerSession = session;
244988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mOpened = true;
245988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (DBG)
246988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                Log.d(TAG, "onServiceConnected - Success");
247988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            while (!mPendingTasks.isEmpty()) {
248988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                processTask(mPendingTasks.poll());
249988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
250988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
251988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
252b4aff97c85e730857893742f73a082f6b8d139casatok        public void cancel() {
253b4aff97c85e730857893742f73a082f6b8d139casatok            if (DBG) {
254b4aff97c85e730857893742f73a082f6b8d139casatok                Log.w(TAG, "cancel");
255b4aff97c85e730857893742f73a082f6b8d139casatok            }
256b4aff97c85e730857893742f73a082f6b8d139casatok            processOrEnqueueTask(new SpellCheckerParams(TASK_CANCEL, null, 0, false));
257b4aff97c85e730857893742f73a082f6b8d139casatok        }
258b4aff97c85e730857893742f73a082f6b8d139casatok
259988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void getSuggestionsMultiple(
260988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) {
2616be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok            if (DBG) {
2626be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok                Log.w(TAG, "getSuggestionsMultiple");
2636be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok            }
264988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            processOrEnqueueTask(
265988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    new SpellCheckerParams(TASK_GET_SUGGESTIONS_MULTIPLE, textInfos,
266988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                            suggestionsLimit, sequentialWords));
267988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
268988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
26974061ff90453c79ddbde468f630a41425da07710satok        public void close() {
27074061ff90453c79ddbde468f630a41425da07710satok            if (DBG) {
27174061ff90453c79ddbde468f630a41425da07710satok                Log.w(TAG, "close");
27274061ff90453c79ddbde468f630a41425da07710satok            }
27374061ff90453c79ddbde468f630a41425da07710satok            processOrEnqueueTask(new SpellCheckerParams(TASK_CLOSE, null, 0, false));
27474061ff90453c79ddbde468f630a41425da07710satok        }
27574061ff90453c79ddbde468f630a41425da07710satok
276988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public boolean isDisconnected() {
277988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            return mOpened && mISpellCheckerSession == null;
278988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
279988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
280988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public boolean checkOpenConnection() {
281988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (mISpellCheckerSession != null) {
282988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                return true;
283988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
284988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            Log.e(TAG, "not connected to the spellchecker service.");
285988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            return false;
286988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
287988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
288988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private void processOrEnqueueTask(SpellCheckerParams scp) {
2896be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok            if (DBG) {
2906be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok                Log.d(TAG, "process or enqueue task: " + mISpellCheckerSession);
2916be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok            }
292b4aff97c85e730857893742f73a082f6b8d139casatok            SpellCheckerParams closeTask = null;
293988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (mISpellCheckerSession == null) {
294b4aff97c85e730857893742f73a082f6b8d139casatok                if (scp.mWhat == TASK_CANCEL) {
295b4aff97c85e730857893742f73a082f6b8d139casatok                    while (!mPendingTasks.isEmpty()) {
296b4aff97c85e730857893742f73a082f6b8d139casatok                        final SpellCheckerParams tmp = mPendingTasks.poll();
297b4aff97c85e730857893742f73a082f6b8d139casatok                        if (tmp.mWhat == TASK_CLOSE) {
298b4aff97c85e730857893742f73a082f6b8d139casatok                            // Only one close task should be processed, while we need to remove all
299b4aff97c85e730857893742f73a082f6b8d139casatok                            // close tasks from the queue
300b4aff97c85e730857893742f73a082f6b8d139casatok                            closeTask = tmp;
301b4aff97c85e730857893742f73a082f6b8d139casatok                        }
302b4aff97c85e730857893742f73a082f6b8d139casatok                    }
303b4aff97c85e730857893742f73a082f6b8d139casatok                }
304988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                mPendingTasks.offer(scp);
305b4aff97c85e730857893742f73a082f6b8d139casatok                if (closeTask != null) {
306b4aff97c85e730857893742f73a082f6b8d139casatok                    mPendingTasks.offer(closeTask);
307b4aff97c85e730857893742f73a082f6b8d139casatok                }
308988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            } else {
309988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                processTask(scp);
310988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
311988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
312988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
313988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private void processCancel() {
314988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (!checkOpenConnection()) {
315988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                return;
316988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
3176be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok            if (DBG) {
3186be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok                Log.w(TAG, "Cancel spell checker tasks.");
3196be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok            }
320988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            try {
3215357806980269d846a15c845a6fcc0384fb18860satok                mISpellCheckerSession.onCancel();
322988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            } catch (RemoteException e) {
323988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                Log.e(TAG, "Failed to cancel " + e);
324988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
325988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
326988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
32774061ff90453c79ddbde468f630a41425da07710satok        private void processClose() {
32874061ff90453c79ddbde468f630a41425da07710satok            if (!checkOpenConnection()) {
32974061ff90453c79ddbde468f630a41425da07710satok                return;
33074061ff90453c79ddbde468f630a41425da07710satok            }
33174061ff90453c79ddbde468f630a41425da07710satok            if (DBG) {
33274061ff90453c79ddbde468f630a41425da07710satok                Log.w(TAG, "Close spell checker tasks.");
33374061ff90453c79ddbde468f630a41425da07710satok            }
33474061ff90453c79ddbde468f630a41425da07710satok            try {
33574061ff90453c79ddbde468f630a41425da07710satok                mISpellCheckerSession.onClose();
33674061ff90453c79ddbde468f630a41425da07710satok                mISpellCheckerSession = null;
33774061ff90453c79ddbde468f630a41425da07710satok            } catch (RemoteException e) {
33874061ff90453c79ddbde468f630a41425da07710satok                Log.e(TAG, "Failed to close " + e);
33974061ff90453c79ddbde468f630a41425da07710satok            }
34074061ff90453c79ddbde468f630a41425da07710satok        }
34174061ff90453c79ddbde468f630a41425da07710satok
342988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private void processGetSuggestionsMultiple(SpellCheckerParams scp) {
343988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (!checkOpenConnection()) {
344988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                return;
345988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
3466be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok            if (DBG) {
3476be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok                Log.w(TAG, "Get suggestions from the spell checker.");
3486be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok            }
349988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            try {
3505357806980269d846a15c845a6fcc0384fb18860satok                mISpellCheckerSession.onGetSuggestionsMultiple(
351988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                        scp.mTextInfos, scp.mSuggestionsLimit, scp.mSequentialWords);
352988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            } catch (RemoteException e) {
353988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                Log.e(TAG, "Failed to get suggestions " + e);
354988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
355988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
356988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
357988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        @Override
358988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void onGetSuggestions(SuggestionsInfo[] results) {
359988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mHandler.sendMessage(Message.obtain(mHandler, MSG_ON_GET_SUGGESTION_MULTIPLE, results));
360988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
361988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
362988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
363988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    /**
364988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * Callback for getting results from text services
365988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     */
366988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    public interface SpellCheckerSessionListener {
367988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        /**
368988323c57bd25a58f05dfa492d9b9c8ab62c5153satok         * Callback for "getSuggestions"
369988323c57bd25a58f05dfa492d9b9c8ab62c5153satok         * @param results an array of results of getSuggestions
370988323c57bd25a58f05dfa492d9b9c8ab62c5153satok         */
371988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void onGetSuggestions(SuggestionsInfo[] results);
372988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
373988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
374a80838d9d63fcc9a83a9e7c99884e5b50316d4f0Jean Chalard    private static class InternalListener extends ITextServicesSessionListener.Stub {
375a80838d9d63fcc9a83a9e7c99884e5b50316d4f0Jean Chalard        private final SpellCheckerSessionListenerImpl mParentSpellCheckerSessionListenerImpl;
376a80838d9d63fcc9a83a9e7c99884e5b50316d4f0Jean Chalard
377a80838d9d63fcc9a83a9e7c99884e5b50316d4f0Jean Chalard        public InternalListener(SpellCheckerSessionListenerImpl spellCheckerSessionListenerImpl) {
378a80838d9d63fcc9a83a9e7c99884e5b50316d4f0Jean Chalard            mParentSpellCheckerSessionListenerImpl = spellCheckerSessionListenerImpl;
379a80838d9d63fcc9a83a9e7c99884e5b50316d4f0Jean Chalard        }
380a80838d9d63fcc9a83a9e7c99884e5b50316d4f0Jean Chalard
381988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        @Override
382988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void onServiceConnected(ISpellCheckerSession session) {
3836be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok            if (DBG) {
3846be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok                Log.w(TAG, "SpellCheckerSession connected.");
3856be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok            }
386a80838d9d63fcc9a83a9e7c99884e5b50316d4f0Jean Chalard            mParentSpellCheckerSessionListenerImpl.onServiceConnected(session);
387988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
388988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
389988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
390988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    @Override
391988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    protected void finalize() throws Throwable {
392988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        super.finalize();
393988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        if (mIsUsed) {
394988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            Log.e(TAG, "SpellCheckerSession was not finished properly." +
395988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    "You should call finishShession() when you finished to use a spell checker.");
396988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            close();
397988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
398988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
399988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
400988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    /**
401988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * @hide
402988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     */
403988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    public ITextServicesSessionListener getTextServicesSessionListener() {
404988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        return mInternalListener;
405988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
406988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
407988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    /**
408988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     * @hide
409988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     */
410988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    public ISpellCheckerSessionListener getSpellCheckerSessionListener() {
411988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        return mSpellCheckerSessionListenerImpl;
412988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
413988323c57bd25a58f05dfa492d9b9c8ab62c5153satok}
414