blob: fe5e8d658dc31ce02319437f60100b9069dcb539 [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;
Abodunrinwa Tokie8492692019-07-01 19:41:44 +010021import android.annotation.UserIdInt;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080022import android.annotation.WorkerThread;
23import android.content.Context;
Tony Mak9920dbb2019-01-23 19:49:30 +000024import android.os.Bundle;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080025import android.os.Looper;
Tony Mak9920dbb2019-01-23 19:49:30 +000026import android.os.Parcelable;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080027import android.os.RemoteException;
28import android.os.ServiceManager;
Tony Mak9920dbb2019-01-23 19:49:30 +000029import android.service.textclassifier.ITextClassifierCallback;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080030import android.service.textclassifier.ITextClassifierService;
Tony Mak9920dbb2019-01-23 19:49:30 +000031import android.service.textclassifier.TextClassifierService;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080032
Abodunrinwa Toki65638332018-03-16 21:08:50 +000033import com.android.internal.annotations.VisibleForTesting;
34import com.android.internal.annotations.VisibleForTesting.Visibility;
Tony Makf93e9e52018-07-16 14:46:29 +020035import com.android.internal.util.IndentingPrintWriter;
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +000036
Daulet Zhanguzine1559472019-12-18 14:17:56 +000037import java.util.Objects;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080038import java.util.concurrent.CountDownLatch;
39import java.util.concurrent.TimeUnit;
40
41/**
42 * Proxy to the system's default TextClassifier.
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;
Jan Althaus31efdc32018-02-19 22:23:13 +010053 private final String mPackageName;
Abodunrinwa Tokie8492692019-07-01 19:41:44 +010054 // NOTE: Always set this before sending a request to the manager service otherwise the manager
55 // service will throw a remote exception.
56 @UserIdInt
57 private final int mUserId;
Tony Makc5a74322020-02-04 17:18:15 +000058 private final boolean mUseDefault;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010059 private TextClassificationSessionId mSessionId;
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +000060
Tony Makc5a74322020-02-04 17:18:15 +000061 public SystemTextClassifier(
62 Context context,
63 TextClassificationConstants settings,
64 boolean useDefault) throws ServiceManager.ServiceNotFoundException {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080065 mManagerService = ITextClassifierService.Stub.asInterface(
66 ServiceManager.getServiceOrThrow(Context.TEXT_CLASSIFICATION_SERVICE));
Daulet Zhanguzine1559472019-12-18 14:17:56 +000067 mSettings = Objects.requireNonNull(settings);
Tony Makc5a74322020-02-04 17:18:15 +000068 mFallback = TextClassifier.NO_OP;
Daulet Zhanguzine1559472019-12-18 14:17:56 +000069 mPackageName = Objects.requireNonNull(context.getOpPackageName());
Abodunrinwa Tokie8492692019-07-01 19:41:44 +010070 mUserId = context.getUserId();
Tony Makc5a74322020-02-04 17:18:15 +000071 mUseDefault = useDefault;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080072 }
73
74 /**
75 * @inheritDoc
76 */
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +000077 @Override
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080078 @WorkerThread
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010079 public TextSelection suggestSelection(TextSelection.Request request) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +000080 Objects.requireNonNull(request);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010081 Utils.checkMainThread();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080082 try {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +000083 request.setCallingPackageName(mPackageName);
Abodunrinwa Tokie8492692019-07-01 19:41:44 +010084 request.setUserId(mUserId);
Tony Makc5a74322020-02-04 17:18:15 +000085 request.setUseDefaultTextClassifier(mUseDefault);
Tony Mak9920dbb2019-01-23 19:49:30 +000086 final BlockingCallback<TextSelection> callback =
87 new BlockingCallback<>("textselection");
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010088 mManagerService.onSuggestSelection(mSessionId, request, callback);
Tony Mak9920dbb2019-01-23 19:49:30 +000089 final TextSelection selection = callback.get();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080090 if (selection != null) {
91 return selection;
92 }
Tony Mak0be540b2018-11-09 16:58:35 +000093 } catch (RemoteException e) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010094 Log.e(LOG_TAG, "Error suggesting selection for text. Using fallback.", e);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080095 }
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010096 return mFallback.suggestSelection(request);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080097 }
98
99 /**
100 * @inheritDoc
101 */
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000102 @Override
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800103 @WorkerThread
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100104 public TextClassification classifyText(TextClassification.Request request) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000105 Objects.requireNonNull(request);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100106 Utils.checkMainThread();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800107 try {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000108 request.setCallingPackageName(mPackageName);
Abodunrinwa Tokie8492692019-07-01 19:41:44 +0100109 request.setUserId(mUserId);
Tony Makc5a74322020-02-04 17:18:15 +0000110 request.setUseDefaultTextClassifier(mUseDefault);
Tony Mak9920dbb2019-01-23 19:49:30 +0000111 final BlockingCallback<TextClassification> callback =
112 new BlockingCallback<>("textclassification");
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100113 mManagerService.onClassifyText(mSessionId, request, callback);
Tony Mak9920dbb2019-01-23 19:49:30 +0000114 final TextClassification classification = callback.get();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800115 if (classification != null) {
116 return classification;
117 }
Tony Mak0be540b2018-11-09 16:58:35 +0000118 } catch (RemoteException e) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100119 Log.e(LOG_TAG, "Error classifying text. Using fallback.", e);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800120 }
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100121 return mFallback.classifyText(request);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800122 }
123
124 /**
125 * @inheritDoc
126 */
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000127 @Override
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800128 @WorkerThread
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100129 public TextLinks generateLinks(@NonNull TextLinks.Request request) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000130 Objects.requireNonNull(request);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100131 Utils.checkMainThread();
Tony Makc5a74322020-02-04 17:18:15 +0000132 if (!Utils.checkTextLength(request.getText(), getMaxGenerateLinksTextLength())) {
133 return mFallback.generateLinks(request);
134 }
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100135 if (!mSettings.isSmartLinkifyEnabled() && request.isLegacyFallback()) {
136 return Utils.generateLegacyLinks(request);
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +0000137 }
138
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800139 try {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100140 request.setCallingPackageName(mPackageName);
Abodunrinwa Tokie8492692019-07-01 19:41:44 +0100141 request.setUserId(mUserId);
Tony Makc5a74322020-02-04 17:18:15 +0000142 request.setUseDefaultTextClassifier(mUseDefault);
Tony Mak9920dbb2019-01-23 19:49:30 +0000143 final BlockingCallback<TextLinks> callback =
144 new BlockingCallback<>("textlinks");
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100145 mManagerService.onGenerateLinks(mSessionId, request, callback);
Tony Mak9920dbb2019-01-23 19:49:30 +0000146 final TextLinks links = callback.get();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800147 if (links != null) {
148 return links;
149 }
Tony Mak0be540b2018-11-09 16:58:35 +0000150 } catch (RemoteException e) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100151 Log.e(LOG_TAG, "Error generating links. Using fallback.", e);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800152 }
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100153 return mFallback.generateLinks(request);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800154 }
155
Abodunrinwa Tokidfd3c652018-05-08 18:23:10 +0100156 @Override
157 public void onSelectionEvent(SelectionEvent event) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000158 Objects.requireNonNull(event);
Abodunrinwa Tokidfd3c652018-05-08 18:23:10 +0100159 Utils.checkMainThread();
160
161 try {
Abodunrinwa Toki1480ed12019-07-15 16:01:51 +0100162 event.setUserId(mUserId);
Tony Makc5a74322020-02-04 17:18:15 +0000163 event.setUseDefaultTextClassifier(mUseDefault);
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 {
Abodunrinwa Toki1480ed12019-07-15 16:01:51 +0100176 final TextClassificationContext tcContext = event.getEventContext() == null
177 ? new TextClassificationContext.Builder(mPackageName, WIDGET_TYPE_UNKNOWN)
178 .build()
179 : event.getEventContext();
180 tcContext.setUserId(mUserId);
Tony Makc5a74322020-02-04 17:18:15 +0000181 tcContext.setUseDefaultTextClassifier(mUseDefault);
Abodunrinwa Toki1480ed12019-07-15 16:01:51 +0100182 event.setEventContext(tcContext);
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000183 mManagerService.onTextClassifierEvent(mSessionId, event);
184 } catch (RemoteException e) {
185 Log.e(LOG_TAG, "Error reporting textclassifier event.", e);
186 }
187 }
188
189 @Override
Tony Mak0be540b2018-11-09 16:58:35 +0000190 public TextLanguage detectLanguage(TextLanguage.Request request) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000191 Objects.requireNonNull(request);
Tony Mak0be540b2018-11-09 16:58:35 +0000192 Utils.checkMainThread();
193
194 try {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000195 request.setCallingPackageName(mPackageName);
Abodunrinwa Tokie8492692019-07-01 19:41:44 +0100196 request.setUserId(mUserId);
Tony Makc5a74322020-02-04 17:18:15 +0000197 request.setUseDefaultTextClassifier(mUseDefault);
Tony Mak9920dbb2019-01-23 19:49:30 +0000198 final BlockingCallback<TextLanguage> callback =
199 new BlockingCallback<>("textlanguage");
Tony Mak0be540b2018-11-09 16:58:35 +0000200 mManagerService.onDetectLanguage(mSessionId, request, callback);
Tony Mak9920dbb2019-01-23 19:49:30 +0000201 final TextLanguage textLanguage = callback.get();
Tony Mak0be540b2018-11-09 16:58:35 +0000202 if (textLanguage != null) {
203 return textLanguage;
204 }
205 } catch (RemoteException e) {
206 Log.e(LOG_TAG, "Error detecting language.", e);
207 }
208 return mFallback.detectLanguage(request);
209 }
210
211 @Override
212 public ConversationActions suggestConversationActions(ConversationActions.Request request) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000213 Objects.requireNonNull(request);
Tony Mak0be540b2018-11-09 16:58:35 +0000214 Utils.checkMainThread();
215
216 try {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000217 request.setCallingPackageName(mPackageName);
Abodunrinwa Tokie8492692019-07-01 19:41:44 +0100218 request.setUserId(mUserId);
Tony Makc5a74322020-02-04 17:18:15 +0000219 request.setUseDefaultTextClassifier(mUseDefault);
Tony Mak9920dbb2019-01-23 19:49:30 +0000220 final BlockingCallback<ConversationActions> callback =
221 new BlockingCallback<>("conversation-actions");
Tony Mak0be540b2018-11-09 16:58:35 +0000222 mManagerService.onSuggestConversationActions(mSessionId, request, callback);
Tony Mak9920dbb2019-01-23 19:49:30 +0000223 final ConversationActions conversationActions = callback.get();
Tony Mak0be540b2018-11-09 16:58:35 +0000224 if (conversationActions != null) {
225 return conversationActions;
226 }
227 } catch (RemoteException e) {
228 Log.e(LOG_TAG, "Error reporting selection event.", e);
229 }
230 return mFallback.suggestConversationActions(request);
231 }
232
Jan Althaus108aad32018-01-30 15:26:55 +0100233 /**
234 * @inheritDoc
235 */
236 @Override
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000237 @WorkerThread
Jan Althaus108aad32018-01-30 15:26:55 +0100238 public int getMaxGenerateLinksTextLength() {
239 // TODO: retrieve this from the bound service.
Tony Makc5a74322020-02-04 17:18:15 +0000240 return mSettings.getGenerateLinksMaxTextLength();
Jan Althaus108aad32018-01-30 15:26:55 +0100241 }
242
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000243 @Override
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100244 public void destroy() {
245 try {
246 if (mSessionId != null) {
247 mManagerService.onDestroyTextClassificationSession(mSessionId);
248 }
249 } catch (RemoteException e) {
250 Log.e(LOG_TAG, "Error destroying classification session.", e);
251 }
252 }
253
Tony Makf93e9e52018-07-16 14:46:29 +0200254 @Override
255 public void dump(@NonNull IndentingPrintWriter printWriter) {
256 printWriter.println("SystemTextClassifier:");
257 printWriter.increaseIndent();
258 printWriter.printPair("mFallback", mFallback);
259 printWriter.printPair("mPackageName", mPackageName);
260 printWriter.printPair("mSessionId", mSessionId);
Abodunrinwa Tokie8492692019-07-01 19:41:44 +0100261 printWriter.printPair("mUserId", mUserId);
Tony Makc5a74322020-02-04 17:18:15 +0000262 printWriter.printPair("mUseDefault", mUseDefault);
Tony Makf93e9e52018-07-16 14:46:29 +0200263 printWriter.decreaseIndent();
264 printWriter.println();
265 }
266
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100267 /**
268 * Attempts to initialize a new classification session.
269 *
270 * @param classificationContext the classification context
271 * @param sessionId the session's id
272 */
273 void initializeRemoteSession(
274 @NonNull TextClassificationContext classificationContext,
275 @NonNull TextClassificationSessionId sessionId) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000276 mSessionId = Objects.requireNonNull(sessionId);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100277 try {
Abodunrinwa Tokie8492692019-07-01 19:41:44 +0100278 classificationContext.setUserId(mUserId);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100279 mManagerService.onCreateTextClassificationSession(classificationContext, mSessionId);
280 } catch (RemoteException e) {
281 Log.e(LOG_TAG, "Error starting a new classification session.", e);
282 }
283 }
284
Tony Mak9920dbb2019-01-23 19:49:30 +0000285 private static final class BlockingCallback<T extends Parcelable>
286 extends ITextClassifierCallback.Stub {
287 private final ResponseReceiver<T> mReceiver;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800288
Tony Mak9920dbb2019-01-23 19:49:30 +0000289 BlockingCallback(String name) {
290 mReceiver = new ResponseReceiver<>(name);
291 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800292
293 @Override
Tony Mak9920dbb2019-01-23 19:49:30 +0000294 public void onSuccess(Bundle result) {
295 mReceiver.onSuccess(TextClassifierService.getResponse(result));
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800296 }
297
298 @Override
299 public void onFailure() {
300 mReceiver.onFailure();
301 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800302
Tony Mak9920dbb2019-01-23 19:49:30 +0000303 public T get() {
304 return mReceiver.get();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800305 }
306
Tony Mak0be540b2018-11-09 16:58:35 +0000307 }
308
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800309 private static final class ResponseReceiver<T> {
310
311 private final CountDownLatch mLatch = new CountDownLatch(1);
Tony Mak0be540b2018-11-09 16:58:35 +0000312 private final String mName;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800313 private T mResponse;
314
Tony Mak0be540b2018-11-09 16:58:35 +0000315 private ResponseReceiver(String name) {
316 mName = name;
317 }
318
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800319 public void onSuccess(T response) {
320 mResponse = response;
321 mLatch.countDown();
322 }
323
324 public void onFailure() {
Joanne Chung0b7c2c42019-08-16 16:55:11 +0800325 Log.e(LOG_TAG, "Request failed at " + mName, null);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800326 mLatch.countDown();
327 }
328
329 @Nullable
Tony Mak0be540b2018-11-09 16:58:35 +0000330 public T get() {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800331 // If this is running on the main thread, do not block for a response.
332 // The response will unfortunately be null and the TextClassifier should depend on its
333 // fallback.
334 // NOTE that TextClassifier calls should preferably always be called on a worker thread.
335 if (Looper.myLooper() != Looper.getMainLooper()) {
Tony Mak0be540b2018-11-09 16:58:35 +0000336 try {
337 boolean success = mLatch.await(2, TimeUnit.SECONDS);
338 if (!success) {
339 Log.w(LOG_TAG, "Timeout in ResponseReceiver.get(): " + mName);
340 }
341 } catch (InterruptedException e) {
342 Thread.currentThread().interrupt();
343 Log.e(LOG_TAG, "Interrupted during ResponseReceiver.get(): " + mName, e);
344 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800345 }
346 return mResponse;
347 }
348 }
349}