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