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