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