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