blob: a5d291f94751f45a1d7942ceb495aef83483a7d5 [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 com.android.server.textclassifier;
18
Eugene Susla7b24b2b2018-03-16 14:33:31 -070019import android.annotation.NonNull;
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +000020import android.annotation.Nullable;
Eugene Susla7b24b2b2018-03-16 14:33:31 -070021import android.annotation.UserIdInt;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080022import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.content.ServiceConnection;
26import android.os.Binder;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080027import android.os.IBinder;
28import android.os.RemoteException;
Eugene Susla7b24b2b2018-03-16 14:33:31 -070029import android.os.UserHandle;
Tony Mak9920dbb2019-01-23 19:49:30 +000030import android.service.textclassifier.ITextClassifierCallback;
Eugene Susla7b24b2b2018-03-16 14:33:31 -070031import android.service.textclassifier.ITextClassifierService;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080032import android.service.textclassifier.TextClassifierService;
Eugene Susla7b24b2b2018-03-16 14:33:31 -070033import android.util.Slog;
34import android.util.SparseArray;
Tony Mak0be540b2018-11-09 16:58:35 +000035import android.view.textclassifier.ConversationActions;
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +000036import android.view.textclassifier.SelectionEvent;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080037import android.view.textclassifier.TextClassification;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010038import android.view.textclassifier.TextClassificationContext;
Tony Makf93e9e52018-07-16 14:46:29 +020039import android.view.textclassifier.TextClassificationManager;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010040import android.view.textclassifier.TextClassificationSessionId;
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +000041import android.view.textclassifier.TextClassifierEvent;
Tony Mak0be540b2018-11-09 16:58:35 +000042import android.view.textclassifier.TextLanguage;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080043import android.view.textclassifier.TextLinks;
44import android.view.textclassifier.TextSelection;
45
46import com.android.internal.annotations.GuardedBy;
Tony Makf93e9e52018-07-16 14:46:29 +020047import com.android.internal.util.DumpUtils;
Eugene Susla7b24b2b2018-03-16 14:33:31 -070048import com.android.internal.util.FunctionalUtils;
49import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
Tony Makf93e9e52018-07-16 14:46:29 +020050import com.android.internal.util.IndentingPrintWriter;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080051import com.android.internal.util.Preconditions;
52import com.android.server.SystemService;
53
Tony Makf93e9e52018-07-16 14:46:29 +020054import java.io.FileDescriptor;
55import java.io.PrintWriter;
Eugene Susla7b24b2b2018-03-16 14:33:31 -070056import java.util.ArrayDeque;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080057import java.util.Queue;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080058
59/**
60 * A manager for TextClassifier services.
61 * Apps bind to the TextClassificationManagerService for text classification. This service
62 * reroutes calls to it to a {@link TextClassifierService} that it manages.
63 */
64public final class TextClassificationManagerService extends ITextClassifierService.Stub {
65
66 private static final String LOG_TAG = "TextClassificationManagerService";
67
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080068 public static final class Lifecycle extends SystemService {
69
70 private final TextClassificationManagerService mManagerService;
71
72 public Lifecycle(Context context) {
73 super(context);
74 mManagerService = new TextClassificationManagerService(context);
75 }
76
77 @Override
78 public void onStart() {
79 try {
80 publishBinderService(Context.TEXT_CLASSIFICATION_SERVICE, mManagerService);
81 } catch (Throwable t) {
82 // Starting this service is not critical to the running of this device and should
83 // therefore not crash the device. If it fails, log the error and continue.
84 Slog.e(LOG_TAG, "Could not start the TextClassificationManagerService.", t);
85 }
86 }
Eugene Susla7b24b2b2018-03-16 14:33:31 -070087
88 @Override
89 public void onStartUser(int userId) {
90 processAnyPendingWork(userId);
91 }
92
93 @Override
94 public void onUnlockUser(int userId) {
95 // Rebind if we failed earlier due to locked encrypted user
96 processAnyPendingWork(userId);
97 }
98
99 private void processAnyPendingWork(int userId) {
100 synchronized (mManagerService.mLock) {
101 mManagerService.getUserStateLocked(userId).bindIfHasPendingRequestsLocked();
102 }
103 }
104
105 @Override
106 public void onStopUser(int userId) {
107 synchronized (mManagerService.mLock) {
108 UserState userState = mManagerService.peekUserStateLocked(userId);
109 if (userState != null) {
110 userState.mConnection.cleanupService();
111 mManagerService.mUserStates.remove(userId);
112 }
113 }
114 }
115
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800116 }
117
118 private final Context mContext;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800119 private final Object mLock;
120 @GuardedBy("mLock")
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700121 final SparseArray<UserState> mUserStates = new SparseArray<>();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800122
123 private TextClassificationManagerService(Context context) {
124 mContext = Preconditions.checkNotNull(context);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800125 mLock = new Object();
126 }
127
128 @Override
129 public void onSuggestSelection(
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100130 TextClassificationSessionId sessionId,
Tony Mak9920dbb2019-01-23 19:49:30 +0000131 TextSelection.Request request, ITextClassifierCallback callback)
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800132 throws RemoteException {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100133 Preconditions.checkNotNull(request);
134 Preconditions.checkNotNull(callback);
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000135 validateInput(mContext, request.getCallingPackageName());
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800136
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800137 synchronized (mLock) {
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700138 UserState userState = getCallingUserStateLocked();
139 if (!userState.bindLocked()) {
140 callback.onFailure();
141 } else if (userState.isBoundLocked()) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100142 userState.mService.onSuggestSelection(sessionId, request, callback);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800143 } else {
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700144 userState.mPendingRequests.add(new PendingRequest(
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100145 () -> onSuggestSelection(sessionId, request, callback),
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700146 callback::onFailure, callback.asBinder(), this, userState));
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800147 }
148 }
149 }
150
151 @Override
152 public void onClassifyText(
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100153 TextClassificationSessionId sessionId,
Tony Mak9920dbb2019-01-23 19:49:30 +0000154 TextClassification.Request request, ITextClassifierCallback callback)
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800155 throws RemoteException {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100156 Preconditions.checkNotNull(request);
157 Preconditions.checkNotNull(callback);
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000158 validateInput(mContext, request.getCallingPackageName());
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800159
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800160 synchronized (mLock) {
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700161 UserState userState = getCallingUserStateLocked();
162 if (!userState.bindLocked()) {
163 callback.onFailure();
164 } else if (userState.isBoundLocked()) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100165 userState.mService.onClassifyText(sessionId, request, callback);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800166 } else {
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700167 userState.mPendingRequests.add(new PendingRequest(
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100168 () -> onClassifyText(sessionId, request, callback),
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700169 callback::onFailure, callback.asBinder(), this, userState));
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800170 }
171 }
172 }
173
174 @Override
175 public void onGenerateLinks(
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100176 TextClassificationSessionId sessionId,
Tony Mak9920dbb2019-01-23 19:49:30 +0000177 TextLinks.Request request, ITextClassifierCallback callback)
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800178 throws RemoteException {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100179 Preconditions.checkNotNull(request);
180 Preconditions.checkNotNull(callback);
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000181 validateInput(mContext, request.getCallingPackageName());
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800182
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800183 synchronized (mLock) {
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700184 UserState userState = getCallingUserStateLocked();
185 if (!userState.bindLocked()) {
186 callback.onFailure();
187 } else if (userState.isBoundLocked()) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100188 userState.mService.onGenerateLinks(sessionId, request, callback);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800189 } else {
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700190 userState.mPendingRequests.add(new PendingRequest(
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100191 () -> onGenerateLinks(sessionId, request, callback),
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700192 callback::onFailure, callback.asBinder(), this, userState));
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800193 }
194 }
195 }
196
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000197 @Override
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100198 public void onSelectionEvent(
199 TextClassificationSessionId sessionId, SelectionEvent event) throws RemoteException {
200 Preconditions.checkNotNull(event);
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000201 validateInput(mContext, event.getPackageName());
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000202
203 synchronized (mLock) {
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700204 UserState userState = getCallingUserStateLocked();
205 if (userState.isBoundLocked()) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100206 userState.mService.onSelectionEvent(sessionId, event);
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000207 } else {
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700208 userState.mPendingRequests.add(new PendingRequest(
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100209 () -> onSelectionEvent(sessionId, event),
210 null /* onServiceFailure */, null /* binder */, this, userState));
211 }
212 }
213 }
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000214 @Override
215 public void onTextClassifierEvent(
216 TextClassificationSessionId sessionId,
217 TextClassifierEvent event) throws RemoteException {
218 Preconditions.checkNotNull(event);
219 final String packageName = event.getEventContext() == null
220 ? null
221 : event.getEventContext().getPackageName();
222 validateInput(mContext, packageName);
223
224 synchronized (mLock) {
225 UserState userState = getCallingUserStateLocked();
226 if (userState.isBoundLocked()) {
227 userState.mService.onTextClassifierEvent(sessionId, event);
228 } else {
229 userState.mPendingRequests.add(new PendingRequest(
230 () -> onTextClassifierEvent(sessionId, event),
231 null /* onServiceFailure */, null /* binder */, this, userState));
232 }
233 }
234 }
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100235
236 @Override
Tony Mak0be540b2018-11-09 16:58:35 +0000237 public void onDetectLanguage(
238 TextClassificationSessionId sessionId,
239 TextLanguage.Request request,
Tony Mak9920dbb2019-01-23 19:49:30 +0000240 ITextClassifierCallback callback) throws RemoteException {
Tony Mak0be540b2018-11-09 16:58:35 +0000241 Preconditions.checkNotNull(request);
242 Preconditions.checkNotNull(callback);
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000243 validateInput(mContext, request.getCallingPackageName());
Tony Mak0be540b2018-11-09 16:58:35 +0000244
245 synchronized (mLock) {
246 UserState userState = getCallingUserStateLocked();
247 if (!userState.bindLocked()) {
248 callback.onFailure();
249 } else if (userState.isBoundLocked()) {
250 userState.mService.onDetectLanguage(sessionId, request, callback);
251 } else {
252 userState.mPendingRequests.add(new PendingRequest(
253 () -> onDetectLanguage(sessionId, request, callback),
254 callback::onFailure, callback.asBinder(), this, userState));
255 }
256 }
257 }
258
259 @Override
260 public void onSuggestConversationActions(
261 TextClassificationSessionId sessionId,
262 ConversationActions.Request request,
Tony Mak9920dbb2019-01-23 19:49:30 +0000263 ITextClassifierCallback callback) throws RemoteException {
Tony Mak0be540b2018-11-09 16:58:35 +0000264 Preconditions.checkNotNull(request);
265 Preconditions.checkNotNull(callback);
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000266 validateInput(mContext, request.getCallingPackageName());
Tony Mak0be540b2018-11-09 16:58:35 +0000267
268 synchronized (mLock) {
269 UserState userState = getCallingUserStateLocked();
270 if (!userState.bindLocked()) {
271 callback.onFailure();
272 } else if (userState.isBoundLocked()) {
273 userState.mService.onSuggestConversationActions(sessionId, request, callback);
274 } else {
275 userState.mPendingRequests.add(new PendingRequest(
276 () -> onSuggestConversationActions(sessionId, request, callback),
277 callback::onFailure, callback.asBinder(), this, userState));
278 }
279 }
280 }
281
282 @Override
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100283 public void onCreateTextClassificationSession(
284 TextClassificationContext classificationContext, TextClassificationSessionId sessionId)
285 throws RemoteException {
286 Preconditions.checkNotNull(sessionId);
287 Preconditions.checkNotNull(classificationContext);
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000288 validateInput(mContext, classificationContext.getPackageName());
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100289
290 synchronized (mLock) {
291 UserState userState = getCallingUserStateLocked();
292 if (userState.isBoundLocked()) {
293 userState.mService.onCreateTextClassificationSession(
294 classificationContext, sessionId);
295 } else {
296 userState.mPendingRequests.add(new PendingRequest(
297 () -> onCreateTextClassificationSession(classificationContext, sessionId),
298 null /* onServiceFailure */, null /* binder */, this, userState));
299 }
300 }
301 }
302
303 @Override
304 public void onDestroyTextClassificationSession(TextClassificationSessionId sessionId)
305 throws RemoteException {
306 Preconditions.checkNotNull(sessionId);
307
308 synchronized (mLock) {
309 UserState userState = getCallingUserStateLocked();
310 if (userState.isBoundLocked()) {
311 userState.mService.onDestroyTextClassificationSession(sessionId);
312 } else {
313 userState.mPendingRequests.add(new PendingRequest(
314 () -> onDestroyTextClassificationSession(sessionId),
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700315 null /* onServiceFailure */, null /* binder */, this, userState));
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000316 }
317 }
318 }
319
Andreas Gamped6d42062018-07-20 13:08:21 -0700320 @GuardedBy("mLock")
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700321 private UserState getCallingUserStateLocked() {
322 return getUserStateLocked(UserHandle.getCallingUserId());
323 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800324
Andreas Gamped6d42062018-07-20 13:08:21 -0700325 @GuardedBy("mLock")
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700326 private UserState getUserStateLocked(int userId) {
327 UserState result = mUserStates.get(userId);
328 if (result == null) {
329 result = new UserState(userId, mContext, mLock);
330 mUserStates.put(userId, result);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800331 }
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700332 return result;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800333 }
334
Andreas Gamped6d42062018-07-20 13:08:21 -0700335 @GuardedBy("mLock")
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700336 UserState peekUserStateLocked(int userId) {
337 return mUserStates.get(userId);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800338 }
339
Tony Makf93e9e52018-07-16 14:46:29 +0200340 @Override
341 protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
342 if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, fout)) return;
343 IndentingPrintWriter pw = new IndentingPrintWriter(fout, " ");
344 TextClassificationManager tcm = mContext.getSystemService(TextClassificationManager.class);
345 tcm.dump(pw);
Felipe Lemef8192132018-10-12 13:42:40 -0700346
347 pw.printPair("context", mContext); pw.println();
348 synchronized (mLock) {
349 int size = mUserStates.size();
350 pw.print("Number user states: "); pw.println(size);
351 if (size > 0) {
352 for (int i = 0; i < size; i++) {
353 pw.increaseIndent();
354 UserState userState = mUserStates.valueAt(i);
355 pw.print(i); pw.print(":"); userState.dump(pw); pw.println();
356 pw.decreaseIndent();
357 }
358 }
359 }
Tony Makf93e9e52018-07-16 14:46:29 +0200360 }
361
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700362 private static final class PendingRequest implements IBinder.DeathRecipient {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800363
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000364 @Nullable private final IBinder mBinder;
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700365 @NonNull private final Runnable mRequest;
366 @Nullable private final Runnable mOnServiceFailure;
367 @GuardedBy("mLock")
368 @NonNull private final UserState mOwningUser;
369 @NonNull private final TextClassificationManagerService mService;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800370
371 /**
372 * Initializes a new pending request.
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800373 * @param request action to perform when the service is bound
374 * @param onServiceFailure action to perform when the service dies or disconnects
375 * @param binder binder to the process that made this pending request
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700376 * @param service
377 * @param owningUser
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800378 */
379 PendingRequest(
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700380 @NonNull ThrowingRunnable request, @Nullable ThrowingRunnable onServiceFailure,
381 @Nullable IBinder binder,
382 TextClassificationManagerService service,
383 UserState owningUser) {
384 mRequest =
385 logOnFailure(Preconditions.checkNotNull(request), "handling pending request");
386 mOnServiceFailure =
387 logOnFailure(onServiceFailure, "notifying callback of service failure");
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000388 mBinder = binder;
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700389 mService = service;
390 mOwningUser = owningUser;
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000391 if (mBinder != null) {
392 try {
393 mBinder.linkToDeath(this, 0);
394 } catch (RemoteException e) {
395 e.printStackTrace();
396 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800397 }
398 }
399
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800400 @Override
401 public void binderDied() {
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700402 synchronized (mService.mLock) {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800403 // No need to handle this pending request anymore. Remove.
404 removeLocked();
405 }
406 }
407
408 @GuardedBy("mLock")
409 private void removeLocked() {
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700410 mOwningUser.mPendingRequests.remove(this);
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000411 if (mBinder != null) {
412 mBinder.unlinkToDeath(this, 0);
413 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800414 }
415 }
416
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700417 private static Runnable logOnFailure(@Nullable ThrowingRunnable r, String opDesc) {
418 if (r == null) return null;
419 return FunctionalUtils.handleExceptions(r,
420 e -> Slog.d(LOG_TAG, "Error " + opDesc + ": " + e.getMessage()));
421 }
422
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000423 private static void validateInput(Context context, @Nullable String packageName)
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000424 throws RemoteException {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000425 if (packageName == null) return;
426
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000427 try {
428 final int uid = context.getPackageManager()
Eugene Susla7d3ea932018-11-26 13:59:09 -0800429 .getPackageUidAsUser(packageName, UserHandle.getCallingUserId());
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000430 Preconditions.checkArgument(Binder.getCallingUid() == uid);
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000431 } catch (Exception e) {
432 throw new RemoteException(
433 String.format("Invalid package: name=%s, error=%s", packageName, e));
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000434 }
435 }
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700436
437 private static final class UserState {
438 @UserIdInt final int mUserId;
439 final TextClassifierServiceConnection mConnection = new TextClassifierServiceConnection();
440 @GuardedBy("mLock")
441 final Queue<PendingRequest> mPendingRequests = new ArrayDeque<>();
442 @GuardedBy("mLock")
443 ITextClassifierService mService;
444 @GuardedBy("mLock")
445 boolean mBinding;
446
447 private final Context mContext;
448 private final Object mLock;
449
450 private UserState(int userId, Context context, Object lock) {
451 mUserId = userId;
452 mContext = Preconditions.checkNotNull(context);
453 mLock = Preconditions.checkNotNull(lock);
454 }
455
456 @GuardedBy("mLock")
457 boolean isBoundLocked() {
458 return mService != null;
459 }
460
461 @GuardedBy("mLock")
462 private void handlePendingRequestsLocked() {
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700463 PendingRequest request;
464 while ((request = mPendingRequests.poll()) != null) {
465 if (isBoundLocked()) {
466 request.mRequest.run();
467 } else {
468 if (request.mOnServiceFailure != null) {
469 request.mOnServiceFailure.run();
470 }
471 }
472
473 if (request.mBinder != null) {
474 request.mBinder.unlinkToDeath(request, 0);
475 }
476 }
477 }
478
Andreas Gamped6d42062018-07-20 13:08:21 -0700479 @GuardedBy("mLock")
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700480 private boolean bindIfHasPendingRequestsLocked() {
481 return !mPendingRequests.isEmpty() && bindLocked();
482 }
483
484 /**
485 * @return true if the service is bound or in the process of being bound.
486 * Returns false otherwise.
487 */
Andreas Gamped6d42062018-07-20 13:08:21 -0700488 @GuardedBy("mLock")
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700489 private boolean bindLocked() {
490 if (isBoundLocked() || mBinding) {
491 return true;
492 }
493
494 // TODO: Handle bind timeout.
495 final boolean willBind;
496 final long identity = Binder.clearCallingIdentity();
497 try {
498 ComponentName componentName =
499 TextClassifierService.getServiceComponentName(mContext);
500 if (componentName == null) {
501 // Might happen if the storage is encrypted and the user is not unlocked
502 return false;
503 }
504 Intent serviceIntent = new Intent(TextClassifierService.SERVICE_INTERFACE)
505 .setComponent(componentName);
506 Slog.d(LOG_TAG, "Binding to " + serviceIntent.getComponent());
507 willBind = mContext.bindServiceAsUser(
508 serviceIntent, mConnection,
Dianne Hackbornc390aa82019-01-09 16:38:22 -0800509 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
510 | Context.BIND_RESTRICT_ASSOCIATIONS,
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700511 UserHandle.of(mUserId));
512 mBinding = willBind;
513 } finally {
514 Binder.restoreCallingIdentity(identity);
515 }
516 return willBind;
517 }
518
Felipe Lemef8192132018-10-12 13:42:40 -0700519 private void dump(IndentingPrintWriter pw) {
520 pw.printPair("context", mContext);
521 pw.printPair("userId", mUserId);
522 synchronized (mLock) {
523 pw.printPair("binding", mBinding);
524 pw.printPair("numberRequests", mPendingRequests.size());
525 }
526 }
527
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700528 private final class TextClassifierServiceConnection implements ServiceConnection {
529 @Override
530 public void onServiceConnected(ComponentName name, IBinder service) {
531 init(ITextClassifierService.Stub.asInterface(service));
532 }
533
534 @Override
535 public void onServiceDisconnected(ComponentName name) {
536 cleanupService();
537 }
538
539 @Override
540 public void onBindingDied(ComponentName name) {
541 cleanupService();
542 }
543
544 @Override
545 public void onNullBinding(ComponentName name) {
546 cleanupService();
547 }
548
549 void cleanupService() {
550 init(null);
551 }
552
553 private void init(@Nullable ITextClassifierService service) {
554 synchronized (mLock) {
555 mService = service;
556 mBinding = false;
557 handlePendingRequestsLocked();
558 }
559 }
560 }
561 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800562}