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.getSanitizedString; 19 import static android.view.contentcapture.ContentCaptureManager.DEBUG; 20 import static android.view.contentcapture.ContentCaptureManager.NO_SESSION_ID; 21 22 import android.annotation.IntDef; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.annotation.SystemApi; 26 import android.graphics.Insets; 27 import android.graphics.Rect; 28 import android.os.Parcel; 29 import android.os.Parcelable; 30 import android.text.Selection; 31 import android.text.Spannable; 32 import android.text.SpannableString; 33 import android.util.Log; 34 import android.view.autofill.AutofillId; 35 import android.view.inputmethod.BaseInputConnection; 36 37 import java.io.PrintWriter; 38 import java.lang.annotation.Retention; 39 import java.lang.annotation.RetentionPolicy; 40 import java.util.ArrayList; 41 import java.util.List; 42 import java.util.Objects; 43 44 /** @hide */ 45 @SystemApi 46 public final class ContentCaptureEvent implements Parcelable { 47 48 private static final String TAG = ContentCaptureEvent.class.getSimpleName(); 49 50 /** @hide */ 51 public static final int TYPE_SESSION_FINISHED = -2; 52 /** @hide */ 53 public static final int TYPE_SESSION_STARTED = -1; 54 55 /** 56 * Called when a node has been added to the screen and is visible to the user. 57 * 58 * On API level 33, this event may be re-sent with additional information if a view's children 59 * have changed, e.g. scrolling Views inside of a ListView. This information will be stored in 60 * the extras Bundle associated with the event's ViewNode. Within the Bundle, the 61 * "android.view.ViewStructure.extra.ACTIVE_CHILDREN_IDS" key may be used to get a list of 62 * Autofill IDs of active child views, and the 63 * "android.view.ViewStructure.extra.FIRST_ACTIVE_POSITION" key may be used to get the 0-based 64 * position of the first active child view in the list relative to the positions of child views 65 * in the container View's dataset. 66 * 67 * <p>The metadata of the node is available through {@link #getViewNode()}. 68 */ 69 public static final int TYPE_VIEW_APPEARED = 1; 70 71 /** 72 * Called when one or more nodes have been removed from the screen and is not visible to the 73 * user anymore. 74 * 75 * <p>To get the id(s), first call {@link #getIds()} - if it returns {@code null}, then call 76 * {@link #getId()}. 77 */ 78 public static final int TYPE_VIEW_DISAPPEARED = 2; 79 80 /** 81 * Called when the text of a node has been changed. 82 * 83 * <p>The id of the node is available through {@link #getId()}, and the new text is 84 * available through {@link #getText()}. 85 */ 86 public static final int TYPE_VIEW_TEXT_CHANGED = 3; 87 88 /** 89 * Called before events (such as {@link #TYPE_VIEW_APPEARED} and/or 90 * {@link #TYPE_VIEW_DISAPPEARED}) representing a view hierarchy are sent. 91 * 92 * <p><b>NOTE</b>: there is no guarantee this event will be sent. For example, it's not sent 93 * if the initial view hierarchy doesn't initially have any view that's important for content 94 * capture. 95 */ 96 public static final int TYPE_VIEW_TREE_APPEARING = 4; 97 98 /** 99 * Called after events (such as {@link #TYPE_VIEW_APPEARED} and/or 100 * {@link #TYPE_VIEW_DISAPPEARED}) representing a view hierarchy were sent. 101 * 102 * <p><b>NOTE</b>: there is no guarantee this event will be sent. For example, it's not sent 103 * if the initial view hierarchy doesn't initially have any view that's important for content 104 * capture. 105 */ 106 public static final int TYPE_VIEW_TREE_APPEARED = 5; 107 108 /** 109 * Called after a call to 110 * {@link ContentCaptureSession#setContentCaptureContext(ContentCaptureContext)}. 111 * 112 * <p>The passed context is available through {@link #getContentCaptureContext()}. 113 */ 114 public static final int TYPE_CONTEXT_UPDATED = 6; 115 116 /** 117 * Called after the session is ready, typically after the activity resumed and the 118 * initial views appeared 119 */ 120 public static final int TYPE_SESSION_RESUMED = 7; 121 122 /** 123 * Called after the session is paused, typically after the activity paused and the 124 * views disappeared. 125 */ 126 public static final int TYPE_SESSION_PAUSED = 8; 127 128 /** 129 * Called when the view's insets are changed. The new insets associated with the 130 * event may then be retrieved by calling {@link #getInsets()} 131 */ 132 public static final int TYPE_VIEW_INSETS_CHANGED = 9; 133 134 /** 135 * Called before {@link #TYPE_VIEW_TREE_APPEARING}, or after the size of the window containing 136 * the views changed. 137 */ 138 public static final int TYPE_WINDOW_BOUNDS_CHANGED = 10; 139 140 /** @hide */ 141 @IntDef(prefix = { "TYPE_" }, value = { 142 TYPE_VIEW_APPEARED, 143 TYPE_VIEW_DISAPPEARED, 144 TYPE_VIEW_TEXT_CHANGED, 145 TYPE_VIEW_TREE_APPEARING, 146 TYPE_VIEW_TREE_APPEARED, 147 TYPE_CONTEXT_UPDATED, 148 TYPE_SESSION_PAUSED, 149 TYPE_SESSION_RESUMED, 150 TYPE_VIEW_INSETS_CHANGED, 151 TYPE_WINDOW_BOUNDS_CHANGED, 152 }) 153 @Retention(RetentionPolicy.SOURCE) 154 public @interface EventType{} 155 156 /** @hide */ 157 public static final int MAX_INVALID_VALUE = -1; 158 159 private final int mSessionId; 160 private final int mType; 161 private final long mEventTime; 162 private @Nullable AutofillId mId; 163 private @Nullable ArrayList<AutofillId> mIds; 164 private @Nullable ViewNode mNode; 165 private @Nullable CharSequence mText; 166 private int mParentSessionId = NO_SESSION_ID; 167 private @Nullable ContentCaptureContext mClientContext; 168 private @Nullable Insets mInsets; 169 private @Nullable Rect mBounds; 170 171 private int mComposingStart = MAX_INVALID_VALUE; 172 private int mComposingEnd = MAX_INVALID_VALUE; 173 private int mSelectionStartIndex = MAX_INVALID_VALUE; 174 private int mSelectionEndIndex = MAX_INVALID_VALUE; 175 176 /** Only used in the main Content Capture session, no need to parcel */ 177 private boolean mTextHasComposingSpan; 178 179 /** @hide */ ContentCaptureEvent(int sessionId, int type, long eventTime)180 public ContentCaptureEvent(int sessionId, int type, long eventTime) { 181 mSessionId = sessionId; 182 mType = type; 183 mEventTime = eventTime; 184 } 185 186 /** @hide */ ContentCaptureEvent(int sessionId, int type)187 public ContentCaptureEvent(int sessionId, int type) { 188 this(sessionId, type, System.currentTimeMillis()); 189 } 190 191 /** @hide */ setAutofillId(@onNull AutofillId id)192 public ContentCaptureEvent setAutofillId(@NonNull AutofillId id) { 193 mId = Objects.requireNonNull(id); 194 return this; 195 } 196 197 /** @hide */ setAutofillIds(@onNull ArrayList<AutofillId> ids)198 public ContentCaptureEvent setAutofillIds(@NonNull ArrayList<AutofillId> ids) { 199 mIds = Objects.requireNonNull(ids); 200 return this; 201 } 202 203 /** 204 * Adds an autofill id to the this event, merging the single id into a list if necessary. 205 * 206 * @hide 207 */ addAutofillId(@onNull AutofillId id)208 public ContentCaptureEvent addAutofillId(@NonNull AutofillId id) { 209 Objects.requireNonNull(id); 210 if (mIds == null) { 211 mIds = new ArrayList<>(); 212 if (mId == null) { 213 Log.w(TAG, "addAutofillId(" + id + ") called without an initial id"); 214 } else { 215 mIds.add(mId); 216 mId = null; 217 } 218 } 219 mIds.add(id); 220 return this; 221 } 222 223 /** 224 * Used by {@link #TYPE_SESSION_STARTED} and {@link #TYPE_SESSION_FINISHED}. 225 * 226 * @hide 227 */ setParentSessionId(int parentSessionId)228 public ContentCaptureEvent setParentSessionId(int parentSessionId) { 229 mParentSessionId = parentSessionId; 230 return this; 231 } 232 233 /** 234 * Used by {@link #TYPE_SESSION_STARTED} and {@link #TYPE_SESSION_FINISHED}. 235 * 236 * @hide 237 */ setClientContext(@onNull ContentCaptureContext clientContext)238 public ContentCaptureEvent setClientContext(@NonNull ContentCaptureContext clientContext) { 239 mClientContext = clientContext; 240 return this; 241 } 242 243 /** @hide */ 244 @NonNull getSessionId()245 public int getSessionId() { 246 return mSessionId; 247 } 248 249 /** 250 * Used by {@link #TYPE_SESSION_STARTED} and {@link #TYPE_SESSION_FINISHED}. 251 * 252 * @hide 253 */ 254 @Nullable getParentSessionId()255 public int getParentSessionId() { 256 return mParentSessionId; 257 } 258 259 /** 260 * Gets the {@link ContentCaptureContext} set calls to 261 * {@link ContentCaptureSession#setContentCaptureContext(ContentCaptureContext)}. 262 * 263 * <p>Only set on {@link #TYPE_CONTEXT_UPDATED} events. 264 */ 265 @Nullable getContentCaptureContext()266 public ContentCaptureContext getContentCaptureContext() { 267 return mClientContext; 268 } 269 270 /** @hide */ 271 @NonNull setViewNode(@onNull ViewNode node)272 public ContentCaptureEvent setViewNode(@NonNull ViewNode node) { 273 mNode = Objects.requireNonNull(node); 274 return this; 275 } 276 277 /** @hide */ 278 @NonNull setText(@ullable CharSequence text)279 public ContentCaptureEvent setText(@Nullable CharSequence text) { 280 mText = text; 281 return this; 282 } 283 284 /** @hide */ 285 @NonNull setComposingIndex(int start, int end)286 public ContentCaptureEvent setComposingIndex(int start, int end) { 287 mComposingStart = start; 288 mComposingEnd = end; 289 return this; 290 } 291 292 /** @hide */ 293 @NonNull hasComposingSpan()294 public boolean hasComposingSpan() { 295 return mComposingStart > MAX_INVALID_VALUE; 296 } 297 298 /** @hide */ 299 @NonNull setSelectionIndex(int start, int end)300 public ContentCaptureEvent setSelectionIndex(int start, int end) { 301 mSelectionStartIndex = start; 302 mSelectionEndIndex = end; 303 return this; 304 } 305 hasSameComposingSpan(@onNull ContentCaptureEvent other)306 boolean hasSameComposingSpan(@NonNull ContentCaptureEvent other) { 307 return mComposingStart == other.mComposingStart && mComposingEnd == other.mComposingEnd; 308 } 309 hasSameSelectionSpan(@onNull ContentCaptureEvent other)310 boolean hasSameSelectionSpan(@NonNull ContentCaptureEvent other) { 311 return mSelectionStartIndex == other.mSelectionStartIndex 312 && mSelectionEndIndex == other.mSelectionEndIndex; 313 } 314 getComposingStart()315 private int getComposingStart() { 316 return mComposingStart; 317 } 318 getComposingEnd()319 private int getComposingEnd() { 320 return mComposingEnd; 321 } 322 getSelectionStart()323 private int getSelectionStart() { 324 return mSelectionStartIndex; 325 } 326 getSelectionEnd()327 private int getSelectionEnd() { 328 return mSelectionEndIndex; 329 } 330 restoreComposingSpan()331 private void restoreComposingSpan() { 332 if (mComposingStart <= MAX_INVALID_VALUE 333 || mComposingEnd <= MAX_INVALID_VALUE) { 334 return; 335 } 336 if (mText instanceof Spannable) { 337 BaseInputConnection.setComposingSpans((Spannable) mText, mComposingStart, 338 mComposingEnd); 339 } else { 340 Log.w(TAG, "Text is not a Spannable."); 341 } 342 } 343 restoreSelectionSpans()344 private void restoreSelectionSpans() { 345 if (mSelectionStartIndex <= MAX_INVALID_VALUE 346 || mSelectionEndIndex <= MAX_INVALID_VALUE) { 347 return; 348 } 349 350 if (mText instanceof SpannableString) { 351 SpannableString ss = (SpannableString) mText; 352 ss.setSpan(Selection.SELECTION_START, mSelectionStartIndex, mSelectionStartIndex, 0); 353 ss.setSpan(Selection.SELECTION_END, mSelectionEndIndex, mSelectionEndIndex, 0); 354 } else { 355 Log.w(TAG, "Text is not a SpannableString."); 356 } 357 } 358 359 /** @hide */ 360 @NonNull setInsets(@onNull Insets insets)361 public ContentCaptureEvent setInsets(@NonNull Insets insets) { 362 mInsets = insets; 363 return this; 364 } 365 366 /** @hide */ 367 @NonNull setBounds(@onNull Rect bounds)368 public ContentCaptureEvent setBounds(@NonNull Rect bounds) { 369 mBounds = bounds; 370 return this; 371 } 372 373 /** 374 * Gets the type of the event. 375 * 376 * @return one of {@link #TYPE_VIEW_APPEARED}, {@link #TYPE_VIEW_DISAPPEARED}, 377 * {@link #TYPE_VIEW_TEXT_CHANGED}, {@link #TYPE_VIEW_TREE_APPEARING}, 378 * {@link #TYPE_VIEW_TREE_APPEARED}, {@link #TYPE_CONTEXT_UPDATED}, 379 * {@link #TYPE_SESSION_RESUMED}, or {@link #TYPE_SESSION_PAUSED}. 380 */ getType()381 public @EventType int getType() { 382 return mType; 383 } 384 385 /** 386 * Gets when the event was generated, in millis since epoch. 387 */ getEventTime()388 public long getEventTime() { 389 return mEventTime; 390 } 391 392 /** 393 * Gets the whole metadata of the node associated with the event. 394 * 395 * <p>Only set on {@link #TYPE_VIEW_APPEARED} events. 396 */ 397 @Nullable getViewNode()398 public ViewNode getViewNode() { 399 return mNode; 400 } 401 402 /** 403 * Gets the {@link AutofillId} of the node associated with the event. 404 * 405 * <p>Only set on {@link #TYPE_VIEW_DISAPPEARED} (when the event contains just one node - if 406 * it contains more than one, this method returns {@code null} and the actual ids should be 407 * retrived by {@link #getIds()}) and {@link #TYPE_VIEW_TEXT_CHANGED} events. 408 */ 409 @Nullable getId()410 public AutofillId getId() { 411 return mId; 412 } 413 414 /** 415 * Gets the {@link AutofillId AutofillIds} of the nodes associated with the event. 416 * 417 * <p>Only set on {@link #TYPE_VIEW_DISAPPEARED}, when the event contains more than one node 418 * (if it contains just one node, it's returned by {@link #getId()} instead. 419 */ 420 @Nullable getIds()421 public List<AutofillId> getIds() { 422 return mIds; 423 } 424 425 /** 426 * Gets the current text of the node associated with the event. 427 * 428 * <p>Only set on {@link #TYPE_VIEW_TEXT_CHANGED} events. 429 */ 430 @Nullable getText()431 public CharSequence getText() { 432 return mText; 433 } 434 435 /** 436 * Gets the rectangle of the insets associated with the event. Valid insets will only be 437 * returned if the type of the event is {@link #TYPE_VIEW_INSETS_CHANGED}, otherwise they 438 * will be null. 439 */ 440 @Nullable getInsets()441 public Insets getInsets() { 442 return mInsets; 443 } 444 445 /** 446 * Gets the {@link Rect} bounds of the window associated with the event. Valid bounds will only 447 * be returned if the type of the event is {@link #TYPE_WINDOW_BOUNDS_CHANGED}, otherwise they 448 * will be null. 449 */ 450 @Nullable getBounds()451 public Rect getBounds() { 452 return mBounds; 453 } 454 455 /** 456 * Merges event of the same type, either {@link #TYPE_VIEW_TEXT_CHANGED} 457 * or {@link #TYPE_VIEW_DISAPPEARED}. 458 * 459 * @hide 460 */ mergeEvent(@onNull ContentCaptureEvent event)461 public void mergeEvent(@NonNull ContentCaptureEvent event) { 462 Objects.requireNonNull(event); 463 final int eventType = event.getType(); 464 if (mType != eventType) { 465 Log.e(TAG, "mergeEvent(" + getTypeAsString(eventType) + ") cannot be merged " 466 + "with different eventType=" + getTypeAsString(mType)); 467 return; 468 } 469 470 if (eventType == TYPE_VIEW_DISAPPEARED) { 471 final List<AutofillId> ids = event.getIds(); 472 final AutofillId id = event.getId(); 473 if (ids != null) { 474 if (id != null) { 475 Log.w(TAG, "got TYPE_VIEW_DISAPPEARED event with both id and ids: " + event); 476 } 477 for (int i = 0; i < ids.size(); i++) { 478 addAutofillId(ids.get(i)); 479 } 480 return; 481 } 482 if (id != null) { 483 addAutofillId(id); 484 return; 485 } 486 throw new IllegalArgumentException("mergeEvent(): got " 487 + "TYPE_VIEW_DISAPPEARED event with neither id or ids: " + event); 488 } else if (eventType == TYPE_VIEW_TEXT_CHANGED) { 489 setText(event.getText()); 490 setComposingIndex(event.getComposingStart(), event.getComposingEnd()); 491 setSelectionIndex(event.getSelectionStart(), event.getSelectionEnd()); 492 } else { 493 Log.e(TAG, "mergeEvent(" + getTypeAsString(eventType) 494 + ") does not support this event type."); 495 } 496 } 497 498 /** @hide */ dump(@onNull PrintWriter pw)499 public void dump(@NonNull PrintWriter pw) { 500 pw.print("type="); pw.print(getTypeAsString(mType)); 501 pw.print(", time="); pw.print(mEventTime); 502 if (mId != null) { 503 pw.print(", id="); pw.print(mId); 504 } 505 if (mIds != null) { 506 pw.print(", ids="); pw.print(mIds); 507 } 508 if (mNode != null) { 509 pw.print(", mNode.id="); pw.print(mNode.getAutofillId()); 510 } 511 if (mSessionId != NO_SESSION_ID) { 512 pw.print(", sessionId="); pw.print(mSessionId); 513 } 514 if (mParentSessionId != NO_SESSION_ID) { 515 pw.print(", parentSessionId="); pw.print(mParentSessionId); 516 } 517 if (mText != null) { 518 pw.print(", text="); pw.println(getSanitizedString(mText)); 519 } 520 if (mClientContext != null) { 521 pw.print(", context="); mClientContext.dump(pw); pw.println(); 522 } 523 if (mInsets != null) { 524 pw.print(", insets="); pw.println(mInsets); 525 } 526 if (mBounds != null) { 527 pw.print(", bounds="); pw.println(mBounds); 528 } 529 if (mComposingStart > MAX_INVALID_VALUE) { 530 pw.print(", composing("); pw.print(mComposingStart); 531 pw.print(", "); pw.print(mComposingEnd); pw.print(")"); 532 } 533 if (mSelectionStartIndex > MAX_INVALID_VALUE) { 534 pw.print(", selection("); pw.print(mSelectionStartIndex); 535 pw.print(", "); pw.print(mSelectionEndIndex); pw.print(")"); 536 } 537 } 538 539 @NonNull 540 @Override toString()541 public String toString() { 542 final StringBuilder string = new StringBuilder("ContentCaptureEvent[type=") 543 .append(getTypeAsString(mType)); 544 string.append(", session=").append(mSessionId); 545 if (mType == TYPE_SESSION_STARTED && mParentSessionId != NO_SESSION_ID) { 546 string.append(", parent=").append(mParentSessionId); 547 } 548 if (mId != null) { 549 string.append(", id=").append(mId); 550 } 551 if (mIds != null) { 552 string.append(", ids=").append(mIds); 553 } 554 if (mNode != null) { 555 final String className = mNode.getClassName(); 556 string.append(", class=").append(className); 557 string.append(", id=").append(mNode.getAutofillId()); 558 if (mNode.getText() != null) { 559 string.append(", text=") 560 .append(DEBUG ? mNode.getText() : getSanitizedString(mNode.getText())); 561 } 562 } 563 if (mText != null) { 564 string.append(", text=") 565 .append(DEBUG ? mText : getSanitizedString(mText)); 566 } 567 if (mClientContext != null) { 568 string.append(", context=").append(mClientContext); 569 } 570 if (mInsets != null) { 571 string.append(", insets=").append(mInsets); 572 } 573 if (mBounds != null) { 574 string.append(", bounds=").append(mBounds); 575 } 576 if (mComposingStart > MAX_INVALID_VALUE) { 577 string.append(", composing=[") 578 .append(mComposingStart).append(",").append(mComposingEnd).append("]"); 579 } 580 if (mSelectionStartIndex > MAX_INVALID_VALUE) { 581 string.append(", selection=[") 582 .append(mSelectionStartIndex).append(",") 583 .append(mSelectionEndIndex).append("]"); 584 } 585 return string.append(']').toString(); 586 } 587 588 @Override describeContents()589 public int describeContents() { 590 return 0; 591 } 592 593 @Override writeToParcel(Parcel parcel, int flags)594 public void writeToParcel(Parcel parcel, int flags) { 595 parcel.writeInt(mSessionId); 596 parcel.writeInt(mType); 597 parcel.writeLong(mEventTime); 598 parcel.writeParcelable(mId, flags); 599 parcel.writeTypedList(mIds); 600 ViewNode.writeToParcel(parcel, mNode, flags); 601 parcel.writeCharSequence(mText); 602 if (mType == TYPE_SESSION_STARTED || mType == TYPE_SESSION_FINISHED) { 603 parcel.writeInt(mParentSessionId); 604 } 605 if (mType == TYPE_SESSION_STARTED || mType == TYPE_CONTEXT_UPDATED) { 606 parcel.writeParcelable(mClientContext, flags); 607 } 608 if (mType == TYPE_VIEW_INSETS_CHANGED) { 609 parcel.writeParcelable(mInsets, flags); 610 } 611 if (mType == TYPE_WINDOW_BOUNDS_CHANGED) { 612 parcel.writeParcelable(mBounds, flags); 613 } 614 if (mType == TYPE_VIEW_TEXT_CHANGED) { 615 parcel.writeInt(mComposingStart); 616 parcel.writeInt(mComposingEnd); 617 parcel.writeInt(mSelectionStartIndex); 618 parcel.writeInt(mSelectionEndIndex); 619 } 620 } 621 622 public static final @android.annotation.NonNull Parcelable.Creator<ContentCaptureEvent> CREATOR = 623 new Parcelable.Creator<ContentCaptureEvent>() { 624 625 @Override 626 @NonNull 627 public ContentCaptureEvent createFromParcel(Parcel parcel) { 628 final int sessionId = parcel.readInt(); 629 final int type = parcel.readInt(); 630 final long eventTime = parcel.readLong(); 631 final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, type, eventTime); 632 final AutofillId id = parcel.readParcelable(null, android.view.autofill.AutofillId.class); 633 if (id != null) { 634 event.setAutofillId(id); 635 } 636 final ArrayList<AutofillId> ids = parcel.createTypedArrayList(AutofillId.CREATOR); 637 if (ids != null) { 638 event.setAutofillIds(ids); 639 } 640 final ViewNode node = ViewNode.readFromParcel(parcel); 641 if (node != null) { 642 event.setViewNode(node); 643 } 644 event.setText(parcel.readCharSequence()); 645 if (type == TYPE_SESSION_STARTED || type == TYPE_SESSION_FINISHED) { 646 event.setParentSessionId(parcel.readInt()); 647 } 648 if (type == TYPE_SESSION_STARTED || type == TYPE_CONTEXT_UPDATED) { 649 event.setClientContext(parcel.readParcelable(null, android.view.contentcapture.ContentCaptureContext.class)); 650 } 651 if (type == TYPE_VIEW_INSETS_CHANGED) { 652 event.setInsets(parcel.readParcelable(null, android.graphics.Insets.class)); 653 } 654 if (type == TYPE_WINDOW_BOUNDS_CHANGED) { 655 event.setBounds(parcel.readParcelable(null, android.graphics.Rect.class)); 656 } 657 if (type == TYPE_VIEW_TEXT_CHANGED) { 658 event.setComposingIndex(parcel.readInt(), parcel.readInt()); 659 event.restoreComposingSpan(); 660 event.setSelectionIndex(parcel.readInt(), parcel.readInt()); 661 event.restoreSelectionSpans(); 662 } 663 return event; 664 } 665 666 @Override 667 @NonNull 668 public ContentCaptureEvent[] newArray(int size) { 669 return new ContentCaptureEvent[size]; 670 } 671 }; 672 673 /** @hide */ getTypeAsString(@ventType int type)674 public static String getTypeAsString(@EventType int type) { 675 switch (type) { 676 case TYPE_SESSION_STARTED: 677 return "SESSION_STARTED"; 678 case TYPE_SESSION_FINISHED: 679 return "SESSION_FINISHED"; 680 case TYPE_SESSION_RESUMED: 681 return "SESSION_RESUMED"; 682 case TYPE_SESSION_PAUSED: 683 return "SESSION_PAUSED"; 684 case TYPE_VIEW_APPEARED: 685 return "VIEW_APPEARED"; 686 case TYPE_VIEW_DISAPPEARED: 687 return "VIEW_DISAPPEARED"; 688 case TYPE_VIEW_TEXT_CHANGED: 689 return "VIEW_TEXT_CHANGED"; 690 case TYPE_VIEW_TREE_APPEARING: 691 return "VIEW_TREE_APPEARING"; 692 case TYPE_VIEW_TREE_APPEARED: 693 return "VIEW_TREE_APPEARED"; 694 case TYPE_CONTEXT_UPDATED: 695 return "CONTEXT_UPDATED"; 696 case TYPE_VIEW_INSETS_CHANGED: 697 return "VIEW_INSETS_CHANGED"; 698 case TYPE_WINDOW_BOUNDS_CHANGED: 699 return "TYPE_WINDOW_BOUNDS_CHANGED"; 700 default: 701 return "UKNOWN_TYPE: " + type; 702 } 703 } 704 } 705