File: TextClassifierService.java

package info (click to toggle)
android-platform-frameworks-base 1%3A10.0.0%2Br36-3
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 321,788 kB
  • sloc: java: 962,234; cpp: 274,314; xml: 242,770; python: 5,060; sh: 1,432; ansic: 494; makefile: 47; sed: 19
file content (459 lines) | stat: -rw-r--r-- 17,963 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.service.textclassifier;

import android.Manifest;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcelable;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Slog;
import android.view.textclassifier.ConversationActions;
import android.view.textclassifier.SelectionEvent;
import android.view.textclassifier.TextClassification;
import android.view.textclassifier.TextClassificationContext;
import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassificationSessionId;
import android.view.textclassifier.TextClassifier;
import android.view.textclassifier.TextClassifierEvent;
import android.view.textclassifier.TextLanguage;
import android.view.textclassifier.TextLinks;
import android.view.textclassifier.TextSelection;

import com.android.internal.util.Preconditions;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Abstract base class for the TextClassifier service.
 *
 * <p>A TextClassifier service provides text classification related features for the system.
 * The system's default TextClassifierService is configured in
 * {@code config_defaultTextClassifierService}. If this config has no value, a
 * {@link android.view.textclassifier.TextClassifierImpl} is loaded in the calling app's process.
 *
 * <p>See: {@link TextClassifier}.
 * See: {@link TextClassificationManager}.
 *
 * <p>Include the following in the manifest:
 *
 * <pre>
 * {@literal
 * <service android:name=".YourTextClassifierService"
 *          android:permission="android.permission.BIND_TEXTCLASSIFIER_SERVICE">
 *     <intent-filter>
 *         <action android:name="android.service.textclassifier.TextClassifierService" />
 *     </intent-filter>
 * </service>}</pre>
 *
 * <p>From {@link android.os.Build.VERSION_CODES#Q} onward, all callbacks are called on the main
 * thread. Prior to Q, there is no guarantee on what thread the callback will happen. You should
 * make sure the callbacks are executed in your desired thread by using a executor, a handler or
 * something else along the line.
 *
 * @see TextClassifier
 * @hide
 */
@SystemApi
public abstract class TextClassifierService extends Service {

    private static final String LOG_TAG = "TextClassifierService";

    /**
     * The {@link Intent} that must be declared as handled by the service.
     * To be supported, the service must also require the
     * {@link android.Manifest.permission#BIND_TEXTCLASSIFIER_SERVICE} permission so
     * that other applications can not abuse it.
     */
    public static final String SERVICE_INTERFACE =
            "android.service.textclassifier.TextClassifierService";

    /** @hide **/
    private static final String KEY_RESULT = "key_result";

    private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper(), null, true);
    private final ExecutorService mSingleThreadExecutor = Executors.newSingleThreadExecutor();

    private final ITextClassifierService.Stub mBinder = new ITextClassifierService.Stub() {

        // TODO(b/72533911): Implement cancellation signal
        @NonNull private final CancellationSignal mCancellationSignal = new CancellationSignal();

        @Override
        public void onSuggestSelection(
                TextClassificationSessionId sessionId,
                TextSelection.Request request, ITextClassifierCallback callback) {
            Preconditions.checkNotNull(request);
            Preconditions.checkNotNull(callback);
            mMainThreadHandler.post(() -> TextClassifierService.this.onSuggestSelection(
                    sessionId, request, mCancellationSignal, new ProxyCallback<>(callback)));

        }

        @Override
        public void onClassifyText(
                TextClassificationSessionId sessionId,
                TextClassification.Request request, ITextClassifierCallback callback) {
            Preconditions.checkNotNull(request);
            Preconditions.checkNotNull(callback);
            mMainThreadHandler.post(() -> TextClassifierService.this.onClassifyText(
                    sessionId, request, mCancellationSignal, new ProxyCallback<>(callback)));
        }

        @Override
        public void onGenerateLinks(
                TextClassificationSessionId sessionId,
                TextLinks.Request request, ITextClassifierCallback callback) {
            Preconditions.checkNotNull(request);
            Preconditions.checkNotNull(callback);
            mMainThreadHandler.post(() -> TextClassifierService.this.onGenerateLinks(
                    sessionId, request, mCancellationSignal, new ProxyCallback<>(callback)));
        }

        @Override
        public void onSelectionEvent(
                TextClassificationSessionId sessionId,
                SelectionEvent event) {
            Preconditions.checkNotNull(event);
            mMainThreadHandler.post(
                    () -> TextClassifierService.this.onSelectionEvent(sessionId, event));
        }

        @Override
        public void onTextClassifierEvent(
                TextClassificationSessionId sessionId,
                TextClassifierEvent event) {
            Preconditions.checkNotNull(event);
            mMainThreadHandler.post(
                    () -> TextClassifierService.this.onTextClassifierEvent(sessionId, event));
        }

        @Override
        public void onDetectLanguage(
                TextClassificationSessionId sessionId,
                TextLanguage.Request request,
                ITextClassifierCallback callback) {
            Preconditions.checkNotNull(request);
            Preconditions.checkNotNull(callback);
            mMainThreadHandler.post(() -> TextClassifierService.this.onDetectLanguage(
                    sessionId, request, mCancellationSignal, new ProxyCallback<>(callback)));
        }

        @Override
        public void onSuggestConversationActions(
                TextClassificationSessionId sessionId,
                ConversationActions.Request request,
                ITextClassifierCallback callback) {
            Preconditions.checkNotNull(request);
            Preconditions.checkNotNull(callback);
            mMainThreadHandler.post(() -> TextClassifierService.this.onSuggestConversationActions(
                    sessionId, request, mCancellationSignal, new ProxyCallback<>(callback)));
        }

        @Override
        public void onCreateTextClassificationSession(
                TextClassificationContext context, TextClassificationSessionId sessionId) {
            Preconditions.checkNotNull(context);
            Preconditions.checkNotNull(sessionId);
            mMainThreadHandler.post(
                    () -> TextClassifierService.this.onCreateTextClassificationSession(
                            context, sessionId));
        }

        @Override
        public void onDestroyTextClassificationSession(TextClassificationSessionId sessionId) {
            mMainThreadHandler.post(
                    () -> TextClassifierService.this.onDestroyTextClassificationSession(sessionId));
        }
    };

    @Nullable
    @Override
    public final IBinder onBind(Intent intent) {
        if (SERVICE_INTERFACE.equals(intent.getAction())) {
            return mBinder;
        }
        return null;
    }

    /**
     * Returns suggested text selection start and end indices, recognized entity types, and their
     * associated confidence scores. The entity types are ordered from highest to lowest scoring.
     *
     * @param sessionId the session id
     * @param request the text selection request
     * @param cancellationSignal object to watch for canceling the current operation
     * @param callback the callback to return the result to
     */
    @MainThread
    public abstract void onSuggestSelection(
            @Nullable TextClassificationSessionId sessionId,
            @NonNull TextSelection.Request request,
            @NonNull CancellationSignal cancellationSignal,
            @NonNull Callback<TextSelection> callback);

    /**
     * Classifies the specified text and returns a {@link TextClassification} object that can be
     * used to generate a widget for handling the classified text.
     *
     * @param sessionId the session id
     * @param request the text classification request
     * @param cancellationSignal object to watch for canceling the current operation
     * @param callback the callback to return the result to
     */
    @MainThread
    public abstract void onClassifyText(
            @Nullable TextClassificationSessionId sessionId,
            @NonNull TextClassification.Request request,
            @NonNull CancellationSignal cancellationSignal,
            @NonNull Callback<TextClassification> callback);

    /**
     * Generates and returns a {@link TextLinks} that may be applied to the text to annotate it with
     * links information.
     *
     * @param sessionId the session id
     * @param request the text classification request
     * @param cancellationSignal object to watch for canceling the current operation
     * @param callback the callback to return the result to
     */
    @MainThread
    public abstract void onGenerateLinks(
            @Nullable TextClassificationSessionId sessionId,
            @NonNull TextLinks.Request request,
            @NonNull CancellationSignal cancellationSignal,
            @NonNull Callback<TextLinks> callback);

    /**
     * Detects and returns the language of the give text.
     *
     * @param sessionId the session id
     * @param request the language detection request
     * @param cancellationSignal object to watch for canceling the current operation
     * @param callback the callback to return the result to
     */
    @MainThread
    public void onDetectLanguage(
            @Nullable TextClassificationSessionId sessionId,
            @NonNull TextLanguage.Request request,
            @NonNull CancellationSignal cancellationSignal,
            @NonNull Callback<TextLanguage> callback) {
        mSingleThreadExecutor.submit(() ->
                callback.onSuccess(getLocalTextClassifier().detectLanguage(request)));
    }

    /**
     * Suggests and returns a list of actions according to the given conversation.
     *
     * @param sessionId the session id
     * @param request the conversation actions request
     * @param cancellationSignal object to watch for canceling the current operation
     * @param callback the callback to return the result to
     */
    @MainThread
    public void onSuggestConversationActions(
            @Nullable TextClassificationSessionId sessionId,
            @NonNull ConversationActions.Request request,
            @NonNull CancellationSignal cancellationSignal,
            @NonNull Callback<ConversationActions> callback) {
        mSingleThreadExecutor.submit(() ->
                callback.onSuccess(getLocalTextClassifier().suggestConversationActions(request)));
    }

    /**
     * Writes the selection event.
     * This is called when a selection event occurs. e.g. user changed selection; or smart selection
     * happened.
     *
     * <p>The default implementation ignores the event.
     *
     * @param sessionId the session id
     * @param event the selection event
     * @deprecated
     *      Use {@link #onTextClassifierEvent(TextClassificationSessionId, TextClassifierEvent)}
     *      instead
     */
    @Deprecated
    @MainThread
    public void onSelectionEvent(
            @Nullable TextClassificationSessionId sessionId, @NonNull SelectionEvent event) {}

    /**
     * Writes the TextClassifier event.
     * This is called when a TextClassifier event occurs. e.g. user changed selection,
     * smart selection happened, or a link was clicked.
     *
     * <p>The default implementation ignores the event.
     *
     * @param sessionId the session id
     * @param event the TextClassifier event
     */
    @MainThread
    public void onTextClassifierEvent(
            @Nullable TextClassificationSessionId sessionId, @NonNull TextClassifierEvent event) {}

    /**
     * Creates a new text classification session for the specified context.
     *
     * @param context the text classification context
     * @param sessionId the session's Id
     */
    @MainThread
    public void onCreateTextClassificationSession(
            @NonNull TextClassificationContext context,
            @NonNull TextClassificationSessionId sessionId) {}

    /**
     * Destroys the text classification session identified by the specified sessionId.
     *
     * @param sessionId the id of the session to destroy
     */
    @MainThread
    public void onDestroyTextClassificationSession(
            @NonNull TextClassificationSessionId sessionId) {}

    /**
     * Returns a TextClassifier that runs in this service's process.
     * If the local TextClassifier is disabled, this returns {@link TextClassifier#NO_OP}.
     *
     * @deprecated Use {@link #getDefaultTextClassifierImplementation(Context)} instead.
     */
    @Deprecated
    public final TextClassifier getLocalTextClassifier() {
        // Deprecated: In the future, we may not guarantee that this runs in the service's process.
        return getDefaultTextClassifierImplementation(this);
    }

    /**
     * Returns the platform's default TextClassifier implementation.
     */
    @NonNull
    public static TextClassifier getDefaultTextClassifierImplementation(@NonNull Context context) {
        final TextClassificationManager tcm =
                context.getSystemService(TextClassificationManager.class);
        if (tcm != null) {
            return tcm.getTextClassifier(TextClassifier.LOCAL);
        }
        return TextClassifier.NO_OP;
    }

    /** @hide **/
    public static <T extends Parcelable> T getResponse(Bundle bundle) {
        return bundle.getParcelable(KEY_RESULT);
    }

    /**
     * Callbacks for TextClassifierService results.
     *
     * @param <T> the type of the result
     */
    public interface Callback<T> {
        /**
         * Returns the result.
         */
        void onSuccess(T result);

        /**
         * Signals a failure.
         */
        void onFailure(CharSequence error);
    }

    /**
     * Returns the component name of the system default textclassifier service if it can be found
     * on the system. Otherwise, returns null.
     * @hide
     */
    @Nullable
    public static ComponentName getServiceComponentName(Context context) {
        final String packageName = context.getPackageManager().getSystemTextClassifierPackageName();
        if (TextUtils.isEmpty(packageName)) {
            Slog.d(LOG_TAG, "No configured system TextClassifierService");
            return null;
        }

        final Intent intent = new Intent(SERVICE_INTERFACE).setPackage(packageName);

        final ResolveInfo ri = context.getPackageManager().resolveService(intent,
                PackageManager.MATCH_SYSTEM_ONLY);

        if ((ri == null) || (ri.serviceInfo == null)) {
            Slog.w(LOG_TAG, String.format("Package or service not found in package %s for user %d",
                    packageName, context.getUserId()));
            return null;
        }
        final ServiceInfo si = ri.serviceInfo;

        final String permission = si.permission;
        if (Manifest.permission.BIND_TEXTCLASSIFIER_SERVICE.equals(permission)) {
            return si.getComponentName();
        }
        Slog.w(LOG_TAG, String.format(
                "Service %s should require %s permission. Found %s permission",
                si.getComponentName(),
                Manifest.permission.BIND_TEXTCLASSIFIER_SERVICE,
                si.permission));
        return null;
    }

    /**
     * Forwards the callback result to a wrapped binder callback.
     */
    private static final class ProxyCallback<T extends Parcelable> implements Callback<T> {
        private ITextClassifierCallback mTextClassifierCallback;

        private ProxyCallback(ITextClassifierCallback textClassifierCallback) {
            mTextClassifierCallback = Preconditions.checkNotNull(textClassifierCallback);
        }

        @Override
        public void onSuccess(T result) {
            try {
                Bundle bundle = new Bundle(1);
                bundle.putParcelable(KEY_RESULT, result);
                mTextClassifierCallback.onSuccess(bundle);
            } catch (RemoteException e) {
                Slog.d(LOG_TAG, "Error calling callback");
            }
        }

        @Override
        public void onFailure(CharSequence error) {
            try {
                mTextClassifierCallback.onFailure();
            } catch (RemoteException e) {
                Slog.d(LOG_TAG, "Error calling callback");
            }
        }
    }
}