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