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