blob: ae707756e6904131bce27dc1a1ddda092f0ccdb2 [file] [log] [blame]
Felipe Leme749b8892018-12-03 16:30:30 -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 */
16package android.service.contentcapture;
17
18import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
19
20import android.annotation.CallSuper;
21import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.annotation.SystemApi;
Felipe Leme19652c02019-02-04 13:01:29 -080024import android.annotation.TestApi;
Felipe Leme749b8892018-12-03 16:30:30 -080025import android.app.Service;
26import android.content.ComponentName;
27import android.content.Intent;
Felipe Lemeb96878492018-12-17 12:22:29 -080028import android.content.pm.ParceledListSlice;
29import android.os.Binder;
30import android.os.Bundle;
Felipe Leme749b8892018-12-03 16:30:30 -080031import android.os.Handler;
32import android.os.IBinder;
33import android.os.Looper;
34import android.os.RemoteException;
Felipe Lemebb98ed62018-12-21 09:29:27 -080035import android.service.autofill.AutofillService;
Felipe Lemeb96878492018-12-17 12:22:29 -080036import android.util.ArrayMap;
Felipe Leme749b8892018-12-03 16:30:30 -080037import android.util.Log;
Felipe Lemeb96878492018-12-17 12:22:29 -080038import android.util.Slog;
Felipe Lemeaa5088e2018-12-10 14:53:58 -080039import android.view.contentcapture.ContentCaptureContext;
Felipe Leme749b8892018-12-03 16:30:30 -080040import android.view.contentcapture.ContentCaptureEvent;
Felipe Lemeb96878492018-12-17 12:22:29 -080041import android.view.contentcapture.ContentCaptureManager;
42import android.view.contentcapture.ContentCaptureSession;
Felipe Lemeaa5088e2018-12-10 14:53:58 -080043import android.view.contentcapture.ContentCaptureSessionId;
Felipe Lemeb96878492018-12-17 12:22:29 -080044import android.view.contentcapture.IContentCaptureDirectManager;
Felipe Leme87a9dc92018-12-18 14:28:07 -080045import android.view.contentcapture.MainContentCaptureSession;
Felipe Leme62e45b02019-01-10 10:22:00 -080046import android.view.contentcapture.UserDataRemovalRequest;
Felipe Leme749b8892018-12-03 16:30:30 -080047
Felipe Lemeb96878492018-12-17 12:22:29 -080048import com.android.internal.os.IResultReceiver;
49
50import java.io.FileDescriptor;
51import java.io.PrintWriter;
Felipe Leme7a3c9f52019-02-13 16:32:49 -080052import java.util.ArrayList;
Felipe Leme749b8892018-12-03 16:30:30 -080053import java.util.List;
Felipe Leme7a3c9f52019-02-13 16:32:49 -080054import java.util.Set;
Felipe Leme749b8892018-12-03 16:30:30 -080055
56/**
57 * A service used to capture the content of the screen to provide contextual data in other areas of
58 * the system such as Autofill.
59 *
60 * @hide
61 */
62@SystemApi
Felipe Leme19652c02019-02-04 13:01:29 -080063@TestApi
Felipe Leme749b8892018-12-03 16:30:30 -080064public abstract class ContentCaptureService extends Service {
65
66 private static final String TAG = ContentCaptureService.class.getSimpleName();
67
Felipe Lemeaa5088e2018-12-10 14:53:58 -080068 // TODO(b/121044306): STOPSHIP use dynamic value, or change to false
Felipe Leme749b8892018-12-03 16:30:30 -080069 static final boolean DEBUG = true;
70 static final boolean VERBOSE = false;
71
72 /**
73 * The {@link Intent} that must be declared as handled by the service.
74 *
75 * <p>To be supported, the service must also require the
76 * {@link android.Manifest.permission#BIND_CONTENT_CAPTURE_SERVICE} permission so
77 * that other applications can not abuse it.
78 */
79 public static final String SERVICE_INTERFACE =
80 "android.service.contentcapture.ContentCaptureService";
81
82 private Handler mHandler;
Felipe Lemeaf4e4fd2019-01-15 08:32:42 -080083 private IContentCaptureServiceCallback mCallback;
Felipe Leme749b8892018-12-03 16:30:30 -080084
Felipe Lemeb96878492018-12-17 12:22:29 -080085 /**
86 * Binder that receives calls from the system server.
87 */
88 private final IContentCaptureService mServerInterface = new IContentCaptureService.Stub() {
Felipe Leme749b8892018-12-03 16:30:30 -080089
90 @Override
Felipe Lemeaf4e4fd2019-01-15 08:32:42 -080091 public void onConnected(IBinder callback) {
92 mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnConnected,
93 ContentCaptureService.this, callback));
94 }
95
96 @Override
97 public void onDisconnected() {
98 mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnDisconnected,
99 ContentCaptureService.this));
Felipe Lemebb98ed62018-12-21 09:29:27 -0800100 }
101
102 @Override
Felipe Lemeb96878492018-12-17 12:22:29 -0800103 public void onSessionStarted(ContentCaptureContext context, String sessionId, int uid,
104 IResultReceiver clientReceiver) {
105 mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnCreateSession,
106 ContentCaptureService.this, context, sessionId, uid, clientReceiver));
Felipe Leme749b8892018-12-03 16:30:30 -0800107 }
108
109 @Override
110 public void onActivitySnapshot(String sessionId, SnapshotData snapshotData) {
111 mHandler.sendMessage(
112 obtainMessage(ContentCaptureService::handleOnActivitySnapshot,
113 ContentCaptureService.this, sessionId, snapshotData));
114 }
Felipe Lemeb96878492018-12-17 12:22:29 -0800115
116 @Override
117 public void onSessionFinished(String sessionId) {
118 mHandler.sendMessage(obtainMessage(ContentCaptureService::handleFinishSession,
119 ContentCaptureService.this, sessionId));
120 }
Adam He3d0409b2019-01-15 14:22:04 -0800121
122 @Override
123 public void onUserDataRemovalRequest(UserDataRemovalRequest request) {
124 mHandler.sendMessage(
125 obtainMessage(ContentCaptureService::handleOnUserDataRemovalRequest,
126 ContentCaptureService.this, request));
127 }
Felipe Leme749b8892018-12-03 16:30:30 -0800128 };
129
Felipe Lemeb96878492018-12-17 12:22:29 -0800130 /**
131 * Binder that receives calls from the app.
132 */
133 private final IContentCaptureDirectManager mClientInterface =
134 new IContentCaptureDirectManager.Stub() {
135
136 @Override
Felipe Lemefc24bea2018-12-18 13:19:01 -0800137 public void sendEvents(@SuppressWarnings("rawtypes") ParceledListSlice events) {
Felipe Lemeb96878492018-12-17 12:22:29 -0800138 mHandler.sendMessage(obtainMessage(ContentCaptureService::handleSendEvents,
Felipe Lemefc24bea2018-12-18 13:19:01 -0800139 ContentCaptureService.this, Binder.getCallingUid(), events));
Felipe Lemeb96878492018-12-17 12:22:29 -0800140 }
141 };
142
143 /**
Felipe Lemee127c9a2019-01-04 14:56:38 -0800144 * UIDs associated with each session.
Felipe Lemeb96878492018-12-17 12:22:29 -0800145 *
146 * <p>This map is populated when an session is started, which is called by the system server
147 * and can be trusted. Then subsequent calls made by the app are verified against this map.
148 */
Felipe Lemee127c9a2019-01-04 14:56:38 -0800149 private final ArrayMap<String, Integer> mSessionUids = new ArrayMap<>();
Felipe Lemeb96878492018-12-17 12:22:29 -0800150
Felipe Leme749b8892018-12-03 16:30:30 -0800151 @CallSuper
152 @Override
153 public void onCreate() {
154 super.onCreate();
155 mHandler = new Handler(Looper.getMainLooper(), null, true);
156 }
157
158 /** @hide */
159 @Override
160 public final IBinder onBind(Intent intent) {
161 if (SERVICE_INTERFACE.equals(intent.getAction())) {
Felipe Lemeb96878492018-12-17 12:22:29 -0800162 return mServerInterface.asBinder();
Felipe Leme749b8892018-12-03 16:30:30 -0800163 }
164 Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
165 return null;
166 }
167
168 /**
Felipe Leme7a3c9f52019-02-13 16:32:49 -0800169 * @deprecated use {@link #setContentCaptureWhitelist(Set, Set)} instead
Felipe Leme749b8892018-12-03 16:30:30 -0800170 */
Felipe Leme7a3c9f52019-02-13 16:32:49 -0800171 @Deprecated
Felipe Leme749b8892018-12-03 16:30:30 -0800172 public final void setContentCaptureWhitelist(@Nullable List<String> packages,
173 @Nullable List<ComponentName> activities) {
Felipe Lemeaf4e4fd2019-01-15 08:32:42 -0800174 final IContentCaptureServiceCallback callback = mCallback;
175 if (callback == null) {
176 Log.w(TAG, "setContentCaptureWhitelist(): no server callback");
177 return;
178 }
179 try {
180 callback.setContentCaptureWhitelist(packages, activities);
181 } catch (RemoteException e) {
182 e.rethrowFromSystemServer();
183 }
Felipe Leme749b8892018-12-03 16:30:30 -0800184 }
185
186 /**
Felipe Leme7a3c9f52019-02-13 16:32:49 -0800187 * Explicitly limits content capture to the given packages and activities.
188 *
189 * <p>To reset the whitelist, call it passing {@code null} to both arguments.
190 *
191 * <p>Useful when the service wants to restrict content capture to a category of apps, like
192 * chat apps. For example, if the service wants to support view captures on all activities of
193 * app {@code ChatApp1} and just activities {@code act1} and {@code act2} of {@code ChatApp2},
194 * it would call: {@code setContentCaptureWhitelist(Sets.newArraySet("ChatApp1"),
195 * Sets.newArraySet(new ComponentName("ChatApp2", "act1"),
196 * new ComponentName("ChatApp2", "act2")));}
197 */
198 public final void setContentCaptureWhitelist(@Nullable Set<String> packages,
199 @Nullable Set<ComponentName> activities) {
200 setContentCaptureWhitelist(toList(packages), toList(activities));
201 }
202
203 private <T> ArrayList<T> toList(@Nullable Set<T> set) {
204 return set == null ? null : new ArrayList<T>(set);
205 }
206
207 /**
Felipe Lemebb98ed62018-12-21 09:29:27 -0800208 * Called when the Android system connects to service.
209 *
210 * <p>You should generally do initialization here rather than in {@link #onCreate}.
211 */
212 public void onConnected() {
213 Slog.i(TAG, "bound to " + getClass().getName());
214 }
215
216 /**
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800217 * Creates a new content capture session.
Felipe Leme749b8892018-12-03 16:30:30 -0800218 *
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800219 * @param context content capture context
Felipe Leme749b8892018-12-03 16:30:30 -0800220 * @param sessionId the session's Id
221 */
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800222 public void onCreateContentCaptureSession(@NonNull ContentCaptureContext context,
223 @NonNull ContentCaptureSessionId sessionId) {
Felipe Leme749b8892018-12-03 16:30:30 -0800224 if (VERBOSE) {
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800225 Log.v(TAG, "onCreateContentCaptureSession(id=" + sessionId + ", ctx=" + context + ")");
Felipe Leme749b8892018-12-03 16:30:30 -0800226 }
227 }
228
229 /**
Felipe Leme749b8892018-12-03 16:30:30 -0800230 *
Felipe Lemefc24bea2018-12-18 13:19:01 -0800231 * @deprecated use {@link #onContentCaptureEvent(ContentCaptureSessionId, ContentCaptureEvent)}
232 * instead.
Felipe Leme749b8892018-12-03 16:30:30 -0800233 */
Felipe Lemefc24bea2018-12-18 13:19:01 -0800234 @Deprecated
Felipe Lemeb96878492018-12-17 12:22:29 -0800235 public void onContentCaptureEventsRequest(@NonNull ContentCaptureSessionId sessionId,
236 @NonNull ContentCaptureEventsRequest request) {
237 if (VERBOSE) Log.v(TAG, "onContentCaptureEventsRequest(id=" + sessionId + ")");
238 }
Felipe Leme749b8892018-12-03 16:30:30 -0800239
240 /**
Felipe Lemefc24bea2018-12-18 13:19:01 -0800241 * Notifies the service of {@link ContentCaptureEvent events} associated with a content capture
242 * session.
243 *
244 * @param sessionId the session's Id
245 * @param event the event
246 */
247 public void onContentCaptureEvent(@NonNull ContentCaptureSessionId sessionId,
248 @NonNull ContentCaptureEvent event) {
249 if (VERBOSE) Log.v(TAG, "onContentCaptureEventsRequest(id=" + sessionId + ")");
250 onContentCaptureEventsRequest(sessionId, new ContentCaptureEventsRequest(event));
251 }
Felipe Leme62e45b02019-01-10 10:22:00 -0800252
253 /**
254 * Notifies the service that the app requested to remove data associated with the user.
255 *
256 * @param request the user data requested to be removed
257 */
258 public void onUserDataRemovalRequest(@NonNull UserDataRemovalRequest request) {
259 if (VERBOSE) Log.v(TAG, "onUserDataRemovalRequest()");
260 }
261
Felipe Lemefc24bea2018-12-18 13:19:01 -0800262 /**
Felipe Leme749b8892018-12-03 16:30:30 -0800263 * Notifies the service of {@link SnapshotData snapshot data} associated with a session.
264 *
265 * @param sessionId the session's Id
266 * @param snapshotData the data
267 */
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800268 public void onActivitySnapshot(@NonNull ContentCaptureSessionId sessionId,
Felipe Leme749b8892018-12-03 16:30:30 -0800269 @NonNull SnapshotData snapshotData) {}
270
271 /**
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800272 * Destroys the content capture session.
Felipe Leme749b8892018-12-03 16:30:30 -0800273 *
274 * @param sessionId the id of the session to destroy
Felipe Lemeb96878492018-12-17 12:22:29 -0800275 * */
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800276 public void onDestroyContentCaptureSession(@NonNull ContentCaptureSessionId sessionId) {
Felipe Lemeb96878492018-12-17 12:22:29 -0800277 if (VERBOSE) Log.v(TAG, "onDestroyContentCaptureSession(id=" + sessionId + ")");
278 }
279
Felipe Lemebb98ed62018-12-21 09:29:27 -0800280 /**
281 * Called when the Android system disconnects from the service.
282 *
283 * <p> At this point this service may no longer be an active {@link AutofillService}.
284 */
285 public void onDisconnected() {
286 Slog.i(TAG, "unbinding from " + getClass().getName());
287 }
288
Felipe Lemeb96878492018-12-17 12:22:29 -0800289 @Override
290 @CallSuper
291 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Felipe Lemee127c9a2019-01-04 14:56:38 -0800292 final int size = mSessionUids.size();
Felipe Lemeb96878492018-12-17 12:22:29 -0800293 pw.print("Number sessions: "); pw.println(size);
294 if (size > 0) {
295 final String prefix = " ";
296 for (int i = 0; i < size; i++) {
Felipe Lemee127c9a2019-01-04 14:56:38 -0800297 pw.print(prefix); pw.print(mSessionUids.keyAt(i));
298 pw.print(": uid="); pw.println(mSessionUids.valueAt(i));
Felipe Lemeb96878492018-12-17 12:22:29 -0800299 }
Felipe Leme749b8892018-12-03 16:30:30 -0800300 }
301 }
302
Felipe Lemeaf4e4fd2019-01-15 08:32:42 -0800303 private void handleOnConnected(@NonNull IBinder callback) {
304 mCallback = IContentCaptureServiceCallback.Stub.asInterface(callback);
305 onConnected();
306 }
307
308 private void handleOnDisconnected() {
309 onDisconnected();
310 mCallback = null;
Felipe Lemebb98ed62018-12-21 09:29:27 -0800311 }
312
Felipe Leme749b8892018-12-03 16:30:30 -0800313 //TODO(b/111276913): consider caching the InteractionSessionId for the lifetime of the session,
314 // so we don't need to create a temporary InteractionSessionId for each event.
315
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800316 private void handleOnCreateSession(@NonNull ContentCaptureContext context,
Felipe Lemeb96878492018-12-17 12:22:29 -0800317 @NonNull String sessionId, int uid, IResultReceiver clientReceiver) {
Felipe Lemee127c9a2019-01-04 14:56:38 -0800318 mSessionUids.put(sessionId, uid);
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800319 onCreateContentCaptureSession(context, new ContentCaptureSessionId(sessionId));
Adam He328c0e32019-01-03 15:19:22 -0800320
Felipe Leme01b87492019-01-15 13:26:52 -0800321 final int clientFlags = context.getFlags();
322 int stateFlags = 0;
323 if ((clientFlags & ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE) != 0) {
324 stateFlags |= ContentCaptureSession.STATE_FLAG_SECURE;
Adam He328c0e32019-01-03 15:19:22 -0800325 }
Felipe Leme01b87492019-01-15 13:26:52 -0800326 if ((clientFlags & ContentCaptureContext.FLAG_DISABLED_BY_APP) != 0) {
327 stateFlags |= ContentCaptureSession.STATE_BY_APP;
Adam He6079d152019-01-10 11:37:17 -0800328 }
Felipe Leme01b87492019-01-15 13:26:52 -0800329 if (stateFlags == 0) {
330 stateFlags = ContentCaptureSession.STATE_ACTIVE;
331 } else {
332 stateFlags |= ContentCaptureSession.STATE_DISABLED;
Adam He328c0e32019-01-03 15:19:22 -0800333
Felipe Leme01b87492019-01-15 13:26:52 -0800334 }
335 setClientState(clientReceiver, stateFlags, mClientInterface.asBinder());
Felipe Leme749b8892018-12-03 16:30:30 -0800336 }
337
Felipe Lemefc24bea2018-12-18 13:19:01 -0800338 private void handleSendEvents(int uid,
339 @NonNull ParceledListSlice<ContentCaptureEvent> parceledEvents) {
340
341 // Most events belong to the same session, so we can keep a reference to the last one
342 // to avoid creating too many ContentCaptureSessionId objects
343 String lastSessionId = null;
344 ContentCaptureSessionId sessionId = null;
345
346 final List<ContentCaptureEvent> events = parceledEvents.getList();
347 for (int i = 0; i < events.size(); i++) {
348 final ContentCaptureEvent event = events.get(i);
Felipe Leme87a9dc92018-12-18 14:28:07 -0800349 if (!handleIsRightCallerFor(event, uid)) continue;
Felipe Lemefc24bea2018-12-18 13:19:01 -0800350 String sessionIdString = event.getSessionId();
351 if (!sessionIdString.equals(lastSessionId)) {
Felipe Lemefc24bea2018-12-18 13:19:01 -0800352 sessionId = new ContentCaptureSessionId(sessionIdString);
353 lastSessionId = sessionIdString;
354 }
Felipe Leme87a9dc92018-12-18 14:28:07 -0800355 switch (event.getType()) {
356 case ContentCaptureEvent.TYPE_SESSION_STARTED:
Felipe Leme4eecbe62019-02-11 17:50:17 -0800357 final ContentCaptureContext clientContext = event.getContentCaptureContext();
Felipe Leme87a9dc92018-12-18 14:28:07 -0800358 clientContext.setParentSessionId(event.getParentSessionId());
Felipe Lemee127c9a2019-01-04 14:56:38 -0800359 mSessionUids.put(sessionIdString, uid);
Felipe Leme87a9dc92018-12-18 14:28:07 -0800360 onCreateContentCaptureSession(clientContext, sessionId);
361 break;
362 case ContentCaptureEvent.TYPE_SESSION_FINISHED:
Felipe Lemee127c9a2019-01-04 14:56:38 -0800363 mSessionUids.remove(sessionIdString);
Felipe Leme87a9dc92018-12-18 14:28:07 -0800364 onDestroyContentCaptureSession(sessionId);
365 break;
366 default:
367 onContentCaptureEvent(sessionId, event);
368 }
Felipe Lemeb96878492018-12-17 12:22:29 -0800369 }
Felipe Leme749b8892018-12-03 16:30:30 -0800370 }
371
372 private void handleOnActivitySnapshot(@NonNull String sessionId,
373 @NonNull SnapshotData snapshotData) {
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800374 onActivitySnapshot(new ContentCaptureSessionId(sessionId), snapshotData);
Felipe Leme749b8892018-12-03 16:30:30 -0800375 }
376
Felipe Lemeb96878492018-12-17 12:22:29 -0800377 private void handleFinishSession(@NonNull String sessionId) {
Felipe Lemee127c9a2019-01-04 14:56:38 -0800378 mSessionUids.remove(sessionId);
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800379 onDestroyContentCaptureSession(new ContentCaptureSessionId(sessionId));
Felipe Leme749b8892018-12-03 16:30:30 -0800380 }
Felipe Lemeb96878492018-12-17 12:22:29 -0800381
Adam He3d0409b2019-01-15 14:22:04 -0800382 private void handleOnUserDataRemovalRequest(@NonNull UserDataRemovalRequest request) {
383 onUserDataRemovalRequest(request);
384 }
385
Felipe Lemeb96878492018-12-17 12:22:29 -0800386 /**
Felipe Leme87a9dc92018-12-18 14:28:07 -0800387 * Checks if the given {@code uid} owns the session associated with the event.
Felipe Lemeb96878492018-12-17 12:22:29 -0800388 */
Felipe Leme87a9dc92018-12-18 14:28:07 -0800389 private boolean handleIsRightCallerFor(@NonNull ContentCaptureEvent event, int uid) {
390 final String sessionId;
391 switch (event.getType()) {
392 case ContentCaptureEvent.TYPE_SESSION_STARTED:
393 case ContentCaptureEvent.TYPE_SESSION_FINISHED:
394 sessionId = event.getParentSessionId();
395 break;
396 default:
397 sessionId = event.getSessionId();
398 }
Felipe Lemee127c9a2019-01-04 14:56:38 -0800399 final Integer rightUid = mSessionUids.get(sessionId);
Felipe Lemeb96878492018-12-17 12:22:29 -0800400 if (rightUid == null) {
Felipe Leme4eecbe62019-02-11 17:50:17 -0800401 if (VERBOSE) {
402 Log.v(TAG, "handleIsRightCallerFor(" + event + "): no session for " + sessionId
Felipe Lemee127c9a2019-01-04 14:56:38 -0800403 + ": " + mSessionUids);
404 }
405 // Just ignore, as the session could have been finished already
Felipe Lemeb96878492018-12-17 12:22:29 -0800406 return false;
407 }
408 if (rightUid != uid) {
409 Log.e(TAG, "invalid call from UID " + uid + ": session " + sessionId + " belongs to "
410 + rightUid);
411 //TODO(b/111276913): log metrics as this could be a malicious app forging a sessionId
412 return false;
413 }
414 return true;
415
416 }
417
418 /**
Adam He328c0e32019-01-03 15:19:22 -0800419 * Sends the state of the {@link ContentCaptureManager} in the client app.
Felipe Lemeb96878492018-12-17 12:22:29 -0800420 *
421 * @param clientReceiver receiver in the client app.
Adam He328c0e32019-01-03 15:19:22 -0800422 * @param sessionState state of the session
Felipe Lemeb96878492018-12-17 12:22:29 -0800423 * @param binder handle to the {@code IContentCaptureDirectManager} object that resides in the
424 * service.
425 * @hide
426 */
427 public static void setClientState(@NonNull IResultReceiver clientReceiver,
Adam He328c0e32019-01-03 15:19:22 -0800428 int sessionState, @Nullable IBinder binder) {
Felipe Lemeb96878492018-12-17 12:22:29 -0800429 try {
430 final Bundle extras;
431 if (binder != null) {
432 extras = new Bundle();
Felipe Leme87a9dc92018-12-18 14:28:07 -0800433 extras.putBinder(MainContentCaptureSession.EXTRA_BINDER, binder);
Felipe Lemeb96878492018-12-17 12:22:29 -0800434 } else {
435 extras = null;
436 }
Adam He328c0e32019-01-03 15:19:22 -0800437 clientReceiver.send(sessionState, extras);
Felipe Lemeb96878492018-12-17 12:22:29 -0800438 } catch (RemoteException e) {
439 Slog.w(TAG, "Error async reporting result to client: " + e);
440 }
441 }
Felipe Leme749b8892018-12-03 16:30:30 -0800442}