blob: 8b370f543cccfdf102611827a482f806ed45fb15 [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;
23import android.os.Looper;
24import android.os.RemoteException;
25import android.os.ServiceManager;
Tony Mak0be540b2018-11-09 16:58:35 +000026import android.service.textclassifier.IConversationActionsCallback;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080027import android.service.textclassifier.ITextClassificationCallback;
28import android.service.textclassifier.ITextClassifierService;
Tony Mak0be540b2018-11-09 16:58:35 +000029import android.service.textclassifier.ITextLanguageCallback;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080030import android.service.textclassifier.ITextLinksCallback;
31import android.service.textclassifier.ITextSelectionCallback;
32
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 +000036import com.android.internal.util.Preconditions;
37
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 Toki080c8542018-03-27 00:04:06 +010054 private TextClassificationSessionId mSessionId;
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +000055
Abodunrinwa Toki65638332018-03-16 21:08:50 +000056 public SystemTextClassifier(Context context, TextClassificationConstants settings)
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +000057 throws ServiceManager.ServiceNotFoundException {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080058 mManagerService = ITextClassifierService.Stub.asInterface(
59 ServiceManager.getServiceOrThrow(Context.TEXT_CLASSIFICATION_SERVICE));
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +000060 mSettings = Preconditions.checkNotNull(settings);
Abodunrinwa Toki253827f2018-04-24 19:19:48 +010061 mFallback = context.getSystemService(TextClassificationManager.class)
62 .getTextClassifier(TextClassifier.LOCAL);
Abodunrinwa Toki0f138962018-12-03 22:35:10 +000063 mPackageName = Preconditions.checkNotNull(context.getOpPackageName());
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080064 }
65
66 /**
67 * @inheritDoc
68 */
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +000069 @Override
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080070 @WorkerThread
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010071 public TextSelection suggestSelection(TextSelection.Request request) {
72 Preconditions.checkNotNull(request);
73 Utils.checkMainThread();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080074 try {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +000075 request.setCallingPackageName(mPackageName);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080076 final TextSelectionCallback callback = new TextSelectionCallback();
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010077 mManagerService.onSuggestSelection(mSessionId, request, callback);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080078 final TextSelection selection = callback.mReceiver.get();
79 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);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080098 final TextClassificationCallback callback = new TextClassificationCallback();
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010099 mManagerService.onClassifyText(mSessionId, request, callback);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800100 final TextClassification classification = callback.mReceiver.get();
101 if (classification != null) {
102 return classification;
103 }
Tony Mak0be540b2018-11-09 16:58:35 +0000104 } catch (RemoteException e) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100105 Log.e(LOG_TAG, "Error classifying text. Using fallback.", e);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800106 }
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100107 return mFallback.classifyText(request);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800108 }
109
110 /**
111 * @inheritDoc
112 */
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000113 @Override
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800114 @WorkerThread
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100115 public TextLinks generateLinks(@NonNull TextLinks.Request request) {
116 Preconditions.checkNotNull(request);
117 Utils.checkMainThread();
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +0000118
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100119 if (!mSettings.isSmartLinkifyEnabled() && request.isLegacyFallback()) {
120 return Utils.generateLegacyLinks(request);
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +0000121 }
122
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800123 try {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100124 request.setCallingPackageName(mPackageName);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800125 final TextLinksCallback callback = new TextLinksCallback();
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100126 mManagerService.onGenerateLinks(mSessionId, request, callback);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800127 final TextLinks links = callback.mReceiver.get();
128 if (links != null) {
129 return links;
130 }
Tony Mak0be540b2018-11-09 16:58:35 +0000131 } catch (RemoteException e) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100132 Log.e(LOG_TAG, "Error generating links. Using fallback.", e);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800133 }
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100134 return mFallback.generateLinks(request);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800135 }
136
Abodunrinwa Tokidfd3c652018-05-08 18:23:10 +0100137 @Override
138 public void onSelectionEvent(SelectionEvent event) {
139 Preconditions.checkNotNull(event);
140 Utils.checkMainThread();
141
142 try {
143 mManagerService.onSelectionEvent(mSessionId, event);
144 } catch (RemoteException e) {
145 Log.e(LOG_TAG, "Error reporting selection event.", e);
146 }
147 }
148
Tony Mak0be540b2018-11-09 16:58:35 +0000149 @Override
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000150 public void onTextClassifierEvent(@NonNull TextClassifierEvent event) {
151 Preconditions.checkNotNull(event);
152 Utils.checkMainThread();
153
154 try {
155 mManagerService.onTextClassifierEvent(mSessionId, event);
156 } catch (RemoteException e) {
157 Log.e(LOG_TAG, "Error reporting textclassifier event.", e);
158 }
159 }
160
161 @Override
Tony Mak0be540b2018-11-09 16:58:35 +0000162 public TextLanguage detectLanguage(TextLanguage.Request request) {
163 Preconditions.checkNotNull(request);
164 Utils.checkMainThread();
165
166 try {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000167 request.setCallingPackageName(mPackageName);
Tony Mak0be540b2018-11-09 16:58:35 +0000168 final TextLanguageCallback callback = new TextLanguageCallback();
169 mManagerService.onDetectLanguage(mSessionId, request, callback);
170 final TextLanguage textLanguage = callback.mReceiver.get();
171 if (textLanguage != null) {
172 return textLanguage;
173 }
174 } catch (RemoteException e) {
175 Log.e(LOG_TAG, "Error detecting language.", e);
176 }
177 return mFallback.detectLanguage(request);
178 }
179
180 @Override
181 public ConversationActions suggestConversationActions(ConversationActions.Request request) {
182 Preconditions.checkNotNull(request);
183 Utils.checkMainThread();
184
185 try {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000186 request.setCallingPackageName(mPackageName);
Tony Mak0be540b2018-11-09 16:58:35 +0000187 final ConversationActionsCallback callback = new ConversationActionsCallback();
188 mManagerService.onSuggestConversationActions(mSessionId, request, callback);
189 final ConversationActions conversationActions = callback.mReceiver.get();
190 if (conversationActions != null) {
191 return conversationActions;
192 }
193 } catch (RemoteException e) {
194 Log.e(LOG_TAG, "Error reporting selection event.", e);
195 }
196 return mFallback.suggestConversationActions(request);
197 }
198
Jan Althaus108aad32018-01-30 15:26:55 +0100199 /**
200 * @inheritDoc
201 */
202 @Override
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000203 @WorkerThread
Jan Althaus108aad32018-01-30 15:26:55 +0100204 public int getMaxGenerateLinksTextLength() {
205 // TODO: retrieve this from the bound service.
206 return mFallback.getMaxGenerateLinksTextLength();
207 }
208
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000209 @Override
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100210 public void destroy() {
211 try {
212 if (mSessionId != null) {
213 mManagerService.onDestroyTextClassificationSession(mSessionId);
214 }
215 } catch (RemoteException e) {
216 Log.e(LOG_TAG, "Error destroying classification session.", e);
217 }
218 }
219
Tony Makf93e9e52018-07-16 14:46:29 +0200220 @Override
221 public void dump(@NonNull IndentingPrintWriter printWriter) {
222 printWriter.println("SystemTextClassifier:");
223 printWriter.increaseIndent();
224 printWriter.printPair("mFallback", mFallback);
225 printWriter.printPair("mPackageName", mPackageName);
226 printWriter.printPair("mSessionId", mSessionId);
227 printWriter.decreaseIndent();
228 printWriter.println();
229 }
230
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100231 /**
232 * Attempts to initialize a new classification session.
233 *
234 * @param classificationContext the classification context
235 * @param sessionId the session's id
236 */
237 void initializeRemoteSession(
238 @NonNull TextClassificationContext classificationContext,
239 @NonNull TextClassificationSessionId sessionId) {
240 mSessionId = Preconditions.checkNotNull(sessionId);
241 try {
242 mManagerService.onCreateTextClassificationSession(classificationContext, mSessionId);
243 } catch (RemoteException e) {
244 Log.e(LOG_TAG, "Error starting a new classification session.", e);
245 }
246 }
247
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800248 private static final class TextSelectionCallback extends ITextSelectionCallback.Stub {
249
Tony Mak0be540b2018-11-09 16:58:35 +0000250 final ResponseReceiver<TextSelection> mReceiver = new ResponseReceiver<>("textselection");
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800251
252 @Override
253 public void onSuccess(TextSelection selection) {
254 mReceiver.onSuccess(selection);
255 }
256
257 @Override
258 public void onFailure() {
259 mReceiver.onFailure();
260 }
261 }
262
263 private static final class TextClassificationCallback extends ITextClassificationCallback.Stub {
264
Tony Mak0be540b2018-11-09 16:58:35 +0000265 final ResponseReceiver<TextClassification> mReceiver =
266 new ResponseReceiver<>("textclassification");
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800267
268 @Override
269 public void onSuccess(TextClassification classification) {
270 mReceiver.onSuccess(classification);
271 }
272
273 @Override
274 public void onFailure() {
275 mReceiver.onFailure();
276 }
277 }
278
279 private static final class TextLinksCallback extends ITextLinksCallback.Stub {
280
Tony Mak0be540b2018-11-09 16:58:35 +0000281 final ResponseReceiver<TextLinks> mReceiver = new ResponseReceiver<>("textlinks");
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800282
283 @Override
284 public void onSuccess(TextLinks links) {
285 mReceiver.onSuccess(links);
286 }
287
288 @Override
289 public void onFailure() {
290 mReceiver.onFailure();
291 }
292 }
293
Tony Mak0be540b2018-11-09 16:58:35 +0000294 private static final class TextLanguageCallback extends ITextLanguageCallback.Stub {
295
296 final ResponseReceiver<TextLanguage> mReceiver = new ResponseReceiver<>("textlanguage");
297
298 @Override
299 public void onSuccess(TextLanguage textLanguage) {
300 mReceiver.onSuccess(textLanguage);
301 }
302
303 @Override
304 public void onFailure() {
305 mReceiver.onFailure();
306 }
307 }
308
309 private static final class ConversationActionsCallback
310 extends IConversationActionsCallback.Stub {
311
312 final ResponseReceiver<ConversationActions> mReceiver =
313 new ResponseReceiver<>("conversationaction");
314
315 @Override
316 public void onSuccess(ConversationActions conversationActions) {
317 mReceiver.onSuccess(conversationActions);
318 }
319
320 @Override
321 public void onFailure() {
322 mReceiver.onFailure();
323 }
324 }
325
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800326 private static final class ResponseReceiver<T> {
327
328 private final CountDownLatch mLatch = new CountDownLatch(1);
Tony Mak0be540b2018-11-09 16:58:35 +0000329 private final String mName;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800330 private T mResponse;
331
Tony Mak0be540b2018-11-09 16:58:35 +0000332 private ResponseReceiver(String name) {
333 mName = name;
334 }
335
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800336 public void onSuccess(T response) {
337 mResponse = response;
338 mLatch.countDown();
339 }
340
341 public void onFailure() {
342 Log.e(LOG_TAG, "Request failed.", null);
343 mLatch.countDown();
344 }
345
346 @Nullable
Tony Mak0be540b2018-11-09 16:58:35 +0000347 public T get() {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800348 // If this is running on the main thread, do not block for a response.
349 // The response will unfortunately be null and the TextClassifier should depend on its
350 // fallback.
351 // NOTE that TextClassifier calls should preferably always be called on a worker thread.
352 if (Looper.myLooper() != Looper.getMainLooper()) {
Tony Mak0be540b2018-11-09 16:58:35 +0000353 try {
354 boolean success = mLatch.await(2, TimeUnit.SECONDS);
355 if (!success) {
356 Log.w(LOG_TAG, "Timeout in ResponseReceiver.get(): " + mName);
357 }
358 } catch (InterruptedException e) {
359 Thread.currentThread().interrupt();
360 Log.e(LOG_TAG, "Interrupted during ResponseReceiver.get(): " + mName, e);
361 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800362 }
363 return mResponse;
364 }
365 }
366}