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