blob: ac2532dcea7df2d32bc6363b6f85a766eb4083e9 [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
Felipe Leme326f15a2019-02-19 09:42:24 -080018import static android.view.contentcapture.ContentCaptureHelper.sDebug;
19import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
Felipe Lemea8d33c22019-03-25 16:36:09 -070020import static android.view.contentcapture.ContentCaptureHelper.toList;
Felipe Leme08054202019-03-28 11:29:25 -070021import static android.view.contentcapture.ContentCaptureSession.NO_SESSION_ID;
Felipe Leme326f15a2019-02-19 09:42:24 -080022
Felipe Leme749b8892018-12-03 16:30:30 -080023import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
24
25import android.annotation.CallSuper;
26import android.annotation.NonNull;
27import android.annotation.Nullable;
28import android.annotation.SystemApi;
Felipe Leme19652c02019-02-04 13:01:29 -080029import android.annotation.TestApi;
Felipe Leme749b8892018-12-03 16:30:30 -080030import android.app.Service;
31import android.content.ComponentName;
Adam He420947c2019-01-23 15:59:09 -080032import android.content.ContentCaptureOptions;
Felipe Leme749b8892018-12-03 16:30:30 -080033import android.content.Intent;
Felipe Lemeb96878492018-12-17 12:22:29 -080034import android.content.pm.ParceledListSlice;
35import android.os.Binder;
36import android.os.Bundle;
Sergey Volnov6e049012020-01-10 18:10:48 +000037import android.os.CancellationSignal;
Felipe Leme749b8892018-12-03 16:30:30 -080038import android.os.Handler;
39import android.os.IBinder;
Sergey Volnov6e049012020-01-10 18:10:48 +000040import android.os.ICancellationSignal;
Felipe Leme749b8892018-12-03 16:30:30 -080041import android.os.Looper;
Sergey Volnov6e049012020-01-10 18:10:48 +000042import android.os.ParcelFileDescriptor;
Felipe Leme749b8892018-12-03 16:30:30 -080043import android.os.RemoteException;
44import android.util.Log;
Felipe Lemeb96878492018-12-17 12:22:29 -080045import android.util.Slog;
Felipe Leme08054202019-03-28 11:29:25 -070046import android.util.SparseIntArray;
Adam He420947c2019-01-23 15:59:09 -080047import android.util.StatsLog;
Felipe Leme790be042019-03-25 09:52:19 -070048import android.view.contentcapture.ContentCaptureCondition;
Felipe Lemeaa5088e2018-12-10 14:53:58 -080049import android.view.contentcapture.ContentCaptureContext;
Felipe Leme749b8892018-12-03 16:30:30 -080050import android.view.contentcapture.ContentCaptureEvent;
Felipe Lemeb96878492018-12-17 12:22:29 -080051import android.view.contentcapture.ContentCaptureManager;
52import android.view.contentcapture.ContentCaptureSession;
Felipe Lemeaa5088e2018-12-10 14:53:58 -080053import android.view.contentcapture.ContentCaptureSessionId;
Felipe Leme4439ee62019-05-08 10:27:49 -070054import android.view.contentcapture.DataRemovalRequest;
Sergey Volnov6e049012020-01-10 18:10:48 +000055import android.view.contentcapture.DataShareRequest;
Felipe Lemeb96878492018-12-17 12:22:29 -080056import android.view.contentcapture.IContentCaptureDirectManager;
Felipe Leme87a9dc92018-12-18 14:28:07 -080057import android.view.contentcapture.MainContentCaptureSession;
Felipe Leme749b8892018-12-03 16:30:30 -080058
Felipe Lemeb96878492018-12-17 12:22:29 -080059import com.android.internal.os.IResultReceiver;
Sergey Volnov6e049012020-01-10 18:10:48 +000060import com.android.internal.util.Preconditions;
Felipe Lemeb96878492018-12-17 12:22:29 -080061
62import java.io.FileDescriptor;
63import java.io.PrintWriter;
Sergey Volnov6e049012020-01-10 18:10:48 +000064import java.lang.ref.WeakReference;
Felipe Leme749b8892018-12-03 16:30:30 -080065import java.util.List;
Felipe Leme7a3c9f52019-02-13 16:32:49 -080066import java.util.Set;
Sergey Volnov6e049012020-01-10 18:10:48 +000067import java.util.concurrent.Executor;
68import java.util.function.Consumer;
Felipe Leme749b8892018-12-03 16:30:30 -080069
70/**
71 * A service used to capture the content of the screen to provide contextual data in other areas of
72 * the system such as Autofill.
73 *
74 * @hide
75 */
76@SystemApi
Felipe Leme19652c02019-02-04 13:01:29 -080077@TestApi
Felipe Leme749b8892018-12-03 16:30:30 -080078public abstract class ContentCaptureService extends Service {
79
80 private static final String TAG = ContentCaptureService.class.getSimpleName();
81
Felipe Leme749b8892018-12-03 16:30:30 -080082 /**
83 * The {@link Intent} that must be declared as handled by the service.
84 *
85 * <p>To be supported, the service must also require the
86 * {@link android.Manifest.permission#BIND_CONTENT_CAPTURE_SERVICE} permission so
87 * that other applications can not abuse it.
88 */
89 public static final String SERVICE_INTERFACE =
90 "android.service.contentcapture.ContentCaptureService";
91
Felipe Lemea5d5e2d2019-03-19 16:54:55 -070092 /**
93 * Name under which a ContentCaptureService component publishes information about itself.
94 *
95 * <p>This meta-data should reference an XML resource containing a
96 * <code>&lt;{@link
97 * android.R.styleable#ContentCaptureService content-capture-service}&gt;</code> tag.
98 *
Felipe Leme8b456582019-04-15 10:12:14 -070099 * <p>Here's an example of how to use it on {@code AndroidManifest.xml}:
100 *
101 * <pre>
102 * &lt;service android:name=".MyContentCaptureService"
103 * android:permission="android.permission.BIND_CONTENT_CAPTURE_SERVICE"&gt;
104 * &lt;intent-filter&gt;
105 * &lt;action android:name="android.service.contentcapture.ContentCaptureService" /&gt;
106 * &lt;/intent-filter&gt;
107 *
108 * &lt;meta-data
109 * android:name="android.content_capture"
110 * android:resource="@xml/my_content_capture_service"/&gt;
111 * &lt;/service&gt;
112 * </pre>
113 *
114 * <p>And then on {@code res/xml/my_content_capture_service.xml}:
115 *
116 * <pre>
117 * &lt;content-capture-service xmlns:android="http://schemas.android.com/apk/res/android"
118 * android:settingsActivity="my.package.MySettingsActivity"&gt;
119 * &lt;/content-capture-service&gt;
120 * </pre>
Felipe Lemea5d5e2d2019-03-19 16:54:55 -0700121 */
122 public static final String SERVICE_META_DATA = "android.content_capture";
123
Felipe Leme749b8892018-12-03 16:30:30 -0800124 private Handler mHandler;
Felipe Lemeaf4e4fd2019-01-15 08:32:42 -0800125 private IContentCaptureServiceCallback mCallback;
Felipe Leme749b8892018-12-03 16:30:30 -0800126
Adam He420947c2019-01-23 15:59:09 -0800127 private long mCallerMismatchTimeout = 1000;
128 private long mLastCallerMismatchLog;
129
Felipe Lemeb96878492018-12-17 12:22:29 -0800130 /**
131 * Binder that receives calls from the system server.
132 */
133 private final IContentCaptureService mServerInterface = new IContentCaptureService.Stub() {
Felipe Leme749b8892018-12-03 16:30:30 -0800134
135 @Override
Felipe Leme326f15a2019-02-19 09:42:24 -0800136 public void onConnected(IBinder callback, boolean verbose, boolean debug) {
137 sVerbose = verbose;
138 sDebug = debug;
Felipe Lemeaf4e4fd2019-01-15 08:32:42 -0800139 mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnConnected,
140 ContentCaptureService.this, callback));
141 }
142
143 @Override
144 public void onDisconnected() {
145 mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnDisconnected,
146 ContentCaptureService.this));
Felipe Lemebb98ed62018-12-21 09:29:27 -0800147 }
148
149 @Override
Felipe Leme08054202019-03-28 11:29:25 -0700150 public void onSessionStarted(ContentCaptureContext context, int sessionId, int uid,
Felipe Leme518fb622019-03-08 15:35:04 -0800151 IResultReceiver clientReceiver, int initialState) {
Felipe Lemeb96878492018-12-17 12:22:29 -0800152 mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnCreateSession,
Felipe Leme518fb622019-03-08 15:35:04 -0800153 ContentCaptureService.this, context, sessionId, uid, clientReceiver,
154 initialState));
Felipe Leme749b8892018-12-03 16:30:30 -0800155 }
156
157 @Override
Felipe Leme08054202019-03-28 11:29:25 -0700158 public void onActivitySnapshot(int sessionId, SnapshotData snapshotData) {
Felipe Leme749b8892018-12-03 16:30:30 -0800159 mHandler.sendMessage(
160 obtainMessage(ContentCaptureService::handleOnActivitySnapshot,
161 ContentCaptureService.this, sessionId, snapshotData));
162 }
Felipe Lemeb96878492018-12-17 12:22:29 -0800163
164 @Override
Felipe Leme08054202019-03-28 11:29:25 -0700165 public void onSessionFinished(int sessionId) {
Felipe Lemeb96878492018-12-17 12:22:29 -0800166 mHandler.sendMessage(obtainMessage(ContentCaptureService::handleFinishSession,
167 ContentCaptureService.this, sessionId));
168 }
Adam He3d0409b2019-01-15 14:22:04 -0800169
170 @Override
Felipe Leme4439ee62019-05-08 10:27:49 -0700171 public void onDataRemovalRequest(DataRemovalRequest request) {
172 mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnDataRemovalRequest,
173 ContentCaptureService.this, request));
Adam He3d0409b2019-01-15 14:22:04 -0800174 }
Felipe Leme141864d2019-02-27 17:01:51 -0800175
176 @Override
Sergey Volnov6e049012020-01-10 18:10:48 +0000177 public void onDataShared(DataShareRequest request, IDataShareCallback callback) {
178 mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnDataShared,
179 ContentCaptureService.this, request, callback));
180 }
181
182 @Override
Felipe Leme141864d2019-02-27 17:01:51 -0800183 public void onActivityEvent(ActivityEvent event) {
184 mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnActivityEvent,
185 ContentCaptureService.this, event));
Felipe Leme141864d2019-02-27 17:01:51 -0800186 }
Felipe Leme749b8892018-12-03 16:30:30 -0800187 };
188
Felipe Lemeb96878492018-12-17 12:22:29 -0800189 /**
190 * Binder that receives calls from the app.
191 */
192 private final IContentCaptureDirectManager mClientInterface =
193 new IContentCaptureDirectManager.Stub() {
194
195 @Override
Adam He420947c2019-01-23 15:59:09 -0800196 public void sendEvents(@SuppressWarnings("rawtypes") ParceledListSlice events, int reason,
197 ContentCaptureOptions options) {
Felipe Lemeb96878492018-12-17 12:22:29 -0800198 mHandler.sendMessage(obtainMessage(ContentCaptureService::handleSendEvents,
Adam He420947c2019-01-23 15:59:09 -0800199 ContentCaptureService.this, Binder.getCallingUid(), events, reason, options));
Felipe Lemeb96878492018-12-17 12:22:29 -0800200 }
201 };
202
203 /**
Felipe Lemee127c9a2019-01-04 14:56:38 -0800204 * UIDs associated with each session.
Felipe Lemeb96878492018-12-17 12:22:29 -0800205 *
206 * <p>This map is populated when an session is started, which is called by the system server
207 * and can be trusted. Then subsequent calls made by the app are verified against this map.
208 */
Felipe Leme08054202019-03-28 11:29:25 -0700209 private final SparseIntArray mSessionUids = new SparseIntArray();
Felipe Lemeb96878492018-12-17 12:22:29 -0800210
Felipe Leme749b8892018-12-03 16:30:30 -0800211 @CallSuper
212 @Override
213 public void onCreate() {
214 super.onCreate();
215 mHandler = new Handler(Looper.getMainLooper(), null, true);
216 }
217
218 /** @hide */
219 @Override
220 public final IBinder onBind(Intent intent) {
221 if (SERVICE_INTERFACE.equals(intent.getAction())) {
Felipe Lemeb96878492018-12-17 12:22:29 -0800222 return mServerInterface.asBinder();
Felipe Leme749b8892018-12-03 16:30:30 -0800223 }
224 Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
225 return null;
226 }
227
228 /**
Felipe Leme7a3c9f52019-02-13 16:32:49 -0800229 * Explicitly limits content capture to the given packages and activities.
230 *
231 * <p>To reset the whitelist, call it passing {@code null} to both arguments.
232 *
233 * <p>Useful when the service wants to restrict content capture to a category of apps, like
234 * chat apps. For example, if the service wants to support view captures on all activities of
235 * app {@code ChatApp1} and just activities {@code act1} and {@code act2} of {@code ChatApp2},
236 * it would call: {@code setContentCaptureWhitelist(Sets.newArraySet("ChatApp1"),
237 * Sets.newArraySet(new ComponentName("ChatApp2", "act1"),
238 * new ComponentName("ChatApp2", "act2")));}
239 */
240 public final void setContentCaptureWhitelist(@Nullable Set<String> packages,
241 @Nullable Set<ComponentName> activities) {
Felipe Lemee8eae9d42019-02-13 15:41:55 -0800242 final IContentCaptureServiceCallback callback = mCallback;
243 if (callback == null) {
244 Log.w(TAG, "setContentCaptureWhitelist(): no server callback");
245 return;
246 }
247
248 try {
249 callback.setContentCaptureWhitelist(toList(packages), toList(activities));
250 } catch (RemoteException e) {
251 e.rethrowFromSystemServer();
252 }
Felipe Leme7a3c9f52019-02-13 16:32:49 -0800253 }
254
Felipe Leme790be042019-03-25 09:52:19 -0700255 /**
256 * Explicitly sets the conditions for which content capture should be available by an app.
257 *
258 * <p>Typically used to restrict content capture to a few websites on browser apps. Example:
259 *
260 * <code>
261 * ArraySet<ContentCaptureCondition> conditions = new ArraySet<>(1);
262 * conditions.add(new ContentCaptureCondition(new LocusId("^https://.*\\.example\\.com$"),
263 * ContentCaptureCondition.FLAG_IS_REGEX));
264 * service.setContentCaptureConditions("com.example.browser_app", conditions);
265 *
266 * </code>
267 *
268 * <p>NOTE: </p> this method doesn't automatically disable content capture for the given
269 * conditions; it's up to the {@code packageName} implementation to call
270 * {@link ContentCaptureManager#getContentCaptureConditions()} and disable it accordingly.
271 *
272 * @param packageName name of the packages where the restrictions are set.
273 * @param conditions list of conditions, or {@code null} to reset the conditions for the
274 * package.
275 */
276 public final void setContentCaptureConditions(@NonNull String packageName,
277 @Nullable Set<ContentCaptureCondition> conditions) {
Felipe Lemea8d33c22019-03-25 16:36:09 -0700278 final IContentCaptureServiceCallback callback = mCallback;
279 if (callback == null) {
280 Log.w(TAG, "setContentCaptureConditions(): no server callback");
281 return;
282 }
Felipe Leme790be042019-03-25 09:52:19 -0700283
Felipe Lemea8d33c22019-03-25 16:36:09 -0700284 try {
285 callback.setContentCaptureConditions(packageName, toList(conditions));
286 } catch (RemoteException e) {
287 e.rethrowFromSystemServer();
288 }
Felipe Leme7a3c9f52019-02-13 16:32:49 -0800289 }
290
291 /**
Felipe Lemebb98ed62018-12-21 09:29:27 -0800292 * Called when the Android system connects to service.
293 *
294 * <p>You should generally do initialization here rather than in {@link #onCreate}.
295 */
296 public void onConnected() {
297 Slog.i(TAG, "bound to " + getClass().getName());
298 }
299
300 /**
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800301 * Creates a new content capture session.
Felipe Leme749b8892018-12-03 16:30:30 -0800302 *
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800303 * @param context content capture context
Felipe Leme749b8892018-12-03 16:30:30 -0800304 * @param sessionId the session's Id
305 */
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800306 public void onCreateContentCaptureSession(@NonNull ContentCaptureContext context,
307 @NonNull ContentCaptureSessionId sessionId) {
Felipe Leme326f15a2019-02-19 09:42:24 -0800308 if (sVerbose) {
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800309 Log.v(TAG, "onCreateContentCaptureSession(id=" + sessionId + ", ctx=" + context + ")");
Felipe Leme749b8892018-12-03 16:30:30 -0800310 }
311 }
312
313 /**
Felipe Lemefc24bea2018-12-18 13:19:01 -0800314 * Notifies the service of {@link ContentCaptureEvent events} associated with a content capture
315 * session.
316 *
317 * @param sessionId the session's Id
318 * @param event the event
319 */
320 public void onContentCaptureEvent(@NonNull ContentCaptureSessionId sessionId,
321 @NonNull ContentCaptureEvent event) {
Felipe Leme326f15a2019-02-19 09:42:24 -0800322 if (sVerbose) Log.v(TAG, "onContentCaptureEventsRequest(id=" + sessionId + ")");
Felipe Lemefc24bea2018-12-18 13:19:01 -0800323 }
Felipe Leme62e45b02019-01-10 10:22:00 -0800324
325 /**
Felipe Leme4439ee62019-05-08 10:27:49 -0700326 * Notifies the service that the app requested to remove content capture data.
Felipe Leme62e45b02019-01-10 10:22:00 -0800327 *
Felipe Leme4439ee62019-05-08 10:27:49 -0700328 * @param request the content capture data requested to be removed
Felipe Leme62e45b02019-01-10 10:22:00 -0800329 */
Felipe Leme4439ee62019-05-08 10:27:49 -0700330 public void onDataRemovalRequest(@NonNull DataRemovalRequest request) {
331 if (sVerbose) Log.v(TAG, "onDataRemovalRequest()");
Felipe Leme62e45b02019-01-10 10:22:00 -0800332 }
333
Felipe Lemefc24bea2018-12-18 13:19:01 -0800334 /**
Sergey Volnov6e049012020-01-10 18:10:48 +0000335 * Notifies the service that data has been shared via a readable file.
336 *
337 * @param request request object containing information about data being shared
338 * @param callback callback to be fired with response on whether the request is "needed" and can
339 * be handled by the Content Capture service.
340 *
341 * @hide
342 */
343 @SystemApi
344 public void onDataShareRequest(@NonNull DataShareRequest request,
345 @NonNull DataShareCallback callback) {
346 if (sVerbose) Log.v(TAG, "onDataShareRequest()");
347 }
348
349 /**
Felipe Leme749b8892018-12-03 16:30:30 -0800350 * Notifies the service of {@link SnapshotData snapshot data} associated with a session.
351 *
352 * @param sessionId the session's Id
353 * @param snapshotData the data
354 */
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800355 public void onActivitySnapshot(@NonNull ContentCaptureSessionId sessionId,
Felipe Leme141864d2019-02-27 17:01:51 -0800356 @NonNull SnapshotData snapshotData) {
357 if (sVerbose) Log.v(TAG, "onActivitySnapshot(id=" + sessionId + ")");
358 }
359
360 /**
361 * Notifies the service of an activity-level event that is not associated with a session.
362 *
363 * <p>This method can be used to track some high-level events for all activities, even those
364 * that are not whitelisted for Content Capture.
365 *
366 * @param event high-level activity event
367 */
368 public void onActivityEvent(@NonNull ActivityEvent event) {
369 if (sVerbose) Log.v(TAG, "onActivityEvent(): " + event);
370 }
Felipe Leme749b8892018-12-03 16:30:30 -0800371
372 /**
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800373 * Destroys the content capture session.
Felipe Leme749b8892018-12-03 16:30:30 -0800374 *
375 * @param sessionId the id of the session to destroy
Felipe Lemeb96878492018-12-17 12:22:29 -0800376 * */
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800377 public void onDestroyContentCaptureSession(@NonNull ContentCaptureSessionId sessionId) {
Felipe Leme326f15a2019-02-19 09:42:24 -0800378 if (sVerbose) Log.v(TAG, "onDestroyContentCaptureSession(id=" + sessionId + ")");
Felipe Lemeb96878492018-12-17 12:22:29 -0800379 }
380
Felipe Lemebb98ed62018-12-21 09:29:27 -0800381 /**
Felipe Leme72e83d82019-02-13 14:51:17 -0800382 * Disables the Content Capture service for the given user.
383 */
Felipe Lemebedcd612019-03-22 10:29:30 -0700384 public final void disableSelf() {
385 if (sDebug) Log.d(TAG, "disableSelf()");
Felipe Leme72e83d82019-02-13 14:51:17 -0800386
387 final IContentCaptureServiceCallback callback = mCallback;
388 if (callback == null) {
Felipe Lemebedcd612019-03-22 10:29:30 -0700389 Log.w(TAG, "disableSelf(): no server callback");
Felipe Leme72e83d82019-02-13 14:51:17 -0800390 return;
391 }
392 try {
393 callback.disableSelf();
394 } catch (RemoteException e) {
395 e.rethrowFromSystemServer();
396 }
397 }
398
399 /**
Felipe Lemebb98ed62018-12-21 09:29:27 -0800400 * Called when the Android system disconnects from the service.
401 *
Adam He5023abd2019-04-05 13:49:50 -0700402 * <p> At this point this service may no longer be an active {@link ContentCaptureService}.
403 * It should not make calls on {@link ContentCaptureManager} that requires the caller to be
404 * the current service.
Felipe Lemebb98ed62018-12-21 09:29:27 -0800405 */
406 public void onDisconnected() {
407 Slog.i(TAG, "unbinding from " + getClass().getName());
408 }
409
Felipe Lemeb96878492018-12-17 12:22:29 -0800410 @Override
411 @CallSuper
412 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Felipe Leme326f15a2019-02-19 09:42:24 -0800413 pw.print("Debug: "); pw.print(sDebug); pw.print(" Verbose: "); pw.println(sVerbose);
Felipe Lemee127c9a2019-01-04 14:56:38 -0800414 final int size = mSessionUids.size();
Felipe Lemeb96878492018-12-17 12:22:29 -0800415 pw.print("Number sessions: "); pw.println(size);
416 if (size > 0) {
417 final String prefix = " ";
418 for (int i = 0; i < size; i++) {
Felipe Lemee127c9a2019-01-04 14:56:38 -0800419 pw.print(prefix); pw.print(mSessionUids.keyAt(i));
420 pw.print(": uid="); pw.println(mSessionUids.valueAt(i));
Felipe Lemeb96878492018-12-17 12:22:29 -0800421 }
Felipe Leme749b8892018-12-03 16:30:30 -0800422 }
423 }
424
Felipe Lemeaf4e4fd2019-01-15 08:32:42 -0800425 private void handleOnConnected(@NonNull IBinder callback) {
426 mCallback = IContentCaptureServiceCallback.Stub.asInterface(callback);
427 onConnected();
428 }
429
430 private void handleOnDisconnected() {
431 onDisconnected();
432 mCallback = null;
Felipe Lemebb98ed62018-12-21 09:29:27 -0800433 }
434
Felipe Leme749b8892018-12-03 16:30:30 -0800435 //TODO(b/111276913): consider caching the InteractionSessionId for the lifetime of the session,
436 // so we don't need to create a temporary InteractionSessionId for each event.
437
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800438 private void handleOnCreateSession(@NonNull ContentCaptureContext context,
Felipe Leme08054202019-03-28 11:29:25 -0700439 int sessionId, int uid, IResultReceiver clientReceiver, int initialState) {
Felipe Lemee127c9a2019-01-04 14:56:38 -0800440 mSessionUids.put(sessionId, uid);
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800441 onCreateContentCaptureSession(context, new ContentCaptureSessionId(sessionId));
Adam He328c0e32019-01-03 15:19:22 -0800442
Felipe Leme01b87492019-01-15 13:26:52 -0800443 final int clientFlags = context.getFlags();
444 int stateFlags = 0;
445 if ((clientFlags & ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE) != 0) {
446 stateFlags |= ContentCaptureSession.STATE_FLAG_SECURE;
Adam He328c0e32019-01-03 15:19:22 -0800447 }
Felipe Leme01b87492019-01-15 13:26:52 -0800448 if ((clientFlags & ContentCaptureContext.FLAG_DISABLED_BY_APP) != 0) {
449 stateFlags |= ContentCaptureSession.STATE_BY_APP;
Adam He6079d152019-01-10 11:37:17 -0800450 }
Felipe Leme01b87492019-01-15 13:26:52 -0800451 if (stateFlags == 0) {
Felipe Leme518fb622019-03-08 15:35:04 -0800452 stateFlags = initialState;
Felipe Leme01b87492019-01-15 13:26:52 -0800453 } else {
454 stateFlags |= ContentCaptureSession.STATE_DISABLED;
Felipe Leme01b87492019-01-15 13:26:52 -0800455 }
456 setClientState(clientReceiver, stateFlags, mClientInterface.asBinder());
Felipe Leme749b8892018-12-03 16:30:30 -0800457 }
458
Felipe Lemefc24bea2018-12-18 13:19:01 -0800459 private void handleSendEvents(int uid,
Adam He420947c2019-01-23 15:59:09 -0800460 @NonNull ParceledListSlice<ContentCaptureEvent> parceledEvents, int reason,
461 @Nullable ContentCaptureOptions options) {
462 final List<ContentCaptureEvent> events = parceledEvents.getList();
463 if (events.isEmpty()) {
464 Log.w(TAG, "handleSendEvents() received empty list of events");
465 return;
466 }
467
468 // Metrics.
469 final FlushMetrics metrics = new FlushMetrics();
470 ComponentName activityComponent = null;
Felipe Lemefc24bea2018-12-18 13:19:01 -0800471
472 // Most events belong to the same session, so we can keep a reference to the last one
473 // to avoid creating too many ContentCaptureSessionId objects
Felipe Leme08054202019-03-28 11:29:25 -0700474 int lastSessionId = NO_SESSION_ID;
Felipe Lemefc24bea2018-12-18 13:19:01 -0800475 ContentCaptureSessionId sessionId = null;
476
Felipe Lemefc24bea2018-12-18 13:19:01 -0800477 for (int i = 0; i < events.size(); i++) {
478 final ContentCaptureEvent event = events.get(i);
Felipe Leme87a9dc92018-12-18 14:28:07 -0800479 if (!handleIsRightCallerFor(event, uid)) continue;
Felipe Leme08054202019-03-28 11:29:25 -0700480 int sessionIdInt = event.getSessionId();
481 if (sessionIdInt != lastSessionId) {
482 sessionId = new ContentCaptureSessionId(sessionIdInt);
483 lastSessionId = sessionIdInt;
Adam He420947c2019-01-23 15:59:09 -0800484 if (i != 0) {
485 writeFlushMetrics(lastSessionId, activityComponent, metrics, options, reason);
486 metrics.reset();
487 }
488 }
489 final ContentCaptureContext clientContext = event.getContentCaptureContext();
490 if (activityComponent == null && clientContext != null) {
491 activityComponent = clientContext.getActivityComponent();
Felipe Lemefc24bea2018-12-18 13:19:01 -0800492 }
Felipe Leme87a9dc92018-12-18 14:28:07 -0800493 switch (event.getType()) {
494 case ContentCaptureEvent.TYPE_SESSION_STARTED:
Felipe Leme87a9dc92018-12-18 14:28:07 -0800495 clientContext.setParentSessionId(event.getParentSessionId());
Felipe Leme08054202019-03-28 11:29:25 -0700496 mSessionUids.put(sessionIdInt, uid);
Felipe Leme87a9dc92018-12-18 14:28:07 -0800497 onCreateContentCaptureSession(clientContext, sessionId);
Adam He420947c2019-01-23 15:59:09 -0800498 metrics.sessionStarted++;
Felipe Leme87a9dc92018-12-18 14:28:07 -0800499 break;
500 case ContentCaptureEvent.TYPE_SESSION_FINISHED:
Felipe Leme08054202019-03-28 11:29:25 -0700501 mSessionUids.delete(sessionIdInt);
Felipe Leme87a9dc92018-12-18 14:28:07 -0800502 onDestroyContentCaptureSession(sessionId);
Adam He420947c2019-01-23 15:59:09 -0800503 metrics.sessionFinished++;
504 break;
505 case ContentCaptureEvent.TYPE_VIEW_APPEARED:
506 onContentCaptureEvent(sessionId, event);
507 metrics.viewAppearedCount++;
508 break;
509 case ContentCaptureEvent.TYPE_VIEW_DISAPPEARED:
510 onContentCaptureEvent(sessionId, event);
511 metrics.viewDisappearedCount++;
512 break;
513 case ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED:
514 onContentCaptureEvent(sessionId, event);
515 metrics.viewTextChangedCount++;
Felipe Leme87a9dc92018-12-18 14:28:07 -0800516 break;
517 default:
518 onContentCaptureEvent(sessionId, event);
519 }
Felipe Lemeb96878492018-12-17 12:22:29 -0800520 }
Adam He420947c2019-01-23 15:59:09 -0800521 writeFlushMetrics(lastSessionId, activityComponent, metrics, options, reason);
Felipe Leme749b8892018-12-03 16:30:30 -0800522 }
523
Felipe Leme08054202019-03-28 11:29:25 -0700524 private void handleOnActivitySnapshot(int sessionId, @NonNull SnapshotData snapshotData) {
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800525 onActivitySnapshot(new ContentCaptureSessionId(sessionId), snapshotData);
Felipe Leme749b8892018-12-03 16:30:30 -0800526 }
527
Felipe Leme08054202019-03-28 11:29:25 -0700528 private void handleFinishSession(int sessionId) {
529 mSessionUids.delete(sessionId);
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800530 onDestroyContentCaptureSession(new ContentCaptureSessionId(sessionId));
Felipe Leme749b8892018-12-03 16:30:30 -0800531 }
Felipe Lemeb96878492018-12-17 12:22:29 -0800532
Felipe Leme4439ee62019-05-08 10:27:49 -0700533 private void handleOnDataRemovalRequest(@NonNull DataRemovalRequest request) {
534 onDataRemovalRequest(request);
Adam He3d0409b2019-01-15 14:22:04 -0800535 }
536
Sergey Volnov6e049012020-01-10 18:10:48 +0000537 private void handleOnDataShared(@NonNull DataShareRequest request,
538 IDataShareCallback callback) {
539 onDataShareRequest(request, new DataShareCallback() {
540
541 @Override
542 public void onAccept(@NonNull Executor executor,
543 @NonNull DataShareReadAdapter adapter) {
544 Preconditions.checkNotNull(adapter);
545 Preconditions.checkNotNull(executor);
546
Sergey Volnovd7e98442020-01-24 15:48:02 +0000547 ICancellationSignal cancellationSignalTransport =
548 CancellationSignal.createTransport();
549
550 DataShareReadAdapterDelegate delegate = new DataShareReadAdapterDelegate(
551 executor, cancellationSignalTransport, adapter);
Sergey Volnov6e049012020-01-10 18:10:48 +0000552
553 try {
Sergey Volnovd7e98442020-01-24 15:48:02 +0000554 callback.accept(cancellationSignalTransport, delegate);
Sergey Volnov6e049012020-01-10 18:10:48 +0000555 } catch (RemoteException e) {
556 Slog.e(TAG, "Failed to accept data sharing", e);
557 }
558 }
559
560 @Override
561 public void onReject() {
562 try {
563 callback.reject();
564 } catch (RemoteException e) {
565 Slog.e(TAG, "Failed to reject data sharing", e);
566 }
567 }
568 });
569 }
570
Felipe Leme141864d2019-02-27 17:01:51 -0800571 private void handleOnActivityEvent(@NonNull ActivityEvent event) {
572 onActivityEvent(event);
573 }
574
Felipe Lemeb96878492018-12-17 12:22:29 -0800575 /**
Felipe Leme87a9dc92018-12-18 14:28:07 -0800576 * Checks if the given {@code uid} owns the session associated with the event.
Felipe Lemeb96878492018-12-17 12:22:29 -0800577 */
Felipe Leme87a9dc92018-12-18 14:28:07 -0800578 private boolean handleIsRightCallerFor(@NonNull ContentCaptureEvent event, int uid) {
Felipe Leme08054202019-03-28 11:29:25 -0700579 final int sessionId;
Felipe Leme87a9dc92018-12-18 14:28:07 -0800580 switch (event.getType()) {
581 case ContentCaptureEvent.TYPE_SESSION_STARTED:
582 case ContentCaptureEvent.TYPE_SESSION_FINISHED:
583 sessionId = event.getParentSessionId();
584 break;
585 default:
586 sessionId = event.getSessionId();
587 }
Felipe Leme08054202019-03-28 11:29:25 -0700588 if (mSessionUids.indexOfKey(sessionId) < 0) {
Felipe Leme326f15a2019-02-19 09:42:24 -0800589 if (sVerbose) {
Felipe Leme4eecbe62019-02-11 17:50:17 -0800590 Log.v(TAG, "handleIsRightCallerFor(" + event + "): no session for " + sessionId
Felipe Lemee127c9a2019-01-04 14:56:38 -0800591 + ": " + mSessionUids);
592 }
593 // Just ignore, as the session could have been finished already
Felipe Lemeb96878492018-12-17 12:22:29 -0800594 return false;
595 }
Felipe Leme08054202019-03-28 11:29:25 -0700596 final int rightUid = mSessionUids.get(sessionId);
Felipe Lemeb96878492018-12-17 12:22:29 -0800597 if (rightUid != uid) {
598 Log.e(TAG, "invalid call from UID " + uid + ": session " + sessionId + " belongs to "
599 + rightUid);
Adam He420947c2019-01-23 15:59:09 -0800600 long now = System.currentTimeMillis();
601 if (now - mLastCallerMismatchLog > mCallerMismatchTimeout) {
602 StatsLog.write(StatsLog.CONTENT_CAPTURE_CALLER_MISMATCH_REPORTED,
603 getPackageManager().getNameForUid(rightUid),
604 getPackageManager().getNameForUid(uid));
605 mLastCallerMismatchLog = now;
606 }
Felipe Lemeb96878492018-12-17 12:22:29 -0800607 return false;
608 }
609 return true;
610
611 }
612
613 /**
Adam He328c0e32019-01-03 15:19:22 -0800614 * Sends the state of the {@link ContentCaptureManager} in the client app.
Felipe Lemeb96878492018-12-17 12:22:29 -0800615 *
616 * @param clientReceiver receiver in the client app.
Adam He328c0e32019-01-03 15:19:22 -0800617 * @param sessionState state of the session
Felipe Lemeb96878492018-12-17 12:22:29 -0800618 * @param binder handle to the {@code IContentCaptureDirectManager} object that resides in the
619 * service.
620 * @hide
621 */
622 public static void setClientState(@NonNull IResultReceiver clientReceiver,
Adam He328c0e32019-01-03 15:19:22 -0800623 int sessionState, @Nullable IBinder binder) {
Felipe Lemeb96878492018-12-17 12:22:29 -0800624 try {
625 final Bundle extras;
626 if (binder != null) {
627 extras = new Bundle();
Felipe Leme87a9dc92018-12-18 14:28:07 -0800628 extras.putBinder(MainContentCaptureSession.EXTRA_BINDER, binder);
Felipe Lemeb96878492018-12-17 12:22:29 -0800629 } else {
630 extras = null;
631 }
Adam He328c0e32019-01-03 15:19:22 -0800632 clientReceiver.send(sessionState, extras);
Felipe Lemeb96878492018-12-17 12:22:29 -0800633 } catch (RemoteException e) {
634 Slog.w(TAG, "Error async reporting result to client: " + e);
635 }
636 }
Adam He420947c2019-01-23 15:59:09 -0800637
638 /**
639 * Logs the metrics for content capture events flushing.
640 */
641 private void writeFlushMetrics(int sessionId, @Nullable ComponentName app,
642 @NonNull FlushMetrics flushMetrics, @Nullable ContentCaptureOptions options,
643 int flushReason) {
644 if (mCallback == null) {
645 Log.w(TAG, "writeSessionFlush(): no server callback");
646 return;
647 }
648
649 try {
650 mCallback.writeSessionFlush(sessionId, app, flushMetrics, options, flushReason);
651 } catch (RemoteException e) {
652 Log.e(TAG, "failed to write flush metrics: " + e);
653 }
654 }
Sergey Volnov6e049012020-01-10 18:10:48 +0000655
656 private static class DataShareReadAdapterDelegate extends IDataShareReadAdapter.Stub {
657
658 private final Object mLock = new Object();
659 private final WeakReference<DataShareReadAdapter> mAdapterReference;
660 private final WeakReference<Executor> mExecutorReference;
Sergey Volnovd7e98442020-01-24 15:48:02 +0000661 private final WeakReference<ICancellationSignal> mCancellationSignalReference;
Sergey Volnov6e049012020-01-10 18:10:48 +0000662
Sergey Volnovd7e98442020-01-24 15:48:02 +0000663 DataShareReadAdapterDelegate(Executor executor,
664 ICancellationSignal cancellationSignalTransport, DataShareReadAdapter adapter) {
Sergey Volnov6e049012020-01-10 18:10:48 +0000665 Preconditions.checkNotNull(executor);
Sergey Volnovd7e98442020-01-24 15:48:02 +0000666 Preconditions.checkNotNull(cancellationSignalTransport);
Sergey Volnov6e049012020-01-10 18:10:48 +0000667 Preconditions.checkNotNull(adapter);
668
669 mExecutorReference = new WeakReference<>(executor);
Sergey Volnovd7e98442020-01-24 15:48:02 +0000670 mCancellationSignalReference = new WeakReference<>(cancellationSignalTransport);
Sergey Volnov6e049012020-01-10 18:10:48 +0000671 mAdapterReference = new WeakReference<>(adapter);
672 }
673
674 @Override
675 public void start(ParcelFileDescriptor fd, ICancellationSignal remoteCancellationSignal)
676 throws RemoteException {
677 synchronized (mLock) {
Sergey Volnovd7e98442020-01-24 15:48:02 +0000678 ICancellationSignal serverControlledCancellationSignal =
679 mCancellationSignalReference.get();
680
681 if (serverControlledCancellationSignal == null) {
682 Slog.w(TAG, "Can't execute onStart(), reference to cancellation signal has "
683 + "been GC'ed");
684 return;
685 }
686
687 CancellationSignal cancellationSignal =
688 CancellationSignal.fromTransport(serverControlledCancellationSignal);
Sergey Volnov6e049012020-01-10 18:10:48 +0000689 cancellationSignal.setRemote(remoteCancellationSignal);
690
691 executeAdapterMethodLocked(
692 adapter -> adapter.onStart(fd, cancellationSignal), "onStart");
693 }
694 }
695
696 @Override
697 public void error(int errorCode) throws RemoteException {
698 synchronized (mLock) {
699 executeAdapterMethodLocked(
700 adapter -> adapter.onError(errorCode), "onError");
701 }
702 }
703
704 private void executeAdapterMethodLocked(Consumer<DataShareReadAdapter> adapterFn,
705 String methodName) {
706 DataShareReadAdapter adapter = mAdapterReference.get();
707 Executor executor = mExecutorReference.get();
708
709 if (adapter == null || executor == null) {
710 Slog.w(TAG, "Can't execute " + methodName + "(), references have been GC'ed");
711 return;
712 }
713
714 final long identity = Binder.clearCallingIdentity();
715 try {
716 executor.execute(() -> adapterFn.accept(adapter));
717 } finally {
718 Binder.restoreCallingIdentity(identity);
719 }
720 }
721 }
Felipe Leme749b8892018-12-03 16:30:30 -0800722}