blob: 86ef4e103990798ea29bdd386bc02aff6adf3e4e [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.view.textclassifier;
18
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080019import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.annotation.WorkerThread;
22import android.content.Context;
Tony Mak9920dbb2019-01-23 19:49:30 +000023import android.os.Bundle;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080024import android.os.Looper;
Tony Mak9920dbb2019-01-23 19:49:30 +000025import android.os.Parcelable;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080026import android.os.RemoteException;
27import android.os.ServiceManager;
Tony Mak9920dbb2019-01-23 19:49:30 +000028import android.service.textclassifier.ITextClassifierCallback;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080029import android.service.textclassifier.ITextClassifierService;
Tony Mak9920dbb2019-01-23 19:49:30 +000030import android.service.textclassifier.TextClassifierService;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080031
Abodunrinwa Toki65638332018-03-16 21:08:50 +000032import com.android.internal.annotations.VisibleForTesting;
33import com.android.internal.annotations.VisibleForTesting.Visibility;
Tony Makf93e9e52018-07-16 14:46:29 +020034import com.android.internal.util.IndentingPrintWriter;
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +000035
Daulet Zhanguzine1559472019-12-18 14:17:56 +000036import java.util.Objects;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080037import java.util.concurrent.CountDownLatch;
38import java.util.concurrent.TimeUnit;
39
40/**
Joanne Chung97d3a452020-03-04 19:03:11 +080041 * proxy to the request to TextClassifierService via the TextClassificationManagerService.
42 *
Abodunrinwa Toki65638332018-03-16 21:08:50 +000043 * @hide
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080044 */
Abodunrinwa Toki65638332018-03-16 21:08:50 +000045@VisibleForTesting(visibility = Visibility.PACKAGE)
46public final class SystemTextClassifier implements TextClassifier {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080047
48 private static final String LOG_TAG = "SystemTextClassifier";
49
50 private final ITextClassifierService mManagerService;
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +000051 private final TextClassificationConstants mSettings;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080052 private final TextClassifier mFallback;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010053 private TextClassificationSessionId mSessionId;
Joanne Chung97d3a452020-03-04 19:03:11 +080054 // NOTE: Always set this before sending a request to the manager service otherwise the
55 // manager service will throw a remote exception.
56 @NonNull
57 private final SystemTextClassifierMetadata mSystemTcMetadata;
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +000058
Joanne Chung97d3a452020-03-04 19:03:11 +080059 /**
60 * Constructor of {@link SystemTextClassifier}
61 *
62 * @param context the context of the request.
63 * @param settings TextClassifier specific settings.
64 * @param useDefault whether to use the default text classifier to handle this request
65 */
Tony Makc5a74322020-02-04 17:18:15 +000066 public SystemTextClassifier(
67 Context context,
68 TextClassificationConstants settings,
69 boolean useDefault) throws ServiceManager.ServiceNotFoundException {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080070 mManagerService = ITextClassifierService.Stub.asInterface(
71 ServiceManager.getServiceOrThrow(Context.TEXT_CLASSIFICATION_SERVICE));
Daulet Zhanguzine1559472019-12-18 14:17:56 +000072 mSettings = Objects.requireNonNull(settings);
Tony Makc5a74322020-02-04 17:18:15 +000073 mFallback = TextClassifier.NO_OP;
Joanne Chung97d3a452020-03-04 19:03:11 +080074 // NOTE: Always set this before sending a request to the manager service otherwise the
75 // manager service will throw a remote exception.
76 mSystemTcMetadata = new SystemTextClassifierMetadata(
77 Objects.requireNonNull(context.getOpPackageName()), context.getUserId(),
78 useDefault);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080079 }
80
81 /**
82 * @inheritDoc
83 */
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +000084 @Override
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080085 @WorkerThread
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010086 public TextSelection suggestSelection(TextSelection.Request request) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +000087 Objects.requireNonNull(request);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010088 Utils.checkMainThread();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080089 try {
Joanne Chung97d3a452020-03-04 19:03:11 +080090 request.setSystemTextClassifierMetadata(mSystemTcMetadata);
Tony Mak9920dbb2019-01-23 19:49:30 +000091 final BlockingCallback<TextSelection> callback =
92 new BlockingCallback<>("textselection");
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010093 mManagerService.onSuggestSelection(mSessionId, request, callback);
Tony Mak9920dbb2019-01-23 19:49:30 +000094 final TextSelection selection = callback.get();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080095 if (selection != null) {
96 return selection;
97 }
Tony Mak0be540b2018-11-09 16:58:35 +000098 } catch (RemoteException e) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010099 Log.e(LOG_TAG, "Error suggesting selection for text. Using fallback.", e);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800100 }
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100101 return mFallback.suggestSelection(request);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800102 }
103
104 /**
105 * @inheritDoc
106 */
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000107 @Override
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800108 @WorkerThread
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100109 public TextClassification classifyText(TextClassification.Request request) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000110 Objects.requireNonNull(request);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100111 Utils.checkMainThread();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800112 try {
Joanne Chung97d3a452020-03-04 19:03:11 +0800113 request.setSystemTextClassifierMetadata(mSystemTcMetadata);
Tony Mak9920dbb2019-01-23 19:49:30 +0000114 final BlockingCallback<TextClassification> callback =
115 new BlockingCallback<>("textclassification");
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100116 mManagerService.onClassifyText(mSessionId, request, callback);
Tony Mak9920dbb2019-01-23 19:49:30 +0000117 final TextClassification classification = callback.get();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800118 if (classification != null) {
119 return classification;
120 }
Tony Mak0be540b2018-11-09 16:58:35 +0000121 } catch (RemoteException e) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100122 Log.e(LOG_TAG, "Error classifying text. Using fallback.", e);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800123 }
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100124 return mFallback.classifyText(request);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800125 }
126
127 /**
128 * @inheritDoc
129 */
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000130 @Override
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800131 @WorkerThread
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100132 public TextLinks generateLinks(@NonNull TextLinks.Request request) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000133 Objects.requireNonNull(request);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100134 Utils.checkMainThread();
Tony Makc5a74322020-02-04 17:18:15 +0000135 if (!Utils.checkTextLength(request.getText(), getMaxGenerateLinksTextLength())) {
136 return mFallback.generateLinks(request);
137 }
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100138 if (!mSettings.isSmartLinkifyEnabled() && request.isLegacyFallback()) {
139 return Utils.generateLegacyLinks(request);
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +0000140 }
141
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800142 try {
Joanne Chung97d3a452020-03-04 19:03:11 +0800143 request.setSystemTextClassifierMetadata(mSystemTcMetadata);
Tony Mak9920dbb2019-01-23 19:49:30 +0000144 final BlockingCallback<TextLinks> callback =
145 new BlockingCallback<>("textlinks");
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100146 mManagerService.onGenerateLinks(mSessionId, request, callback);
Tony Mak9920dbb2019-01-23 19:49:30 +0000147 final TextLinks links = callback.get();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800148 if (links != null) {
149 return links;
150 }
Tony Mak0be540b2018-11-09 16:58:35 +0000151 } catch (RemoteException e) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100152 Log.e(LOG_TAG, "Error generating links. Using fallback.", e);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800153 }
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100154 return mFallback.generateLinks(request);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800155 }
156
Abodunrinwa Tokidfd3c652018-05-08 18:23:10 +0100157 @Override
158 public void onSelectionEvent(SelectionEvent event) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000159 Objects.requireNonNull(event);
Abodunrinwa Tokidfd3c652018-05-08 18:23:10 +0100160 Utils.checkMainThread();
161
162 try {
Joanne Chung97d3a452020-03-04 19:03:11 +0800163 event.setSystemTextClassifierMetadata(mSystemTcMetadata);
Abodunrinwa Tokidfd3c652018-05-08 18:23:10 +0100164 mManagerService.onSelectionEvent(mSessionId, event);
165 } catch (RemoteException e) {
166 Log.e(LOG_TAG, "Error reporting selection event.", e);
167 }
168 }
169
Tony Mak0be540b2018-11-09 16:58:35 +0000170 @Override
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000171 public void onTextClassifierEvent(@NonNull TextClassifierEvent event) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000172 Objects.requireNonNull(event);
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000173 Utils.checkMainThread();
174
175 try {
Joanne Chung97d3a452020-03-04 19:03:11 +0800176 final TextClassificationContext tcContext =
177 event.getEventContext() == null ? new TextClassificationContext.Builder(
178 mSystemTcMetadata.getCallingPackageName(), WIDGET_TYPE_UNKNOWN).build()
179 : event.getEventContext();
180 tcContext.setSystemTextClassifierMetadata(mSystemTcMetadata);
Abodunrinwa Toki1480ed12019-07-15 16:01:51 +0100181 event.setEventContext(tcContext);
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000182 mManagerService.onTextClassifierEvent(mSessionId, event);
183 } catch (RemoteException e) {
184 Log.e(LOG_TAG, "Error reporting textclassifier event.", e);
185 }
186 }
187
188 @Override
Tony Mak0be540b2018-11-09 16:58:35 +0000189 public TextLanguage detectLanguage(TextLanguage.Request request) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000190 Objects.requireNonNull(request);
Tony Mak0be540b2018-11-09 16:58:35 +0000191 Utils.checkMainThread();
192
193 try {
Joanne Chung97d3a452020-03-04 19:03:11 +0800194 request.setSystemTextClassifierMetadata(mSystemTcMetadata);
Tony Mak9920dbb2019-01-23 19:49:30 +0000195 final BlockingCallback<TextLanguage> callback =
196 new BlockingCallback<>("textlanguage");
Tony Mak0be540b2018-11-09 16:58:35 +0000197 mManagerService.onDetectLanguage(mSessionId, request, callback);
Tony Mak9920dbb2019-01-23 19:49:30 +0000198 final TextLanguage textLanguage = callback.get();
Tony Mak0be540b2018-11-09 16:58:35 +0000199 if (textLanguage != null) {
200 return textLanguage;
201 }
202 } catch (RemoteException e) {
203 Log.e(LOG_TAG, "Error detecting language.", e);
204 }
205 return mFallback.detectLanguage(request);
206 }
207
208 @Override
209 public ConversationActions suggestConversationActions(ConversationActions.Request request) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000210 Objects.requireNonNull(request);
Tony Mak0be540b2018-11-09 16:58:35 +0000211 Utils.checkMainThread();
212
213 try {
Joanne Chung97d3a452020-03-04 19:03:11 +0800214 request.setSystemTextClassifierMetadata(mSystemTcMetadata);
Tony Mak9920dbb2019-01-23 19:49:30 +0000215 final BlockingCallback<ConversationActions> callback =
216 new BlockingCallback<>("conversation-actions");
Tony Mak0be540b2018-11-09 16:58:35 +0000217 mManagerService.onSuggestConversationActions(mSessionId, request, callback);
Tony Mak9920dbb2019-01-23 19:49:30 +0000218 final ConversationActions conversationActions = callback.get();
Tony Mak0be540b2018-11-09 16:58:35 +0000219 if (conversationActions != null) {
220 return conversationActions;
221 }
222 } catch (RemoteException e) {
223 Log.e(LOG_TAG, "Error reporting selection event.", e);
224 }
225 return mFallback.suggestConversationActions(request);
226 }
227
Jan Althaus108aad32018-01-30 15:26:55 +0100228 /**
229 * @inheritDoc
230 */
231 @Override
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000232 @WorkerThread
Jan Althaus108aad32018-01-30 15:26:55 +0100233 public int getMaxGenerateLinksTextLength() {
234 // TODO: retrieve this from the bound service.
Tony Makc5a74322020-02-04 17:18:15 +0000235 return mSettings.getGenerateLinksMaxTextLength();
Jan Althaus108aad32018-01-30 15:26:55 +0100236 }
237
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000238 @Override
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100239 public void destroy() {
240 try {
241 if (mSessionId != null) {
242 mManagerService.onDestroyTextClassificationSession(mSessionId);
243 }
244 } catch (RemoteException e) {
245 Log.e(LOG_TAG, "Error destroying classification session.", e);
246 }
247 }
248
Tony Makf93e9e52018-07-16 14:46:29 +0200249 @Override
250 public void dump(@NonNull IndentingPrintWriter printWriter) {
251 printWriter.println("SystemTextClassifier:");
252 printWriter.increaseIndent();
253 printWriter.printPair("mFallback", mFallback);
Tony Makf93e9e52018-07-16 14:46:29 +0200254 printWriter.printPair("mSessionId", mSessionId);
Joanne Chung97d3a452020-03-04 19:03:11 +0800255 printWriter.printPair("mSystemTcMetadata", mSystemTcMetadata);
Tony Makf93e9e52018-07-16 14:46:29 +0200256 printWriter.decreaseIndent();
257 printWriter.println();
258 }
259
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100260 /**
261 * Attempts to initialize a new classification session.
262 *
263 * @param classificationContext the classification context
264 * @param sessionId the session's id
265 */
266 void initializeRemoteSession(
267 @NonNull TextClassificationContext classificationContext,
268 @NonNull TextClassificationSessionId sessionId) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000269 mSessionId = Objects.requireNonNull(sessionId);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100270 try {
Joanne Chung97d3a452020-03-04 19:03:11 +0800271 classificationContext.setSystemTextClassifierMetadata(mSystemTcMetadata);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100272 mManagerService.onCreateTextClassificationSession(classificationContext, mSessionId);
273 } catch (RemoteException e) {
274 Log.e(LOG_TAG, "Error starting a new classification session.", e);
275 }
276 }
277
Tony Mak9920dbb2019-01-23 19:49:30 +0000278 private static final class BlockingCallback<T extends Parcelable>
279 extends ITextClassifierCallback.Stub {
280 private final ResponseReceiver<T> mReceiver;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800281
Tony Mak9920dbb2019-01-23 19:49:30 +0000282 BlockingCallback(String name) {
283 mReceiver = new ResponseReceiver<>(name);
284 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800285
286 @Override
Tony Mak9920dbb2019-01-23 19:49:30 +0000287 public void onSuccess(Bundle result) {
288 mReceiver.onSuccess(TextClassifierService.getResponse(result));
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800289 }
290
291 @Override
292 public void onFailure() {
293 mReceiver.onFailure();
294 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800295
Tony Mak9920dbb2019-01-23 19:49:30 +0000296 public T get() {
297 return mReceiver.get();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800298 }
299
Tony Mak0be540b2018-11-09 16:58:35 +0000300 }
301
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800302 private static final class ResponseReceiver<T> {
303
304 private final CountDownLatch mLatch = new CountDownLatch(1);
Tony Mak0be540b2018-11-09 16:58:35 +0000305 private final String mName;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800306 private T mResponse;
307
Tony Mak0be540b2018-11-09 16:58:35 +0000308 private ResponseReceiver(String name) {
309 mName = name;
310 }
311
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800312 public void onSuccess(T response) {
313 mResponse = response;
314 mLatch.countDown();
315 }
316
317 public void onFailure() {
Joanne Chung0b7c2c42019-08-16 16:55:11 +0800318 Log.e(LOG_TAG, "Request failed at " + mName, null);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800319 mLatch.countDown();
320 }
321
322 @Nullable
Tony Mak0be540b2018-11-09 16:58:35 +0000323 public T get() {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800324 // If this is running on the main thread, do not block for a response.
325 // The response will unfortunately be null and the TextClassifier should depend on its
326 // fallback.
327 // NOTE that TextClassifier calls should preferably always be called on a worker thread.
328 if (Looper.myLooper() != Looper.getMainLooper()) {
Tony Mak0be540b2018-11-09 16:58:35 +0000329 try {
330 boolean success = mLatch.await(2, TimeUnit.SECONDS);
331 if (!success) {
332 Log.w(LOG_TAG, "Timeout in ResponseReceiver.get(): " + mName);
333 }
334 } catch (InterruptedException e) {
335 Thread.currentThread().interrupt();
336 Log.e(LOG_TAG, "Interrupted during ResponseReceiver.get(): " + mName, e);
337 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800338 }
339 return mResponse;
340 }
341 }
342}