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