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