TextClassifierService.java revision ad52f4b97c897689d0b4dbfe344229a9970136eb
1/*
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;
20import android.annotation.IntRange;
21import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.annotation.SystemApi;
24import android.app.Service;
25import android.content.ComponentName;
26import android.content.Context;
27import android.content.Intent;
28import android.content.pm.PackageManager;
29import android.content.pm.ResolveInfo;
30import android.content.pm.ServiceInfo;
31import android.os.CancellationSignal;
32import android.os.IBinder;
33import android.os.RemoteException;
34import android.text.TextUtils;
35import android.util.Slog;
36import android.view.textclassifier.SelectionEvent;
37import android.view.textclassifier.TextClassification;
38import android.view.textclassifier.TextClassificationManager;
39import android.view.textclassifier.TextClassifier;
40import android.view.textclassifier.TextLinks;
41import android.view.textclassifier.TextSelection;
42
43/**
44 * Abstract base class for the TextClassifier service.
45 *
46 * <p>A TextClassifier service provides text classification related features for the system.
47 * The system's default TextClassifierService is configured in
48 * {@code config_defaultTextClassifierService}. If this config has no value, a
49 * {@link android.view.textclassifier.TextClassifierImpl} is loaded in the calling app's process.
50 *
51 * <p>See: {@link TextClassifier}.
52 * See: {@link TextClassificationManager}.
53 *
54 * <p>Include the following in the manifest:
55 *
56 * <pre>
57 * {@literal
58 * <service android:name=".YourTextClassifierService"
59 *          android:permission="android.permission.BIND_TEXTCLASSIFIER_SERVICE">
60 *     <intent-filter>
61 *         <action android:name="android.service.textclassifier.TextClassifierService" />
62 *     </intent-filter>
63 * </service>}</pre>
64 *
65 * @see TextClassifier
66 * @hide
67 */
68@SystemApi
69public abstract class TextClassifierService extends Service {
70
71    private static final String LOG_TAG = "TextClassifierService";
72
73    /**
74     * The {@link Intent} that must be declared as handled by the service.
75     * To be supported, the service must also require the
76     * {@link android.Manifest.permission#BIND_TEXTCLASSIFIER_SERVICE} permission so
77     * that other applications can not abuse it.
78     */
79    @SystemApi
80    public static final String SERVICE_INTERFACE =
81            "android.service.textclassifier.TextClassifierService";
82
83    private final ITextClassifierService.Stub mBinder = new ITextClassifierService.Stub() {
84
85        // TODO(b/72533911): Implement cancellation signal
86        @NonNull private final CancellationSignal mCancellationSignal = new CancellationSignal();
87
88        /** {@inheritDoc} */
89        @Override
90        public void onSuggestSelection(
91                CharSequence text, int selectionStartIndex, int selectionEndIndex,
92                TextSelection.Options options, ITextSelectionCallback callback)
93                throws RemoteException {
94            TextClassifierService.this.onSuggestSelection(
95                    text, selectionStartIndex, selectionEndIndex, options, mCancellationSignal,
96                    new Callback<TextSelection>() {
97                        @Override
98                        public void onSuccess(TextSelection result) {
99                            try {
100                                callback.onSuccess(result);
101                            } catch (RemoteException e) {
102                                Slog.d(LOG_TAG, "Error calling callback");
103                            }
104                        }
105
106                        @Override
107                        public void onFailure(CharSequence error) {
108                            try {
109                                if (callback.asBinder().isBinderAlive()) {
110                                    callback.onFailure();
111                                }
112                            } catch (RemoteException e) {
113                                Slog.d(LOG_TAG, "Error calling callback");
114                            }
115                        }
116                    });
117        }
118
119        /** {@inheritDoc} */
120        @Override
121        public void onClassifyText(
122                CharSequence text, int startIndex, int endIndex,
123                TextClassification.Options options, ITextClassificationCallback callback)
124                throws RemoteException {
125            TextClassifierService.this.onClassifyText(
126                    text, startIndex, endIndex, options, mCancellationSignal,
127                    new Callback<TextClassification>() {
128                        @Override
129                        public void onSuccess(TextClassification result) {
130                            try {
131                                callback.onSuccess(result);
132                            } catch (RemoteException e) {
133                                Slog.d(LOG_TAG, "Error calling callback");
134                            }
135                        }
136
137                        @Override
138                        public void onFailure(CharSequence error) {
139                            try {
140                                callback.onFailure();
141                            } catch (RemoteException e) {
142                                Slog.d(LOG_TAG, "Error calling callback");
143                            }
144                        }
145                    });
146        }
147
148        /** {@inheritDoc} */
149        @Override
150        public void onGenerateLinks(
151                CharSequence text, TextLinks.Options options, ITextLinksCallback callback)
152                throws RemoteException {
153            TextClassifierService.this.onGenerateLinks(
154                    text, options, mCancellationSignal,
155                    new Callback<TextLinks>() {
156                        @Override
157                        public void onSuccess(TextLinks result) {
158                            try {
159                                callback.onSuccess(result);
160                            } catch (RemoteException e) {
161                                Slog.d(LOG_TAG, "Error calling callback");
162                            }
163                        }
164
165                        @Override
166                        public void onFailure(CharSequence error) {
167                            try {
168                                callback.onFailure();
169                            } catch (RemoteException e) {
170                                Slog.d(LOG_TAG, "Error calling callback");
171                            }
172                        }
173                    });
174        }
175
176        /** {@inheritDoc} */
177        @Override
178        public void onSelectionEvent(SelectionEvent event) throws RemoteException {
179            TextClassifierService.this.onSelectionEvent(event);
180        }
181    };
182
183    @Nullable
184    @Override
185    public final IBinder onBind(Intent intent) {
186        if (SERVICE_INTERFACE.equals(intent.getAction())) {
187            return mBinder;
188        }
189        return null;
190    }
191
192    /**
193     * Returns suggested text selection start and end indices, recognized entity types, and their
194     * associated confidence scores. The entity types are ordered from highest to lowest scoring.
195     *
196     * @param text text providing context for the selected text (which is specified
197     *      by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
198     * @param selectionStartIndex start index of the selected part of text
199     * @param selectionEndIndex end index of the selected part of text
200     * @param options optional input parameters
201     * @param cancellationSignal object to watch for canceling the current operation
202     * @param callback the callback to return the result to
203     */
204    public abstract void onSuggestSelection(
205            @NonNull CharSequence text,
206            @IntRange(from = 0) int selectionStartIndex,
207            @IntRange(from = 0) int selectionEndIndex,
208            @Nullable TextSelection.Options options,
209            @NonNull CancellationSignal cancellationSignal,
210            @NonNull Callback<TextSelection> callback);
211
212    /**
213     * Classifies the specified text and returns a {@link TextClassification} object that can be
214     * used to generate a widget for handling the classified text.
215     *
216     * @param text text providing context for the text to classify (which is specified
217     *      by the sub sequence starting at startIndex and ending at endIndex)
218     * @param startIndex start index of the text to classify
219     * @param endIndex end index of the text to classify
220     * @param options optional input parameters
221     * @param cancellationSignal object to watch for canceling the current operation
222     * @param callback the callback to return the result to
223     */
224    public abstract void onClassifyText(
225            @NonNull CharSequence text,
226            @IntRange(from = 0) int startIndex,
227            @IntRange(from = 0) int endIndex,
228            @Nullable TextClassification.Options options,
229            @NonNull CancellationSignal cancellationSignal,
230            @NonNull Callback<TextClassification> callback);
231
232    /**
233     * Generates and returns a {@link TextLinks} that may be applied to the text to annotate it with
234     * links information.
235     *
236     * @param text the text to generate annotations for
237     * @param options configuration for link generation
238     * @param cancellationSignal object to watch for canceling the current operation
239     * @param callback the callback to return the result to
240     */
241    public abstract void onGenerateLinks(
242            @NonNull CharSequence text,
243            @Nullable TextLinks.Options options,
244            @NonNull CancellationSignal cancellationSignal,
245            @NonNull Callback<TextLinks> callback);
246
247    /**
248     * Writes the selection event.
249     * This is called when a selection event occurs. e.g. user changed selection; or smart selection
250     * happened.
251     *
252     * <p>The default implementation ignores the event.
253     */
254    public void onSelectionEvent(@NonNull SelectionEvent event) {}
255
256    /**
257     * Returns a TextClassifier that runs in this service's process.
258     * If the local TextClassifier is disabled, this returns {@link TextClassifier#NO_OP}.
259     */
260    public final TextClassifier getLocalTextClassifier() {
261        final TextClassificationManager tcm = getSystemService(TextClassificationManager.class);
262        if (tcm != null) {
263            return tcm.getTextClassifier(TextClassifier.LOCAL);
264        }
265        return TextClassifier.NO_OP;
266    }
267
268    /**
269     * Callbacks for TextClassifierService results.
270     *
271     * @param <T> the type of the result
272     * @hide
273     */
274    @SystemApi
275    public interface Callback<T> {
276        /**
277         * Returns the result.
278         */
279        void onSuccess(T result);
280
281        /**
282         * Signals a failure.
283         */
284        void onFailure(CharSequence error);
285    }
286
287    /**
288     * Returns the component name of the system default textclassifier service if it can be found
289     * on the system. Otherwise, returns null.
290     * @hide
291     */
292    @Nullable
293    public static ComponentName getServiceComponentName(Context context) {
294        final String packageName = context.getPackageManager().getSystemTextClassifierPackageName();
295        if (TextUtils.isEmpty(packageName)) {
296            Slog.d(LOG_TAG, "No configured system TextClassifierService");
297            return null;
298        }
299
300        final Intent intent = new Intent(SERVICE_INTERFACE).setPackage(packageName);
301
302        final ResolveInfo ri = context.getPackageManager().resolveService(intent,
303                PackageManager.MATCH_SYSTEM_ONLY);
304
305        if ((ri == null) || (ri.serviceInfo == null)) {
306            Slog.w(LOG_TAG, String.format("Package or service not found in package %s",
307                    packageName));
308            return null;
309        }
310        final ServiceInfo si = ri.serviceInfo;
311
312        final String permission = si.permission;
313        if (Manifest.permission.BIND_TEXTCLASSIFIER_SERVICE.equals(permission)) {
314            return si.getComponentName();
315        }
316        Slog.w(LOG_TAG, String.format(
317                "Service %s should require %s permission. Found %s permission",
318                si.getComponentName(),
319                Manifest.permission.BIND_TEXTCLASSIFIER_SERVICE,
320                si.permission));
321        return null;
322    }
323}
324