blob: ef771406805bf4d9ffaee95c18377f0e5354a790 [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 Mak0be540b2018-11-09 16:58:35 +000030import android.service.textclassifier.IConversationActionsCallback;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080031import android.service.textclassifier.ITextClassificationCallback;
Eugene Susla7b24b2b2018-03-16 14:33:31 -070032import android.service.textclassifier.ITextClassifierService;
Tony Mak0be540b2018-11-09 16:58:35 +000033import android.service.textclassifier.ITextLanguageCallback;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080034import android.service.textclassifier.ITextLinksCallback;
35import android.service.textclassifier.ITextSelectionCallback;
36import android.service.textclassifier.TextClassifierService;
Eugene Susla7b24b2b2018-03-16 14:33:31 -070037import android.util.Slog;
38import android.util.SparseArray;
Tony Mak0be540b2018-11-09 16:58:35 +000039import android.view.textclassifier.ConversationActions;
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +000040import android.view.textclassifier.SelectionEvent;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080041import android.view.textclassifier.TextClassification;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010042import android.view.textclassifier.TextClassificationContext;
Tony Makf93e9e52018-07-16 14:46:29 +020043import android.view.textclassifier.TextClassificationManager;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010044import android.view.textclassifier.TextClassificationSessionId;
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +000045import android.view.textclassifier.TextClassifierEvent;
Tony Mak0be540b2018-11-09 16:58:35 +000046import android.view.textclassifier.TextLanguage;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080047import android.view.textclassifier.TextLinks;
48import android.view.textclassifier.TextSelection;
49
50import com.android.internal.annotations.GuardedBy;
Tony Makf93e9e52018-07-16 14:46:29 +020051import com.android.internal.util.DumpUtils;
Eugene Susla7b24b2b2018-03-16 14:33:31 -070052import com.android.internal.util.FunctionalUtils;
53import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
Tony Makf93e9e52018-07-16 14:46:29 +020054import com.android.internal.util.IndentingPrintWriter;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080055import com.android.internal.util.Preconditions;
56import com.android.server.SystemService;
57
Tony Makf93e9e52018-07-16 14:46:29 +020058import java.io.FileDescriptor;
59import java.io.PrintWriter;
Eugene Susla7b24b2b2018-03-16 14:33:31 -070060import java.util.ArrayDeque;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080061import java.util.Queue;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080062
63/**
64 * A manager for TextClassifier services.
65 * Apps bind to the TextClassificationManagerService for text classification. This service
66 * reroutes calls to it to a {@link TextClassifierService} that it manages.
67 */
68public final class TextClassificationManagerService extends ITextClassifierService.Stub {
69
70 private static final String LOG_TAG = "TextClassificationManagerService";
71
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080072 public static final class Lifecycle extends SystemService {
73
74 private final TextClassificationManagerService mManagerService;
75
76 public Lifecycle(Context context) {
77 super(context);
78 mManagerService = new TextClassificationManagerService(context);
79 }
80
81 @Override
82 public void onStart() {
83 try {
84 publishBinderService(Context.TEXT_CLASSIFICATION_SERVICE, mManagerService);
85 } catch (Throwable t) {
86 // Starting this service is not critical to the running of this device and should
87 // therefore not crash the device. If it fails, log the error and continue.
88 Slog.e(LOG_TAG, "Could not start the TextClassificationManagerService.", t);
89 }
90 }
Eugene Susla7b24b2b2018-03-16 14:33:31 -070091
92 @Override
93 public void onStartUser(int userId) {
94 processAnyPendingWork(userId);
95 }
96
97 @Override
98 public void onUnlockUser(int userId) {
99 // Rebind if we failed earlier due to locked encrypted user
100 processAnyPendingWork(userId);
101 }
102
103 private void processAnyPendingWork(int userId) {
104 synchronized (mManagerService.mLock) {
105 mManagerService.getUserStateLocked(userId).bindIfHasPendingRequestsLocked();
106 }
107 }
108
109 @Override
110 public void onStopUser(int userId) {
111 synchronized (mManagerService.mLock) {
112 UserState userState = mManagerService.peekUserStateLocked(userId);
113 if (userState != null) {
114 userState.mConnection.cleanupService();
115 mManagerService.mUserStates.remove(userId);
116 }
117 }
118 }
119
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800120 }
121
122 private final Context mContext;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800123 private final Object mLock;
124 @GuardedBy("mLock")
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700125 final SparseArray<UserState> mUserStates = new SparseArray<>();
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800126
127 private TextClassificationManagerService(Context context) {
128 mContext = Preconditions.checkNotNull(context);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800129 mLock = new Object();
130 }
131
132 @Override
133 public void onSuggestSelection(
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100134 TextClassificationSessionId sessionId,
135 TextSelection.Request request, ITextSelectionCallback callback)
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800136 throws RemoteException {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100137 Preconditions.checkNotNull(request);
138 Preconditions.checkNotNull(callback);
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000139 validateInput(mContext, request.getCallingPackageName());
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800140
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800141 synchronized (mLock) {
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700142 UserState userState = getCallingUserStateLocked();
143 if (!userState.bindLocked()) {
144 callback.onFailure();
145 } else if (userState.isBoundLocked()) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100146 userState.mService.onSuggestSelection(sessionId, request, callback);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800147 } else {
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700148 userState.mPendingRequests.add(new PendingRequest(
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100149 () -> onSuggestSelection(sessionId, request, callback),
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700150 callback::onFailure, callback.asBinder(), this, userState));
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800151 }
152 }
153 }
154
155 @Override
156 public void onClassifyText(
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100157 TextClassificationSessionId sessionId,
158 TextClassification.Request request, ITextClassificationCallback callback)
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800159 throws RemoteException {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100160 Preconditions.checkNotNull(request);
161 Preconditions.checkNotNull(callback);
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000162 validateInput(mContext, request.getCallingPackageName());
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800163
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800164 synchronized (mLock) {
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700165 UserState userState = getCallingUserStateLocked();
166 if (!userState.bindLocked()) {
167 callback.onFailure();
168 } else if (userState.isBoundLocked()) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100169 userState.mService.onClassifyText(sessionId, request, callback);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800170 } else {
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700171 userState.mPendingRequests.add(new PendingRequest(
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100172 () -> onClassifyText(sessionId, request, callback),
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700173 callback::onFailure, callback.asBinder(), this, userState));
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800174 }
175 }
176 }
177
178 @Override
179 public void onGenerateLinks(
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100180 TextClassificationSessionId sessionId,
181 TextLinks.Request request, ITextLinksCallback callback)
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800182 throws RemoteException {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100183 Preconditions.checkNotNull(request);
184 Preconditions.checkNotNull(callback);
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000185 validateInput(mContext, request.getCallingPackageName());
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800186
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800187 synchronized (mLock) {
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700188 UserState userState = getCallingUserStateLocked();
189 if (!userState.bindLocked()) {
190 callback.onFailure();
191 } else if (userState.isBoundLocked()) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100192 userState.mService.onGenerateLinks(sessionId, request, callback);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800193 } else {
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700194 userState.mPendingRequests.add(new PendingRequest(
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100195 () -> onGenerateLinks(sessionId, request, callback),
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700196 callback::onFailure, callback.asBinder(), this, userState));
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800197 }
198 }
199 }
200
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000201 @Override
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100202 public void onSelectionEvent(
203 TextClassificationSessionId sessionId, SelectionEvent event) throws RemoteException {
204 Preconditions.checkNotNull(event);
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000205 validateInput(mContext, event.getPackageName());
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000206
207 synchronized (mLock) {
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700208 UserState userState = getCallingUserStateLocked();
209 if (userState.isBoundLocked()) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100210 userState.mService.onSelectionEvent(sessionId, event);
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000211 } else {
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700212 userState.mPendingRequests.add(new PendingRequest(
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100213 () -> onSelectionEvent(sessionId, event),
214 null /* onServiceFailure */, null /* binder */, this, userState));
215 }
216 }
217 }
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000218 @Override
219 public void onTextClassifierEvent(
220 TextClassificationSessionId sessionId,
221 TextClassifierEvent event) throws RemoteException {
222 Preconditions.checkNotNull(event);
223 final String packageName = event.getEventContext() == null
224 ? null
225 : event.getEventContext().getPackageName();
226 validateInput(mContext, packageName);
227
228 synchronized (mLock) {
229 UserState userState = getCallingUserStateLocked();
230 if (userState.isBoundLocked()) {
231 userState.mService.onTextClassifierEvent(sessionId, event);
232 } else {
233 userState.mPendingRequests.add(new PendingRequest(
234 () -> onTextClassifierEvent(sessionId, event),
235 null /* onServiceFailure */, null /* binder */, this, userState));
236 }
237 }
238 }
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100239
240 @Override
Tony Mak0be540b2018-11-09 16:58:35 +0000241 public void onDetectLanguage(
242 TextClassificationSessionId sessionId,
243 TextLanguage.Request request,
244 ITextLanguageCallback callback) throws RemoteException {
245 Preconditions.checkNotNull(request);
246 Preconditions.checkNotNull(callback);
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000247 validateInput(mContext, request.getCallingPackageName());
Tony Mak0be540b2018-11-09 16:58:35 +0000248
249 synchronized (mLock) {
250 UserState userState = getCallingUserStateLocked();
251 if (!userState.bindLocked()) {
252 callback.onFailure();
253 } else if (userState.isBoundLocked()) {
254 userState.mService.onDetectLanguage(sessionId, request, callback);
255 } else {
256 userState.mPendingRequests.add(new PendingRequest(
257 () -> onDetectLanguage(sessionId, request, callback),
258 callback::onFailure, callback.asBinder(), this, userState));
259 }
260 }
261 }
262
263 @Override
264 public void onSuggestConversationActions(
265 TextClassificationSessionId sessionId,
266 ConversationActions.Request request,
267 IConversationActionsCallback callback) throws RemoteException {
268 Preconditions.checkNotNull(request);
269 Preconditions.checkNotNull(callback);
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000270 validateInput(mContext, request.getCallingPackageName());
Tony Mak0be540b2018-11-09 16:58:35 +0000271
272 synchronized (mLock) {
273 UserState userState = getCallingUserStateLocked();
274 if (!userState.bindLocked()) {
275 callback.onFailure();
276 } else if (userState.isBoundLocked()) {
277 userState.mService.onSuggestConversationActions(sessionId, request, callback);
278 } else {
279 userState.mPendingRequests.add(new PendingRequest(
280 () -> onSuggestConversationActions(sessionId, request, callback),
281 callback::onFailure, callback.asBinder(), this, userState));
282 }
283 }
284 }
285
286 @Override
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100287 public void onCreateTextClassificationSession(
288 TextClassificationContext classificationContext, TextClassificationSessionId sessionId)
289 throws RemoteException {
290 Preconditions.checkNotNull(sessionId);
291 Preconditions.checkNotNull(classificationContext);
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000292 validateInput(mContext, classificationContext.getPackageName());
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100293
294 synchronized (mLock) {
295 UserState userState = getCallingUserStateLocked();
296 if (userState.isBoundLocked()) {
297 userState.mService.onCreateTextClassificationSession(
298 classificationContext, sessionId);
299 } else {
300 userState.mPendingRequests.add(new PendingRequest(
301 () -> onCreateTextClassificationSession(classificationContext, sessionId),
302 null /* onServiceFailure */, null /* binder */, this, userState));
303 }
304 }
305 }
306
307 @Override
308 public void onDestroyTextClassificationSession(TextClassificationSessionId sessionId)
309 throws RemoteException {
310 Preconditions.checkNotNull(sessionId);
311
312 synchronized (mLock) {
313 UserState userState = getCallingUserStateLocked();
314 if (userState.isBoundLocked()) {
315 userState.mService.onDestroyTextClassificationSession(sessionId);
316 } else {
317 userState.mPendingRequests.add(new PendingRequest(
318 () -> onDestroyTextClassificationSession(sessionId),
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700319 null /* onServiceFailure */, null /* binder */, this, userState));
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000320 }
321 }
322 }
323
Andreas Gamped6d42062018-07-20 13:08:21 -0700324 @GuardedBy("mLock")
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700325 private UserState getCallingUserStateLocked() {
326 return getUserStateLocked(UserHandle.getCallingUserId());
327 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800328
Andreas Gamped6d42062018-07-20 13:08:21 -0700329 @GuardedBy("mLock")
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700330 private UserState getUserStateLocked(int userId) {
331 UserState result = mUserStates.get(userId);
332 if (result == null) {
333 result = new UserState(userId, mContext, mLock);
334 mUserStates.put(userId, result);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800335 }
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700336 return result;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800337 }
338
Andreas Gamped6d42062018-07-20 13:08:21 -0700339 @GuardedBy("mLock")
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700340 UserState peekUserStateLocked(int userId) {
341 return mUserStates.get(userId);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800342 }
343
Tony Makf93e9e52018-07-16 14:46:29 +0200344 @Override
345 protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
346 if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, fout)) return;
347 IndentingPrintWriter pw = new IndentingPrintWriter(fout, " ");
348 TextClassificationManager tcm = mContext.getSystemService(TextClassificationManager.class);
349 tcm.dump(pw);
Felipe Lemef8192132018-10-12 13:42:40 -0700350
351 pw.printPair("context", mContext); pw.println();
352 synchronized (mLock) {
353 int size = mUserStates.size();
354 pw.print("Number user states: "); pw.println(size);
355 if (size > 0) {
356 for (int i = 0; i < size; i++) {
357 pw.increaseIndent();
358 UserState userState = mUserStates.valueAt(i);
359 pw.print(i); pw.print(":"); userState.dump(pw); pw.println();
360 pw.decreaseIndent();
361 }
362 }
363 }
Tony Makf93e9e52018-07-16 14:46:29 +0200364 }
365
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700366 private static final class PendingRequest implements IBinder.DeathRecipient {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800367
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000368 @Nullable private final IBinder mBinder;
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700369 @NonNull private final Runnable mRequest;
370 @Nullable private final Runnable mOnServiceFailure;
371 @GuardedBy("mLock")
372 @NonNull private final UserState mOwningUser;
373 @NonNull private final TextClassificationManagerService mService;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800374
375 /**
376 * Initializes a new pending request.
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800377 * @param request action to perform when the service is bound
378 * @param onServiceFailure action to perform when the service dies or disconnects
379 * @param binder binder to the process that made this pending request
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700380 * @param service
381 * @param owningUser
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800382 */
383 PendingRequest(
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700384 @NonNull ThrowingRunnable request, @Nullable ThrowingRunnable onServiceFailure,
385 @Nullable IBinder binder,
386 TextClassificationManagerService service,
387 UserState owningUser) {
388 mRequest =
389 logOnFailure(Preconditions.checkNotNull(request), "handling pending request");
390 mOnServiceFailure =
391 logOnFailure(onServiceFailure, "notifying callback of service failure");
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000392 mBinder = binder;
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700393 mService = service;
394 mOwningUser = owningUser;
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000395 if (mBinder != null) {
396 try {
397 mBinder.linkToDeath(this, 0);
398 } catch (RemoteException e) {
399 e.printStackTrace();
400 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800401 }
402 }
403
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800404 @Override
405 public void binderDied() {
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700406 synchronized (mService.mLock) {
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800407 // No need to handle this pending request anymore. Remove.
408 removeLocked();
409 }
410 }
411
412 @GuardedBy("mLock")
413 private void removeLocked() {
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700414 mOwningUser.mPendingRequests.remove(this);
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000415 if (mBinder != null) {
416 mBinder.unlinkToDeath(this, 0);
417 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800418 }
419 }
420
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700421 private static Runnable logOnFailure(@Nullable ThrowingRunnable r, String opDesc) {
422 if (r == null) return null;
423 return FunctionalUtils.handleExceptions(r,
424 e -> Slog.d(LOG_TAG, "Error " + opDesc + ": " + e.getMessage()));
425 }
426
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000427 private static void validateInput(Context context, @Nullable String packageName)
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000428 throws RemoteException {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000429 if (packageName == null) return;
430
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000431 try {
432 final int uid = context.getPackageManager()
Eugene Susla7d3ea932018-11-26 13:59:09 -0800433 .getPackageUidAsUser(packageName, UserHandle.getCallingUserId());
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000434 Preconditions.checkArgument(Binder.getCallingUid() == uid);
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000435 } catch (Exception e) {
436 throw new RemoteException(
437 String.format("Invalid package: name=%s, error=%s", packageName, e));
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000438 }
439 }
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700440
441 private static final class UserState {
442 @UserIdInt final int mUserId;
443 final TextClassifierServiceConnection mConnection = new TextClassifierServiceConnection();
444 @GuardedBy("mLock")
445 final Queue<PendingRequest> mPendingRequests = new ArrayDeque<>();
446 @GuardedBy("mLock")
447 ITextClassifierService mService;
448 @GuardedBy("mLock")
449 boolean mBinding;
450
451 private final Context mContext;
452 private final Object mLock;
453
454 private UserState(int userId, Context context, Object lock) {
455 mUserId = userId;
456 mContext = Preconditions.checkNotNull(context);
457 mLock = Preconditions.checkNotNull(lock);
458 }
459
460 @GuardedBy("mLock")
461 boolean isBoundLocked() {
462 return mService != null;
463 }
464
465 @GuardedBy("mLock")
466 private void handlePendingRequestsLocked() {
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700467 PendingRequest request;
468 while ((request = mPendingRequests.poll()) != null) {
469 if (isBoundLocked()) {
470 request.mRequest.run();
471 } else {
472 if (request.mOnServiceFailure != null) {
473 request.mOnServiceFailure.run();
474 }
475 }
476
477 if (request.mBinder != null) {
478 request.mBinder.unlinkToDeath(request, 0);
479 }
480 }
481 }
482
Andreas Gamped6d42062018-07-20 13:08:21 -0700483 @GuardedBy("mLock")
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700484 private boolean bindIfHasPendingRequestsLocked() {
485 return !mPendingRequests.isEmpty() && bindLocked();
486 }
487
488 /**
489 * @return true if the service is bound or in the process of being bound.
490 * Returns false otherwise.
491 */
Andreas Gamped6d42062018-07-20 13:08:21 -0700492 @GuardedBy("mLock")
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700493 private boolean bindLocked() {
494 if (isBoundLocked() || mBinding) {
495 return true;
496 }
497
498 // TODO: Handle bind timeout.
499 final boolean willBind;
500 final long identity = Binder.clearCallingIdentity();
501 try {
502 ComponentName componentName =
503 TextClassifierService.getServiceComponentName(mContext);
504 if (componentName == null) {
505 // Might happen if the storage is encrypted and the user is not unlocked
506 return false;
507 }
508 Intent serviceIntent = new Intent(TextClassifierService.SERVICE_INTERFACE)
509 .setComponent(componentName);
510 Slog.d(LOG_TAG, "Binding to " + serviceIntent.getComponent());
511 willBind = mContext.bindServiceAsUser(
512 serviceIntent, mConnection,
Dianne Hackbornc390aa82019-01-09 16:38:22 -0800513 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
514 | Context.BIND_RESTRICT_ASSOCIATIONS,
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700515 UserHandle.of(mUserId));
516 mBinding = willBind;
517 } finally {
518 Binder.restoreCallingIdentity(identity);
519 }
520 return willBind;
521 }
522
Felipe Lemef8192132018-10-12 13:42:40 -0700523 private void dump(IndentingPrintWriter pw) {
524 pw.printPair("context", mContext);
525 pw.printPair("userId", mUserId);
526 synchronized (mLock) {
527 pw.printPair("binding", mBinding);
528 pw.printPair("numberRequests", mPendingRequests.size());
529 }
530 }
531
Eugene Susla7b24b2b2018-03-16 14:33:31 -0700532 private final class TextClassifierServiceConnection implements ServiceConnection {
533 @Override
534 public void onServiceConnected(ComponentName name, IBinder service) {
535 init(ITextClassifierService.Stub.asInterface(service));
536 }
537
538 @Override
539 public void onServiceDisconnected(ComponentName name) {
540 cleanupService();
541 }
542
543 @Override
544 public void onBindingDied(ComponentName name) {
545 cleanupService();
546 }
547
548 @Override
549 public void onNullBinding(ComponentName name) {
550 cleanupService();
551 }
552
553 void cleanupService() {
554 init(null);
555 }
556
557 private void init(@Nullable ITextClassifierService service) {
558 synchronized (mLock) {
559 mService = service;
560 mBinding = false;
561 handlePendingRequestsLocked();
562 }
563 }
564 }
565 }
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800566}