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