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