SpellCheckerService.java revision 431ea84e1fde20139b748a4818c44e85a715e155
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package android.service.textservice;
18
19import com.android.internal.textservice.ISpellCheckerService;
20import com.android.internal.textservice.ISpellCheckerSession;
21import com.android.internal.textservice.ISpellCheckerSessionListener;
22
23import android.app.Service;
24import android.content.Intent;
25import android.os.Bundle;
26import android.os.IBinder;
27import android.os.Process;
28import android.os.RemoteException;
29import android.util.Log;
30import android.view.textservice.SentenceSuggestionsInfo;
31import android.view.textservice.SuggestionsInfo;
32import android.view.textservice.TextInfo;
33
34import java.lang.ref.WeakReference;
35
36/**
37 * SpellCheckerService provides an abstract base class for a spell checker.
38 * This class combines a service to the system with the spell checker service interface that
39 * spell checker must implement.
40 *
41 * <p>In addition to the normal Service lifecycle methods, this class
42 * introduces a new specific callback that subclasses should override
43 * {@link #createSession()} to provide a spell checker session that is corresponding
44 * to requested language and so on. The spell checker session returned by this method
45 * should extend {@link SpellCheckerService.Session}.
46 * </p>
47 *
48 * <h3>Returning spell check results</h3>
49 *
50 * <p>{@link SpellCheckerService.Session#onGetSuggestions(TextInfo, int)}
51 * should return spell check results.
52 * It receives {@link android.view.textservice.TextInfo} and returns
53 * {@link android.view.textservice.SuggestionsInfo} for the input.
54 * You may want to override
55 * {@link SpellCheckerService.Session#onGetSuggestionsMultiple(TextInfo[], int, boolean)} for
56 * better performance and quality.
57 * </p>
58 *
59 * <p>Please note that {@link SpellCheckerService.Session#getLocale()} does not return a valid
60 * locale before {@link SpellCheckerService.Session#onCreate()} </p>
61 *
62 */
63public abstract class SpellCheckerService extends Service {
64    private static final String TAG = SpellCheckerService.class.getSimpleName();
65    private static final boolean DBG = false;
66    public static final String SERVICE_INTERFACE =
67            "android.service.textservice.SpellCheckerService";
68
69    private final SpellCheckerServiceBinder mBinder = new SpellCheckerServiceBinder(this);
70
71
72    /**
73     * Implement to return the implementation of the internal spell checker
74     * service interface. Subclasses should not override.
75     */
76    @Override
77    public final IBinder onBind(final Intent intent) {
78        if (DBG) {
79            Log.w(TAG, "onBind");
80        }
81        return mBinder;
82    }
83
84    /**
85     * Factory method to create a spell checker session impl
86     * @return SpellCheckerSessionImpl which should be overridden by a concrete implementation.
87     */
88    public abstract Session createSession();
89
90    /**
91     * This abstract class should be overridden by a concrete implementation of a spell checker.
92     */
93    public static abstract class Session {
94        private InternalISpellCheckerSession mInternalSession;
95
96        /**
97         * @hide
98         */
99        public final void setInternalISpellCheckerSession(InternalISpellCheckerSession session) {
100            mInternalSession = session;
101        }
102
103        /**
104         * This is called after the class is initialized, at which point it knows it can call
105         * getLocale() etc...
106         */
107        public abstract void onCreate();
108
109        /**
110         * Get suggestions for specified text in TextInfo.
111         * This function will run on the incoming IPC thread.
112         * So, this is not called on the main thread,
113         * but will be called in series on another thread.
114         * @param textInfo the text metadata
115         * @param suggestionsLimit the maximum number of suggestions to be returned
116         * @return SuggestionsInfo which contains suggestions for textInfo
117         */
118        public abstract SuggestionsInfo onGetSuggestions(TextInfo textInfo, int suggestionsLimit);
119
120        /**
121         * A batch process of onGetSuggestions.
122         * This function will run on the incoming IPC thread.
123         * So, this is not called on the main thread,
124         * but will be called in series on another thread.
125         * @param textInfos an array of the text metadata
126         * @param suggestionsLimit the maximum number of suggestions to be returned
127         * @param sequentialWords true if textInfos can be treated as sequential words.
128         * @return an array of {@link SentenceSuggestionsInfo} returned by
129         * {@link SpellCheckerService.Session#onGetSuggestions(TextInfo, int)}
130         */
131        public SuggestionsInfo[] onGetSuggestionsMultiple(TextInfo[] textInfos,
132                int suggestionsLimit, boolean sequentialWords) {
133            final int length = textInfos.length;
134            final SuggestionsInfo[] retval = new SuggestionsInfo[length];
135            for (int i = 0; i < length; ++i) {
136                retval[i] = onGetSuggestions(textInfos[i], suggestionsLimit);
137                retval[i].setCookieAndSequence(
138                        textInfos[i].getCookie(), textInfos[i].getSequence());
139            }
140            return retval;
141        }
142
143        /**
144         * Get sentence suggestions for specified texts in an array of TextInfo.
145         * The default implementation returns an array of SentenceSuggestionsInfo by simply
146         * calling onGetSuggestions.
147         * This function will run on the incoming IPC thread.
148         * So, this is not called on the main thread,
149         * but will be called in series on another thread.
150         * When you override this method, make sure that suggestionsLimit is applied to suggestions
151         * that share the same start position and length.
152         * @param textInfos an array of the text metadata
153         * @param suggestionsLimit the maximum number of suggestions to be returned
154         * @return an array of {@link SentenceSuggestionsInfo} returned by
155         * {@link SpellCheckerService.Session#onGetSuggestions(TextInfo, int)}
156         */
157        public SentenceSuggestionsInfo[] onGetSentenceSuggestionsMultiple(TextInfo[] textInfos,
158                int suggestionsLimit) {
159            final int length = textInfos.length;
160            final SentenceSuggestionsInfo[] retval = new SentenceSuggestionsInfo[length];
161            for (int i = 0; i < length; ++i) {
162                final SuggestionsInfo si = onGetSuggestions(textInfos[i], suggestionsLimit);
163                si.setCookieAndSequence(textInfos[i].getCookie(), textInfos[i].getSequence());
164                final int N = textInfos[i].getText().length();
165                retval[i] = new SentenceSuggestionsInfo(
166                        new SuggestionsInfo[] {si}, new int[]{0}, new int[]{N});
167            }
168            return retval;
169        }
170
171        /**
172         * Request to abort all tasks executed in SpellChecker.
173         * This function will run on the incoming IPC thread.
174         * So, this is not called on the main thread,
175         * but will be called in series on another thread.
176         */
177        public void onCancel() {}
178
179        /**
180         * Request to close this session.
181         * This function will run on the incoming IPC thread.
182         * So, this is not called on the main thread,
183         * but will be called in series on another thread.
184         */
185        public void onClose() {}
186
187        /**
188         * @return Locale for this session
189         */
190        public String getLocale() {
191            return mInternalSession.getLocale();
192        }
193
194        /**
195         * @return Bundle for this session
196         */
197        public Bundle getBundle() {
198            return mInternalSession.getBundle();
199        }
200    }
201
202    // Preventing from exposing ISpellCheckerSession.aidl, create an internal class.
203    private static class InternalISpellCheckerSession extends ISpellCheckerSession.Stub {
204        private ISpellCheckerSessionListener mListener;
205        private final Session mSession;
206        private final String mLocale;
207        private final Bundle mBundle;
208
209        public InternalISpellCheckerSession(String locale, ISpellCheckerSessionListener listener,
210                Bundle bundle, Session session) {
211            mListener = listener;
212            mSession = session;
213            mLocale = locale;
214            mBundle = bundle;
215            session.setInternalISpellCheckerSession(this);
216        }
217
218        @Override
219        public void onGetSuggestionsMultiple(
220                TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) {
221            int pri = Process.getThreadPriority(Process.myTid());
222            try {
223                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
224                mListener.onGetSuggestions(
225                        mSession.onGetSuggestionsMultiple(
226                                textInfos, suggestionsLimit, sequentialWords));
227            } catch (RemoteException e) {
228            } finally {
229                Process.setThreadPriority(pri);
230            }
231        }
232
233        @Override
234        public void onGetSentenceSuggestionsMultiple(TextInfo[] textInfos, int suggestionsLimit) {
235            try {
236                mListener.onGetSentenceSuggestions(
237                        mSession.onGetSentenceSuggestionsMultiple(textInfos, suggestionsLimit));
238            } catch (RemoteException e) {
239            }
240        }
241
242        @Override
243        public void onCancel() {
244            int pri = Process.getThreadPriority(Process.myTid());
245            try {
246                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
247                mSession.onCancel();
248            } finally {
249                Process.setThreadPriority(pri);
250            }
251        }
252
253        @Override
254        public void onClose() {
255            int pri = Process.getThreadPriority(Process.myTid());
256            try {
257                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
258                mSession.onClose();
259            } finally {
260                Process.setThreadPriority(pri);
261                mListener = null;
262            }
263        }
264
265        public String getLocale() {
266            return mLocale;
267        }
268
269        public Bundle getBundle() {
270            return mBundle;
271        }
272    }
273
274    private static class SpellCheckerServiceBinder extends ISpellCheckerService.Stub {
275        private final WeakReference<SpellCheckerService> mInternalServiceRef;
276
277        public SpellCheckerServiceBinder(SpellCheckerService service) {
278            mInternalServiceRef = new WeakReference<SpellCheckerService>(service);
279        }
280
281        @Override
282        public ISpellCheckerSession getISpellCheckerSession(
283                String locale, ISpellCheckerSessionListener listener, Bundle bundle) {
284            final SpellCheckerService service = mInternalServiceRef.get();
285            if (service == null) return null;
286            final Session session = service.createSession();
287            final InternalISpellCheckerSession internalSession =
288                    new InternalISpellCheckerSession(locale, listener, bundle, session);
289            session.onCreate();
290            return internalSession;
291        }
292    }
293}
294