1 /*
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 package android.service.contentcapture;
17 
18 import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_PAUSED;
19 import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_RESUMED;
20 import static android.view.contentcapture.ContentCaptureHelper.sDebug;
21 import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
22 import static android.view.contentcapture.ContentCaptureHelper.toList;
23 import static android.view.contentcapture.ContentCaptureManager.NO_SESSION_ID;
24 
25 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
26 
27 import android.annotation.CallSuper;
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.annotation.SystemApi;
31 import android.app.Service;
32 import android.content.ComponentName;
33 import android.content.ContentCaptureOptions;
34 import android.content.Intent;
35 import android.content.pm.ParceledListSlice;
36 import android.os.Binder;
37 import android.os.Bundle;
38 import android.os.Handler;
39 import android.os.IBinder;
40 import android.os.Looper;
41 import android.os.ParcelFileDescriptor;
42 import android.os.Process;
43 import android.os.RemoteException;
44 import android.util.Log;
45 import android.util.Slog;
46 import android.util.SparseIntArray;
47 import android.view.contentcapture.ContentCaptureCondition;
48 import android.view.contentcapture.ContentCaptureContext;
49 import android.view.contentcapture.ContentCaptureEvent;
50 import android.view.contentcapture.ContentCaptureManager;
51 import android.view.contentcapture.ContentCaptureSession;
52 import android.view.contentcapture.ContentCaptureSessionId;
53 import android.view.contentcapture.DataRemovalRequest;
54 import android.view.contentcapture.DataShareRequest;
55 import android.view.contentcapture.IContentCaptureDirectManager;
56 import android.view.contentcapture.MainContentCaptureSession;
57 
58 import com.android.internal.os.IResultReceiver;
59 import com.android.internal.util.FrameworkStatsLog;
60 
61 import java.io.FileDescriptor;
62 import java.io.PrintWriter;
63 import java.lang.ref.WeakReference;
64 import java.util.HashMap;
65 import java.util.List;
66 import java.util.Map;
67 import java.util.Objects;
68 import java.util.Set;
69 import java.util.concurrent.Executor;
70 import java.util.function.Consumer;
71 
72 /**
73  * A service used to capture the content of the screen to provide contextual data in other areas of
74  * the system such as Autofill.
75  *
76  * @hide
77  */
78 @SystemApi
79 public abstract class ContentCaptureService extends Service {
80 
81     private static final String TAG = ContentCaptureService.class.getSimpleName();
82 
83     /**
84      * The {@link Intent} that must be declared as handled by the service.
85      *
86      * <p>To be supported, the service must also require the
87      * {@link android.Manifest.permission#BIND_CONTENT_CAPTURE_SERVICE} permission so
88      * that other applications can not abuse it.
89      */
90     public static final String SERVICE_INTERFACE =
91             "android.service.contentcapture.ContentCaptureService";
92 
93     /**
94      * The {@link Intent} that must be declared as handled by the protection service.
95      *
96      * <p>To be supported, the service must also require the {@link
97      * android.Manifest.permission#BIND_CONTENT_CAPTURE_SERVICE} permission so that other
98      * applications can not abuse it.
99      *
100      * @hide
101      */
102     public static final String PROTECTION_SERVICE_INTERFACE =
103             "android.service.contentcapture.ContentProtectionService";
104 
105     /**
106      * Name under which a ContentCaptureService component publishes information about itself.
107      *
108      * <p>This meta-data should reference an XML resource containing a
109      * <code>&lt;{@link
110      * android.R.styleable#ContentCaptureService content-capture-service}&gt;</code> tag.
111      *
112      * <p>Here's an example of how to use it on {@code AndroidManifest.xml}:
113      *
114      * <pre>
115      * &lt;service android:name=".MyContentCaptureService"
116      *     android:permission="android.permission.BIND_CONTENT_CAPTURE_SERVICE"&gt;
117      *   &lt;intent-filter&gt;
118      *     &lt;action android:name="android.service.contentcapture.ContentCaptureService" /&gt;
119      *   &lt;/intent-filter&gt;
120      *
121      *   &lt;meta-data
122      *       android:name="android.content_capture"
123      *       android:resource="@xml/my_content_capture_service"/&gt;
124      * &lt;/service&gt;
125      * </pre>
126      *
127      * <p>And then on {@code res/xml/my_content_capture_service.xml}:
128      *
129      * <pre>
130      *   &lt;content-capture-service xmlns:android="http://schemas.android.com/apk/res/android"
131      *       android:settingsActivity="my.package.MySettingsActivity"&gt;
132      *   &lt;/content-capture-service&gt;
133      * </pre>
134      */
135     public static final String SERVICE_META_DATA = "android.content_capture";
136 
137     private final LocalDataShareAdapterResourceManager mDataShareAdapterResourceManager =
138             new LocalDataShareAdapterResourceManager();
139 
140     private Handler mHandler;
141     private IContentCaptureServiceCallback mCallback;
142 
143     private long mCallerMismatchTimeout = 1000;
144     private long mLastCallerMismatchLog;
145 
146     /** Binder that receives calls from the system server in the content capture flow. */
147     private final IContentCaptureService mContentCaptureServerInterface =
148             new IContentCaptureService.Stub() {
149 
150         @Override
151         public void onConnected(IBinder callback, boolean verbose, boolean debug) {
152             sVerbose = verbose;
153             sDebug = debug;
154             mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnConnected,
155                     ContentCaptureService.this, callback));
156         }
157 
158         @Override
159         public void onDisconnected() {
160             mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnDisconnected,
161                     ContentCaptureService.this));
162         }
163 
164         @Override
165         public void onSessionStarted(ContentCaptureContext context, int sessionId, int uid,
166                 IResultReceiver clientReceiver, int initialState) {
167             mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnCreateSession,
168                     ContentCaptureService.this, context, sessionId, uid, clientReceiver,
169                     initialState));
170         }
171 
172         @Override
173         public void onActivitySnapshot(int sessionId, SnapshotData snapshotData) {
174             mHandler.sendMessage(
175                     obtainMessage(ContentCaptureService::handleOnActivitySnapshot,
176                             ContentCaptureService.this, sessionId, snapshotData));
177         }
178 
179         @Override
180         public void onSessionFinished(int sessionId) {
181             mHandler.sendMessage(obtainMessage(ContentCaptureService::handleFinishSession,
182                     ContentCaptureService.this, sessionId));
183         }
184 
185         @Override
186         public void onDataRemovalRequest(DataRemovalRequest request) {
187             mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnDataRemovalRequest,
188                     ContentCaptureService.this, request));
189         }
190 
191         @Override
192         public void onDataShared(DataShareRequest request, IDataShareCallback callback) {
193             mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnDataShared,
194                     ContentCaptureService.this, request, callback));
195         }
196 
197         @Override
198         public void onActivityEvent(ActivityEvent event) {
199             mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnActivityEvent,
200                     ContentCaptureService.this, event));
201         }
202     };
203 
204     /** Binder that receives calls from the system server in the content protection flow. */
205     private final IContentProtectionService mContentProtectionServerInterface =
206             new IContentProtectionService.Stub() {
207 
208                 @Override
209                 public void onLoginDetected(
210                         @SuppressWarnings("rawtypes") ParceledListSlice events) {
211                     mHandler.sendMessage(
212                             obtainMessage(
213                                     ContentCaptureService::handleOnLoginDetected,
214                                     ContentCaptureService.this,
215                                     Binder.getCallingUid(),
216                                     events));
217                 }
218             };
219 
220     /** Binder that receives calls from the app in the content capture flow. */
221     private final IContentCaptureDirectManager mContentCaptureClientInterface =
222             new IContentCaptureDirectManager.Stub() {
223 
224         @Override
225         public void sendEvents(@SuppressWarnings("rawtypes") ParceledListSlice events, int reason,
226                 ContentCaptureOptions options) {
227             mHandler.sendMessage(obtainMessage(ContentCaptureService::handleSendEvents,
228                     ContentCaptureService.this, Binder.getCallingUid(), events, reason, options));
229         }
230     };
231 
232     /**
233      * UIDs associated with each session.
234      *
235      * <p>This map is populated when an session is started, which is called by the system server
236      * and can be trusted. Then subsequent calls made by the app are verified against this map.
237      */
238     private final SparseIntArray mSessionUids = new SparseIntArray();
239 
240     @CallSuper
241     @Override
onCreate()242     public void onCreate() {
243         super.onCreate();
244         mHandler = new Handler(Looper.getMainLooper(), null, true);
245     }
246 
247     /** @hide */
248     @Override
onBind(Intent intent)249     public final IBinder onBind(Intent intent) {
250         if (SERVICE_INTERFACE.equals(intent.getAction())) {
251             return mContentCaptureServerInterface.asBinder();
252         }
253         if (PROTECTION_SERVICE_INTERFACE.equals(intent.getAction())) {
254             return mContentProtectionServerInterface.asBinder();
255         }
256         Log.w(
257                 TAG,
258                 "Tried to bind to wrong intent (should be "
259                         + SERVICE_INTERFACE
260                         + " or "
261                         + PROTECTION_SERVICE_INTERFACE
262                         + "): "
263                         + intent);
264         return null;
265     }
266 
267     /**
268      * Explicitly limits content capture to the given packages and activities.
269      *
270      * <p>To reset the allowlist, call it passing {@code null} to both arguments.
271      *
272      * <p>Useful when the service wants to restrict content capture to a category of apps, like
273      * chat apps. For example, if the service wants to support view captures on all activities of
274      * app {@code ChatApp1} and just activities {@code act1} and {@code act2} of {@code ChatApp2},
275      * it would call: {@code setContentCaptureWhitelist(Sets.newArraySet("ChatApp1"),
276      * Sets.newArraySet(new ComponentName("ChatApp2", "act1"),
277      * new ComponentName("ChatApp2", "act2")));}
278      */
setContentCaptureWhitelist(@ullable Set<String> packages, @Nullable Set<ComponentName> activities)279     public final void setContentCaptureWhitelist(@Nullable Set<String> packages,
280             @Nullable Set<ComponentName> activities) {
281         final IContentCaptureServiceCallback callback = mCallback;
282         if (callback == null) {
283             Log.w(TAG, "setContentCaptureWhitelist(): no server callback");
284             return;
285         }
286 
287         try {
288             callback.setContentCaptureWhitelist(toList(packages), toList(activities));
289         } catch (RemoteException e) {
290             e.rethrowFromSystemServer();
291         }
292     }
293 
294     /**
295      * Explicitly sets the conditions for which content capture should be available by an app.
296      *
297      * <p>Typically used to restrict content capture to a few websites on browser apps. Example:
298      *
299      * <code>
300      *   ArraySet<ContentCaptureCondition> conditions = new ArraySet<>(1);
301      *   conditions.add(new ContentCaptureCondition(new LocusId("^https://.*\\.example\\.com$"),
302      *       ContentCaptureCondition.FLAG_IS_REGEX));
303      *   service.setContentCaptureConditions("com.example.browser_app", conditions);
304      *
305      * </code>
306      *
307      * <p>NOTE: </p> this method doesn't automatically disable content capture for the given
308      * conditions; it's up to the {@code packageName} implementation to call
309      * {@link ContentCaptureManager#getContentCaptureConditions()} and disable it accordingly.
310      *
311      * @param packageName name of the packages where the restrictions are set.
312      * @param conditions list of conditions, or {@code null} to reset the conditions for the
313      * package.
314      */
setContentCaptureConditions(@onNull String packageName, @Nullable Set<ContentCaptureCondition> conditions)315     public final void setContentCaptureConditions(@NonNull String packageName,
316             @Nullable Set<ContentCaptureCondition> conditions) {
317         final IContentCaptureServiceCallback callback = mCallback;
318         if (callback == null) {
319             Log.w(TAG, "setContentCaptureConditions(): no server callback");
320             return;
321         }
322 
323         try {
324             callback.setContentCaptureConditions(packageName, toList(conditions));
325         } catch (RemoteException e) {
326             e.rethrowFromSystemServer();
327         }
328     }
329 
330     /**
331      * Called when the Android system connects to service.
332      *
333      * <p>You should generally do initialization here rather than in {@link #onCreate}.
334      */
onConnected()335     public void onConnected() {
336         Slog.i(TAG, "bound to " + getClass().getName());
337     }
338 
339     /**
340      * Creates a new content capture session.
341      *
342      * @param context content capture context
343      * @param sessionId the session's Id
344      */
onCreateContentCaptureSession(@onNull ContentCaptureContext context, @NonNull ContentCaptureSessionId sessionId)345     public void onCreateContentCaptureSession(@NonNull ContentCaptureContext context,
346             @NonNull ContentCaptureSessionId sessionId) {
347         if (sVerbose) {
348             Log.v(TAG, "onCreateContentCaptureSession(id=" + sessionId + ", ctx=" + context + ")");
349         }
350     }
351 
352     /**
353      * Notifies the service of {@link ContentCaptureEvent events} associated with a content capture
354      * session.
355      *
356      * @param sessionId the session's Id
357      * @param event the event
358      */
onContentCaptureEvent(@onNull ContentCaptureSessionId sessionId, @NonNull ContentCaptureEvent event)359     public void onContentCaptureEvent(@NonNull ContentCaptureSessionId sessionId,
360             @NonNull ContentCaptureEvent event) {
361         if (sVerbose) Log.v(TAG, "onContentCaptureEventsRequest(id=" + sessionId + ")");
362     }
363 
364     /**
365      * Notifies the service that the app requested to remove content capture data.
366      *
367      * @param request the content capture data requested to be removed
368      */
onDataRemovalRequest(@onNull DataRemovalRequest request)369     public void onDataRemovalRequest(@NonNull DataRemovalRequest request) {
370         if (sVerbose) Log.v(TAG, "onDataRemovalRequest()");
371     }
372 
373     /**
374      * Notifies the service that data has been shared via a readable file.
375      *
376      * @param request request object containing information about data being shared
377      * @param callback callback to be fired with response on whether the request is "needed" and can
378      *                 be handled by the Content Capture service.
379      *
380      * @hide
381      */
382     @SystemApi
onDataShareRequest(@onNull DataShareRequest request, @NonNull DataShareCallback callback)383     public void onDataShareRequest(@NonNull DataShareRequest request,
384             @NonNull DataShareCallback callback) {
385         if (sVerbose) Log.v(TAG, "onDataShareRequest()");
386     }
387 
388     /**
389      * Notifies the service of {@link SnapshotData snapshot data} associated with an activity.
390      *
391      * @param sessionId the session's Id. This may also be
392      *                  {@link ContentCaptureSession#NO_SESSION_ID} if no content capture session
393      *                  exists for the activity being snapshotted
394      * @param snapshotData the data
395      */
onActivitySnapshot(@onNull ContentCaptureSessionId sessionId, @NonNull SnapshotData snapshotData)396     public void onActivitySnapshot(@NonNull ContentCaptureSessionId sessionId,
397             @NonNull SnapshotData snapshotData) {
398         if (sVerbose) Log.v(TAG, "onActivitySnapshot(id=" + sessionId + ")");
399     }
400 
401     /**
402      * Notifies the service of an activity-level event that is not associated with a session.
403      *
404      * <p>This method can be used to track some high-level events for all activities, even those
405      * that are not allowlisted for Content Capture.
406      *
407      * @param event high-level activity event
408      */
onActivityEvent(@onNull ActivityEvent event)409     public void onActivityEvent(@NonNull ActivityEvent event) {
410         if (sVerbose) Log.v(TAG, "onActivityEvent(): " + event);
411     }
412 
413     /**
414      * Destroys the content capture session.
415      *
416      * @param sessionId the id of the session to destroy
417      * */
onDestroyContentCaptureSession(@onNull ContentCaptureSessionId sessionId)418     public void onDestroyContentCaptureSession(@NonNull ContentCaptureSessionId sessionId) {
419         if (sVerbose) Log.v(TAG, "onDestroyContentCaptureSession(id=" + sessionId + ")");
420     }
421 
422     /**
423      * Disables the Content Capture service for the given user.
424      */
disableSelf()425     public final void disableSelf() {
426         if (sDebug) Log.d(TAG, "disableSelf()");
427 
428         final IContentCaptureServiceCallback callback = mCallback;
429         if (callback == null) {
430             Log.w(TAG, "disableSelf(): no server callback");
431             return;
432         }
433         try {
434             callback.disableSelf();
435         } catch (RemoteException e) {
436             e.rethrowFromSystemServer();
437         }
438     }
439 
440     /**
441      * Called when the Android system disconnects from the service.
442      *
443      * <p> At this point this service may no longer be an active {@link ContentCaptureService}.
444      * It should not make calls on {@link ContentCaptureManager} that requires the caller to be
445      * the current service.
446      */
onDisconnected()447     public void onDisconnected() {
448         Slog.i(TAG, "unbinding from " + getClass().getName());
449     }
450 
451     @Override
452     @CallSuper
dump(FileDescriptor fd, PrintWriter pw, String[] args)453     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
454         pw.print("Debug: "); pw.print(sDebug); pw.print(" Verbose: "); pw.println(sVerbose);
455         final int size = mSessionUids.size();
456         pw.print("Number sessions: "); pw.println(size);
457         if (size > 0) {
458             final String prefix = "  ";
459             for (int i = 0; i < size; i++) {
460                 pw.print(prefix); pw.print(mSessionUids.keyAt(i));
461                 pw.print(": uid="); pw.println(mSessionUids.valueAt(i));
462             }
463         }
464     }
465 
handleOnConnected(@onNull IBinder callback)466     private void handleOnConnected(@NonNull IBinder callback) {
467         mCallback = IContentCaptureServiceCallback.Stub.asInterface(callback);
468         onConnected();
469     }
470 
handleOnDisconnected()471     private void handleOnDisconnected() {
472         onDisconnected();
473         mCallback = null;
474     }
475 
476     //TODO(b/111276913): consider caching the InteractionSessionId for the lifetime of the session,
477     // so we don't need to create a temporary InteractionSessionId for each event.
478 
handleOnCreateSession(@onNull ContentCaptureContext context, int sessionId, int uid, IResultReceiver clientReceiver, int initialState)479     private void handleOnCreateSession(@NonNull ContentCaptureContext context,
480             int sessionId, int uid, IResultReceiver clientReceiver, int initialState) {
481         mSessionUids.put(sessionId, uid);
482         onCreateContentCaptureSession(context, new ContentCaptureSessionId(sessionId));
483 
484         final int clientFlags = context.getFlags();
485         int stateFlags = 0;
486         if ((clientFlags & ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE) != 0) {
487             stateFlags |= ContentCaptureSession.STATE_FLAG_SECURE;
488         }
489         if ((clientFlags & ContentCaptureContext.FLAG_DISABLED_BY_APP) != 0) {
490             stateFlags |= ContentCaptureSession.STATE_BY_APP;
491         }
492         if (stateFlags == 0) {
493             stateFlags = initialState;
494         } else {
495             stateFlags |= ContentCaptureSession.STATE_DISABLED;
496         }
497         setClientState(clientReceiver, stateFlags, mContentCaptureClientInterface.asBinder());
498     }
499 
handleSendEvents(int uid, @NonNull ParceledListSlice<ContentCaptureEvent> parceledEvents, int reason, @Nullable ContentCaptureOptions options)500     private void handleSendEvents(int uid,
501             @NonNull ParceledListSlice<ContentCaptureEvent> parceledEvents, int reason,
502             @Nullable ContentCaptureOptions options) {
503         final List<ContentCaptureEvent> events = parceledEvents.getList();
504         if (events.isEmpty()) {
505             Log.w(TAG, "handleSendEvents() received empty list of events");
506             return;
507         }
508 
509         // Metrics.
510         final FlushMetrics metrics = new FlushMetrics();
511         ComponentName activityComponent = null;
512 
513         // Most events belong to the same session, so we can keep a reference to the last one
514         // to avoid creating too many ContentCaptureSessionId objects
515         int lastSessionId = NO_SESSION_ID;
516         ContentCaptureSessionId sessionId = null;
517 
518         for (int i = 0; i < events.size(); i++) {
519             final ContentCaptureEvent event = events.get(i);
520             if (!handleIsRightCallerFor(event, uid)) continue;
521             int sessionIdInt = event.getSessionId();
522             if (sessionIdInt != lastSessionId) {
523                 sessionId = new ContentCaptureSessionId(sessionIdInt);
524                 lastSessionId = sessionIdInt;
525                 if (i != 0) {
526                     writeFlushMetrics(lastSessionId, activityComponent, metrics, options, reason);
527                     metrics.reset();
528                 }
529             }
530             final ContentCaptureContext clientContext = event.getContentCaptureContext();
531             if (activityComponent == null && clientContext != null) {
532                 activityComponent = clientContext.getActivityComponent();
533             }
534             switch (event.getType()) {
535                 case ContentCaptureEvent.TYPE_SESSION_STARTED:
536                     clientContext.setParentSessionId(event.getParentSessionId());
537                     mSessionUids.put(sessionIdInt, uid);
538                     onCreateContentCaptureSession(clientContext, sessionId);
539                     metrics.sessionStarted++;
540                     break;
541                 case ContentCaptureEvent.TYPE_SESSION_FINISHED:
542                     mSessionUids.delete(sessionIdInt);
543                     onDestroyContentCaptureSession(sessionId);
544                     metrics.sessionFinished++;
545                     break;
546                 case ContentCaptureEvent.TYPE_VIEW_APPEARED:
547                     onContentCaptureEvent(sessionId, event);
548                     metrics.viewAppearedCount++;
549                     break;
550                 case ContentCaptureEvent.TYPE_VIEW_DISAPPEARED:
551                     onContentCaptureEvent(sessionId, event);
552                     metrics.viewDisappearedCount++;
553                     break;
554                 case ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED:
555                     onContentCaptureEvent(sessionId, event);
556                     metrics.viewTextChangedCount++;
557                     break;
558                 default:
559                     onContentCaptureEvent(sessionId, event);
560             }
561         }
562         writeFlushMetrics(lastSessionId, activityComponent, metrics, options, reason);
563     }
564 
handleOnLoginDetected( int uid, @NonNull ParceledListSlice<ContentCaptureEvent> parceledEvents)565     private void handleOnLoginDetected(
566             int uid, @NonNull ParceledListSlice<ContentCaptureEvent> parceledEvents) {
567         if (uid != Process.SYSTEM_UID) {
568             Log.e(TAG, "handleOnLoginDetected() not allowed for uid: " + uid);
569             return;
570         }
571         List<ContentCaptureEvent> events = parceledEvents.getList();
572         int sessionIdInt = events.isEmpty() ? NO_SESSION_ID : events.get(0).getSessionId();
573         ContentCaptureSessionId sessionId = new ContentCaptureSessionId(sessionIdInt);
574 
575         ContentCaptureEvent startEvent =
576                 new ContentCaptureEvent(sessionIdInt, TYPE_SESSION_RESUMED);
577         startEvent.setSelectionIndex(0, events.size());
578         onContentCaptureEvent(sessionId, startEvent);
579 
580         events.forEach(event -> onContentCaptureEvent(sessionId, event));
581 
582         ContentCaptureEvent endEvent = new ContentCaptureEvent(sessionIdInt, TYPE_SESSION_PAUSED);
583         onContentCaptureEvent(sessionId, endEvent);
584     }
585 
handleOnActivitySnapshot(int sessionId, @NonNull SnapshotData snapshotData)586     private void handleOnActivitySnapshot(int sessionId, @NonNull SnapshotData snapshotData) {
587         onActivitySnapshot(new ContentCaptureSessionId(sessionId), snapshotData);
588     }
589 
handleFinishSession(int sessionId)590     private void handleFinishSession(int sessionId) {
591         mSessionUids.delete(sessionId);
592         onDestroyContentCaptureSession(new ContentCaptureSessionId(sessionId));
593     }
594 
handleOnDataRemovalRequest(@onNull DataRemovalRequest request)595     private void handleOnDataRemovalRequest(@NonNull DataRemovalRequest request) {
596         onDataRemovalRequest(request);
597     }
598 
handleOnDataShared(@onNull DataShareRequest request, IDataShareCallback callback)599     private void handleOnDataShared(@NonNull DataShareRequest request,
600             IDataShareCallback callback) {
601         onDataShareRequest(request, new DataShareCallback() {
602 
603             @Override
604             public void onAccept(@NonNull Executor executor,
605                     @NonNull DataShareReadAdapter adapter) {
606                 Objects.requireNonNull(adapter);
607                 Objects.requireNonNull(executor);
608 
609                 DataShareReadAdapterDelegate delegate =
610                         new DataShareReadAdapterDelegate(executor, adapter,
611                                 mDataShareAdapterResourceManager);
612 
613                 try {
614                     callback.accept(delegate);
615                 } catch (RemoteException e) {
616                     Slog.e(TAG, "Failed to accept data sharing", e);
617                 }
618             }
619 
620             @Override
621             public void onReject() {
622                 try {
623                     callback.reject();
624                 } catch (RemoteException e) {
625                     Slog.e(TAG, "Failed to reject data sharing", e);
626                 }
627             }
628         });
629     }
630 
handleOnActivityEvent(@onNull ActivityEvent event)631     private void handleOnActivityEvent(@NonNull ActivityEvent event) {
632         onActivityEvent(event);
633     }
634 
635     /**
636      * Checks if the given {@code uid} owns the session associated with the event.
637      */
handleIsRightCallerFor(@onNull ContentCaptureEvent event, int uid)638     private boolean handleIsRightCallerFor(@NonNull ContentCaptureEvent event, int uid) {
639         final int sessionId;
640         switch (event.getType()) {
641             case ContentCaptureEvent.TYPE_SESSION_STARTED:
642             case ContentCaptureEvent.TYPE_SESSION_FINISHED:
643                 sessionId = event.getParentSessionId();
644                 break;
645             default:
646                 sessionId = event.getSessionId();
647         }
648         if (mSessionUids.indexOfKey(sessionId) < 0) {
649             if (sVerbose) {
650                 Log.v(TAG, "handleIsRightCallerFor(" + event + "): no session for " + sessionId
651                         + ": " + mSessionUids);
652             }
653             // Just ignore, as the session could have been finished already
654             return false;
655         }
656         final int rightUid = mSessionUids.get(sessionId);
657         if (rightUid != uid) {
658             Log.e(TAG, "invalid call from UID " + uid + ": session " + sessionId + " belongs to "
659                     + rightUid);
660             long now = System.currentTimeMillis();
661             if (now - mLastCallerMismatchLog > mCallerMismatchTimeout) {
662                 FrameworkStatsLog.write(FrameworkStatsLog.CONTENT_CAPTURE_CALLER_MISMATCH_REPORTED,
663                         getPackageManager().getNameForUid(rightUid),
664                         getPackageManager().getNameForUid(uid));
665                 mLastCallerMismatchLog = now;
666             }
667             return false;
668         }
669         return true;
670 
671     }
672 
673     /**
674      * Sends the state of the {@link ContentCaptureManager} in the client app.
675      *
676      * @param clientReceiver receiver in the client app.
677      * @param sessionState state of the session
678      * @param binder handle to the {@code IContentCaptureDirectManager} object that resides in the
679      * service.
680      * @hide
681      */
setClientState(@onNull IResultReceiver clientReceiver, int sessionState, @Nullable IBinder binder)682     public static void setClientState(@NonNull IResultReceiver clientReceiver,
683             int sessionState, @Nullable IBinder binder) {
684         try {
685             final Bundle extras;
686             if (binder != null) {
687                 extras = new Bundle();
688                 extras.putBinder(MainContentCaptureSession.EXTRA_BINDER, binder);
689             } else {
690                 extras = null;
691             }
692             clientReceiver.send(sessionState, extras);
693         } catch (RemoteException e) {
694             Slog.w(TAG, "Error async reporting result to client: " + e);
695         }
696     }
697 
698     /**
699      * Logs the metrics for content capture events flushing.
700      */
writeFlushMetrics(int sessionId, @Nullable ComponentName app, @NonNull FlushMetrics flushMetrics, @Nullable ContentCaptureOptions options, int flushReason)701     private void writeFlushMetrics(int sessionId, @Nullable ComponentName app,
702             @NonNull FlushMetrics flushMetrics, @Nullable ContentCaptureOptions options,
703             int flushReason) {
704         if (mCallback == null) {
705             Log.w(TAG, "writeSessionFlush(): no server callback");
706             return;
707         }
708 
709         try {
710             mCallback.writeSessionFlush(sessionId, app, flushMetrics, options, flushReason);
711         } catch (RemoteException e) {
712             Log.e(TAG, "failed to write flush metrics: " + e);
713         }
714     }
715 
716     private static class DataShareReadAdapterDelegate extends IDataShareReadAdapter.Stub {
717 
718         private final WeakReference<LocalDataShareAdapterResourceManager> mResourceManagerReference;
719         private final Object mLock = new Object();
720 
DataShareReadAdapterDelegate(Executor executor, DataShareReadAdapter adapter, LocalDataShareAdapterResourceManager resourceManager)721         DataShareReadAdapterDelegate(Executor executor, DataShareReadAdapter adapter,
722                 LocalDataShareAdapterResourceManager resourceManager) {
723             Objects.requireNonNull(executor);
724             Objects.requireNonNull(adapter);
725             Objects.requireNonNull(resourceManager);
726 
727             resourceManager.initializeForDelegate(this, adapter, executor);
728             mResourceManagerReference = new WeakReference<>(resourceManager);
729         }
730 
731         @Override
start(ParcelFileDescriptor fd)732         public void start(ParcelFileDescriptor fd)
733                 throws RemoteException {
734             synchronized (mLock) {
735                 executeAdapterMethodLocked(adapter -> adapter.onStart(fd), "onStart");
736             }
737         }
738 
739         @Override
error(int errorCode)740         public void error(int errorCode) throws RemoteException {
741             synchronized (mLock) {
742                 executeAdapterMethodLocked(
743                         adapter -> adapter.onError(errorCode), "onError");
744                 clearHardReferences();
745             }
746         }
747 
748         @Override
finish()749         public void finish() throws RemoteException {
750             synchronized (mLock) {
751                 clearHardReferences();
752             }
753         }
754 
executeAdapterMethodLocked(Consumer<DataShareReadAdapter> adapterFn, String methodName)755         private void executeAdapterMethodLocked(Consumer<DataShareReadAdapter> adapterFn,
756                 String methodName) {
757             LocalDataShareAdapterResourceManager resourceManager = mResourceManagerReference.get();
758             if (resourceManager == null) {
759                 Slog.w(TAG, "Can't execute " + methodName + "(), resource manager has been GC'ed");
760                 return;
761             }
762 
763             DataShareReadAdapter adapter = resourceManager.getAdapter(this);
764             Executor executor = resourceManager.getExecutor(this);
765 
766             if (adapter == null || executor == null) {
767                 Slog.w(TAG, "Can't execute " + methodName + "(), references are null");
768                 return;
769             }
770 
771             final long identity = Binder.clearCallingIdentity();
772             try {
773                 executor.execute(() -> adapterFn.accept(adapter));
774             } finally {
775                 Binder.restoreCallingIdentity(identity);
776             }
777         }
778 
clearHardReferences()779         private void clearHardReferences() {
780             LocalDataShareAdapterResourceManager resourceManager = mResourceManagerReference.get();
781             if (resourceManager == null) {
782                 Slog.w(TAG, "Can't clear references, resource manager has been GC'ed");
783                 return;
784             }
785 
786             resourceManager.clearHardReferences(this);
787         }
788     }
789 
790     /**
791      * Wrapper class making sure dependencies on the current application stay in the application
792      * context.
793      */
794     private static class LocalDataShareAdapterResourceManager {
795 
796         // Keeping hard references to the remote objects in the current process (static context)
797         // to prevent them to be gc'ed during the lifetime of the application. This is an
798         // artifact of only operating with weak references remotely: there has to be at least 1
799         // hard reference in order for this to not be killed.
800         private Map<DataShareReadAdapterDelegate, DataShareReadAdapter>
801                 mDataShareReadAdapterHardReferences = new HashMap<>();
802         private Map<DataShareReadAdapterDelegate, Executor> mExecutorHardReferences =
803                 new HashMap<>();
804 
805 
initializeForDelegate(DataShareReadAdapterDelegate delegate, DataShareReadAdapter adapter, Executor executor)806         void initializeForDelegate(DataShareReadAdapterDelegate delegate,
807                 DataShareReadAdapter adapter, Executor executor) {
808             mDataShareReadAdapterHardReferences.put(delegate, adapter);
809             mExecutorHardReferences.put(delegate, executor);
810         }
811 
getExecutor(DataShareReadAdapterDelegate delegate)812         Executor getExecutor(DataShareReadAdapterDelegate delegate) {
813             return mExecutorHardReferences.get(delegate);
814         }
815 
getAdapter(DataShareReadAdapterDelegate delegate)816         DataShareReadAdapter getAdapter(DataShareReadAdapterDelegate delegate) {
817             return mDataShareReadAdapterHardReferences.get(delegate);
818         }
819 
clearHardReferences(DataShareReadAdapterDelegate delegate)820         void clearHardReferences(DataShareReadAdapterDelegate delegate) {
821             mDataShareReadAdapterHardReferences.remove(delegate);
822             mExecutorHardReferences.remove(delegate);
823         }
824     }
825 }
826