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