blob: 779eefb14c74e6b6d3fbcc7dc0b966359a99464e [file] [log] [blame]
satok988323c2011-06-22 16:38:13 +09001/*
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
satokaafd9552011-08-02 15:24:00 +090017package android.view.textservice;
satok988323c2011-06-22 16:38:13 +090018
Dianne Hackborn33b8ee52011-12-13 15:08:40 -080019import android.os.Binder;
satok988323c2011-06-22 16:38:13 +090020import android.os.Handler;
Dianne Hackborn33b8ee52011-12-13 15:08:40 -080021import android.os.HandlerThread;
satok988323c2011-06-22 16:38:13 +090022import android.os.Message;
Dianne Hackborn33b8ee52011-12-13 15:08:40 -080023import android.os.Process;
satok988323c2011-06-22 16:38:13 +090024import android.os.RemoteException;
25import android.util.Log;
satok988323c2011-06-22 16:38:13 +090026
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -070027import com.android.internal.textservice.ISpellCheckerSession;
28import com.android.internal.textservice.ISpellCheckerSessionListener;
29import com.android.internal.textservice.ITextServicesManager;
30import com.android.internal.textservice.ITextServicesSessionListener;
31
Narayan Kamath9b643672017-03-22 13:25:24 +000032import dalvik.system.CloseGuard;
33
satok988323c2011-06-22 16:38:13 +090034import java.util.LinkedList;
35import java.util.Queue;
36
37/**
38 * The SpellCheckerSession interface provides the per client functionality of SpellCheckerService.
satok44b75032011-10-14 14:48:59 +090039 *
40 *
41 * <a name="Applications"></a>
42 * <h3>Applications</h3>
43 *
44 * <p>In most cases, applications that are using the standard
45 * {@link android.widget.TextView} or its subclasses will have little they need
46 * to do to work well with spell checker services. The main things you need to
47 * be aware of are:</p>
48 *
49 * <ul>
50 * <li> Properly set the {@link android.R.attr#inputType} in your editable
51 * text views, so that the spell checker will have enough context to help the
52 * user in editing text in them.
53 * </ul>
54 *
55 * <p>For the rare people amongst us writing client applications that use the spell checker service
56 * directly, you will need to use {@link #getSuggestions(TextInfo, int)} or
57 * {@link #getSuggestions(TextInfo[], int, boolean)} for obtaining results from the spell checker
58 * service by yourself.</p>
59 *
60 * <h3>Security</h3>
61 *
62 * <p>There are a lot of security issues associated with spell checkers,
63 * since they could monitor all the text being sent to them
64 * through, for instance, {@link android.widget.TextView}.
65 * The Android spell checker framework also allows
66 * arbitrary third party spell checkers, so care must be taken to restrict their
67 * selection and interactions.</p>
68 *
69 * <p>Here are some key points about the security architecture behind the
70 * spell checker framework:</p>
71 *
72 * <ul>
73 * <li>Only the system is allowed to directly access a spell checker framework's
74 * {@link android.service.textservice.SpellCheckerService} interface, via the
75 * {@link android.Manifest.permission#BIND_TEXT_SERVICE} permission. This is
76 * enforced in the system by not binding to a spell checker service that does
77 * not require this permission.
78 *
79 * <li>The user must explicitly enable a new spell checker in settings before
80 * they can be enabled, to confirm with the system that they know about it
81 * and want to make it available for use.
82 * </ul>
83 *
satok988323c2011-06-22 16:38:13 +090084 */
85public class SpellCheckerSession {
86 private static final String TAG = SpellCheckerSession.class.getSimpleName();
87 private static final boolean DBG = false;
satokaafd9552011-08-02 15:24:00 +090088 /**
89 * Name under which a SpellChecker service component publishes information about itself.
90 * This meta-data must reference an XML resource.
91 **/
92 public static final String SERVICE_META_DATA = "android.view.textservice.scs";
satok988323c2011-06-22 16:38:13 +090093
94 private static final int MSG_ON_GET_SUGGESTION_MULTIPLE = 1;
satok0dc1f642011-11-18 11:27:10 +090095 private static final int MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE = 2;
satok988323c2011-06-22 16:38:13 +090096
97 private final InternalListener mInternalListener;
98 private final ITextServicesManager mTextServicesManager;
satok1bedd992011-07-23 11:39:55 +090099 private final SpellCheckerInfo mSpellCheckerInfo;
Seigo Nonaka92adda22015-06-02 16:27:12 +0900100 private final SpellCheckerSessionListener mSpellCheckerSessionListener;
satok988323c2011-06-22 16:38:13 +0900101 private final SpellCheckerSessionListenerImpl mSpellCheckerSessionListenerImpl;
102
Narayan Kamath9b643672017-03-22 13:25:24 +0000103 private final CloseGuard mGuard = CloseGuard.get();
satok988323c2011-06-22 16:38:13 +0900104
105 /** Handler that will execute the main tasks */
106 private final Handler mHandler = new Handler() {
107 @Override
108 public void handleMessage(Message msg) {
109 switch (msg.what) {
110 case MSG_ON_GET_SUGGESTION_MULTIPLE:
111 handleOnGetSuggestionsMultiple((SuggestionsInfo[]) msg.obj);
112 break;
satok0dc1f642011-11-18 11:27:10 +0900113 case MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE:
satokd404fe12012-02-22 06:38:18 +0900114 handleOnGetSentenceSuggestionsMultiple((SentenceSuggestionsInfo[]) msg.obj);
satok0dc1f642011-11-18 11:27:10 +0900115 break;
satok988323c2011-06-22 16:38:13 +0900116 }
117 }
118 };
119
120 /**
121 * Constructor
122 * @hide
123 */
satok1bedd992011-07-23 11:39:55 +0900124 public SpellCheckerSession(
Yohei Yukawa06b4be72017-01-29 14:08:17 -0800125 SpellCheckerInfo info, ITextServicesManager tsm, SpellCheckerSessionListener listener) {
satok1bedd992011-07-23 11:39:55 +0900126 if (info == null || listener == null || tsm == null) {
satok988323c2011-06-22 16:38:13 +0900127 throw new NullPointerException();
128 }
satok1bedd992011-07-23 11:39:55 +0900129 mSpellCheckerInfo = info;
satok988323c2011-06-22 16:38:13 +0900130 mSpellCheckerSessionListenerImpl = new SpellCheckerSessionListenerImpl(mHandler);
Jean Chalarda80838d2011-10-20 19:33:53 +0900131 mInternalListener = new InternalListener(mSpellCheckerSessionListenerImpl);
satok988323c2011-06-22 16:38:13 +0900132 mTextServicesManager = tsm;
satok988323c2011-06-22 16:38:13 +0900133 mSpellCheckerSessionListener = listener;
Narayan Kamath9b643672017-03-22 13:25:24 +0000134
135 mGuard.open("finishSession");
satok988323c2011-06-22 16:38:13 +0900136 }
137
138 /**
139 * @return true if the connection to a text service of this session is disconnected and not
140 * alive.
141 */
142 public boolean isSessionDisconnected() {
143 return mSpellCheckerSessionListenerImpl.isDisconnected();
144 }
145
146 /**
satok1bedd992011-07-23 11:39:55 +0900147 * Get the spell checker service info this spell checker session has.
148 * @return SpellCheckerInfo for the specified locale.
149 */
150 public SpellCheckerInfo getSpellChecker() {
151 return mSpellCheckerInfo;
152 }
153
154 /**
satokb4aff972011-11-03 04:12:51 +0900155 * Cancel pending and running spell check tasks
156 */
157 public void cancel() {
158 mSpellCheckerSessionListenerImpl.cancel();
159 }
160
161 /**
satok988323c2011-06-22 16:38:13 +0900162 * Finish this session and allow TextServicesManagerService to disconnect the bound spell
163 * checker.
164 */
165 public void close() {
Narayan Kamath9b643672017-03-22 13:25:24 +0000166 mGuard.close();
satok988323c2011-06-22 16:38:13 +0900167 try {
satok74061ff2011-11-02 11:20:33 +0900168 mSpellCheckerSessionListenerImpl.close();
satok988323c2011-06-22 16:38:13 +0900169 mTextServicesManager.finishSpellCheckerService(mSpellCheckerSessionListenerImpl);
170 } catch (RemoteException e) {
171 // do nothing
172 }
173 }
174
175 /**
satok6183cd62012-03-27 12:08:29 +0900176 * Get suggestions from the specified sentences
177 * @param textInfos an array of text metadata for a spell checker
178 * @param suggestionsLimit the maximum number of suggestions that will be returned
satok0dc1f642011-11-18 11:27:10 +0900179 */
satok6183cd62012-03-27 12:08:29 +0900180 public void getSentenceSuggestions(TextInfo[] textInfos, int suggestionsLimit) {
satokd404fe12012-02-22 06:38:18 +0900181 mSpellCheckerSessionListenerImpl.getSentenceSuggestionsMultiple(
satok6183cd62012-03-27 12:08:29 +0900182 textInfos, suggestionsLimit);
satok0dc1f642011-11-18 11:27:10 +0900183 }
184
185 /**
satok988323c2011-06-22 16:38:13 +0900186 * Get candidate strings for a substring of the specified text.
187 * @param textInfo text metadata for a spell checker
satok6183cd62012-03-27 12:08:29 +0900188 * @param suggestionsLimit the maximum number of suggestions that will be returned
satokc7ee1b92012-04-11 20:40:07 +0900189 * @deprecated use {@link SpellCheckerSession#getSentenceSuggestions(TextInfo[], int)} instead
satok988323c2011-06-22 16:38:13 +0900190 */
satokc7ee1b92012-04-11 20:40:07 +0900191 @Deprecated
satok988323c2011-06-22 16:38:13 +0900192 public void getSuggestions(TextInfo textInfo, int suggestionsLimit) {
193 getSuggestions(new TextInfo[] {textInfo}, suggestionsLimit, false);
194 }
195
196 /**
197 * A batch process of getSuggestions
198 * @param textInfos an array of text metadata for a spell checker
satok6183cd62012-03-27 12:08:29 +0900199 * @param suggestionsLimit the maximum number of suggestions that will be returned
satok988323c2011-06-22 16:38:13 +0900200 * @param sequentialWords true if textInfos can be treated as sequential words.
satokc7ee1b92012-04-11 20:40:07 +0900201 * @deprecated use {@link SpellCheckerSession#getSentenceSuggestions(TextInfo[], int)} instead
satok988323c2011-06-22 16:38:13 +0900202 */
satokc7ee1b92012-04-11 20:40:07 +0900203 @Deprecated
satok988323c2011-06-22 16:38:13 +0900204 public void getSuggestions(
205 TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) {
satok6be6d752011-07-28 20:40:38 +0900206 if (DBG) {
207 Log.w(TAG, "getSuggestions from " + mSpellCheckerInfo.getId());
208 }
satok988323c2011-06-22 16:38:13 +0900209 mSpellCheckerSessionListenerImpl.getSuggestionsMultiple(
210 textInfos, suggestionsLimit, sequentialWords);
211 }
212
213 private void handleOnGetSuggestionsMultiple(SuggestionsInfo[] suggestionInfos) {
214 mSpellCheckerSessionListener.onGetSuggestions(suggestionInfos);
215 }
216
satokd404fe12012-02-22 06:38:18 +0900217 private void handleOnGetSentenceSuggestionsMultiple(SentenceSuggestionsInfo[] suggestionInfos) {
218 mSpellCheckerSessionListener.onGetSentenceSuggestions(suggestionInfos);
satok0dc1f642011-11-18 11:27:10 +0900219 }
220
Yohei Yukawa06b4be72017-01-29 14:08:17 -0800221 private static final class SpellCheckerSessionListenerImpl
222 extends ISpellCheckerSessionListener.Stub {
satok988323c2011-06-22 16:38:13 +0900223 private static final int TASK_CANCEL = 1;
224 private static final int TASK_GET_SUGGESTIONS_MULTIPLE = 2;
satok74061ff2011-11-02 11:20:33 +0900225 private static final int TASK_CLOSE = 3;
satok0dc1f642011-11-18 11:27:10 +0900226 private static final int TASK_GET_SUGGESTIONS_MULTIPLE_FOR_SENTENCE = 4;
Yohei Yukawaf05ce722015-06-16 00:29:28 -0700227 private static String taskToString(int task) {
228 switch (task) {
229 case TASK_CANCEL:
Yohei Yukawaf4d225a2015-06-16 21:19:00 -0700230 return "TASK_CANCEL";
Yohei Yukawaf05ce722015-06-16 00:29:28 -0700231 case TASK_GET_SUGGESTIONS_MULTIPLE:
232 return "TASK_GET_SUGGESTIONS_MULTIPLE";
233 case TASK_CLOSE:
234 return "TASK_CLOSE";
235 case TASK_GET_SUGGESTIONS_MULTIPLE_FOR_SENTENCE:
236 return "TASK_GET_SUGGESTIONS_MULTIPLE_FOR_SENTENCE";
237 default:
238 return "Unexpected task=" + task;
239 }
240 }
241
242 private final Queue<SpellCheckerParams> mPendingTasks = new LinkedList<>();
satok060677f2011-11-17 09:40:56 +0900243 private Handler mHandler;
satok988323c2011-06-22 16:38:13 +0900244
Yohei Yukawaf05ce722015-06-16 00:29:28 -0700245 private static final int STATE_WAIT_CONNECTION = 0;
246 private static final int STATE_CONNECTED = 1;
247 private static final int STATE_CLOSED_AFTER_CONNECTION = 2;
248 private static final int STATE_CLOSED_BEFORE_CONNECTION = 3;
249 private static String stateToString(int state) {
250 switch (state) {
251 case STATE_WAIT_CONNECTION: return "STATE_WAIT_CONNECTION";
252 case STATE_CONNECTED: return "STATE_CONNECTED";
253 case STATE_CLOSED_AFTER_CONNECTION: return "STATE_CLOSED_AFTER_CONNECTION";
254 case STATE_CLOSED_BEFORE_CONNECTION: return "STATE_CLOSED_BEFORE_CONNECTION";
255 default: return "Unexpected state=" + state;
256 }
257 }
258 private int mState = STATE_WAIT_CONNECTION;
259
satok988323c2011-06-22 16:38:13 +0900260 private ISpellCheckerSession mISpellCheckerSession;
Dianne Hackborn33b8ee52011-12-13 15:08:40 -0800261 private HandlerThread mThread;
262 private Handler mAsyncHandler;
satok988323c2011-06-22 16:38:13 +0900263
264 public SpellCheckerSessionListenerImpl(Handler handler) {
satok988323c2011-06-22 16:38:13 +0900265 mHandler = handler;
266 }
267
268 private static class SpellCheckerParams {
269 public final int mWhat;
270 public final TextInfo[] mTextInfos;
271 public final int mSuggestionsLimit;
272 public final boolean mSequentialWords;
Dianne Hackborn33b8ee52011-12-13 15:08:40 -0800273 public ISpellCheckerSession mSession;
satok988323c2011-06-22 16:38:13 +0900274 public SpellCheckerParams(int what, TextInfo[] textInfos, int suggestionsLimit,
275 boolean sequentialWords) {
276 mWhat = what;
277 mTextInfos = textInfos;
278 mSuggestionsLimit = suggestionsLimit;
279 mSequentialWords = sequentialWords;
280 }
281 }
282
Dianne Hackborn33b8ee52011-12-13 15:08:40 -0800283 private void processTask(ISpellCheckerSession session, SpellCheckerParams scp,
284 boolean async) {
Yohei Yukawaf05ce722015-06-16 00:29:28 -0700285 if (DBG) {
286 synchronized (this) {
287 Log.d(TAG, "entering processTask:"
288 + " session.hashCode()=#" + Integer.toHexString(session.hashCode())
289 + " scp.mWhat=" + taskToString(scp.mWhat) + " async=" + async
290 + " mAsyncHandler=" + mAsyncHandler
291 + " mState=" + stateToString(mState));
292 }
293 }
Dianne Hackborn33b8ee52011-12-13 15:08:40 -0800294 if (async || mAsyncHandler == null) {
295 switch (scp.mWhat) {
296 case TASK_CANCEL:
Dianne Hackborn33b8ee52011-12-13 15:08:40 -0800297 try {
298 session.onCancel();
299 } catch (RemoteException e) {
300 Log.e(TAG, "Failed to cancel " + e);
301 }
302 break;
303 case TASK_GET_SUGGESTIONS_MULTIPLE:
Dianne Hackborn33b8ee52011-12-13 15:08:40 -0800304 try {
305 session.onGetSuggestionsMultiple(scp.mTextInfos,
306 scp.mSuggestionsLimit, scp.mSequentialWords);
307 } catch (RemoteException e) {
308 Log.e(TAG, "Failed to get suggestions " + e);
309 }
310 break;
Dianne Hackbornb5052de2011-12-13 16:31:43 -0800311 case TASK_GET_SUGGESTIONS_MULTIPLE_FOR_SENTENCE:
Dianne Hackbornb5052de2011-12-13 16:31:43 -0800312 try {
satokd404fe12012-02-22 06:38:18 +0900313 session.onGetSentenceSuggestionsMultiple(
Dianne Hackbornb5052de2011-12-13 16:31:43 -0800314 scp.mTextInfos, scp.mSuggestionsLimit);
315 } catch (RemoteException e) {
316 Log.e(TAG, "Failed to get suggestions " + e);
317 }
318 break;
Dianne Hackborn33b8ee52011-12-13 15:08:40 -0800319 case TASK_CLOSE:
Dianne Hackborn33b8ee52011-12-13 15:08:40 -0800320 try {
321 session.onClose();
322 } catch (RemoteException e) {
323 Log.e(TAG, "Failed to close " + e);
324 }
325 break;
326 }
327 } else {
328 // The interface is to a local object, so need to execute it
329 // asynchronously.
330 scp.mSession = session;
331 mAsyncHandler.sendMessage(Message.obtain(mAsyncHandler, 1, scp));
332 }
333
334 if (scp.mWhat == TASK_CLOSE) {
335 // If we are closing, we want to clean up our state now even
336 // if it is pending as an async operation.
337 synchronized (this) {
Yohei Yukawaf05ce722015-06-16 00:29:28 -0700338 processCloseLocked();
Dianne Hackborn33b8ee52011-12-13 15:08:40 -0800339 }
satok988323c2011-06-22 16:38:13 +0900340 }
341 }
342
Yohei Yukawaf05ce722015-06-16 00:29:28 -0700343 private void processCloseLocked() {
344 if (DBG) Log.d(TAG, "entering processCloseLocked:"
345 + " session" + (mISpellCheckerSession != null ? ".hashCode()=#"
346 + Integer.toHexString(mISpellCheckerSession.hashCode()) : "=null")
347 + " mState=" + stateToString(mState));
348 mISpellCheckerSession = null;
349 if (mThread != null) {
350 mThread.quit();
351 }
352 mHandler = null;
353 mPendingTasks.clear();
354 mThread = null;
355 mAsyncHandler = null;
356 switch (mState) {
357 case STATE_WAIT_CONNECTION:
358 mState = STATE_CLOSED_BEFORE_CONNECTION;
359 break;
360 case STATE_CONNECTED:
361 mState = STATE_CLOSED_AFTER_CONNECTION;
362 break;
363 default:
364 Log.e(TAG, "processCloseLocked is called unexpectedly. mState=" +
365 stateToString(mState));
366 break;
367 }
368 }
369
Yohei Yukawa06b4be72017-01-29 14:08:17 -0800370 public void onServiceConnected(ISpellCheckerSession session) {
Dianne Hackborn33b8ee52011-12-13 15:08:40 -0800371 synchronized (this) {
Yohei Yukawaf05ce722015-06-16 00:29:28 -0700372 switch (mState) {
373 case STATE_WAIT_CONNECTION:
374 // OK, go ahead.
375 break;
376 case STATE_CLOSED_BEFORE_CONNECTION:
377 // This is possible, and not an error. The client no longer is interested
378 // in this connection. OK to ignore.
379 if (DBG) Log.i(TAG, "ignoring onServiceConnected since the session is"
380 + " already closed.");
381 return;
382 default:
383 Log.e(TAG, "ignoring onServiceConnected due to unexpected mState="
384 + stateToString(mState));
385 return;
386 }
387 if (session == null) {
388 Log.e(TAG, "ignoring onServiceConnected due to session=null");
389 return;
390 }
Dianne Hackborn33b8ee52011-12-13 15:08:40 -0800391 mISpellCheckerSession = session;
392 if (session.asBinder() instanceof Binder && mThread == null) {
Yohei Yukawaf05ce722015-06-16 00:29:28 -0700393 if (DBG) Log.d(TAG, "starting HandlerThread in onServiceConnected.");
Dianne Hackborn33b8ee52011-12-13 15:08:40 -0800394 // If this is a local object, we need to do our own threading
395 // to make sure we handle it asynchronously.
396 mThread = new HandlerThread("SpellCheckerSession",
397 Process.THREAD_PRIORITY_BACKGROUND);
398 mThread.start();
399 mAsyncHandler = new Handler(mThread.getLooper()) {
400 @Override public void handleMessage(Message msg) {
401 SpellCheckerParams scp = (SpellCheckerParams)msg.obj;
402 processTask(scp.mSession, scp, true);
403 }
404 };
405 }
Yohei Yukawaf05ce722015-06-16 00:29:28 -0700406 mState = STATE_CONNECTED;
407 if (DBG) {
408 Log.d(TAG, "processed onServiceConnected: mISpellCheckerSession.hashCode()=#"
409 + Integer.toHexString(mISpellCheckerSession.hashCode())
410 + " mPendingTasks.size()=" + mPendingTasks.size());
411 }
Yohei Yukawa06b4be72017-01-29 14:08:17 -0800412 while (!mPendingTasks.isEmpty()) {
413 processTask(session, mPendingTasks.poll(), false);
414 }
satok988323c2011-06-22 16:38:13 +0900415 }
416 }
417
satokb4aff972011-11-03 04:12:51 +0900418 public void cancel() {
satokb4aff972011-11-03 04:12:51 +0900419 processOrEnqueueTask(new SpellCheckerParams(TASK_CANCEL, null, 0, false));
420 }
421
satok988323c2011-06-22 16:38:13 +0900422 public void getSuggestionsMultiple(
423 TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) {
424 processOrEnqueueTask(
425 new SpellCheckerParams(TASK_GET_SUGGESTIONS_MULTIPLE, textInfos,
426 suggestionsLimit, sequentialWords));
427 }
428
satokd404fe12012-02-22 06:38:18 +0900429 public void getSentenceSuggestionsMultiple(TextInfo[] textInfos, int suggestionsLimit) {
satok0dc1f642011-11-18 11:27:10 +0900430 processOrEnqueueTask(
431 new SpellCheckerParams(TASK_GET_SUGGESTIONS_MULTIPLE_FOR_SENTENCE,
432 textInfos, suggestionsLimit, false));
433 }
434
satok74061ff2011-11-02 11:20:33 +0900435 public void close() {
satok74061ff2011-11-02 11:20:33 +0900436 processOrEnqueueTask(new SpellCheckerParams(TASK_CLOSE, null, 0, false));
437 }
438
satok988323c2011-06-22 16:38:13 +0900439 public boolean isDisconnected() {
Yohei Yukawaf05ce722015-06-16 00:29:28 -0700440 synchronized (this) {
441 return mState != STATE_CONNECTED;
442 }
satok988323c2011-06-22 16:38:13 +0900443 }
444
satok988323c2011-06-22 16:38:13 +0900445 private void processOrEnqueueTask(SpellCheckerParams scp) {
Dianne Hackborn33b8ee52011-12-13 15:08:40 -0800446 ISpellCheckerSession session;
447 synchronized (this) {
Yohei Yukawaf05ce722015-06-16 00:29:28 -0700448 if (mState != STATE_WAIT_CONNECTION && mState != STATE_CONNECTED) {
449 Log.e(TAG, "ignoring processOrEnqueueTask due to unexpected mState="
450 + taskToString(scp.mWhat)
451 + " scp.mWhat=" + taskToString(scp.mWhat));
452 return;
453 }
454
455 if (mState == STATE_WAIT_CONNECTION) {
456 // If we are still waiting for the connection. Need to pay special attention.
457 if (scp.mWhat == TASK_CLOSE) {
458 processCloseLocked();
459 return;
460 }
461 // Enqueue the task to task queue.
Dianne Hackborn33b8ee52011-12-13 15:08:40 -0800462 SpellCheckerParams closeTask = null;
463 if (scp.mWhat == TASK_CANCEL) {
Yohei Yukawaf05ce722015-06-16 00:29:28 -0700464 if (DBG) Log.d(TAG, "canceling pending tasks in processOrEnqueueTask.");
Dianne Hackborn33b8ee52011-12-13 15:08:40 -0800465 while (!mPendingTasks.isEmpty()) {
466 final SpellCheckerParams tmp = mPendingTasks.poll();
467 if (tmp.mWhat == TASK_CLOSE) {
satokd404fe12012-02-22 06:38:18 +0900468 // Only one close task should be processed, while we need to remove
469 // all close tasks from the queue
Dianne Hackborn33b8ee52011-12-13 15:08:40 -0800470 closeTask = tmp;
471 }
satokb4aff972011-11-03 04:12:51 +0900472 }
473 }
Dianne Hackborn33b8ee52011-12-13 15:08:40 -0800474 mPendingTasks.offer(scp);
475 if (closeTask != null) {
476 mPendingTasks.offer(closeTask);
477 }
Yohei Yukawaf05ce722015-06-16 00:29:28 -0700478 if (DBG) Log.d(TAG, "queueing tasks in processOrEnqueueTask since the"
479 + " connection is not established."
480 + " mPendingTasks.size()=" + mPendingTasks.size());
Dianne Hackborn33b8ee52011-12-13 15:08:40 -0800481 return;
satokb4aff972011-11-03 04:12:51 +0900482 }
Yohei Yukawaf05ce722015-06-16 00:29:28 -0700483
484 session = mISpellCheckerSession;
satok988323c2011-06-22 16:38:13 +0900485 }
Yohei Yukawaf05ce722015-06-16 00:29:28 -0700486 // session must never be null here.
Dianne Hackborn33b8ee52011-12-13 15:08:40 -0800487 processTask(session, scp, false);
satok0dc1f642011-11-18 11:27:10 +0900488 }
489
satok988323c2011-06-22 16:38:13 +0900490 @Override
491 public void onGetSuggestions(SuggestionsInfo[] results) {
Dianne Hackborn33b8ee52011-12-13 15:08:40 -0800492 synchronized (this) {
493 if (mHandler != null) {
494 mHandler.sendMessage(Message.obtain(mHandler,
495 MSG_ON_GET_SUGGESTION_MULTIPLE, results));
496 }
497 }
satok988323c2011-06-22 16:38:13 +0900498 }
satok0dc1f642011-11-18 11:27:10 +0900499
500 @Override
satokd404fe12012-02-22 06:38:18 +0900501 public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) {
Keisuke Kuroyanagibf9767c2014-05-30 19:22:43 +0900502 synchronized (this) {
503 if (mHandler != null) {
504 mHandler.sendMessage(Message.obtain(mHandler,
505 MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE, results));
506 }
507 }
satok0dc1f642011-11-18 11:27:10 +0900508 }
satok988323c2011-06-22 16:38:13 +0900509 }
510
511 /**
512 * Callback for getting results from text services
513 */
514 public interface SpellCheckerSessionListener {
515 /**
satokf6710612012-03-30 18:31:36 +0900516 * Callback for {@link SpellCheckerSession#getSuggestions(TextInfo, int)}
517 * and {@link SpellCheckerSession#getSuggestions(TextInfo[], int, boolean)}
satok6183cd62012-03-27 12:08:29 +0900518 * @param results an array of {@link SuggestionsInfo}s.
519 * These results are suggestions for {@link TextInfo}s queried by
satokf6710612012-03-30 18:31:36 +0900520 * {@link SpellCheckerSession#getSuggestions(TextInfo, int)} or
521 * {@link SpellCheckerSession#getSuggestions(TextInfo[], int, boolean)}
satok988323c2011-06-22 16:38:13 +0900522 */
523 public void onGetSuggestions(SuggestionsInfo[] results);
satok0dc1f642011-11-18 11:27:10 +0900524 /**
satok6183cd62012-03-27 12:08:29 +0900525 * Callback for {@link SpellCheckerSession#getSentenceSuggestions(TextInfo[], int)}
526 * @param results an array of {@link SentenceSuggestionsInfo}s.
527 * These results are suggestions for {@link TextInfo}s
528 * queried by {@link SpellCheckerSession#getSentenceSuggestions(TextInfo[], int)}.
satok0dc1f642011-11-18 11:27:10 +0900529 */
satokd404fe12012-02-22 06:38:18 +0900530 public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results);
satok988323c2011-06-22 16:38:13 +0900531 }
532
Yohei Yukawa06b4be72017-01-29 14:08:17 -0800533 private static final class InternalListener extends ITextServicesSessionListener.Stub {
Jean Chalarda80838d2011-10-20 19:33:53 +0900534 private final SpellCheckerSessionListenerImpl mParentSpellCheckerSessionListenerImpl;
535
536 public InternalListener(SpellCheckerSessionListenerImpl spellCheckerSessionListenerImpl) {
537 mParentSpellCheckerSessionListenerImpl = spellCheckerSessionListenerImpl;
538 }
539
satok988323c2011-06-22 16:38:13 +0900540 @Override
541 public void onServiceConnected(ISpellCheckerSession session) {
Jean Chalarda80838d2011-10-20 19:33:53 +0900542 mParentSpellCheckerSessionListenerImpl.onServiceConnected(session);
satok988323c2011-06-22 16:38:13 +0900543 }
544 }
545
546 @Override
547 protected void finalize() throws Throwable {
Narayan Kamath9b643672017-03-22 13:25:24 +0000548 try {
549 // Note that mGuard will be null if the constructor threw.
550 if (mGuard != null) {
551 mGuard.warnIfOpen();
552 close();
553 }
554 } finally {
555 super.finalize();
satok988323c2011-06-22 16:38:13 +0900556 }
557 }
558
559 /**
560 * @hide
561 */
562 public ITextServicesSessionListener getTextServicesSessionListener() {
563 return mInternalListener;
564 }
565
566 /**
567 * @hide
568 */
569 public ISpellCheckerSessionListener getSpellCheckerSessionListener() {
570 return mSpellCheckerSessionListenerImpl;
571 }
satok988323c2011-06-22 16:38:13 +0900572}