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