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