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.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; 19 import static android.view.contentcapture.ContentCaptureHelper.sDebug; 20 import static android.view.contentcapture.ContentCaptureHelper.sVerbose; 21 import static android.view.contentcapture.ContentCaptureManager.NO_SESSION_ID; 22 23 import android.annotation.CallSuper; 24 import android.annotation.IntDef; 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.app.compat.CompatChanges; 28 import android.compat.annotation.ChangeId; 29 import android.compat.annotation.EnabledSince; 30 import android.content.ComponentName; 31 import android.graphics.Insets; 32 import android.graphics.Rect; 33 import android.os.IBinder; 34 import android.util.DebugUtils; 35 import android.util.Log; 36 import android.util.SparseArray; 37 import android.view.View; 38 import android.view.ViewStructure; 39 import android.view.autofill.AutofillId; 40 import android.view.contentcapture.ViewNode.ViewStructureImpl; 41 42 import com.android.internal.annotations.GuardedBy; 43 import com.android.internal.annotations.VisibleForTesting; 44 import com.android.internal.os.IResultReceiver; 45 import com.android.internal.util.ArrayUtils; 46 import com.android.internal.util.Preconditions; 47 48 import java.io.PrintWriter; 49 import java.lang.annotation.Retention; 50 import java.lang.annotation.RetentionPolicy; 51 import java.security.SecureRandom; 52 import java.util.ArrayList; 53 import java.util.List; 54 import java.util.Objects; 55 56 /** 57 * Session used when notifying the Android system about events associated with views. 58 */ 59 public abstract class ContentCaptureSession implements AutoCloseable { 60 61 private static final String TAG = ContentCaptureSession.class.getSimpleName(); 62 63 // TODO(b/158778794): to make the session ids truly globally unique across 64 // processes, we may need to explore other options. 65 private static final SecureRandom ID_GENERATOR = new SecureRandom(); 66 67 /** 68 * Name of the {@link IResultReceiver} extra used to pass the binder interface to the service. 69 * @hide 70 */ 71 public static final String EXTRA_BINDER = "binder"; 72 73 /** 74 * Name of the {@link IResultReceiver} extra used to pass the content capture enabled state. 75 * @hide 76 */ 77 public static final String EXTRA_ENABLED_STATE = "enabled"; 78 79 /** 80 * Initial state, when there is no session. 81 * 82 * @hide 83 */ 84 // NOTE: not prefixed by STATE_ so it's not printed on getStateAsString() 85 public static final int UNKNOWN_STATE = 0x0; 86 87 /** 88 * Service's startSession() was called, but server didn't confirm it was created yet. 89 * 90 * @hide 91 */ 92 public static final int STATE_WAITING_FOR_SERVER = 0x1; 93 94 /** 95 * Session is active. 96 * 97 * @hide 98 */ 99 public static final int STATE_ACTIVE = 0x2; 100 101 /** 102 * Session is disabled because there is no service for this user. 103 * 104 * @hide 105 */ 106 public static final int STATE_DISABLED = 0x4; 107 108 /** 109 * Session is disabled because its id already existed on server. 110 * 111 * @hide 112 */ 113 public static final int STATE_DUPLICATED_ID = 0x8; 114 115 /** 116 * Session is disabled because service is not set for user. 117 * 118 * @hide 119 */ 120 public static final int STATE_NO_SERVICE = 0x10; 121 122 /** 123 * Session is disabled by FLAG_SECURE 124 * 125 * @hide 126 */ 127 public static final int STATE_FLAG_SECURE = 0x20; 128 129 /** 130 * Session is disabled manually by the specific app 131 * (through {@link ContentCaptureManager#setContentCaptureEnabled(boolean)}). 132 * 133 * @hide 134 */ 135 public static final int STATE_BY_APP = 0x40; 136 137 /** 138 * Session is disabled because session start was never replied. 139 * 140 * @hide 141 */ 142 public static final int STATE_NO_RESPONSE = 0x80; 143 144 /** 145 * Session is disabled because an internal error. 146 * 147 * @hide 148 */ 149 public static final int STATE_INTERNAL_ERROR = 0x100; 150 151 /** 152 * Session is disabled because service didn't allowlist package or activity. 153 * 154 * @hide 155 */ 156 public static final int STATE_NOT_WHITELISTED = 0x200; 157 158 /** 159 * Session is disabled because the service died. 160 * 161 * @hide 162 */ 163 public static final int STATE_SERVICE_DIED = 0x400; 164 165 /** 166 * Session is disabled because the service package is being udpated. 167 * 168 * @hide 169 */ 170 public static final int STATE_SERVICE_UPDATING = 0x800; 171 172 /** 173 * Session is enabled, after the service died and came back to live. 174 * 175 * @hide 176 */ 177 public static final int STATE_SERVICE_RESURRECTED = 0x1000; 178 179 private static final int INITIAL_CHILDREN_CAPACITY = 5; 180 181 /** @hide */ 182 public static final int FLUSH_REASON_FULL = 1; 183 /** @hide */ 184 public static final int FLUSH_REASON_VIEW_ROOT_ENTERED = 2; 185 /** @hide */ 186 public static final int FLUSH_REASON_SESSION_STARTED = 3; 187 /** @hide */ 188 public static final int FLUSH_REASON_SESSION_FINISHED = 4; 189 /** @hide */ 190 public static final int FLUSH_REASON_IDLE_TIMEOUT = 5; 191 /** @hide */ 192 public static final int FLUSH_REASON_TEXT_CHANGE_TIMEOUT = 6; 193 /** @hide */ 194 public static final int FLUSH_REASON_SESSION_CONNECTED = 7; 195 /** @hide */ 196 public static final int FLUSH_REASON_FORCE_FLUSH = 8; 197 /** @hide */ 198 public static final int FLUSH_REASON_VIEW_TREE_APPEARING = 9; 199 /** @hide */ 200 public static final int FLUSH_REASON_VIEW_TREE_APPEARED = 10; 201 202 /** 203 * After {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, 204 * {@link #notifyViewsDisappeared(AutofillId, long[])} wraps 205 * the virtual children with a pair of view tree appearing and view tree appeared events. 206 */ 207 @ChangeId 208 @EnabledSince(targetSdkVersion = UPSIDE_DOWN_CAKE) 209 static final long NOTIFY_NODES_DISAPPEAR_NOW_SENDS_TREE_EVENTS = 258825825L; 210 211 /** @hide */ 212 @IntDef( 213 prefix = {"FLUSH_REASON_"}, 214 value = { 215 FLUSH_REASON_FULL, 216 FLUSH_REASON_VIEW_ROOT_ENTERED, 217 FLUSH_REASON_SESSION_STARTED, 218 FLUSH_REASON_SESSION_FINISHED, 219 FLUSH_REASON_IDLE_TIMEOUT, 220 FLUSH_REASON_TEXT_CHANGE_TIMEOUT, 221 FLUSH_REASON_SESSION_CONNECTED, 222 FLUSH_REASON_FORCE_FLUSH, 223 FLUSH_REASON_VIEW_TREE_APPEARING, 224 FLUSH_REASON_VIEW_TREE_APPEARED 225 }) 226 @Retention(RetentionPolicy.SOURCE) 227 public @interface FlushReason {} 228 229 private final Object mLock = new Object(); 230 231 /** 232 * Guard use to ignore events after it's destroyed. 233 */ 234 @NonNull 235 @GuardedBy("mLock") 236 private boolean mDestroyed; 237 238 /** @hide */ 239 @Nullable 240 protected final int mId; 241 242 private int mState = UNKNOWN_STATE; 243 244 // Lazily created on demand. 245 private ContentCaptureSessionId mContentCaptureSessionId; 246 247 /** 248 * {@link ContentCaptureContext} set by client, or {@code null} when it's the 249 * {@link ContentCaptureManager#getMainContentCaptureSession() default session} for the 250 * context. 251 */ 252 @Nullable 253 private ContentCaptureContext mClientContext; 254 255 /** 256 * List of children session. 257 */ 258 @Nullable 259 @GuardedBy("mLock") 260 private ArrayList<ContentCaptureSession> mChildren; 261 262 /** @hide */ ContentCaptureSession()263 protected ContentCaptureSession() { 264 this(getRandomSessionId()); 265 } 266 267 /** @hide */ 268 @VisibleForTesting ContentCaptureSession(int id)269 public ContentCaptureSession(int id) { 270 Preconditions.checkArgument(id != NO_SESSION_ID); 271 mId = id; 272 } 273 274 // Used by ChildContentCaptureSession ContentCaptureSession(@onNull ContentCaptureContext initialContext)275 ContentCaptureSession(@NonNull ContentCaptureContext initialContext) { 276 this(); 277 mClientContext = Objects.requireNonNull(initialContext); 278 } 279 280 /** @hide */ 281 @NonNull getMainCaptureSession()282 abstract ContentCaptureSession getMainCaptureSession(); 283 start(@onNull IBinder token, @NonNull IBinder shareableActivityToken, @NonNull ComponentName component, int flags)284 abstract void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken, 285 @NonNull ComponentName component, int flags); 286 isDisabled()287 abstract boolean isDisabled(); 288 289 /** 290 * Sets the disabled state of content capture. 291 * 292 * @return whether disabled state was changed. 293 */ setDisabled(boolean disabled)294 abstract boolean setDisabled(boolean disabled); 295 296 /** 297 * Gets the id used to identify this session. 298 */ 299 @NonNull getContentCaptureSessionId()300 public final ContentCaptureSessionId getContentCaptureSessionId() { 301 if (mContentCaptureSessionId == null) { 302 mContentCaptureSessionId = new ContentCaptureSessionId(mId); 303 } 304 return mContentCaptureSessionId; 305 } 306 307 /** @hide */ 308 @NonNull getId()309 public int getId() { 310 return mId; 311 } 312 313 /** 314 * Creates a new {@link ContentCaptureSession}. 315 * 316 * <p>See {@link View#setContentCaptureSession(ContentCaptureSession)} for more info. 317 */ 318 @NonNull createContentCaptureSession( @onNull ContentCaptureContext context)319 public final ContentCaptureSession createContentCaptureSession( 320 @NonNull ContentCaptureContext context) { 321 final ContentCaptureSession child = newChild(context); 322 if (sDebug) { 323 Log.d(TAG, "createContentCaptureSession(" + context + ": parent=" + mId + ", child=" 324 + child.mId); 325 } 326 synchronized (mLock) { 327 if (mChildren == null) { 328 mChildren = new ArrayList<>(INITIAL_CHILDREN_CAPACITY); 329 } 330 mChildren.add(child); 331 } 332 return child; 333 } 334 newChild(@onNull ContentCaptureContext context)335 abstract ContentCaptureSession newChild(@NonNull ContentCaptureContext context); 336 337 /** 338 * Flushes the buffered events to the service. 339 */ flush(@lushReason int reason)340 abstract void flush(@FlushReason int reason); 341 342 /** 343 * Sets the {@link ContentCaptureContext} associated with the session. 344 * 345 * <p>Typically used to change the context associated with the default session from an activity. 346 */ setContentCaptureContext(@ullable ContentCaptureContext context)347 public final void setContentCaptureContext(@Nullable ContentCaptureContext context) { 348 if (!isContentCaptureEnabled()) return; 349 350 mClientContext = context; 351 updateContentCaptureContext(context); 352 } 353 updateContentCaptureContext(@ullable ContentCaptureContext context)354 abstract void updateContentCaptureContext(@Nullable ContentCaptureContext context); 355 356 /** 357 * Gets the {@link ContentCaptureContext} associated with the session. 358 * 359 * @return context set on constructor or by 360 * {@link #setContentCaptureContext(ContentCaptureContext)}, or {@code null} if never 361 * explicitly set. 362 */ 363 @Nullable getContentCaptureContext()364 public final ContentCaptureContext getContentCaptureContext() { 365 return mClientContext; 366 } 367 368 /** 369 * Destroys this session, flushing out all pending notifications to the service. 370 * 371 * <p>Once destroyed, any new notification will be dropped. 372 */ destroy()373 public final void destroy() { 374 synchronized (mLock) { 375 if (mDestroyed) { 376 if (sDebug) Log.d(TAG, "destroy(" + mId + "): already destroyed"); 377 return; 378 } 379 mDestroyed = true; 380 381 // TODO(b/111276913): check state (for example, how to handle if it's waiting for remote 382 // id) and send it to the cache of batched commands 383 if (sVerbose) { 384 Log.v(TAG, "destroy(): state=" + getStateAsString(mState) + ", mId=" + mId); 385 } 386 // Finish children first 387 if (mChildren != null) { 388 final int numberChildren = mChildren.size(); 389 if (sVerbose) Log.v(TAG, "Destroying " + numberChildren + " children first"); 390 for (int i = 0; i < numberChildren; i++) { 391 final ContentCaptureSession child = mChildren.get(i); 392 try { 393 child.destroy(); 394 } catch (Exception e) { 395 Log.w(TAG, "exception destroying child session #" + i + ": " + e); 396 } 397 } 398 } 399 } 400 401 onDestroy(); 402 } 403 onDestroy()404 abstract void onDestroy(); 405 406 /** @hide */ 407 @Override close()408 public void close() { 409 destroy(); 410 } 411 412 /** 413 * Notifies the Content Capture Service that a node has been added to the view structure. 414 * 415 * <p>Typically called "manually" by views that handle their own virtual view hierarchy, or 416 * automatically by the Android System for views that return {@code true} on 417 * {@link View#onProvideContentCaptureStructure(ViewStructure, int)}. 418 * 419 * <p>Consider use {@link #notifyViewsAppeared} which has a better performance when notifying 420 * a list of nodes has appeared. 421 * 422 * @param node node that has been added. 423 */ notifyViewAppeared(@onNull ViewStructure node)424 public final void notifyViewAppeared(@NonNull ViewStructure node) { 425 Objects.requireNonNull(node); 426 if (!isContentCaptureEnabled()) return; 427 428 if (!(node instanceof ViewNode.ViewStructureImpl)) { 429 throw new IllegalArgumentException("Invalid node class: " + node.getClass()); 430 } 431 432 internalNotifyViewAppeared(mId, (ViewStructureImpl) node); 433 } 434 internalNotifyViewAppeared( int sessionId, @NonNull ViewNode.ViewStructureImpl node)435 abstract void internalNotifyViewAppeared( 436 int sessionId, @NonNull ViewNode.ViewStructureImpl node); 437 438 /** 439 * Notifies the Content Capture Service that a node has been removed from the view structure. 440 * 441 * <p>Typically called "manually" by views that handle their own virtual view hierarchy, or 442 * automatically by the Android System for standard views. 443 * 444 * <p>Consider use {@link #notifyViewsDisappeared} which has a better performance when notifying 445 * a list of nodes has disappeared. 446 * 447 * @param id id of the node that has been removed. 448 */ notifyViewDisappeared(@onNull AutofillId id)449 public final void notifyViewDisappeared(@NonNull AutofillId id) { 450 Objects.requireNonNull(id); 451 if (!isContentCaptureEnabled()) return; 452 453 internalNotifyViewDisappeared(mId, id); 454 } 455 internalNotifyViewDisappeared(int sessionId, @NonNull AutofillId id)456 abstract void internalNotifyViewDisappeared(int sessionId, @NonNull AutofillId id); 457 458 /** 459 * Notifies the Content Capture Service that a list of nodes has appeared in the view structure. 460 * 461 * <p>Typically called manually by views that handle their own virtual view hierarchy. 462 * 463 * @param appearedNodes nodes that have appeared. Each element represents a view node that has 464 * been added to the view structure. The order of the elements is important, which should be 465 * preserved as the attached order of when the node is attached to the virtual view hierarchy. 466 */ notifyViewsAppeared(@onNull List<ViewStructure> appearedNodes)467 public final void notifyViewsAppeared(@NonNull List<ViewStructure> appearedNodes) { 468 Preconditions.checkCollectionElementsNotNull(appearedNodes, "appearedNodes"); 469 if (!isContentCaptureEnabled()) return; 470 471 for (int i = 0; i < appearedNodes.size(); i++) { 472 ViewStructure v = appearedNodes.get(i); 473 if (!(v instanceof ViewNode.ViewStructureImpl)) { 474 throw new IllegalArgumentException("Invalid class: " + v.getClass()); 475 } 476 } 477 478 internalNotifyViewTreeEvent(mId, /* started= */ true); 479 for (int i = 0; i < appearedNodes.size(); i++) { 480 ViewStructure v = appearedNodes.get(i); 481 internalNotifyViewAppeared(mId, (ViewStructureImpl) v); 482 } 483 internalNotifyViewTreeEvent(mId, /* started= */ false); 484 } 485 486 /** 487 * Notifies the Content Capture Service that many nodes has been removed from a virtual view 488 * structure. 489 * 490 * <p>Should only be called by views that handle their own virtual view hierarchy. 491 * 492 * <p>After UPSIDE_DOWN_CAKE, this method wraps the virtual children with a pair of view tree 493 * appearing and view tree appeared events. 494 * 495 * @param hostId id of the non-virtual view hosting the virtual view hierarchy (it can be 496 * obtained by calling {@link ViewStructure#getAutofillId()}). 497 * @param virtualIds ids of the virtual children. 498 * 499 * @throws IllegalArgumentException if the {@code hostId} is an autofill id for a virtual view. 500 * @throws IllegalArgumentException if {@code virtualIds} is empty 501 */ notifyViewsDisappeared(@onNull AutofillId hostId, @NonNull long[] virtualIds)502 public final void notifyViewsDisappeared(@NonNull AutofillId hostId, 503 @NonNull long[] virtualIds) { 504 Preconditions.checkArgument(hostId.isNonVirtual(), "hostId cannot be virtual: %s", hostId); 505 Preconditions.checkArgument(!ArrayUtils.isEmpty(virtualIds), "virtual ids cannot be empty"); 506 if (!isContentCaptureEnabled()) return; 507 508 if (CompatChanges.isChangeEnabled(NOTIFY_NODES_DISAPPEAR_NOW_SENDS_TREE_EVENTS)) { 509 internalNotifyViewTreeEvent(mId, /* started= */ true); 510 } 511 // TODO(b/123036895): use a internalNotifyViewsDisappeared that optimizes how the event is 512 // parcelized 513 for (long id : virtualIds) { 514 internalNotifyViewDisappeared(mId, new AutofillId(hostId, id, mId)); 515 } 516 if (CompatChanges.isChangeEnabled(NOTIFY_NODES_DISAPPEAR_NOW_SENDS_TREE_EVENTS)) { 517 internalNotifyViewTreeEvent(mId, /* started= */ false); 518 } 519 } 520 521 /** 522 * Notifies the Intelligence Service that the value of a text node has been changed. 523 * 524 * @param id of the node. 525 * @param text new text. 526 */ notifyViewTextChanged(@onNull AutofillId id, @Nullable CharSequence text)527 public final void notifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text) { 528 Objects.requireNonNull(id); 529 530 if (!isContentCaptureEnabled()) return; 531 532 internalNotifyViewTextChanged(mId, id, text); 533 } 534 internalNotifyViewTextChanged(int sessionId, @NonNull AutofillId id, @Nullable CharSequence text)535 abstract void internalNotifyViewTextChanged(int sessionId, @NonNull AutofillId id, 536 @Nullable CharSequence text); 537 538 /** 539 * Notifies the Intelligence Service that the insets of a view have changed. 540 */ notifyViewInsetsChanged(@onNull Insets viewInsets)541 public final void notifyViewInsetsChanged(@NonNull Insets viewInsets) { 542 Objects.requireNonNull(viewInsets); 543 544 if (!isContentCaptureEnabled()) return; 545 546 internalNotifyViewInsetsChanged(mId, viewInsets); 547 } 548 internalNotifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets)549 abstract void internalNotifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets); 550 551 /** @hide */ notifyViewTreeEvent(boolean started)552 public void notifyViewTreeEvent(boolean started) { 553 internalNotifyViewTreeEvent(mId, started); 554 } 555 556 /** @hide */ internalNotifyViewTreeEvent(int sessionId, boolean started)557 abstract void internalNotifyViewTreeEvent(int sessionId, boolean started); 558 559 /** 560 * Notifies the Content Capture Service that a session has resumed. 561 */ notifySessionResumed()562 public final void notifySessionResumed() { 563 if (!isContentCaptureEnabled()) return; 564 565 internalNotifySessionResumed(); 566 } 567 internalNotifySessionResumed()568 abstract void internalNotifySessionResumed(); 569 570 /** 571 * Notifies the Content Capture Service that a session has paused. 572 */ notifySessionPaused()573 public final void notifySessionPaused() { 574 if (!isContentCaptureEnabled()) return; 575 576 internalNotifySessionPaused(); 577 } 578 internalNotifySessionPaused()579 abstract void internalNotifySessionPaused(); 580 internalNotifyChildSessionStarted(int parentSessionId, int childSessionId, @NonNull ContentCaptureContext clientContext)581 abstract void internalNotifyChildSessionStarted(int parentSessionId, int childSessionId, 582 @NonNull ContentCaptureContext clientContext); 583 internalNotifyChildSessionFinished(int parentSessionId, int childSessionId)584 abstract void internalNotifyChildSessionFinished(int parentSessionId, int childSessionId); 585 internalNotifyContextUpdated( int sessionId, @Nullable ContentCaptureContext context)586 abstract void internalNotifyContextUpdated( 587 int sessionId, @Nullable ContentCaptureContext context); 588 589 /** @hide */ notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds)590 public abstract void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds); 591 592 /** @hide */ notifyContentCaptureEvents( @onNull SparseArray<ArrayList<Object>> contentCaptureEvents)593 public abstract void notifyContentCaptureEvents( 594 @NonNull SparseArray<ArrayList<Object>> contentCaptureEvents); 595 596 /** 597 * Creates a {@link ViewStructure} for a "standard" view. 598 * 599 * <p>This method should be called after a visible view is laid out; the view then must populate 600 * the structure and pass it to {@link #notifyViewAppeared(ViewStructure)}. 601 * 602 * <b>Note: </b>views that manage a virtual structure under this view must populate just the 603 * node representing this view and return right away, then asynchronously report (not 604 * necessarily in the UI thread) when the children nodes appear, disappear or have their text 605 * changed by calling {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)}, 606 * {@link ContentCaptureSession#notifyViewDisappeared(AutofillId)}, and 607 * {@link ContentCaptureSession#notifyViewTextChanged(AutofillId, CharSequence)} respectively. 608 * The structure for the a child must be created using 609 * {@link ContentCaptureSession#newVirtualViewStructure(AutofillId, long)}, and the 610 * {@code autofillId} for a child can be obtained either through 611 * {@code childStructure.getAutofillId()} or 612 * {@link ContentCaptureSession#newAutofillId(AutofillId, long)}. 613 * 614 * <p>When the virtual view hierarchy represents a web page, you should also: 615 * 616 * <ul> 617 * <li>Call {@link ContentCaptureManager#getContentCaptureConditions()} to infer content capture 618 * events should be generate for that URL. 619 * <li>Create a new {@link ContentCaptureSession} child for every HTML element that renders a 620 * new URL (like an {@code IFRAME}) and use that session to notify events from that subtree. 621 * </ul> 622 * 623 * <p><b>Note: </b>the following methods of the {@code structure} will be ignored: 624 * <ul> 625 * <li>{@link ViewStructure#setChildCount(int)} 626 * <li>{@link ViewStructure#addChildCount(int)} 627 * <li>{@link ViewStructure#getChildCount()} 628 * <li>{@link ViewStructure#newChild(int)} 629 * <li>{@link ViewStructure#asyncNewChild(int)} 630 * <li>{@link ViewStructure#asyncCommit()} 631 * <li>{@link ViewStructure#setWebDomain(String)} 632 * <li>{@link ViewStructure#newHtmlInfoBuilder(String)} 633 * <li>{@link ViewStructure#setHtmlInfo(android.view.ViewStructure.HtmlInfo)} 634 * <li>{@link ViewStructure#setDataIsSensitive(boolean)} 635 * <li>{@link ViewStructure#setAlpha(float)} 636 * <li>{@link ViewStructure#setElevation(float)} 637 * <li>{@link ViewStructure#setTransformation(android.graphics.Matrix)} 638 * </ul> 639 */ 640 @NonNull newViewStructure(@onNull View view)641 public final ViewStructure newViewStructure(@NonNull View view) { 642 return new ViewNode.ViewStructureImpl(view); 643 } 644 645 /** 646 * Creates a new {@link AutofillId} for a virtual child, so it can be used to uniquely identify 647 * the children in the session. 648 * 649 * @param hostId id of the non-virtual view hosting the virtual view hierarchy (it can be 650 * obtained by calling {@link ViewStructure#getAutofillId()}). 651 * @param virtualChildId id of the virtual child, relative to the parent. 652 * 653 * @return if for the virtual child 654 * 655 * @throws IllegalArgumentException if the {@code parentId} is a virtual child id. 656 */ newAutofillId(@onNull AutofillId hostId, long virtualChildId)657 public @NonNull AutofillId newAutofillId(@NonNull AutofillId hostId, long virtualChildId) { 658 Objects.requireNonNull(hostId); 659 Preconditions.checkArgument(hostId.isNonVirtual(), "hostId cannot be virtual: %s", hostId); 660 return new AutofillId(hostId, virtualChildId, mId); 661 } 662 663 /** 664 * Creates a {@link ViewStructure} for a "virtual" view, so it can be passed to 665 * {@link #notifyViewAppeared(ViewStructure)} by the view managing the virtual view hierarchy. 666 * 667 * @param parentId id of the virtual view parent (it can be obtained by calling 668 * {@link ViewStructure#getAutofillId()} on the parent). 669 * @param virtualId id of the virtual child, relative to the parent. 670 * 671 * @return a new {@link ViewStructure} that can be used for Content Capture purposes. 672 */ 673 @NonNull newVirtualViewStructure(@onNull AutofillId parentId, long virtualId)674 public final ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId, 675 long virtualId) { 676 return new ViewNode.ViewStructureImpl(parentId, virtualId, mId); 677 } 678 isContentCaptureEnabled()679 boolean isContentCaptureEnabled() { 680 synchronized (mLock) { 681 return !mDestroyed; 682 } 683 } 684 685 @CallSuper dump(@onNull String prefix, @NonNull PrintWriter pw)686 void dump(@NonNull String prefix, @NonNull PrintWriter pw) { 687 pw.print(prefix); pw.print("id: "); pw.println(mId); 688 if (mClientContext != null) { 689 pw.print(prefix); mClientContext.dump(pw); pw.println(); 690 } 691 synchronized (mLock) { 692 pw.print(prefix); pw.print("destroyed: "); pw.println(mDestroyed); 693 if (mChildren != null && !mChildren.isEmpty()) { 694 final String prefix2 = prefix + " "; 695 final int numberChildren = mChildren.size(); 696 pw.print(prefix); pw.print("number children: "); pw.println(numberChildren); 697 for (int i = 0; i < numberChildren; i++) { 698 final ContentCaptureSession child = mChildren.get(i); 699 pw.print(prefix); pw.print(i); pw.println(": "); child.dump(prefix2, pw); 700 } 701 } 702 } 703 } 704 705 @Override toString()706 public String toString() { 707 return Integer.toString(mId); 708 } 709 710 /** @hide */ 711 @NonNull getStateAsString(int state)712 protected static String getStateAsString(int state) { 713 return state + " (" + (state == UNKNOWN_STATE ? "UNKNOWN" 714 : DebugUtils.flagsToString(ContentCaptureSession.class, "STATE_", state)) + ")"; 715 } 716 717 /** @hide */ 718 @NonNull getFlushReasonAsString(@lushReason int reason)719 public static String getFlushReasonAsString(@FlushReason int reason) { 720 switch (reason) { 721 case FLUSH_REASON_FULL: 722 return "FULL"; 723 case FLUSH_REASON_VIEW_ROOT_ENTERED: 724 return "VIEW_ROOT"; 725 case FLUSH_REASON_SESSION_STARTED: 726 return "STARTED"; 727 case FLUSH_REASON_SESSION_FINISHED: 728 return "FINISHED"; 729 case FLUSH_REASON_IDLE_TIMEOUT: 730 return "IDLE"; 731 case FLUSH_REASON_TEXT_CHANGE_TIMEOUT: 732 return "TEXT_CHANGE"; 733 case FLUSH_REASON_SESSION_CONNECTED: 734 return "CONNECTED"; 735 case FLUSH_REASON_FORCE_FLUSH: 736 return "FORCE_FLUSH"; 737 case FLUSH_REASON_VIEW_TREE_APPEARING: 738 return "VIEW_TREE_APPEARING"; 739 case FLUSH_REASON_VIEW_TREE_APPEARED: 740 return "VIEW_TREE_APPEARED"; 741 default: 742 return "UNKNOWN-" + reason; 743 } 744 } 745 getRandomSessionId()746 private static int getRandomSessionId() { 747 int id; 748 do { 749 id = ID_GENERATOR.nextInt(); 750 } while (id == NO_SESSION_ID); 751 return id; 752 } 753 } 754