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