blob: a97c3305208afe9c4f8fabf1127e2f0300f888d2 [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 +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 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;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010058 private TextClassificationSessionId mSessionId;
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +000059
Abodunrinwa Toki65638332018-03-16 21:08:50 +000060 public SystemTextClassifier(Context context, TextClassificationConstants settings)
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +000061 throws ServiceManager.ServiceNotFoundException {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080062 mManagerService = ITextClassifierService.Stub.asInterface(
63 ServiceManager.getServiceOrThrow(Context.TEXT_CLASSIFICATION_SERVICE));
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +000064 mSettings = Preconditions.checkNotNull(settings);
Abodunrinwa Toki253827f2018-04-24 19:19:48 +010065 mFallback = context.getSystemService(TextClassificationManager.class)
66 .getTextClassifier(TextClassifier.LOCAL);
Abodunrinwa Toki0f138962018-12-03 22:35:10 +000067 mPackageName = Preconditions.checkNotNull(context.getOpPackageName());
Abodunrinwa Tokie8492692019-07-01 19:41:44 +010068 mUserId = context.getUserId();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080069 }
70
71 /**
72 * @inheritDoc
73 */
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +000074 @Override
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080075 @WorkerThread
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010076 public TextSelection suggestSelection(TextSelection.Request request) {
77 Preconditions.checkNotNull(request);
78 Utils.checkMainThread();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080079 try {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +000080 request.setCallingPackageName(mPackageName);
Abodunrinwa Tokie8492692019-07-01 19:41:44 +010081 request.setUserId(mUserId);
Tony Mak9920dbb2019-01-23 19:49:30 +000082 final BlockingCallback<TextSelection> callback =
83 new BlockingCallback<>("textselection");
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010084 mManagerService.onSuggestSelection(mSessionId, request, callback);
Tony Mak9920dbb2019-01-23 19:49:30 +000085 final TextSelection selection = callback.get();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080086 if (selection != null) {
87 return selection;
88 }
Tony Mak0be540b2018-11-09 16:58:35 +000089 } catch (RemoteException e) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010090 Log.e(LOG_TAG, "Error suggesting selection for text. Using fallback.", e);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080091 }
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010092 return mFallback.suggestSelection(request);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080093 }
94
95 /**
96 * @inheritDoc
97 */
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +000098 @Override
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080099 @WorkerThread
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100100 public TextClassification classifyText(TextClassification.Request request) {
101 Preconditions.checkNotNull(request);
102 Utils.checkMainThread();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800103 try {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000104 request.setCallingPackageName(mPackageName);
Abodunrinwa Tokie8492692019-07-01 19:41:44 +0100105 request.setUserId(mUserId);
Tony Mak9920dbb2019-01-23 19:49:30 +0000106 final BlockingCallback<TextClassification> callback =
107 new BlockingCallback<>("textclassification");
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100108 mManagerService.onClassifyText(mSessionId, request, callback);
Tony Mak9920dbb2019-01-23 19:49:30 +0000109 final TextClassification classification = callback.get();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800110 if (classification != null) {
111 return classification;
112 }
Tony Mak0be540b2018-11-09 16:58:35 +0000113 } catch (RemoteException e) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100114 Log.e(LOG_TAG, "Error classifying text. Using fallback.", e);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800115 }
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100116 return mFallback.classifyText(request);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800117 }
118
119 /**
120 * @inheritDoc
121 */
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000122 @Override
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800123 @WorkerThread
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100124 public TextLinks generateLinks(@NonNull TextLinks.Request request) {
125 Preconditions.checkNotNull(request);
126 Utils.checkMainThread();
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +0000127
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100128 if (!mSettings.isSmartLinkifyEnabled() && request.isLegacyFallback()) {
129 return Utils.generateLegacyLinks(request);
Abodunrinwa Tokidb8fc312018-02-26 21:37:51 +0000130 }
131
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800132 try {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100133 request.setCallingPackageName(mPackageName);
Abodunrinwa Tokie8492692019-07-01 19:41:44 +0100134 request.setUserId(mUserId);
Tony Mak9920dbb2019-01-23 19:49:30 +0000135 final BlockingCallback<TextLinks> callback =
136 new BlockingCallback<>("textlinks");
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100137 mManagerService.onGenerateLinks(mSessionId, request, callback);
Tony Mak9920dbb2019-01-23 19:49:30 +0000138 final TextLinks links = callback.get();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800139 if (links != null) {
140 return links;
141 }
Tony Mak0be540b2018-11-09 16:58:35 +0000142 } catch (RemoteException e) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100143 Log.e(LOG_TAG, "Error generating links. Using fallback.", e);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800144 }
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100145 return mFallback.generateLinks(request);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800146 }
147
Abodunrinwa Tokidfd3c652018-05-08 18:23:10 +0100148 @Override
149 public void onSelectionEvent(SelectionEvent event) {
150 Preconditions.checkNotNull(event);
151 Utils.checkMainThread();
152
153 try {
Abodunrinwa Toki1480ed12019-07-15 16:01:51 +0100154 event.setUserId(mUserId);
Abodunrinwa Tokidfd3c652018-05-08 18:23:10 +0100155 mManagerService.onSelectionEvent(mSessionId, event);
156 } catch (RemoteException e) {
157 Log.e(LOG_TAG, "Error reporting selection event.", e);
158 }
159 }
160
Tony Mak0be540b2018-11-09 16:58:35 +0000161 @Override
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000162 public void onTextClassifierEvent(@NonNull TextClassifierEvent event) {
163 Preconditions.checkNotNull(event);
164 Utils.checkMainThread();
165
166 try {
Abodunrinwa Toki1480ed12019-07-15 16:01:51 +0100167 final TextClassificationContext tcContext = event.getEventContext() == null
168 ? new TextClassificationContext.Builder(mPackageName, WIDGET_TYPE_UNKNOWN)
169 .build()
170 : event.getEventContext();
171 tcContext.setUserId(mUserId);
172 event.setEventContext(tcContext);
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000173 mManagerService.onTextClassifierEvent(mSessionId, event);
174 } catch (RemoteException e) {
175 Log.e(LOG_TAG, "Error reporting textclassifier event.", e);
176 }
177 }
178
179 @Override
Tony Mak0be540b2018-11-09 16:58:35 +0000180 public TextLanguage detectLanguage(TextLanguage.Request request) {
181 Preconditions.checkNotNull(request);
182 Utils.checkMainThread();
183
184 try {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000185 request.setCallingPackageName(mPackageName);
Abodunrinwa Tokie8492692019-07-01 19:41:44 +0100186 request.setUserId(mUserId);
Tony Mak9920dbb2019-01-23 19:49:30 +0000187 final BlockingCallback<TextLanguage> callback =
188 new BlockingCallback<>("textlanguage");
Tony Mak0be540b2018-11-09 16:58:35 +0000189 mManagerService.onDetectLanguage(mSessionId, request, callback);
Tony Mak9920dbb2019-01-23 19:49:30 +0000190 final TextLanguage textLanguage = callback.get();
Tony Mak0be540b2018-11-09 16:58:35 +0000191 if (textLanguage != null) {
192 return textLanguage;
193 }
194 } catch (RemoteException e) {
195 Log.e(LOG_TAG, "Error detecting language.", e);
196 }
197 return mFallback.detectLanguage(request);
198 }
199
200 @Override
201 public ConversationActions suggestConversationActions(ConversationActions.Request request) {
202 Preconditions.checkNotNull(request);
203 Utils.checkMainThread();
204
205 try {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000206 request.setCallingPackageName(mPackageName);
Abodunrinwa Tokie8492692019-07-01 19:41:44 +0100207 request.setUserId(mUserId);
Tony Mak9920dbb2019-01-23 19:49:30 +0000208 final BlockingCallback<ConversationActions> callback =
209 new BlockingCallback<>("conversation-actions");
Tony Mak0be540b2018-11-09 16:58:35 +0000210 mManagerService.onSuggestConversationActions(mSessionId, request, callback);
Tony Mak9920dbb2019-01-23 19:49:30 +0000211 final ConversationActions conversationActions = callback.get();
Tony Mak0be540b2018-11-09 16:58:35 +0000212 if (conversationActions != null) {
213 return conversationActions;
214 }
215 } catch (RemoteException e) {
216 Log.e(LOG_TAG, "Error reporting selection event.", e);
217 }
218 return mFallback.suggestConversationActions(request);
219 }
220
Jan Althaus108aad32018-01-30 15:26:55 +0100221 /**
222 * @inheritDoc
223 */
224 @Override
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000225 @WorkerThread
Jan Althaus108aad32018-01-30 15:26:55 +0100226 public int getMaxGenerateLinksTextLength() {
227 // TODO: retrieve this from the bound service.
228 return mFallback.getMaxGenerateLinksTextLength();
229 }
230
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000231 @Override
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100232 public void destroy() {
233 try {
234 if (mSessionId != null) {
235 mManagerService.onDestroyTextClassificationSession(mSessionId);
236 }
237 } catch (RemoteException e) {
238 Log.e(LOG_TAG, "Error destroying classification session.", e);
239 }
240 }
241
Tony Makf93e9e52018-07-16 14:46:29 +0200242 @Override
243 public void dump(@NonNull IndentingPrintWriter printWriter) {
244 printWriter.println("SystemTextClassifier:");
245 printWriter.increaseIndent();
246 printWriter.printPair("mFallback", mFallback);
247 printWriter.printPair("mPackageName", mPackageName);
248 printWriter.printPair("mSessionId", mSessionId);
Abodunrinwa Tokie8492692019-07-01 19:41:44 +0100249 printWriter.printPair("mUserId", mUserId);
Tony Makf93e9e52018-07-16 14:46:29 +0200250 printWriter.decreaseIndent();
251 printWriter.println();
252 }
253
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100254 /**
255 * Attempts to initialize a new classification session.
256 *
257 * @param classificationContext the classification context
258 * @param sessionId the session's id
259 */
260 void initializeRemoteSession(
261 @NonNull TextClassificationContext classificationContext,
262 @NonNull TextClassificationSessionId sessionId) {
263 mSessionId = Preconditions.checkNotNull(sessionId);
264 try {
Abodunrinwa Tokie8492692019-07-01 19:41:44 +0100265 classificationContext.setUserId(mUserId);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100266 mManagerService.onCreateTextClassificationSession(classificationContext, mSessionId);
267 } catch (RemoteException e) {
268 Log.e(LOG_TAG, "Error starting a new classification session.", e);
269 }
270 }
271
Tony Mak9920dbb2019-01-23 19:49:30 +0000272 private static final class BlockingCallback<T extends Parcelable>
273 extends ITextClassifierCallback.Stub {
274 private final ResponseReceiver<T> mReceiver;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800275
Tony Mak9920dbb2019-01-23 19:49:30 +0000276 BlockingCallback(String name) {
277 mReceiver = new ResponseReceiver<>(name);
278 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800279
280 @Override
Tony Mak9920dbb2019-01-23 19:49:30 +0000281 public void onSuccess(Bundle result) {
282 mReceiver.onSuccess(TextClassifierService.getResponse(result));
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800283 }
284
285 @Override
286 public void onFailure() {
287 mReceiver.onFailure();
288 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800289
Tony Mak9920dbb2019-01-23 19:49:30 +0000290 public T get() {
291 return mReceiver.get();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800292 }
293
Tony Mak0be540b2018-11-09 16:58:35 +0000294 }
295
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800296 private static final class ResponseReceiver<T> {
297
298 private final CountDownLatch mLatch = new CountDownLatch(1);
Tony Mak0be540b2018-11-09 16:58:35 +0000299 private final String mName;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800300 private T mResponse;
301
Tony Mak0be540b2018-11-09 16:58:35 +0000302 private ResponseReceiver(String name) {
303 mName = name;
304 }
305
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800306 public void onSuccess(T response) {
307 mResponse = response;
308 mLatch.countDown();
309 }
310
311 public void onFailure() {
312 Log.e(LOG_TAG, "Request failed.", null);
313 mLatch.countDown();
314 }
315
316 @Nullable
Tony Mak0be540b2018-11-09 16:58:35 +0000317 public T get() {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800318 // If this is running on the main thread, do not block for a response.
319 // The response will unfortunately be null and the TextClassifier should depend on its
320 // fallback.
321 // NOTE that TextClassifier calls should preferably always be called on a worker thread.
322 if (Looper.myLooper() != Looper.getMainLooper()) {
Tony Mak0be540b2018-11-09 16:58:35 +0000323 try {
324 boolean success = mLatch.await(2, TimeUnit.SECONDS);
325 if (!success) {
326 Log.w(LOG_TAG, "Timeout in ResponseReceiver.get(): " + mName);
327 }
328 } catch (InterruptedException e) {
329 Thread.currentThread().interrupt();
330 Log.e(LOG_TAG, "Interrupted during ResponseReceiver.get(): " + mName, e);
331 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800332 }
333 return mResponse;
334 }
335 }
336}