SpellCheckerSession.java revision 5357806980269d846a15c845a6fcc0384fb18860
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.view.textservice;
18
19import com.android.internal.textservice.ISpellCheckerSession;
20import com.android.internal.textservice.ISpellCheckerSessionListener;
21import com.android.internal.textservice.ITextServicesManager;
22import com.android.internal.textservice.ITextServicesSessionListener;
23
24import android.os.Handler;
25import android.os.Message;
26import android.os.RemoteException;
27import android.util.Log;
28import android.view.textservice.SpellCheckerInfo;
29import android.view.textservice.SuggestionsInfo;
30import android.view.textservice.TextInfo;
31
32import java.util.LinkedList;
33import java.util.Queue;
34
35/**
36 * The SpellCheckerSession interface provides the per client functionality of SpellCheckerService.
37 */
38public class SpellCheckerSession {
39    private static final String TAG = SpellCheckerSession.class.getSimpleName();
40    private static final boolean DBG = false;
41    /**
42     * Name under which a SpellChecker service component publishes information about itself.
43     * This meta-data must reference an XML resource.
44     **/
45    public static final String SERVICE_META_DATA = "android.view.textservice.scs";
46
47
48    private static final int MSG_ON_GET_SUGGESTION_MULTIPLE = 1;
49
50    private final InternalListener mInternalListener;
51    private final ITextServicesManager mTextServicesManager;
52    private final SpellCheckerInfo mSpellCheckerInfo;
53    private final SpellCheckerSessionListenerImpl mSpellCheckerSessionListenerImpl;
54
55    private boolean mIsUsed;
56    private SpellCheckerSessionListener mSpellCheckerSessionListener;
57
58    /** Handler that will execute the main tasks */
59    private final Handler mHandler = new Handler() {
60        @Override
61        public void handleMessage(Message msg) {
62            switch (msg.what) {
63                case MSG_ON_GET_SUGGESTION_MULTIPLE:
64                    handleOnGetSuggestionsMultiple((SuggestionsInfo[]) msg.obj);
65                    break;
66            }
67        }
68    };
69
70    /**
71     * Constructor
72     * @hide
73     */
74    public SpellCheckerSession(
75            SpellCheckerInfo info, ITextServicesManager tsm, SpellCheckerSessionListener listener) {
76        if (info == null || listener == null || tsm == null) {
77            throw new NullPointerException();
78        }
79        mSpellCheckerInfo = info;
80        mSpellCheckerSessionListenerImpl = new SpellCheckerSessionListenerImpl(mHandler);
81        mInternalListener = new InternalListener();
82        mTextServicesManager = tsm;
83        mIsUsed = true;
84        mSpellCheckerSessionListener = listener;
85    }
86
87    /**
88     * @return true if the connection to a text service of this session is disconnected and not
89     * alive.
90     */
91    public boolean isSessionDisconnected() {
92        return mSpellCheckerSessionListenerImpl.isDisconnected();
93    }
94
95    /**
96     * Get the spell checker service info this spell checker session has.
97     * @return SpellCheckerInfo for the specified locale.
98     */
99    public SpellCheckerInfo getSpellChecker() {
100        return mSpellCheckerInfo;
101    }
102
103    /**
104     * Finish this session and allow TextServicesManagerService to disconnect the bound spell
105     * checker.
106     */
107    public void close() {
108        mIsUsed = false;
109        try {
110            mTextServicesManager.finishSpellCheckerService(mSpellCheckerSessionListenerImpl);
111        } catch (RemoteException e) {
112            // do nothing
113        }
114    }
115
116    /**
117     * Get candidate strings for a substring of the specified text.
118     * @param textInfo text metadata for a spell checker
119     * @param suggestionsLimit the number of limit of suggestions returned
120     */
121    public void getSuggestions(TextInfo textInfo, int suggestionsLimit) {
122        getSuggestions(new TextInfo[] {textInfo}, suggestionsLimit, false);
123    }
124
125    /**
126     * A batch process of getSuggestions
127     * @param textInfos an array of text metadata for a spell checker
128     * @param suggestionsLimit the number of limit of suggestions returned
129     * @param sequentialWords true if textInfos can be treated as sequential words.
130     */
131    public void getSuggestions(
132            TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) {
133        if (DBG) {
134            Log.w(TAG, "getSuggestions from " + mSpellCheckerInfo.getId());
135        }
136        // TODO: Handle multiple words suggestions by using WordBreakIterator
137        mSpellCheckerSessionListenerImpl.getSuggestionsMultiple(
138                textInfos, suggestionsLimit, sequentialWords);
139    }
140
141    private void handleOnGetSuggestionsMultiple(SuggestionsInfo[] suggestionInfos) {
142        mSpellCheckerSessionListener.onGetSuggestions(suggestionInfos);
143    }
144
145    private static class SpellCheckerSessionListenerImpl extends ISpellCheckerSessionListener.Stub {
146        private static final int TASK_CANCEL = 1;
147        private static final int TASK_GET_SUGGESTIONS_MULTIPLE = 2;
148        private final Queue<SpellCheckerParams> mPendingTasks =
149                new LinkedList<SpellCheckerParams>();
150        private final Handler mHandler;
151
152        private boolean mOpened;
153        private ISpellCheckerSession mISpellCheckerSession;
154
155        public SpellCheckerSessionListenerImpl(Handler handler) {
156            mOpened = false;
157            mHandler = handler;
158        }
159
160        private static class SpellCheckerParams {
161            public final int mWhat;
162            public final TextInfo[] mTextInfos;
163            public final int mSuggestionsLimit;
164            public final boolean mSequentialWords;
165            public SpellCheckerParams(int what, TextInfo[] textInfos, int suggestionsLimit,
166                    boolean sequentialWords) {
167                mWhat = what;
168                mTextInfos = textInfos;
169                mSuggestionsLimit = suggestionsLimit;
170                mSequentialWords = sequentialWords;
171            }
172        }
173
174        private void processTask(SpellCheckerParams scp) {
175            switch (scp.mWhat) {
176                case TASK_CANCEL:
177                    processCancel();
178                    break;
179                case TASK_GET_SUGGESTIONS_MULTIPLE:
180                    processGetSuggestionsMultiple(scp);
181                    break;
182            }
183        }
184
185        public synchronized void onServiceConnected(ISpellCheckerSession session) {
186            mISpellCheckerSession = session;
187            mOpened = true;
188            if (DBG)
189                Log.d(TAG, "onServiceConnected - Success");
190            while (!mPendingTasks.isEmpty()) {
191                processTask(mPendingTasks.poll());
192            }
193        }
194
195        public void getSuggestionsMultiple(
196                TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) {
197            if (DBG) {
198                Log.w(TAG, "getSuggestionsMultiple");
199            }
200            processOrEnqueueTask(
201                    new SpellCheckerParams(TASK_GET_SUGGESTIONS_MULTIPLE, textInfos,
202                            suggestionsLimit, sequentialWords));
203        }
204
205        public boolean isDisconnected() {
206            return mOpened && mISpellCheckerSession == null;
207        }
208
209        public boolean checkOpenConnection() {
210            if (mISpellCheckerSession != null) {
211                return true;
212            }
213            Log.e(TAG, "not connected to the spellchecker service.");
214            return false;
215        }
216
217        private void processOrEnqueueTask(SpellCheckerParams scp) {
218            if (DBG) {
219                Log.d(TAG, "process or enqueue task: " + mISpellCheckerSession);
220            }
221            if (mISpellCheckerSession == null) {
222                mPendingTasks.offer(scp);
223            } else {
224                processTask(scp);
225            }
226        }
227
228        private void processCancel() {
229            if (!checkOpenConnection()) {
230                return;
231            }
232            if (DBG) {
233                Log.w(TAG, "Cancel spell checker tasks.");
234            }
235            try {
236                mISpellCheckerSession.onCancel();
237            } catch (RemoteException e) {
238                Log.e(TAG, "Failed to cancel " + e);
239            }
240        }
241
242        private void processGetSuggestionsMultiple(SpellCheckerParams scp) {
243            if (!checkOpenConnection()) {
244                return;
245            }
246            if (DBG) {
247                Log.w(TAG, "Get suggestions from the spell checker.");
248            }
249            try {
250                mISpellCheckerSession.onGetSuggestionsMultiple(
251                        scp.mTextInfos, scp.mSuggestionsLimit, scp.mSequentialWords);
252            } catch (RemoteException e) {
253                Log.e(TAG, "Failed to get suggestions " + e);
254            }
255        }
256
257        @Override
258        public void onGetSuggestions(SuggestionsInfo[] results) {
259            mHandler.sendMessage(Message.obtain(mHandler, MSG_ON_GET_SUGGESTION_MULTIPLE, results));
260        }
261    }
262
263    /**
264     * Callback for getting results from text services
265     */
266    public interface SpellCheckerSessionListener {
267        /**
268         * Callback for "getSuggestions"
269         * @param results an array of results of getSuggestions
270         */
271        public void onGetSuggestions(SuggestionsInfo[] results);
272    }
273
274    private class InternalListener extends ITextServicesSessionListener.Stub {
275        @Override
276        public void onServiceConnected(ISpellCheckerSession session) {
277            if (DBG) {
278                Log.w(TAG, "SpellCheckerSession connected.");
279            }
280            mSpellCheckerSessionListenerImpl.onServiceConnected(session);
281        }
282    }
283
284    @Override
285    protected void finalize() throws Throwable {
286        super.finalize();
287        if (mIsUsed) {
288            Log.e(TAG, "SpellCheckerSession was not finished properly." +
289                    "You should call finishShession() when you finished to use a spell checker.");
290            close();
291        }
292    }
293
294    /**
295     * @hide
296     */
297    public ITextServicesSessionListener getTextServicesSessionListener() {
298        return mInternalListener;
299    }
300
301    /**
302     * @hide
303     */
304    public ISpellCheckerSessionListener getSpellCheckerSessionListener() {
305        return mSpellCheckerSessionListenerImpl;
306    }
307}
308