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