blob: 8f8766e3f783d3c9b6301ce7fd2092fcf86c08dc [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 +000035import com.android.internal.util.Preconditions;
36
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080037import java.util.concurrent.CountDownLatch;
38import java.util.concurrent.TimeUnit;
39
40/**
41 * Proxy to the system's default TextClassifier.
Abodunrinwa Toki65638332018-03-16 21:08:50 +000042 * @hide
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080043 */
Abodunrinwa Toki65638332018-03-16 21:08:50 +000044@VisibleForTesting(visibility = Visibility.PACKAGE)
45public final class SystemTextClassifier implements TextClassifier {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080046
47 private static final String LOG_TAG = "SystemTextClassifier";
48
49 private final ITextClassifierService mManagerService;
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +000050 private final TextClassificationConstants mSettings;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080051 private final TextClassifier mFallback;
Jan Althaus31efdc32018-02-19 22:23:13 +010052 private final String mPackageName;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010053 private TextClassificationSessionId mSessionId;
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +000054
Abodunrinwa Toki65638332018-03-16 21:08:50 +000055 public SystemTextClassifier(Context context, TextClassificationConstants settings)
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +000056 throws ServiceManager.ServiceNotFoundException {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080057 mManagerService = ITextClassifierService.Stub.asInterface(
58 ServiceManager.getServiceOrThrow(Context.TEXT_CLASSIFICATION_SERVICE));
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +000059 mSettings = Preconditions.checkNotNull(settings);
Abodunrinwa Toki253827f2018-04-24 19:19:48 +010060 mFallback = context.getSystemService(TextClassificationManager.class)
61 .getTextClassifier(TextClassifier.LOCAL);
Abodunrinwa Toki0f138962018-12-03 22:35:10 +000062 mPackageName = Preconditions.checkNotNull(context.getOpPackageName());
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080063 }
64
65 /**
66 * @inheritDoc
67 */
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +000068 @Override
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080069 @WorkerThread
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010070 public TextSelection suggestSelection(TextSelection.Request request) {
71 Preconditions.checkNotNull(request);
72 Utils.checkMainThread();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080073 try {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +000074 request.setCallingPackageName(mPackageName);
Tony Mak9920dbb2019-01-23 19:49:30 +000075 final BlockingCallback<TextSelection> callback =
76 new BlockingCallback<>("textselection");
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010077 mManagerService.onSuggestSelection(mSessionId, request, callback);
Tony Mak9920dbb2019-01-23 19:49:30 +000078 final TextSelection selection = callback.get();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080079 if (selection != null) {
80 return selection;
81 }
Tony Mak0be540b2018-11-09 16:58:35 +000082 } catch (RemoteException e) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010083 Log.e(LOG_TAG, "Error suggesting selection for text. Using fallback.", e);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080084 }
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010085 return mFallback.suggestSelection(request);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080086 }
87
88 /**
89 * @inheritDoc
90 */
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +000091 @Override
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080092 @WorkerThread
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010093 public TextClassification classifyText(TextClassification.Request request) {
94 Preconditions.checkNotNull(request);
95 Utils.checkMainThread();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080096 try {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +000097 request.setCallingPackageName(mPackageName);
Tony Mak9920dbb2019-01-23 19:49:30 +000098 final BlockingCallback<TextClassification> callback =
99 new BlockingCallback<>("textclassification");
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100100 mManagerService.onClassifyText(mSessionId, request, callback);
Tony Mak9920dbb2019-01-23 19:49:30 +0000101 final TextClassification classification = callback.get();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800102 if (classification != null) {
103 return classification;
104 }
Tony Mak0be540b2018-11-09 16:58:35 +0000105 } catch (RemoteException e) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100106 Log.e(LOG_TAG, "Error classifying text. Using fallback.", e);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800107 }
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100108 return mFallback.classifyText(request);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800109 }
110
111 /**
112 * @inheritDoc
113 */
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000114 @Override
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800115 @WorkerThread
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100116 public TextLinks generateLinks(@NonNull TextLinks.Request request) {
117 Preconditions.checkNotNull(request);
118 Utils.checkMainThread();
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +0000119
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100120 if (!mSettings.isSmartLinkifyEnabled() && request.isLegacyFallback()) {
121 return Utils.generateLegacyLinks(request);
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +0000122 }
123
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800124 try {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100125 request.setCallingPackageName(mPackageName);
Tony Mak9920dbb2019-01-23 19:49:30 +0000126 final BlockingCallback<TextLinks> callback =
127 new BlockingCallback<>("textlinks");
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100128 mManagerService.onGenerateLinks(mSessionId, request, callback);
Tony Mak9920dbb2019-01-23 19:49:30 +0000129 final TextLinks links = callback.get();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800130 if (links != null) {
131 return links;
132 }
Tony Mak0be540b2018-11-09 16:58:35 +0000133 } catch (RemoteException e) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100134 Log.e(LOG_TAG, "Error generating links. Using fallback.", e);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800135 }
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100136 return mFallback.generateLinks(request);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800137 }
138
Abodunrinwa Tokidfd3c652018-05-08 18:23:10 +0100139 @Override
140 public void onSelectionEvent(SelectionEvent event) {
141 Preconditions.checkNotNull(event);
142 Utils.checkMainThread();
143
144 try {
145 mManagerService.onSelectionEvent(mSessionId, event);
146 } catch (RemoteException e) {
147 Log.e(LOG_TAG, "Error reporting selection event.", e);
148 }
149 }
150
Tony Mak0be540b2018-11-09 16:58:35 +0000151 @Override
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000152 public void onTextClassifierEvent(@NonNull TextClassifierEvent event) {
153 Preconditions.checkNotNull(event);
154 Utils.checkMainThread();
155
156 try {
157 mManagerService.onTextClassifierEvent(mSessionId, event);
158 } catch (RemoteException e) {
159 Log.e(LOG_TAG, "Error reporting textclassifier event.", e);
160 }
161 }
162
163 @Override
Tony Mak0be540b2018-11-09 16:58:35 +0000164 public TextLanguage detectLanguage(TextLanguage.Request request) {
165 Preconditions.checkNotNull(request);
166 Utils.checkMainThread();
167
168 try {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000169 request.setCallingPackageName(mPackageName);
Tony Mak9920dbb2019-01-23 19:49:30 +0000170 final BlockingCallback<TextLanguage> callback =
171 new BlockingCallback<>("textlanguage");
Tony Mak0be540b2018-11-09 16:58:35 +0000172 mManagerService.onDetectLanguage(mSessionId, request, callback);
Tony Mak9920dbb2019-01-23 19:49:30 +0000173 final TextLanguage textLanguage = callback.get();
Tony Mak0be540b2018-11-09 16:58:35 +0000174 if (textLanguage != null) {
175 return textLanguage;
176 }
177 } catch (RemoteException e) {
178 Log.e(LOG_TAG, "Error detecting language.", e);
179 }
180 return mFallback.detectLanguage(request);
181 }
182
183 @Override
184 public ConversationActions suggestConversationActions(ConversationActions.Request request) {
185 Preconditions.checkNotNull(request);
186 Utils.checkMainThread();
187
188 try {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000189 request.setCallingPackageName(mPackageName);
Tony Mak9920dbb2019-01-23 19:49:30 +0000190 final BlockingCallback<ConversationActions> callback =
191 new BlockingCallback<>("conversation-actions");
Tony Mak0be540b2018-11-09 16:58:35 +0000192 mManagerService.onSuggestConversationActions(mSessionId, request, callback);
Tony Mak9920dbb2019-01-23 19:49:30 +0000193 final ConversationActions conversationActions = callback.get();
Tony Mak0be540b2018-11-09 16:58:35 +0000194 if (conversationActions != null) {
195 return conversationActions;
196 }
197 } catch (RemoteException e) {
198 Log.e(LOG_TAG, "Error reporting selection event.", e);
199 }
200 return mFallback.suggestConversationActions(request);
201 }
202
Jan Althaus108aad32018-01-30 15:26:55 +0100203 /**
204 * @inheritDoc
205 */
206 @Override
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000207 @WorkerThread
Jan Althaus108aad32018-01-30 15:26:55 +0100208 public int getMaxGenerateLinksTextLength() {
209 // TODO: retrieve this from the bound service.
210 return mFallback.getMaxGenerateLinksTextLength();
211 }
212
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000213 @Override
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100214 public void destroy() {
215 try {
216 if (mSessionId != null) {
217 mManagerService.onDestroyTextClassificationSession(mSessionId);
218 }
219 } catch (RemoteException e) {
220 Log.e(LOG_TAG, "Error destroying classification session.", e);
221 }
222 }
223
Tony Makf93e9e52018-07-16 14:46:29 +0200224 @Override
225 public void dump(@NonNull IndentingPrintWriter printWriter) {
226 printWriter.println("SystemTextClassifier:");
227 printWriter.increaseIndent();
228 printWriter.printPair("mFallback", mFallback);
229 printWriter.printPair("mPackageName", mPackageName);
230 printWriter.printPair("mSessionId", mSessionId);
231 printWriter.decreaseIndent();
232 printWriter.println();
233 }
234
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100235 /**
236 * Attempts to initialize a new classification session.
237 *
238 * @param classificationContext the classification context
239 * @param sessionId the session's id
240 */
241 void initializeRemoteSession(
242 @NonNull TextClassificationContext classificationContext,
243 @NonNull TextClassificationSessionId sessionId) {
244 mSessionId = Preconditions.checkNotNull(sessionId);
245 try {
246 mManagerService.onCreateTextClassificationSession(classificationContext, mSessionId);
247 } catch (RemoteException e) {
248 Log.e(LOG_TAG, "Error starting a new classification session.", e);
249 }
250 }
251
Tony Mak9920dbb2019-01-23 19:49:30 +0000252 private static final class BlockingCallback<T extends Parcelable>
253 extends ITextClassifierCallback.Stub {
254 private final ResponseReceiver<T> mReceiver;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800255
Tony Mak9920dbb2019-01-23 19:49:30 +0000256 BlockingCallback(String name) {
257 mReceiver = new ResponseReceiver<>(name);
258 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800259
260 @Override
Tony Mak9920dbb2019-01-23 19:49:30 +0000261 public void onSuccess(Bundle result) {
262 mReceiver.onSuccess(TextClassifierService.getResponse(result));
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800263 }
264
265 @Override
266 public void onFailure() {
267 mReceiver.onFailure();
268 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800269
Tony Mak9920dbb2019-01-23 19:49:30 +0000270 public T get() {
271 return mReceiver.get();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800272 }
273
Tony Mak0be540b2018-11-09 16:58:35 +0000274 }
275
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800276 private static final class ResponseReceiver<T> {
277
278 private final CountDownLatch mLatch = new CountDownLatch(1);
Tony Mak0be540b2018-11-09 16:58:35 +0000279 private final String mName;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800280 private T mResponse;
281
Tony Mak0be540b2018-11-09 16:58:35 +0000282 private ResponseReceiver(String name) {
283 mName = name;
284 }
285
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800286 public void onSuccess(T response) {
287 mResponse = response;
288 mLatch.countDown();
289 }
290
291 public void onFailure() {
292 Log.e(LOG_TAG, "Request failed.", null);
293 mLatch.countDown();
294 }
295
296 @Nullable
Tony Mak0be540b2018-11-09 16:58:35 +0000297 public T get() {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800298 // If this is running on the main thread, do not block for a response.
299 // The response will unfortunately be null and the TextClassifier should depend on its
300 // fallback.
301 // NOTE that TextClassifier calls should preferably always be called on a worker thread.
302 if (Looper.myLooper() != Looper.getMainLooper()) {
Tony Mak0be540b2018-11-09 16:58:35 +0000303 try {
304 boolean success = mLatch.await(2, TimeUnit.SECONDS);
305 if (!success) {
306 Log.w(LOG_TAG, "Timeout in ResponseReceiver.get(): " + mName);
307 }
308 } catch (InterruptedException e) {
309 Thread.currentThread().interrupt();
310 Log.e(LOG_TAG, "Interrupted during ResponseReceiver.get(): " + mName, e);
311 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800312 }
313 return mResponse;
314 }
315 }
316}