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