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.view.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.toSet; 21 import static android.view.contentcapture.flags.Flags.runOnBackgroundThreadEnabled; 22 23 import android.annotation.CallbackExecutor; 24 import android.annotation.IntDef; 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.annotation.RequiresPermission; 28 import android.annotation.SystemApi; 29 import android.annotation.SystemService; 30 import android.annotation.TestApi; 31 import android.annotation.UiThread; 32 import android.annotation.UserIdInt; 33 import android.app.Activity; 34 import android.app.Service; 35 import android.content.ComponentName; 36 import android.content.ContentCaptureOptions; 37 import android.content.Context; 38 import android.graphics.Canvas; 39 import android.os.Binder; 40 import android.os.Handler; 41 import android.os.IBinder; 42 import android.os.Looper; 43 import android.os.ParcelFileDescriptor; 44 import android.os.RemoteException; 45 import android.os.ServiceManager; 46 import android.util.Dumpable; 47 import android.util.Log; 48 import android.util.Slog; 49 import android.view.View; 50 import android.view.ViewStructure; 51 import android.view.WindowManager; 52 import android.view.contentcapture.ContentCaptureSession.FlushReason; 53 54 import com.android.internal.annotations.GuardedBy; 55 import com.android.internal.annotations.VisibleForTesting; 56 import com.android.internal.os.BackgroundThread; 57 import com.android.internal.util.RingBuffer; 58 import com.android.internal.util.SyncResultReceiver; 59 60 import java.io.PrintWriter; 61 import java.lang.annotation.Retention; 62 import java.lang.annotation.RetentionPolicy; 63 import java.lang.ref.WeakReference; 64 import java.util.ArrayList; 65 import java.util.Collections; 66 import java.util.HashMap; 67 import java.util.List; 68 import java.util.Map; 69 import java.util.Objects; 70 import java.util.Set; 71 import java.util.concurrent.Executor; 72 import java.util.function.Consumer; 73 74 /** 75 * <p>Provides additional ways for apps to integrate with the content capture subsystem. 76 * 77 * <p>Content capture provides real-time, continuous capture of application activity, display and 78 * events to an intelligence service that is provided by the Android system. The intelligence 79 * service then uses that info to mediate and speed user journey through different apps. For 80 * example, when the user receives a restaurant address in a chat app and switches to a map app 81 * to search for that restaurant, the intelligence service could offer an autofill dialog to 82 * let the user automatically select its address. 83 * 84 * <p>Content capture was designed with two major concerns in mind: privacy and performance. 85 * 86 * <ul> 87 * <li><b>Privacy:</b> the intelligence service is a trusted component provided that is provided 88 * by the device manufacturer and that cannot be changed by the user (although the user can 89 * globaly disable content capture using the Android Settings app). This service can only use the 90 * data for in-device machine learning, which is enforced both by process isolation and 91 * <a href="https://source.android.com/compatibility/cdd">CDD requirements</a>. 92 * <li><b>Performance:</b> content capture is highly optimized to minimize its impact in the app 93 * jankiness and overall device system health. For example, its only enabled on apps (or even 94 * specific activities from an app) that were explicitly allowlisted by the intelligence service, 95 * and it buffers the events so they are sent in a batch to the service (see 96 * {@link #isContentCaptureEnabled()} for other cases when its disabled). 97 * </ul> 98 * 99 * <p>In fact, before using this manager, the app developer should check if it's available. Example: 100 * <pre><code> 101 * ContentCaptureManager mgr = context.getSystemService(ContentCaptureManager.class); 102 * if (mgr != null && mgr.isContentCaptureEnabled()) { 103 * // ... 104 * } 105 * </code></pre> 106 * 107 * <p>App developers usually don't need to explicitly interact with content capture, except when the 108 * app: 109 * 110 * <ul> 111 * <li>Can define a contextual {@link android.content.LocusId} to identify unique state (such as a 112 * conversation between 2 chat users). 113 * <li>Can have multiple view hierarchies with different contextual meaning (for example, a 114 * browser app with multiple tabs, each representing a different URL). 115 * <li>Contains custom views (that extend View directly and are not provided by the standard 116 * Android SDK. 117 * <li>Contains views that provide their own virtual hierarchy (like a web browser that render the 118 * HTML elements using a Canvas). 119 * </ul> 120 * 121 * <p>The main integration point with content capture is the {@link ContentCaptureSession}. A "main" 122 * session is automatically created by the Android System when content capture is enabled for the 123 * activity and its used by the standard Android views to notify the content capture service of 124 * events such as views being added, views been removed, and text changed by user input. The session 125 * could have a {@link ContentCaptureContext} to provide more contextual info about it, such as 126 * the locus associated with the view hierarchy (see {@link android.content.LocusId} for more info 127 * about locus). By default, the main session doesn't have a {@code ContentCaptureContext}, but you 128 * can change it after its created. Example: 129 * 130 * <pre><code> 131 * protected void onCreate(Bundle savedInstanceState) { 132 * // Initialize view structure 133 * ContentCaptureSession session = rootView.getContentCaptureSession(); 134 * if (session != null) { 135 * session.setContentCaptureContext(ContentCaptureContext.forLocusId("chat_UserA_UserB")); 136 * } 137 * } 138 * </code></pre> 139 * 140 * <p>If your activity contains view hierarchies with a different contextual meaning, you should 141 * created child sessions for each view hierarchy root. For example, if your activity is a browser, 142 * you could use the main session for the main URL being rendered, then child sessions for each 143 * {@code IFRAME}: 144 * 145 * <pre><code> 146 * ContentCaptureSession mMainSession; 147 * 148 * protected void onCreate(Bundle savedInstanceState) { 149 * // Initialize view structure... 150 * mMainSession = rootView.getContentCaptureSession(); 151 * if (mMainSession != null) { 152 * mMainSession.setContentCaptureContext( 153 * ContentCaptureContext.forLocusId("https://example.com")); 154 * } 155 * } 156 * 157 * private void loadIFrame(View iframeRootView, String url) { 158 * if (mMainSession != null) { 159 * ContentCaptureSession iFrameSession = mMainSession.newChild( 160 * ContentCaptureContext.forLocusId(url)); 161 * } 162 * iframeRootView.setContentCaptureSession(iFrameSession); 163 * } 164 * // Load iframe... 165 * } 166 * </code></pre> 167 * 168 * <p>If your activity has custom views (i.e., views that extend {@link View} directly and provide 169 * just one logical view, not a virtual tree hiearchy) and it provides content that's relevant for 170 * content capture (as of {@link android.os.Build.VERSION_CODES#Q Android Q}, the only relevant 171 * content is text), then your view implementation should: 172 * 173 * <ul> 174 * <li>Set it as important for content capture. 175 * <li>Fill {@link ViewStructure} used for content capture. 176 * <li>Notify the {@link ContentCaptureSession} when the text is changed by user input. 177 * </ul> 178 * 179 * <p>Here's an example of the relevant methods for an {@code EditText}-like view: 180 * 181 * <pre><code> 182 * public class MyEditText extends View { 183 * 184 * public MyEditText(...) { 185 * if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) { 186 * setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES); 187 * } 188 * } 189 * 190 * public void onProvideContentCaptureStructure(@NonNull ViewStructure structure, int flags) { 191 * super.onProvideContentCaptureStructure(structure, flags); 192 * 193 * structure.setText(getText(), getSelectionStart(), getSelectionEnd()); 194 * structure.setHint(getHint()); 195 * structure.setInputType(getInputType()); 196 * // set other properties like setTextIdEntry(), setTextLines(), setTextStyle(), 197 * // setMinTextEms(), setMaxTextEms(), setMaxTextLength() 198 * } 199 * 200 * private void onTextChanged() { 201 * if (isLaidOut() && isImportantForContentCapture() && isTextEditable()) { 202 * ContentCaptureManager mgr = mContext.getSystemService(ContentCaptureManager.class); 203 * if (cm != null && cm.isContentCaptureEnabled()) { 204 * ContentCaptureSession session = getContentCaptureSession(); 205 * if (session != null) { 206 * session.notifyViewTextChanged(getAutofillId(), getText()); 207 * } 208 * } 209 * } 210 * </code></pre> 211 * 212 * <p>If your view provides its own virtual hierarchy (for example, if it's a browser that draws 213 * the HTML using {@link Canvas} or native libraries in a different render process), then the view 214 * is also responsible to notify the session when the virtual elements appear and disappear - see 215 * {@link View#onProvideContentCaptureStructure(ViewStructure, int)} for more info. 216 */ 217 @SystemService(Context.CONTENT_CAPTURE_MANAGER_SERVICE) 218 public final class ContentCaptureManager { 219 220 private static final String TAG = ContentCaptureManager.class.getSimpleName(); 221 222 /** @hide */ 223 public static final boolean DEBUG = false; 224 225 /** @hide */ 226 @TestApi 227 public static final String DUMPABLE_NAME = "ContentCaptureManager"; 228 229 /** Error happened during the data sharing session. */ 230 public static final int DATA_SHARE_ERROR_UNKNOWN = 1; 231 232 /** Request has been rejected, because a concurrent data share sessions is in progress. */ 233 public static final int DATA_SHARE_ERROR_CONCURRENT_REQUEST = 2; 234 235 /** Request has been interrupted because of data share session timeout. */ 236 public static final int DATA_SHARE_ERROR_TIMEOUT_INTERRUPTED = 3; 237 238 /** @hide */ 239 @IntDef(flag = false, value = { 240 DATA_SHARE_ERROR_UNKNOWN, 241 DATA_SHARE_ERROR_CONCURRENT_REQUEST, 242 DATA_SHARE_ERROR_TIMEOUT_INTERRUPTED 243 }) 244 @Retention(RetentionPolicy.SOURCE) 245 public @interface DataShareError {} 246 247 /** @hide */ 248 public static final int RESULT_CODE_OK = 0; 249 /** @hide */ 250 public static final int RESULT_CODE_TRUE = 1; 251 /** @hide */ 252 public static final int RESULT_CODE_FALSE = 2; 253 /** @hide */ 254 public static final int RESULT_CODE_SECURITY_EXCEPTION = -1; 255 256 /** 257 * ID used to indicate that a session does not exist 258 * @hide 259 */ 260 @SystemApi 261 public static final int NO_SESSION_ID = 0; 262 263 /** 264 * Timeout for calls to system_server. 265 */ 266 private static final int SYNC_CALLS_TIMEOUT_MS = 5000; 267 268 /** 269 * DeviceConfig property used by {@code com.android.server.SystemServer} on start to decide 270 * whether the content capture service should be created or not 271 * 272 * <p>By default it should *NOT* be set (or set to {@code "default"}, so the decision is based 273 * on whether the OEM provides an implementation for the service), but it can be overridden to: 274 * 275 * <ul> 276 * <li>Provide a "kill switch" so OEMs can disable it remotely in case of emergency (when 277 * it's set to {@code "false"}). 278 * <li>Enable the CTS tests to be run on AOSP builds (when it's set to {@code "true"}). 279 * </ul> 280 * 281 * @hide 282 */ 283 @TestApi 284 public static final String DEVICE_CONFIG_PROPERTY_SERVICE_EXPLICITLY_ENABLED = 285 "service_explicitly_enabled"; 286 287 /** 288 * Device config property used by {@code android.widget.AbsListView} to determine whether or 289 * not it should report the positions of its children to Content Capture. 290 * 291 * @hide 292 */ 293 public static final String DEVICE_CONFIG_PROPERTY_REPORT_LIST_VIEW_CHILDREN = 294 "report_list_view_children"; 295 296 /** 297 * Maximum number of events that are buffered before sent to the app. 298 * 299 * @hide 300 */ 301 @TestApi 302 public static final String DEVICE_CONFIG_PROPERTY_MAX_BUFFER_SIZE = "max_buffer_size"; 303 304 /** 305 * Frequency (in ms) of buffer flushes when no events are received. 306 * 307 * @hide 308 */ 309 @TestApi 310 public static final String DEVICE_CONFIG_PROPERTY_IDLE_FLUSH_FREQUENCY = "idle_flush_frequency"; 311 312 /** 313 * Frequency (in ms) of buffer flushes when no events are received and the last one was a 314 * text change event. 315 * 316 * @hide 317 */ 318 @TestApi 319 public static final String DEVICE_CONFIG_PROPERTY_TEXT_CHANGE_FLUSH_FREQUENCY = 320 "text_change_flush_frequency"; 321 322 /** 323 * Size of events that are logging on {@code dump}. 324 * 325 * <p>Set it to {@code 0} or less to disable history. 326 * 327 * @hide 328 */ 329 @TestApi 330 public static final String DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE = "log_history_size"; 331 332 /** 333 * Sets the logging level for {@code logcat} statements. 334 * 335 * <p>Valid values are: {@link #LOGGING_LEVEL_OFF}, {@value #LOGGING_LEVEL_DEBUG}, and 336 * {@link #LOGGING_LEVEL_VERBOSE}. 337 * 338 * @hide 339 */ 340 @TestApi 341 public static final String DEVICE_CONFIG_PROPERTY_LOGGING_LEVEL = "logging_level"; 342 343 /** 344 * Sets how long (in ms) the service is bound while idle. 345 * 346 * <p>Use {@code 0} to keep it permanently bound. 347 * 348 * @hide 349 */ 350 public static final String DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT = "idle_unbind_timeout"; 351 352 /** 353 * Sets to disable flush when receiving a VIEW_TREE_APPEARING event. 354 * 355 * @hide 356 */ 357 public static final String DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING = 358 "disable_flush_for_view_tree_appearing"; 359 360 /** 361 * Enables the content protection receiver. 362 * 363 * @hide 364 */ 365 public static final String DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER = 366 "enable_content_protection_receiver"; 367 368 /** 369 * Whether AssistContent snapshot should be sent on activity start. 370 * 371 * @hide 372 */ 373 public static final String DEVICE_CONFIG_ENABLE_ACTIVITY_START_ASSIST_CONTENT = 374 "enable_activity_start_assist_content"; 375 376 /** 377 * Sets the size of the in-memory ring buffer for the content protection flow. 378 * 379 * @hide 380 */ 381 public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE = 382 "content_protection_buffer_size"; 383 384 /** 385 * Sets the config for content protection required groups. 386 * 387 * @hide 388 */ 389 public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG = 390 "content_protection_required_groups_config"; 391 392 /** 393 * Sets the config for content protection optional groups. 394 * 395 * @hide 396 */ 397 public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG = 398 "content_protection_optional_groups_config"; 399 400 /** 401 * Sets the threshold for content protection optional groups. 402 * 403 * @hide 404 */ 405 public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD = 406 "content_protection_optional_groups_threshold"; 407 408 /** 409 * Sets the initial delay for fetching content protection allowlist in milliseconds. 410 * 411 * @hide 412 */ 413 public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS = 414 "content_protection_allowlist_delay_ms"; 415 416 /** 417 * Sets the timeout for fetching content protection allowlist in milliseconds. 418 * 419 * @hide 420 */ 421 public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS = 422 "content_protection_allowlist_timeout_ms"; 423 424 /** 425 * Sets the auto disconnect timeout for the content protection service in milliseconds. 426 * 427 * @hide 428 */ 429 // Unit can't be in the name in order to pass the checkstyle hook, line would be too long. 430 public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_AUTO_DISCONNECT_TIMEOUT = 431 "content_protection_auto_disconnect_timeout_ms"; 432 433 /** @hide */ 434 @TestApi 435 public static final int LOGGING_LEVEL_OFF = 0; 436 437 /** @hide */ 438 @TestApi 439 public static final int LOGGING_LEVEL_DEBUG = 1; 440 441 /** @hide */ 442 @TestApi 443 public static final int LOGGING_LEVEL_VERBOSE = 2; 444 445 /** @hide */ 446 @IntDef(flag = false, value = { 447 LOGGING_LEVEL_OFF, 448 LOGGING_LEVEL_DEBUG, 449 LOGGING_LEVEL_VERBOSE 450 }) 451 @Retention(RetentionPolicy.SOURCE) 452 public @interface LoggingLevel {} 453 454 455 /** @hide */ 456 public static final int DEFAULT_MAX_BUFFER_SIZE = 500; // Enough for typical busy screen. 457 /** @hide */ 458 public static final int DEFAULT_IDLE_FLUSHING_FREQUENCY_MS = 5_000; 459 /** @hide */ 460 public static final int DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS = 1_000; 461 /** @hide */ 462 public static final int DEFAULT_LOG_HISTORY_SIZE = 10; 463 /** @hide */ 464 public static final boolean DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING = false; 465 /** @hide */ 466 public static final boolean DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER = true; 467 /** @hide */ 468 public static final boolean DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER = false; 469 /** @hide */ 470 public static final int DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE = 150; 471 /** @hide */ 472 public static final List<List<String>> DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS = 473 Collections.emptyList(); 474 /** @hide */ 475 public static final String DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG = ""; 476 /** @hide */ 477 public static final List<List<String>> DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS = 478 Collections.emptyList(); 479 /** @hide */ 480 public static final String DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG = ""; 481 /** @hide */ 482 public static final int DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD = 0; 483 /** @hide */ 484 public static final long DEFAULT_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS = 30000; 485 /** @hide */ 486 public static final long DEFAULT_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS = 250; 487 /** @hide */ 488 public static final long DEFAULT_CONTENT_PROTECTION_AUTO_DISCONNECT_TIMEOUT_MS = 3000; 489 490 private final Object mLock = new Object(); 491 492 @NonNull 493 private final StrippedContext mContext; 494 495 @NonNull 496 private final IContentCaptureManager mService; 497 498 @GuardedBy("mLock") 499 private final LocalDataShareAdapterResourceManager mDataShareAdapterResourceManager; 500 501 @NonNull 502 final ContentCaptureOptions mOptions; 503 504 // Flags used for starting session. 505 @GuardedBy("mLock") 506 private int mFlags; 507 508 @Nullable 509 @GuardedBy("mLock") 510 private Handler mUiHandler; 511 512 @Nullable 513 @GuardedBy("mLock") 514 private Handler mContentCaptureHandler; 515 516 @GuardedBy("mLock") 517 private ContentCaptureSession mMainSession; 518 519 @Nullable // set on-demand by addDumpable() 520 private Dumper mDumpable; 521 522 // Created here in order to live across activity and session changes 523 @Nullable private final RingBuffer<ContentCaptureEvent> mContentProtectionEventBuffer; 524 525 /** @hide */ 526 public interface ContentCaptureClient { 527 /** 528 * Gets the component name of the client. 529 */ 530 @NonNull contentCaptureClientGetComponentName()531 ComponentName contentCaptureClientGetComponentName(); 532 } 533 534 /** @hide */ 535 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 536 public static class StrippedContext { 537 @NonNull final String mPackageName; 538 @NonNull final String mContext; 539 final @UserIdInt int mUserId; 540 541 /** @hide */ 542 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) StrippedContext(@onNull Context context)543 public StrippedContext(@NonNull Context context) { 544 mPackageName = context.getPackageName(); 545 mContext = context.toString(); 546 mUserId = context.getUserId(); 547 } 548 549 @Override toString()550 public String toString() { 551 return mContext; 552 } 553 554 @NonNull getPackageName()555 public String getPackageName() { 556 return mPackageName; 557 } 558 559 @UserIdInt getUserId()560 public int getUserId() { 561 return mUserId; 562 } 563 } 564 565 /** @hide */ ContentCaptureManager(@onNull Context context, @NonNull IContentCaptureManager service, @NonNull ContentCaptureOptions options)566 public ContentCaptureManager(@NonNull Context context, 567 @NonNull IContentCaptureManager service, @NonNull ContentCaptureOptions options) { 568 Objects.requireNonNull(context, "context cannot be null"); 569 mContext = new StrippedContext(context); 570 mService = Objects.requireNonNull(service, "service cannot be null"); 571 mOptions = Objects.requireNonNull(options, "options cannot be null"); 572 573 ContentCaptureHelper.setLoggingLevel(mOptions.loggingLevel); 574 setFlushViewTreeAppearingEventDisabled(mOptions.disableFlushForViewTreeAppearing); 575 576 if (sVerbose) Log.v(TAG, "Constructor for " + context.getPackageName()); 577 578 mDataShareAdapterResourceManager = new LocalDataShareAdapterResourceManager(); 579 580 if (mOptions.contentProtectionOptions.enableReceiver 581 && mOptions.contentProtectionOptions.bufferSize > 0) { 582 mContentProtectionEventBuffer = 583 new RingBuffer( 584 ContentCaptureEvent.class, 585 mOptions.contentProtectionOptions.bufferSize); 586 } else { 587 mContentProtectionEventBuffer = null; 588 } 589 } 590 591 /** 592 * Gets the main session associated with the context. 593 * 594 * <p>By default there's just one (associated with the activity lifecycle), but apps could 595 * explicitly add more using 596 * {@link ContentCaptureSession#createContentCaptureSession(ContentCaptureContext)}. 597 * 598 * @hide 599 */ 600 @NonNull 601 @UiThread getMainContentCaptureSession()602 public ContentCaptureSession getMainContentCaptureSession() { 603 synchronized (mLock) { 604 if (mMainSession == null) { 605 mMainSession = prepareMainSession(); 606 if (sVerbose) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession); 607 } 608 return mMainSession; 609 } 610 } 611 612 @NonNull 613 @GuardedBy("mLock") prepareMainSession()614 private ContentCaptureSession prepareMainSession() { 615 if (runOnBackgroundThreadEnabled()) { 616 return new MainContentCaptureSessionV2( 617 mContext, 618 this, 619 prepareUiHandler(), 620 prepareContentCaptureHandler(), 621 mService 622 ); 623 } else { 624 return new MainContentCaptureSession(mContext, this, prepareUiHandler(), mService); 625 } 626 } 627 628 @NonNull 629 @GuardedBy("mLock") prepareContentCaptureHandler()630 private Handler prepareContentCaptureHandler() { 631 if (mContentCaptureHandler == null) { 632 mContentCaptureHandler = BackgroundThread.getHandler(); 633 } 634 return mContentCaptureHandler; 635 } 636 637 @NonNull 638 @GuardedBy("mLock") prepareUiHandler()639 private Handler prepareUiHandler() { 640 if (mUiHandler == null) { 641 mUiHandler = Handler.createAsync(Looper.getMainLooper()); 642 } 643 return mUiHandler; 644 } 645 646 /** @hide */ 647 @UiThread onActivityCreated(@onNull IBinder applicationToken, @NonNull IBinder shareableActivityToken, @NonNull ComponentName activityComponent)648 public void onActivityCreated(@NonNull IBinder applicationToken, 649 @NonNull IBinder shareableActivityToken, @NonNull ComponentName activityComponent) { 650 if (mOptions.lite) return; 651 synchronized (mLock) { 652 getMainContentCaptureSession().start(applicationToken, shareableActivityToken, 653 activityComponent, mFlags); 654 } 655 } 656 657 /** @hide */ 658 @UiThread onActivityResumed()659 public void onActivityResumed() { 660 if (mOptions.lite) return; 661 getMainContentCaptureSession().notifySessionResumed(); 662 } 663 664 /** @hide */ 665 @UiThread onActivityPaused()666 public void onActivityPaused() { 667 if (mOptions.lite) return; 668 getMainContentCaptureSession().notifySessionPaused(); 669 } 670 671 /** @hide */ 672 @UiThread onActivityDestroyed()673 public void onActivityDestroyed() { 674 if (mOptions.lite) return; 675 getMainContentCaptureSession().destroy(); 676 } 677 678 /** 679 * Flushes the content of all sessions. 680 * 681 * <p>Typically called by {@code Activity} when it's paused / resumed. 682 * 683 * @hide 684 */ 685 @UiThread flush(@lushReason int reason)686 public void flush(@FlushReason int reason) { 687 if (mOptions.lite) return; 688 getMainContentCaptureSession().flush(reason); 689 } 690 691 /** 692 * Returns the component name of the system service that is consuming the captured events for 693 * the current user. 694 * 695 * @throws RuntimeException if getting the component name is timed out. 696 */ 697 @Nullable getServiceComponentName()698 public ComponentName getServiceComponentName() { 699 if (!isContentCaptureEnabled() && !mOptions.lite) return null; 700 701 final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); 702 try { 703 mService.getServiceComponentName(resultReceiver); 704 return resultReceiver.getParcelableResult(); 705 } catch (RemoteException e) { 706 throw e.rethrowFromSystemServer(); 707 } catch (SyncResultReceiver.TimeoutException e) { 708 throw new RuntimeException("Fail to get service componentName."); 709 } 710 } 711 712 /** 713 * Gets the (optional) intent used to launch the service-specific settings. 714 * 715 * <p>This method is static because it's called by Settings, which might not be allowlisted 716 * for content capture (in which case the ContentCaptureManager on its context would be null). 717 * 718 * @hide 719 */ 720 // TODO: use "lite" options as it's done by activities from the content capture service 721 @Nullable getServiceSettingsComponentName()722 public static ComponentName getServiceSettingsComponentName() { 723 final IBinder binder = ServiceManager 724 .checkService(Context.CONTENT_CAPTURE_MANAGER_SERVICE); 725 if (binder == null) return null; 726 727 final IContentCaptureManager service = IContentCaptureManager.Stub.asInterface(binder); 728 final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); 729 try { 730 service.getServiceSettingsActivity(resultReceiver); 731 final int resultCode = resultReceiver.getIntResult(); 732 if (resultCode == RESULT_CODE_SECURITY_EXCEPTION) { 733 throw new SecurityException(resultReceiver.getStringResult()); 734 } 735 return resultReceiver.getParcelableResult(); 736 } catch (RemoteException e) { 737 throw e.rethrowFromSystemServer(); 738 } catch (SyncResultReceiver.TimeoutException e) { 739 Log.e(TAG, "Fail to get service settings componentName: " + e); 740 return null; 741 } 742 } 743 744 /** 745 * Checks whether content capture is enabled for this activity. 746 * 747 * <p>There are many reasons it could be disabled, such as: 748 * <ul> 749 * <li>App itself disabled content capture through {@link #setContentCaptureEnabled(boolean)}. 750 * <li>Intelligence service did not allowlist content capture for this activity's package. 751 * <li>Intelligence service did not allowlist content capture for this specific activity. 752 * <li>Intelligence service disabled content capture globally. 753 * <li>User disabled content capture globally through the Android Settings app. 754 * <li>Device manufacturer (OEM) disabled content capture globally. 755 * <li>Transient errors, such as intelligence service package being updated. 756 * </ul> 757 */ isContentCaptureEnabled()758 public boolean isContentCaptureEnabled() { 759 if (mOptions.lite) return false; 760 761 final ContentCaptureSession mainSession; 762 synchronized (mLock) { 763 mainSession = mMainSession; 764 } 765 // The main session is only set when the activity starts, so we need to return true until 766 // then. 767 if (mainSession != null && mainSession.isDisabled()) return false; 768 769 return true; 770 } 771 772 /** 773 * Gets the list of conditions for when content capture should be allowed. 774 * 775 * <p>This method is typically used by web browsers so they don't generate unnecessary content 776 * capture events for websites the content capture service is not interested on. 777 * 778 * @return list of conditions, or {@code null} if the service didn't set any restriction 779 * (in which case content capture events should always be generated). If the list is empty, 780 * then it should not generate any event at all. 781 */ 782 @Nullable getContentCaptureConditions()783 public Set<ContentCaptureCondition> getContentCaptureConditions() { 784 // NOTE: we could cache the conditions on ContentCaptureOptions, but then it would be stick 785 // to the lifetime of the app. OTOH, by dynamically calling the server every time, we allow 786 // the service to fine tune how long-lived apps (like browsers) are allowlisted. 787 if (!isContentCaptureEnabled() && !mOptions.lite) return null; 788 789 final SyncResultReceiver resultReceiver = syncRun( 790 (r) -> mService.getContentCaptureConditions(mContext.getPackageName(), r)); 791 792 try { 793 final ArrayList<ContentCaptureCondition> result = resultReceiver 794 .getParcelableListResult(); 795 return toSet(result); 796 } catch (SyncResultReceiver.TimeoutException e) { 797 throw new RuntimeException("Fail to get content capture conditions."); 798 } 799 } 800 801 /** 802 * Called by apps to explicitly enable or disable content capture. 803 * 804 * <p><b>Note: </b> this call is not persisted accross reboots, so apps should typically call 805 * it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}. 806 */ setContentCaptureEnabled(boolean enabled)807 public void setContentCaptureEnabled(boolean enabled) { 808 if (sDebug) { 809 Log.d(TAG, "setContentCaptureEnabled(): setting to " + enabled + " for " + mContext); 810 } 811 812 ContentCaptureSession mainSession; 813 synchronized (mLock) { 814 if (enabled) { 815 mFlags &= ~ContentCaptureContext.FLAG_DISABLED_BY_APP; 816 } else { 817 mFlags |= ContentCaptureContext.FLAG_DISABLED_BY_APP; 818 } 819 mainSession = mMainSession; 820 } 821 if (mainSession != null) { 822 mainSession.setDisabled(!enabled); 823 } 824 } 825 826 /** 827 * Called by apps to update flag secure when window attributes change. 828 * 829 * @hide 830 */ updateWindowAttributes(@onNull WindowManager.LayoutParams params)831 public void updateWindowAttributes(@NonNull WindowManager.LayoutParams params) { 832 if (sDebug) { 833 Log.d(TAG, "updateWindowAttributes(): window flags=" + params.flags); 834 } 835 final boolean flagSecureEnabled = 836 (params.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0; 837 838 ContentCaptureSession mainSession; 839 boolean alreadyDisabledByApp; 840 synchronized (mLock) { 841 alreadyDisabledByApp = (mFlags & ContentCaptureContext.FLAG_DISABLED_BY_APP) != 0; 842 if (flagSecureEnabled) { 843 mFlags |= ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE; 844 } else { 845 mFlags &= ~ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE; 846 } 847 mainSession = mMainSession; 848 } 849 850 // Prevent overriding the status of disabling by app 851 if (mainSession != null && !alreadyDisabledByApp) { 852 mainSession.setDisabled(flagSecureEnabled); 853 } 854 } 855 856 /** 857 * Explicitly sets enable or disable flush for view tree appearing event. 858 * 859 * @hide 860 */ 861 @VisibleForTesting setFlushViewTreeAppearingEventDisabled(boolean disabled)862 public void setFlushViewTreeAppearingEventDisabled(boolean disabled) { 863 if (sDebug) { 864 Log.d(TAG, "setFlushViewTreeAppearingEventDisabled(): setting to " + disabled); 865 } 866 867 synchronized (mLock) { 868 if (disabled) { 869 mFlags |= ContentCaptureContext.FLAG_DISABLED_FLUSH_FOR_VIEW_TREE_APPEARING; 870 } else { 871 mFlags &= ~ContentCaptureContext.FLAG_DISABLED_FLUSH_FOR_VIEW_TREE_APPEARING; 872 } 873 } 874 } 875 876 /** 877 * Gets whether content capture is needed to flush for view tree appearing event. 878 * 879 * @hide 880 */ getFlushViewTreeAppearingEventDisabled()881 public boolean getFlushViewTreeAppearingEventDisabled() { 882 synchronized (mLock) { 883 return (mFlags & ContentCaptureContext.FLAG_DISABLED_FLUSH_FOR_VIEW_TREE_APPEARING) 884 != 0; 885 } 886 } 887 888 /** 889 * Gets whether content capture is enabled for the given user. 890 * 891 * <p>This method is typically used by the content capture service settings page, so it can 892 * provide a toggle to enable / disable it. 893 * 894 * @throws SecurityException if caller is not the app that owns the content capture service 895 * associated with the user. 896 * 897 * @hide 898 */ 899 @SystemApi isContentCaptureFeatureEnabled()900 public boolean isContentCaptureFeatureEnabled() { 901 final SyncResultReceiver resultReceiver = syncRun( 902 (r) -> mService.isContentCaptureFeatureEnabled(r)); 903 904 try { 905 final int resultCode = resultReceiver.getIntResult(); 906 switch (resultCode) { 907 case RESULT_CODE_TRUE: 908 return true; 909 case RESULT_CODE_FALSE: 910 return false; 911 default: 912 Log.wtf(TAG, "received invalid result: " + resultCode); 913 return false; 914 } 915 } catch (SyncResultReceiver.TimeoutException e) { 916 Log.e(TAG, "Fail to get content capture feature enable status: " + e); 917 return false; 918 } 919 } 920 921 /** 922 * Called by the app to request the content capture service to remove content capture data 923 * associated with some context. 924 * 925 * @param request object specifying what user data should be removed. 926 */ removeData(@onNull DataRemovalRequest request)927 public void removeData(@NonNull DataRemovalRequest request) { 928 Objects.requireNonNull(request); 929 930 try { 931 mService.removeData(request); 932 } catch (RemoteException e) { 933 throw e.rethrowFromSystemServer(); 934 } 935 } 936 937 /** 938 * Called by the app to request data sharing via writing to a file. 939 * 940 * <p>The ContentCaptureService app will receive a read-only file descriptor pointing to the 941 * same file and will be able to read data being shared from it. 942 * 943 * <p>Note: using this API doesn't guarantee the app staying alive and is "best-effort". 944 * Starting a foreground service would minimize the chances of the app getting killed during the 945 * file sharing session. 946 * 947 * @param request object specifying details of the data being shared. 948 */ shareData(@onNull DataShareRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull DataShareWriteAdapter dataShareWriteAdapter)949 public void shareData(@NonNull DataShareRequest request, 950 @NonNull @CallbackExecutor Executor executor, 951 @NonNull DataShareWriteAdapter dataShareWriteAdapter) { 952 Objects.requireNonNull(request); 953 Objects.requireNonNull(dataShareWriteAdapter); 954 Objects.requireNonNull(executor); 955 956 try { 957 mService.shareData(request, 958 new DataShareAdapterDelegate(executor, dataShareWriteAdapter, 959 mDataShareAdapterResourceManager)); 960 } catch (RemoteException e) { 961 throw e.rethrowFromSystemServer(); 962 } 963 } 964 965 /** 966 * Runs a sync method in the service, properly handling exceptions. 967 * 968 * @throws SecurityException if caller is not allowed to execute the method. 969 */ 970 @NonNull syncRun(@onNull MyRunnable r)971 private SyncResultReceiver syncRun(@NonNull MyRunnable r) { 972 final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); 973 try { 974 r.run(resultReceiver); 975 final int resultCode = resultReceiver.getIntResult(); 976 if (resultCode == RESULT_CODE_SECURITY_EXCEPTION) { 977 throw new SecurityException(resultReceiver.getStringResult()); 978 } 979 } catch (RemoteException e) { 980 throw e.rethrowFromSystemServer(); 981 } catch (SyncResultReceiver.TimeoutException e) { 982 throw new RuntimeException("Fail to get syn run result from SyncResultReceiver."); 983 } 984 return resultReceiver; 985 } 986 987 /** @hide */ addDumpable(Activity activity)988 public void addDumpable(Activity activity) { 989 if (mDumpable == null) { 990 mDumpable = new Dumper(); 991 } 992 activity.addDumpable(mDumpable); 993 } 994 995 /** @hide */ 996 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 997 @Nullable getContentProtectionEventBuffer()998 public RingBuffer<ContentCaptureEvent> getContentProtectionEventBuffer() { 999 return mContentProtectionEventBuffer; 1000 } 1001 1002 // NOTE: ContentCaptureManager cannot implement it directly as it would be exposed as public API 1003 private final class Dumper implements Dumpable { 1004 @Override dump(@onNull PrintWriter pw, @Nullable String[] args)1005 public void dump(@NonNull PrintWriter pw, @Nullable String[] args) { 1006 String prefix = ""; 1007 pw.print(prefix); pw.println("ContentCaptureManager"); 1008 final String prefix2 = prefix + " "; 1009 synchronized (mLock) { 1010 pw.print(prefix2); pw.print("isContentCaptureEnabled(): "); 1011 pw.println(isContentCaptureEnabled()); 1012 pw.print(prefix2); pw.print("Debug: "); pw.print(sDebug); 1013 pw.print(" Verbose: "); pw.println(sVerbose); 1014 pw.print(prefix2); pw.print("Context: "); pw.println(mContext); 1015 pw.print(prefix2); pw.print("User: "); pw.println(mContext.getUserId()); 1016 pw.print(prefix2); pw.print("Service: "); pw.println(mService); 1017 pw.print(prefix2); pw.print("Flags: "); pw.println(mFlags); 1018 pw.print(prefix2); pw.print("Options: "); mOptions.dumpShort(pw); pw.println(); 1019 if (mMainSession != null) { 1020 final String prefix3 = prefix2 + " "; 1021 pw.print(prefix2); pw.println("Main session:"); 1022 mMainSession.dump(prefix3, pw); 1023 } else { 1024 pw.print(prefix2); pw.println("No sessions"); 1025 } 1026 } 1027 } 1028 1029 @Override getDumpableName()1030 public String getDumpableName() { 1031 return DUMPABLE_NAME; 1032 } 1033 } 1034 1035 /** 1036 * Resets the temporary content capture service implementation to the default component. 1037 * 1038 * @hide 1039 */ 1040 @TestApi 1041 @RequiresPermission(android.Manifest.permission.MANAGE_CONTENT_CAPTURE) resetTemporaryService(@serIdInt int userId)1042 public static void resetTemporaryService(@UserIdInt int userId) { 1043 final IContentCaptureManager service = getService(); 1044 if (service == null) { 1045 Log.e(TAG, "IContentCaptureManager is null"); 1046 } 1047 try { 1048 service.resetTemporaryService(userId); 1049 } catch (RemoteException e) { 1050 throw e.rethrowFromSystemServer(); 1051 } 1052 } 1053 1054 /** 1055 * Temporarily sets the content capture service implementation. 1056 * 1057 * @param userId user Id to set the temporary service on. 1058 * @param serviceName name of the new component 1059 * @param duration how long the change will be valid (the service will be automatically reset 1060 * to the default component after this timeout expires). 1061 * 1062 * @hide 1063 */ 1064 @TestApi 1065 @RequiresPermission(android.Manifest.permission.MANAGE_CONTENT_CAPTURE) setTemporaryService( @serIdInt int userId, @NonNull String serviceName, int duration)1066 public static void setTemporaryService( 1067 @UserIdInt int userId, @NonNull String serviceName, int duration) { 1068 final IContentCaptureManager service = getService(); 1069 if (service == null) { 1070 Log.e(TAG, "IContentCaptureManager is null"); 1071 } 1072 try { 1073 service.setTemporaryService(userId, serviceName, duration); 1074 } catch (RemoteException e) { 1075 throw e.rethrowFromSystemServer(); 1076 } 1077 } 1078 1079 /** 1080 * Sets whether the default content capture service should be used. 1081 * 1082 * @hide 1083 */ 1084 @TestApi 1085 @RequiresPermission(android.Manifest.permission.MANAGE_CONTENT_CAPTURE) setDefaultServiceEnabled(@serIdInt int userId, boolean enabled)1086 public static void setDefaultServiceEnabled(@UserIdInt int userId, boolean enabled) { 1087 final IContentCaptureManager service = getService(); 1088 if (service == null) { 1089 Log.e(TAG, "IContentCaptureManager is null"); 1090 } 1091 try { 1092 service.setDefaultServiceEnabled(userId, enabled); 1093 } catch (RemoteException e) { 1094 throw e.rethrowFromSystemServer(); 1095 } 1096 } 1097 getService()1098 private static IContentCaptureManager getService() { 1099 return IContentCaptureManager.Stub.asInterface(ServiceManager.getService( 1100 Service.CONTENT_CAPTURE_MANAGER_SERVICE)); 1101 } 1102 1103 private interface MyRunnable { run(@onNull SyncResultReceiver receiver)1104 void run(@NonNull SyncResultReceiver receiver) throws RemoteException; 1105 } 1106 1107 private static class DataShareAdapterDelegate extends IDataShareWriteAdapter.Stub { 1108 1109 private final WeakReference<LocalDataShareAdapterResourceManager> mResourceManagerReference; 1110 DataShareAdapterDelegate(Executor executor, DataShareWriteAdapter adapter, LocalDataShareAdapterResourceManager resourceManager)1111 private DataShareAdapterDelegate(Executor executor, DataShareWriteAdapter adapter, 1112 LocalDataShareAdapterResourceManager resourceManager) { 1113 Objects.requireNonNull(executor); 1114 Objects.requireNonNull(adapter); 1115 Objects.requireNonNull(resourceManager); 1116 1117 resourceManager.initializeForDelegate(this, adapter, executor); 1118 mResourceManagerReference = new WeakReference<>(resourceManager); 1119 } 1120 1121 @Override write(ParcelFileDescriptor destination)1122 public void write(ParcelFileDescriptor destination) 1123 throws RemoteException { 1124 executeAdapterMethodLocked(adapter -> adapter.onWrite(destination), "onWrite"); 1125 } 1126 1127 @Override error(int errorCode)1128 public void error(int errorCode) throws RemoteException { 1129 executeAdapterMethodLocked(adapter -> adapter.onError(errorCode), "onError"); 1130 clearHardReferences(); 1131 } 1132 1133 @Override rejected()1134 public void rejected() throws RemoteException { 1135 executeAdapterMethodLocked(DataShareWriteAdapter::onRejected, "onRejected"); 1136 clearHardReferences(); 1137 } 1138 1139 @Override finish()1140 public void finish() throws RemoteException { 1141 clearHardReferences(); 1142 } 1143 executeAdapterMethodLocked(Consumer<DataShareWriteAdapter> adapterFn, String methodName)1144 private void executeAdapterMethodLocked(Consumer<DataShareWriteAdapter> adapterFn, 1145 String methodName) { 1146 LocalDataShareAdapterResourceManager resourceManager = mResourceManagerReference.get(); 1147 if (resourceManager == null) { 1148 Slog.w(TAG, "Can't execute " + methodName + "(), resource manager has been GC'ed"); 1149 return; 1150 } 1151 1152 DataShareWriteAdapter adapter = resourceManager.getAdapter(this); 1153 Executor executor = resourceManager.getExecutor(this); 1154 1155 if (adapter == null || executor == null) { 1156 Slog.w(TAG, "Can't execute " + methodName + "(), references are null"); 1157 return; 1158 } 1159 1160 final long identity = Binder.clearCallingIdentity(); 1161 try { 1162 executor.execute(() -> adapterFn.accept(adapter)); 1163 } finally { 1164 Binder.restoreCallingIdentity(identity); 1165 } 1166 } 1167 clearHardReferences()1168 private void clearHardReferences() { 1169 LocalDataShareAdapterResourceManager resourceManager = mResourceManagerReference.get(); 1170 if (resourceManager == null) { 1171 Slog.w(TAG, "Can't clear references, resource manager has been GC'ed"); 1172 return; 1173 } 1174 1175 resourceManager.clearHardReferences(this); 1176 } 1177 } 1178 1179 /** 1180 * Wrapper class making sure dependencies on the current application stay in the application 1181 * context. 1182 */ 1183 private static class LocalDataShareAdapterResourceManager { 1184 1185 // Keeping hard references to the remote objects in the current process (static context) 1186 // to prevent them to be gc'ed during the lifetime of the application. This is an 1187 // artifact of only operating with weak references remotely: there has to be at least 1 1188 // hard reference in order for this to not be killed. 1189 private Map<DataShareAdapterDelegate, DataShareWriteAdapter> mWriteAdapterHardReferences = 1190 new HashMap<>(); 1191 private Map<DataShareAdapterDelegate, Executor> mExecutorHardReferences = 1192 new HashMap<>(); 1193 initializeForDelegate(DataShareAdapterDelegate delegate, DataShareWriteAdapter adapter, Executor executor)1194 void initializeForDelegate(DataShareAdapterDelegate delegate, DataShareWriteAdapter adapter, 1195 Executor executor) { 1196 mWriteAdapterHardReferences.put(delegate, adapter); 1197 mExecutorHardReferences.put(delegate, executor); 1198 } 1199 getExecutor(DataShareAdapterDelegate delegate)1200 Executor getExecutor(DataShareAdapterDelegate delegate) { 1201 return mExecutorHardReferences.get(delegate); 1202 } 1203 getAdapter(DataShareAdapterDelegate delegate)1204 DataShareWriteAdapter getAdapter(DataShareAdapterDelegate delegate) { 1205 return mWriteAdapterHardReferences.get(delegate); 1206 } 1207 clearHardReferences(DataShareAdapterDelegate delegate)1208 void clearHardReferences(DataShareAdapterDelegate delegate) { 1209 mWriteAdapterHardReferences.remove(delegate); 1210 mExecutorHardReferences.remove(delegate); 1211 } 1212 } 1213 } 1214