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