blob: 235b8e8c9176c6e92ef2b023e3ae2340de9806f2 [file] [log] [blame]
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -08001/*
2 * Copyright (C) 2018 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.service.textclassifier;
18
19import android.Manifest;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080020import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.annotation.SystemApi;
23import android.app.Service;
24import android.content.ComponentName;
25import android.content.Context;
26import android.content.Intent;
27import android.content.pm.PackageManager;
Makoto Onuki700feef2018-02-15 10:59:41 -080028import android.content.pm.ResolveInfo;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080029import android.content.pm.ServiceInfo;
30import android.os.CancellationSignal;
31import android.os.IBinder;
32import android.os.RemoteException;
33import android.text.TextUtils;
34import android.util.Slog;
Tony Mak0be540b2018-11-09 16:58:35 +000035import android.view.textclassifier.ConversationActions;
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +000036import android.view.textclassifier.SelectionEvent;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080037import android.view.textclassifier.TextClassification;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010038import android.view.textclassifier.TextClassificationContext;
Abodunrinwa Tokie6d974a2018-03-06 18:18:30 +000039import android.view.textclassifier.TextClassificationManager;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010040import android.view.textclassifier.TextClassificationSessionId;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080041import android.view.textclassifier.TextClassifier;
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +000042import android.view.textclassifier.TextClassifierEvent;
Tony Mak0be540b2018-11-09 16:58:35 +000043import android.view.textclassifier.TextLanguage;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080044import android.view.textclassifier.TextLinks;
45import android.view.textclassifier.TextSelection;
46
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010047import com.android.internal.util.Preconditions;
48
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080049/**
50 * Abstract base class for the TextClassifier service.
51 *
52 * <p>A TextClassifier service provides text classification related features for the system.
53 * The system's default TextClassifierService is configured in
54 * {@code config_defaultTextClassifierService}. If this config has no value, a
55 * {@link android.view.textclassifier.TextClassifierImpl} is loaded in the calling app's process.
56 *
57 * <p>See: {@link TextClassifier}.
Abodunrinwa Tokie6d974a2018-03-06 18:18:30 +000058 * See: {@link TextClassificationManager}.
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080059 *
60 * <p>Include the following in the manifest:
61 *
62 * <pre>
63 * {@literal
64 * <service android:name=".YourTextClassifierService"
65 * android:permission="android.permission.BIND_TEXTCLASSIFIER_SERVICE">
66 * <intent-filter>
67 * <action android:name="android.service.textclassifier.TextClassifierService" />
68 * </intent-filter>
69 * </service>}</pre>
70 *
71 * @see TextClassifier
72 * @hide
73 */
74@SystemApi
75public abstract class TextClassifierService extends Service {
76
77 private static final String LOG_TAG = "TextClassifierService";
78
79 /**
80 * The {@link Intent} that must be declared as handled by the service.
81 * To be supported, the service must also require the
82 * {@link android.Manifest.permission#BIND_TEXTCLASSIFIER_SERVICE} permission so
83 * that other applications can not abuse it.
84 */
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080085 public static final String SERVICE_INTERFACE =
86 "android.service.textclassifier.TextClassifierService";
87
88 private final ITextClassifierService.Stub mBinder = new ITextClassifierService.Stub() {
89
90 // TODO(b/72533911): Implement cancellation signal
91 @NonNull private final CancellationSignal mCancellationSignal = new CancellationSignal();
92
93 /** {@inheritDoc} */
94 @Override
95 public void onSuggestSelection(
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010096 TextClassificationSessionId sessionId,
Tony Mak0be540b2018-11-09 16:58:35 +000097 TextSelection.Request request, ITextSelectionCallback callback) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010098 Preconditions.checkNotNull(request);
99 Preconditions.checkNotNull(callback);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800100 TextClassifierService.this.onSuggestSelection(
Tony Mak41eaf642018-10-26 17:24:48 +0100101 sessionId, request, mCancellationSignal,
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800102 new Callback<TextSelection>() {
103 @Override
104 public void onSuccess(TextSelection result) {
105 try {
106 callback.onSuccess(result);
107 } catch (RemoteException e) {
108 Slog.d(LOG_TAG, "Error calling callback");
109 }
110 }
111
112 @Override
113 public void onFailure(CharSequence error) {
114 try {
115 if (callback.asBinder().isBinderAlive()) {
116 callback.onFailure();
117 }
118 } catch (RemoteException e) {
119 Slog.d(LOG_TAG, "Error calling callback");
120 }
121 }
122 });
123 }
124
125 /** {@inheritDoc} */
126 @Override
127 public void onClassifyText(
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100128 TextClassificationSessionId sessionId,
Tony Mak0be540b2018-11-09 16:58:35 +0000129 TextClassification.Request request, ITextClassificationCallback callback) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100130 Preconditions.checkNotNull(request);
131 Preconditions.checkNotNull(callback);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800132 TextClassifierService.this.onClassifyText(
Tony Mak41eaf642018-10-26 17:24:48 +0100133 sessionId, request, mCancellationSignal,
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800134 new Callback<TextClassification>() {
135 @Override
136 public void onSuccess(TextClassification result) {
137 try {
138 callback.onSuccess(result);
139 } catch (RemoteException e) {
140 Slog.d(LOG_TAG, "Error calling callback");
141 }
142 }
143
144 @Override
145 public void onFailure(CharSequence error) {
146 try {
147 callback.onFailure();
148 } catch (RemoteException e) {
149 Slog.d(LOG_TAG, "Error calling callback");
150 }
151 }
152 });
153 }
154
155 /** {@inheritDoc} */
156 @Override
157 public void onGenerateLinks(
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100158 TextClassificationSessionId sessionId,
Tony Mak0be540b2018-11-09 16:58:35 +0000159 TextLinks.Request request, ITextLinksCallback callback) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100160 Preconditions.checkNotNull(request);
161 Preconditions.checkNotNull(callback);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800162 TextClassifierService.this.onGenerateLinks(
Tony Mak41eaf642018-10-26 17:24:48 +0100163 sessionId, request,
Abodunrinwa Tokiae82e7a2018-04-03 23:49:16 +0100164 mCancellationSignal,
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800165 new Callback<TextLinks>() {
166 @Override
167 public void onSuccess(TextLinks result) {
168 try {
169 callback.onSuccess(result);
170 } catch (RemoteException e) {
171 Slog.d(LOG_TAG, "Error calling callback");
172 }
173 }
174
175 @Override
176 public void onFailure(CharSequence error) {
177 try {
178 callback.onFailure();
179 } catch (RemoteException e) {
180 Slog.d(LOG_TAG, "Error calling callback");
181 }
182 }
183 });
184 }
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000185
186 /** {@inheritDoc} */
187 @Override
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100188 public void onSelectionEvent(
189 TextClassificationSessionId sessionId,
Tony Mak0be540b2018-11-09 16:58:35 +0000190 SelectionEvent event) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100191 Preconditions.checkNotNull(event);
192 TextClassifierService.this.onSelectionEvent(sessionId, event);
193 }
194
195 /** {@inheritDoc} */
196 @Override
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000197 public void onTextClassifierEvent(
198 TextClassificationSessionId sessionId,
199 TextClassifierEvent event) {
200 Preconditions.checkNotNull(event);
201 TextClassifierService.this.onTextClassifierEvent(sessionId, event);
202 }
203
204 /** {@inheritDoc} */
205 @Override
Tony Mak0be540b2018-11-09 16:58:35 +0000206 public void onDetectLanguage(
207 TextClassificationSessionId sessionId,
208 TextLanguage.Request request,
209 ITextLanguageCallback callback) {
210 Preconditions.checkNotNull(request);
211 Preconditions.checkNotNull(callback);
212 TextClassifierService.this.onDetectLanguage(
213 sessionId,
214 request,
215 mCancellationSignal,
216 new Callback<TextLanguage>() {
217 @Override
218 public void onSuccess(TextLanguage result) {
219 try {
220 callback.onSuccess(result);
221 } catch (RemoteException e) {
222 Slog.d(LOG_TAG, "Error calling callback");
223 }
224 }
225
226 @Override
227 public void onFailure(CharSequence error) {
228 try {
229 callback.onFailure();
230 } catch (RemoteException e) {
231 Slog.d(LOG_TAG, "Error calling callback");
232 }
233 };
234 });
235 }
236
237 /** {@inheritDoc} */
238 @Override
239 public void onSuggestConversationActions(
240 TextClassificationSessionId sessionId,
241 ConversationActions.Request request,
242 IConversationActionsCallback callback) {
243 Preconditions.checkNotNull(request);
244 Preconditions.checkNotNull(callback);
245 TextClassifierService.this.onSuggestConversationActions(
246 sessionId,
247 request,
248 mCancellationSignal,
249 new Callback<ConversationActions>() {
250 @Override
251 public void onSuccess(ConversationActions result) {
252 try {
253 callback.onSuccess(result);
254 } catch (RemoteException e) {
255 Slog.d(LOG_TAG, "Error calling callback");
256 }
257 }
258
259 @Override
260 public void onFailure(CharSequence error) {
261 try {
262 callback.onFailure();
263 } catch (RemoteException e) {
264 Slog.d(LOG_TAG, "Error calling callback");
265 }
266 }
267 });
268 }
269
270 /** {@inheritDoc} */
271 @Override
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100272 public void onCreateTextClassificationSession(
Tony Mak0be540b2018-11-09 16:58:35 +0000273 TextClassificationContext context, TextClassificationSessionId sessionId) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100274 Preconditions.checkNotNull(context);
275 Preconditions.checkNotNull(sessionId);
276 TextClassifierService.this.onCreateTextClassificationSession(context, sessionId);
277 }
278
279 /** {@inheritDoc} */
280 @Override
Tony Mak0be540b2018-11-09 16:58:35 +0000281 public void onDestroyTextClassificationSession(TextClassificationSessionId sessionId) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100282 TextClassifierService.this.onDestroyTextClassificationSession(sessionId);
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000283 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800284 };
285
286 @Nullable
287 @Override
288 public final IBinder onBind(Intent intent) {
289 if (SERVICE_INTERFACE.equals(intent.getAction())) {
290 return mBinder;
291 }
292 return null;
293 }
294
295 /**
296 * Returns suggested text selection start and end indices, recognized entity types, and their
297 * associated confidence scores. The entity types are ordered from highest to lowest scoring.
298 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100299 * @param sessionId the session id
300 * @param request the text selection request
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800301 * @param cancellationSignal object to watch for canceling the current operation
302 * @param callback the callback to return the result to
303 */
304 public abstract void onSuggestSelection(
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100305 @Nullable TextClassificationSessionId sessionId,
306 @NonNull TextSelection.Request request,
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800307 @NonNull CancellationSignal cancellationSignal,
308 @NonNull Callback<TextSelection> callback);
309
310 /**
311 * Classifies the specified text and returns a {@link TextClassification} object that can be
312 * used to generate a widget for handling the classified text.
313 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100314 * @param sessionId the session id
315 * @param request the text classification request
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800316 * @param cancellationSignal object to watch for canceling the current operation
317 * @param callback the callback to return the result to
318 */
319 public abstract void onClassifyText(
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100320 @Nullable TextClassificationSessionId sessionId,
321 @NonNull TextClassification.Request request,
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800322 @NonNull CancellationSignal cancellationSignal,
323 @NonNull Callback<TextClassification> callback);
324
325 /**
326 * Generates and returns a {@link TextLinks} that may be applied to the text to annotate it with
327 * links information.
328 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100329 * @param sessionId the session id
330 * @param request the text classification request
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800331 * @param cancellationSignal object to watch for canceling the current operation
332 * @param callback the callback to return the result to
333 */
334 public abstract void onGenerateLinks(
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100335 @Nullable TextClassificationSessionId sessionId,
336 @NonNull TextLinks.Request request,
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800337 @NonNull CancellationSignal cancellationSignal,
338 @NonNull Callback<TextLinks> callback);
339
340 /**
Tony Mak0be540b2018-11-09 16:58:35 +0000341 * Detects and returns the language of the give text.
342 *
343 * @param sessionId the session id
344 * @param request the language detection request
345 * @param cancellationSignal object to watch for canceling the current operation
346 * @param callback the callback to return the result to
347 */
348 public void onDetectLanguage(
349 @Nullable TextClassificationSessionId sessionId,
350 @NonNull TextLanguage.Request request,
351 @NonNull CancellationSignal cancellationSignal,
352 @NonNull Callback<TextLanguage> callback) {
353 callback.onSuccess(getLocalTextClassifier().detectLanguage(request));
354 }
355
356 /**
357 * Suggests and returns a list of actions according to the given conversation.
358 *
359 * @param sessionId the session id
360 * @param request the conversation actions request
361 * @param cancellationSignal object to watch for canceling the current operation
362 * @param callback the callback to return the result to
363 */
364 public void onSuggestConversationActions(
365 @Nullable TextClassificationSessionId sessionId,
366 @NonNull ConversationActions.Request request,
367 @NonNull CancellationSignal cancellationSignal,
368 @NonNull Callback<ConversationActions> callback) {
369 callback.onSuccess(getLocalTextClassifier().suggestConversationActions(request));
370 }
371
372 /**
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000373 * Writes the selection event.
374 * This is called when a selection event occurs. e.g. user changed selection; or smart selection
375 * happened.
376 *
377 * <p>The default implementation ignores the event.
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100378 *
379 * @param sessionId the session id
380 * @param event the selection event
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000381 * @deprecated
382 * Use {@link #onTextClassifierEvent(TextClassificationSessionId, TextClassifierEvent)}
383 * instead
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000384 */
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000385 @Deprecated
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100386 public void onSelectionEvent(
387 @Nullable TextClassificationSessionId sessionId, @NonNull SelectionEvent event) {}
388
389 /**
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000390 * Writes the TextClassifier event.
391 * This is called when a TextClassifier event occurs. e.g. user changed selection,
392 * smart selection happened, or a link was clicked.
393 *
394 * <p>The default implementation ignores the event.
395 *
396 * @param sessionId the session id
397 * @param event the TextClassifier event
398 */
399 public void onTextClassifierEvent(
400 @Nullable TextClassificationSessionId sessionId, @NonNull TextClassifierEvent event) {}
401
402 /**
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100403 * Creates a new text classification session for the specified context.
404 *
405 * @param context the text classification context
406 * @param sessionId the session's Id
407 */
Jan Althaus39ccc7e2018-04-04 13:56:40 +0200408 public void onCreateTextClassificationSession(
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100409 @NonNull TextClassificationContext context,
Jan Althaus39ccc7e2018-04-04 13:56:40 +0200410 @NonNull TextClassificationSessionId sessionId) {}
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100411
412 /**
413 * Destroys the text classification session identified by the specified sessionId.
414 *
415 * @param sessionId the id of the session to destroy
416 */
Jan Althaus39ccc7e2018-04-04 13:56:40 +0200417 public void onDestroyTextClassificationSession(
418 @NonNull TextClassificationSessionId sessionId) {}
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000419
420 /**
Abodunrinwa Tokie6d974a2018-03-06 18:18:30 +0000421 * Returns a TextClassifier that runs in this service's process.
422 * If the local TextClassifier is disabled, this returns {@link TextClassifier#NO_OP}.
Abodunrinwa Toki324572e2019-02-12 19:01:01 +0000423 *
424 * @deprecated Use {@link #getDefaultTextClassifierImplementation(Context)} instead.
Abodunrinwa Tokie6d974a2018-03-06 18:18:30 +0000425 */
Abodunrinwa Toki324572e2019-02-12 19:01:01 +0000426 @Deprecated
Abodunrinwa Tokie6d974a2018-03-06 18:18:30 +0000427 public final TextClassifier getLocalTextClassifier() {
Abodunrinwa Toki324572e2019-02-12 19:01:01 +0000428 // Deprecated: In the future, we may not guarantee that this runs in the service's process.
429 return getDefaultTextClassifierImplementation(this);
430 }
431
432 /**
433 * Returns the platform's default TextClassifier implementation.
434 */
435 public static TextClassifier getDefaultTextClassifierImplementation(@NonNull Context context) {
436 final TextClassificationManager tcm =
437 context.getSystemService(TextClassificationManager.class);
Abodunrinwa Tokie6d974a2018-03-06 18:18:30 +0000438 if (tcm != null) {
439 return tcm.getTextClassifier(TextClassifier.LOCAL);
440 }
441 return TextClassifier.NO_OP;
442 }
443
444 /**
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800445 * Callbacks for TextClassifierService results.
446 *
447 * @param <T> the type of the result
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800448 */
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800449 public interface Callback<T> {
450 /**
451 * Returns the result.
452 */
453 void onSuccess(T result);
454
455 /**
456 * Signals a failure.
457 */
458 void onFailure(CharSequence error);
459 }
460
461 /**
462 * Returns the component name of the system default textclassifier service if it can be found
463 * on the system. Otherwise, returns null.
464 * @hide
465 */
466 @Nullable
467 public static ComponentName getServiceComponentName(Context context) {
Makoto Onuki700feef2018-02-15 10:59:41 -0800468 final String packageName = context.getPackageManager().getSystemTextClassifierPackageName();
469 if (TextUtils.isEmpty(packageName)) {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800470 Slog.d(LOG_TAG, "No configured system TextClassifierService");
Makoto Onuki700feef2018-02-15 10:59:41 -0800471 return null;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800472 }
Makoto Onuki700feef2018-02-15 10:59:41 -0800473
474 final Intent intent = new Intent(SERVICE_INTERFACE).setPackage(packageName);
475
476 final ResolveInfo ri = context.getPackageManager().resolveService(intent,
477 PackageManager.MATCH_SYSTEM_ONLY);
478
479 if ((ri == null) || (ri.serviceInfo == null)) {
Felipe Lemef8192132018-10-12 13:42:40 -0700480 Slog.w(LOG_TAG, String.format("Package or service not found in package %s for user %d",
481 packageName, context.getUserId()));
Makoto Onuki700feef2018-02-15 10:59:41 -0800482 return null;
483 }
484 final ServiceInfo si = ri.serviceInfo;
485
486 final String permission = si.permission;
487 if (Manifest.permission.BIND_TEXTCLASSIFIER_SERVICE.equals(permission)) {
488 return si.getComponentName();
489 }
490 Slog.w(LOG_TAG, String.format(
491 "Service %s should require %s permission. Found %s permission",
492 si.getComponentName(),
493 Manifest.permission.BIND_TEXTCLASSIFIER_SERVICE,
494 si.permission));
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800495 return null;
496 }
497}