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 @Nullable getServiceComponentName()488 public ComponentName getServiceComponentName() { 489 if (!isContentCaptureEnabled() && !mOptions.lite) return null; 490 491 final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); 492 try { 493 mService.getServiceComponentName(resultReceiver); 494 return resultReceiver.getParcelableResult(); 495 } catch (RemoteException e) { 496 throw e.rethrowFromSystemServer(); 497 } catch (SyncResultReceiver.TimeoutException e) { 498 throw new RuntimeException("Fail to get service componentName."); 499 } 500 } 501 502 /** 503 * Gets the (optional) intent used to launch the service-specific settings. 504 * 505 * <p>This method is static because it's called by Settings, which might not be allowlisted 506 * for content capture (in which case the ContentCaptureManager on its context would be null). 507 * 508 * @hide 509 */ 510 // TODO: use "lite" options as it's done by activities from the content capture service 511 @Nullable getServiceSettingsComponentName()512 public static ComponentName getServiceSettingsComponentName() { 513 final IBinder binder = ServiceManager 514 .checkService(Context.CONTENT_CAPTURE_MANAGER_SERVICE); 515 if (binder == null) return null; 516 517 final IContentCaptureManager service = IContentCaptureManager.Stub.asInterface(binder); 518 final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); 519 try { 520 service.getServiceSettingsActivity(resultReceiver); 521 final int resultCode = resultReceiver.getIntResult(); 522 if (resultCode == RESULT_CODE_SECURITY_EXCEPTION) { 523 throw new SecurityException(resultReceiver.getStringResult()); 524 } 525 return resultReceiver.getParcelableResult(); 526 } catch (RemoteException e) { 527 throw e.rethrowFromSystemServer(); 528 } catch (SyncResultReceiver.TimeoutException e) { 529 Log.e(TAG, "Fail to get service settings componentName: " + e); 530 return null; 531 } 532 } 533 534 /** 535 * Checks whether content capture is enabled for this activity. 536 * 537 * <p>There are many reasons it could be disabled, such as: 538 * <ul> 539 * <li>App itself disabled content capture through {@link #setContentCaptureEnabled(boolean)}. 540 * <li>Intelligence service did not allowlist content capture for this activity's package. 541 * <li>Intelligence service did not allowlist content capture for this specific activity. 542 * <li>Intelligence service disabled content capture globally. 543 * <li>User disabled content capture globally through the Android Settings app. 544 * <li>Device manufacturer (OEM) disabled content capture globally. 545 * <li>Transient errors, such as intelligence service package being updated. 546 * </ul> 547 */ isContentCaptureEnabled()548 public boolean isContentCaptureEnabled() { 549 if (mOptions.lite) return false; 550 551 final MainContentCaptureSession mainSession; 552 synchronized (mLock) { 553 mainSession = mMainSession; 554 } 555 // The main session is only set when the activity starts, so we need to return true until 556 // then. 557 if (mainSession != null && mainSession.isDisabled()) return false; 558 559 return true; 560 } 561 562 /** 563 * Gets the list of conditions for when content capture should be allowed. 564 * 565 * <p>This method is typically used by web browsers so they don't generate unnecessary content 566 * capture events for websites the content capture service is not interested on. 567 * 568 * @return list of conditions, or {@code null} if the service didn't set any restriction 569 * (in which case content capture events should always be generated). If the list is empty, 570 * then it should not generate any event at all. 571 */ 572 @Nullable getContentCaptureConditions()573 public Set<ContentCaptureCondition> getContentCaptureConditions() { 574 // NOTE: we could cache the conditions on ContentCaptureOptions, but then it would be stick 575 // to the lifetime of the app. OTOH, by dynamically calling the server every time, we allow 576 // the service to fine tune how long-lived apps (like browsers) are allowlisted. 577 if (!isContentCaptureEnabled() && !mOptions.lite) return null; 578 579 final SyncResultReceiver resultReceiver = syncRun( 580 (r) -> mService.getContentCaptureConditions(mContext.getPackageName(), r)); 581 582 try { 583 final ArrayList<ContentCaptureCondition> result = resultReceiver 584 .getParcelableListResult(); 585 return toSet(result); 586 } catch (SyncResultReceiver.TimeoutException e) { 587 throw new RuntimeException("Fail to get content capture conditions."); 588 } 589 } 590 591 /** 592 * Called by apps to explicitly enable or disable content capture. 593 * 594 * <p><b>Note: </b> this call is not persisted accross reboots, so apps should typically call 595 * it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}. 596 */ setContentCaptureEnabled(boolean enabled)597 public void setContentCaptureEnabled(boolean enabled) { 598 if (sDebug) { 599 Log.d(TAG, "setContentCaptureEnabled(): setting to " + enabled + " for " + mContext); 600 } 601 602 MainContentCaptureSession mainSession; 603 synchronized (mLock) { 604 if (enabled) { 605 mFlags &= ~ContentCaptureContext.FLAG_DISABLED_BY_APP; 606 } else { 607 mFlags |= ContentCaptureContext.FLAG_DISABLED_BY_APP; 608 } 609 mainSession = mMainSession; 610 } 611 if (mainSession != null) { 612 mainSession.setDisabled(!enabled); 613 } 614 } 615 616 /** 617 * Called by apps to update flag secure when window attributes change. 618 * 619 * @hide 620 */ updateWindowAttributes(@onNull WindowManager.LayoutParams params)621 public void updateWindowAttributes(@NonNull WindowManager.LayoutParams params) { 622 if (sDebug) { 623 Log.d(TAG, "updateWindowAttributes(): window flags=" + params.flags); 624 } 625 final boolean flagSecureEnabled = 626 (params.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0; 627 628 MainContentCaptureSession mainSession; 629 synchronized (mLock) { 630 if (flagSecureEnabled) { 631 mFlags |= ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE; 632 } else { 633 mFlags &= ~ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE; 634 } 635 mainSession = mMainSession; 636 } 637 if (mainSession != null) { 638 mainSession.setDisabled(flagSecureEnabled); 639 } 640 } 641 642 /** 643 * Gets whether content capture is enabled for the given user. 644 * 645 * <p>This method is typically used by the content capture service settings page, so it can 646 * provide a toggle to enable / disable it. 647 * 648 * @throws SecurityException if caller is not the app that owns the content capture service 649 * associated with the user. 650 * 651 * @hide 652 */ 653 @SystemApi isContentCaptureFeatureEnabled()654 public boolean isContentCaptureFeatureEnabled() { 655 final SyncResultReceiver resultReceiver = syncRun( 656 (r) -> mService.isContentCaptureFeatureEnabled(r)); 657 658 try { 659 final int resultCode = resultReceiver.getIntResult(); 660 switch (resultCode) { 661 case RESULT_CODE_TRUE: 662 return true; 663 case RESULT_CODE_FALSE: 664 return false; 665 default: 666 Log.wtf(TAG, "received invalid result: " + resultCode); 667 return false; 668 } 669 } catch (SyncResultReceiver.TimeoutException e) { 670 Log.e(TAG, "Fail to get content capture feature enable status: " + e); 671 return false; 672 } 673 } 674 675 /** 676 * Called by the app to request the content capture service to remove content capture data 677 * associated with some context. 678 * 679 * @param request object specifying what user data should be removed. 680 */ removeData(@onNull DataRemovalRequest request)681 public void removeData(@NonNull DataRemovalRequest request) { 682 Preconditions.checkNotNull(request); 683 684 try { 685 mService.removeData(request); 686 } catch (RemoteException e) { 687 throw e.rethrowFromSystemServer(); 688 } 689 } 690 691 /** 692 * Called by the app to request data sharing via writing to a file. 693 * 694 * <p>The ContentCaptureService app will receive a read-only file descriptor pointing to the 695 * same file and will be able to read data being shared from it. 696 * 697 * <p>Note: using this API doesn't guarantee the app staying alive and is "best-effort". 698 * Starting a foreground service would minimize the chances of the app getting killed during the 699 * file sharing session. 700 * 701 * @param request object specifying details of the data being shared. 702 */ shareData(@onNull DataShareRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull DataShareWriteAdapter dataShareWriteAdapter)703 public void shareData(@NonNull DataShareRequest request, 704 @NonNull @CallbackExecutor Executor executor, 705 @NonNull DataShareWriteAdapter dataShareWriteAdapter) { 706 Preconditions.checkNotNull(request); 707 Preconditions.checkNotNull(dataShareWriteAdapter); 708 Preconditions.checkNotNull(executor); 709 710 try { 711 mService.shareData(request, 712 new DataShareAdapterDelegate(executor, dataShareWriteAdapter, 713 mDataShareAdapterResourceManager)); 714 } catch (RemoteException e) { 715 throw e.rethrowFromSystemServer(); 716 } 717 } 718 719 /** 720 * Runs a sync method in the service, properly handling exceptions. 721 * 722 * @throws SecurityException if caller is not allowed to execute the method. 723 */ 724 @NonNull syncRun(@onNull MyRunnable r)725 private SyncResultReceiver syncRun(@NonNull MyRunnable r) { 726 final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); 727 try { 728 r.run(resultReceiver); 729 final int resultCode = resultReceiver.getIntResult(); 730 if (resultCode == RESULT_CODE_SECURITY_EXCEPTION) { 731 throw new SecurityException(resultReceiver.getStringResult()); 732 } 733 } catch (RemoteException e) { 734 throw e.rethrowFromSystemServer(); 735 } catch (SyncResultReceiver.TimeoutException e) { 736 throw new RuntimeException("Fail to get syn run result from SyncResultReceiver."); 737 } 738 return resultReceiver; 739 } 740 741 /** @hide */ dump(String prefix, PrintWriter pw)742 public void dump(String prefix, PrintWriter pw) { 743 pw.print(prefix); pw.println("ContentCaptureManager"); 744 final String prefix2 = prefix + " "; 745 synchronized (mLock) { 746 pw.print(prefix2); pw.print("isContentCaptureEnabled(): "); 747 pw.println(isContentCaptureEnabled()); 748 pw.print(prefix2); pw.print("Debug: "); pw.print(sDebug); 749 pw.print(" Verbose: "); pw.println(sVerbose); 750 pw.print(prefix2); pw.print("Context: "); pw.println(mContext); 751 pw.print(prefix2); pw.print("User: "); pw.println(mContext.getUserId()); 752 pw.print(prefix2); pw.print("Service: "); pw.println(mService); 753 pw.print(prefix2); pw.print("Flags: "); pw.println(mFlags); 754 pw.print(prefix2); pw.print("Options: "); mOptions.dumpShort(pw); pw.println(); 755 if (mMainSession != null) { 756 final String prefix3 = prefix2 + " "; 757 pw.print(prefix2); pw.println("Main session:"); 758 mMainSession.dump(prefix3, pw); 759 } else { 760 pw.print(prefix2); pw.println("No sessions"); 761 } 762 } 763 } 764 765 /** 766 * Resets the temporary content capture service implementation to the default component. 767 * 768 * @hide 769 */ 770 @TestApi 771 @RequiresPermission(android.Manifest.permission.MANAGE_CONTENT_CAPTURE) resetTemporaryService(@serIdInt int userId)772 public static void resetTemporaryService(@UserIdInt int userId) { 773 final IContentCaptureManager service = getService(); 774 if (service == null) { 775 Log.e(TAG, "IContentCaptureManager is null"); 776 } 777 try { 778 service.resetTemporaryService(userId); 779 } catch (RemoteException e) { 780 throw e.rethrowFromSystemServer(); 781 } 782 } 783 784 /** 785 * Temporarily sets the content capture service implementation. 786 * 787 * @param userId user Id to set the temporary service on. 788 * @param serviceName name of the new component 789 * @param duration how long the change will be valid (the service will be automatically reset 790 * to the default component after this timeout expires). 791 * 792 * @hide 793 */ 794 @TestApi 795 @RequiresPermission(android.Manifest.permission.MANAGE_CONTENT_CAPTURE) setTemporaryService( @serIdInt int userId, @NonNull String serviceName, int duration)796 public static void setTemporaryService( 797 @UserIdInt int userId, @NonNull String serviceName, int duration) { 798 final IContentCaptureManager service = getService(); 799 if (service == null) { 800 Log.e(TAG, "IContentCaptureManager is null"); 801 } 802 try { 803 service.setTemporaryService(userId, serviceName, duration); 804 } catch (RemoteException e) { 805 throw e.rethrowFromSystemServer(); 806 } 807 } 808 809 /** 810 * Sets whether the default content capture service should be used. 811 * 812 * @hide 813 */ 814 @TestApi 815 @RequiresPermission(android.Manifest.permission.MANAGE_CONTENT_CAPTURE) setDefaultServiceEnabled(@serIdInt int userId, boolean enabled)816 public static void setDefaultServiceEnabled(@UserIdInt int userId, boolean enabled) { 817 final IContentCaptureManager service = getService(); 818 if (service == null) { 819 Log.e(TAG, "IContentCaptureManager is null"); 820 } 821 try { 822 service.setDefaultServiceEnabled(userId, enabled); 823 } catch (RemoteException e) { 824 throw e.rethrowFromSystemServer(); 825 } 826 } 827 getService()828 private static IContentCaptureManager getService() { 829 return IContentCaptureManager.Stub.asInterface(ServiceManager.getService( 830 Service.CONTENT_CAPTURE_MANAGER_SERVICE)); 831 } 832 833 private interface MyRunnable { run(@onNull SyncResultReceiver receiver)834 void run(@NonNull SyncResultReceiver receiver) throws RemoteException; 835 } 836 837 private static class DataShareAdapterDelegate extends IDataShareWriteAdapter.Stub { 838 839 private final WeakReference<LocalDataShareAdapterResourceManager> mResourceManagerReference; 840 DataShareAdapterDelegate(Executor executor, DataShareWriteAdapter adapter, LocalDataShareAdapterResourceManager resourceManager)841 private DataShareAdapterDelegate(Executor executor, DataShareWriteAdapter adapter, 842 LocalDataShareAdapterResourceManager resourceManager) { 843 Preconditions.checkNotNull(executor); 844 Preconditions.checkNotNull(adapter); 845 Preconditions.checkNotNull(resourceManager); 846 847 resourceManager.initializeForDelegate(this, adapter, executor); 848 mResourceManagerReference = new WeakReference<>(resourceManager); 849 } 850 851 @Override write(ParcelFileDescriptor destination)852 public void write(ParcelFileDescriptor destination) 853 throws RemoteException { 854 executeAdapterMethodLocked(adapter -> adapter.onWrite(destination), "onWrite"); 855 } 856 857 @Override error(int errorCode)858 public void error(int errorCode) throws RemoteException { 859 executeAdapterMethodLocked(adapter -> adapter.onError(errorCode), "onError"); 860 clearHardReferences(); 861 } 862 863 @Override rejected()864 public void rejected() throws RemoteException { 865 executeAdapterMethodLocked(DataShareWriteAdapter::onRejected, "onRejected"); 866 clearHardReferences(); 867 } 868 869 @Override finish()870 public void finish() throws RemoteException { 871 clearHardReferences(); 872 } 873 executeAdapterMethodLocked(Consumer<DataShareWriteAdapter> adapterFn, String methodName)874 private void executeAdapterMethodLocked(Consumer<DataShareWriteAdapter> adapterFn, 875 String methodName) { 876 LocalDataShareAdapterResourceManager resourceManager = mResourceManagerReference.get(); 877 if (resourceManager == null) { 878 Slog.w(TAG, "Can't execute " + methodName + "(), resource manager has been GC'ed"); 879 return; 880 } 881 882 DataShareWriteAdapter adapter = resourceManager.getAdapter(this); 883 Executor executor = resourceManager.getExecutor(this); 884 885 if (adapter == null || executor == null) { 886 Slog.w(TAG, "Can't execute " + methodName + "(), references are null"); 887 return; 888 } 889 890 final long identity = Binder.clearCallingIdentity(); 891 try { 892 executor.execute(() -> adapterFn.accept(adapter)); 893 } finally { 894 Binder.restoreCallingIdentity(identity); 895 } 896 } 897 clearHardReferences()898 private void clearHardReferences() { 899 LocalDataShareAdapterResourceManager resourceManager = mResourceManagerReference.get(); 900 if (resourceManager == null) { 901 Slog.w(TAG, "Can't clear references, resource manager has been GC'ed"); 902 return; 903 } 904 905 resourceManager.clearHardReferences(this); 906 } 907 } 908 909 /** 910 * Wrapper class making sure dependencies on the current application stay in the application 911 * context. 912 */ 913 private static class LocalDataShareAdapterResourceManager { 914 915 // Keeping hard references to the remote objects in the current process (static context) 916 // to prevent them to be gc'ed during the lifetime of the application. This is an 917 // artifact of only operating with weak references remotely: there has to be at least 1 918 // hard reference in order for this to not be killed. 919 private Map<DataShareAdapterDelegate, DataShareWriteAdapter> mWriteAdapterHardReferences = 920 new HashMap<>(); 921 private Map<DataShareAdapterDelegate, Executor> mExecutorHardReferences = 922 new HashMap<>(); 923 initializeForDelegate(DataShareAdapterDelegate delegate, DataShareWriteAdapter adapter, Executor executor)924 void initializeForDelegate(DataShareAdapterDelegate delegate, DataShareWriteAdapter adapter, 925 Executor executor) { 926 mWriteAdapterHardReferences.put(delegate, adapter); 927 mExecutorHardReferences.put(delegate, executor); 928 } 929 getExecutor(DataShareAdapterDelegate delegate)930 Executor getExecutor(DataShareAdapterDelegate delegate) { 931 return mExecutorHardReferences.get(delegate); 932 } 933 getAdapter(DataShareAdapterDelegate delegate)934 DataShareWriteAdapter getAdapter(DataShareAdapterDelegate delegate) { 935 return mWriteAdapterHardReferences.get(delegate); 936 } 937 clearHardReferences(DataShareAdapterDelegate delegate)938 void clearHardReferences(DataShareAdapterDelegate delegate) { 939 mWriteAdapterHardReferences.remove(delegate); 940 mExecutorHardReferences.remove(delegate); 941 } 942 } 943 } 944