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><{@link 95 * android.R.styleable#ContentCaptureService content-capture-service}></code> tag. 96 * 97 * <p>Here's an example of how to use it on {@code AndroidManifest.xml}: 98 * 99 * <pre> 100 * <service android:name=".MyContentCaptureService" 101 * android:permission="android.permission.BIND_CONTENT_CAPTURE_SERVICE"> 102 * <intent-filter> 103 * <action android:name="android.service.contentcapture.ContentCaptureService" /> 104 * </intent-filter> 105 * 106 * <meta-data 107 * android:name="android.content_capture" 108 * android:resource="@xml/my_content_capture_service"/> 109 * </service> 110 * </pre> 111 * 112 * <p>And then on {@code res/xml/my_content_capture_service.xml}: 113 * 114 * <pre> 115 * <content-capture-service xmlns:android="http://schemas.android.com/apk/res/android" 116 * android:settingsActivity="my.package.MySettingsActivity"> 117 * </content-capture-service> 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