• 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.NO_SESSION_ID;
20 
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.SystemApi;
25 import android.annotation.TestApi;
26 import android.graphics.Insets;
27 import android.os.Parcel;
28 import android.os.Parcelable;
29 import android.util.Log;
30 import android.view.autofill.AutofillId;
31 
32 import com.android.internal.util.Preconditions;
33 
34 import java.io.PrintWriter;
35 import java.lang.annotation.Retention;
36 import java.lang.annotation.RetentionPolicy;
37 import java.util.ArrayList;
38 import java.util.List;
39 
40 /** @hide */
41 @SystemApi
42 @TestApi
43 public final class ContentCaptureEvent implements Parcelable {
44 
45     private static final String TAG = ContentCaptureEvent.class.getSimpleName();
46 
47     /** @hide */
48     public static final int TYPE_SESSION_FINISHED = -2;
49     /** @hide */
50     public static final int TYPE_SESSION_STARTED = -1;
51 
52     /**
53      * Called when a node has been added to the screen and is visible to the user.
54      *
55      * <p>The metadata of the node is available through {@link #getViewNode()}.
56      */
57     public static final int TYPE_VIEW_APPEARED = 1;
58 
59     /**
60      * Called when one or more nodes have been removed from the screen and is not visible to the
61      * user anymore.
62      *
63      * <p>To get the id(s), first call {@link #getIds()} - if it returns {@code null}, then call
64      * {@link #getId()}.
65      */
66     public static final int TYPE_VIEW_DISAPPEARED = 2;
67 
68     /**
69      * Called when the text of a node has been changed.
70      *
71      * <p>The id of the node is available through {@link #getId()}, and the new text is
72      * available through {@link #getText()}.
73      */
74     public static final int TYPE_VIEW_TEXT_CHANGED = 3;
75 
76     /**
77      * Called before events (such as {@link #TYPE_VIEW_APPEARED} and/or
78      * {@link #TYPE_VIEW_DISAPPEARED}) representing a view hierarchy are sent.
79      *
80      * <p><b>NOTE</b>: there is no guarantee this event will be sent. For example, it's not sent
81      * if the initial view hierarchy doesn't initially have any view that's important for content
82      * capture.
83      */
84     public static final int TYPE_VIEW_TREE_APPEARING = 4;
85 
86     /**
87      * Called after events (such as {@link #TYPE_VIEW_APPEARED} and/or
88      * {@link #TYPE_VIEW_DISAPPEARED}) representing a view hierarchy were sent.
89      *
90      * <p><b>NOTE</b>: there is no guarantee this event will be sent. For example, it's not sent
91      * if the initial view hierarchy doesn't initially have any view that's important for content
92      * capture.
93      */
94     public static final int TYPE_VIEW_TREE_APPEARED = 5;
95 
96     /**
97      * Called after a call to
98      * {@link ContentCaptureSession#setContentCaptureContext(ContentCaptureContext)}.
99      *
100      * <p>The passed context is available through {@link #getContentCaptureContext()}.
101      */
102     public static final int TYPE_CONTEXT_UPDATED = 6;
103 
104     /**
105      * Called after the session is ready, typically after the activity resumed and the
106      * initial views appeared
107      */
108     public static final int TYPE_SESSION_RESUMED = 7;
109 
110     /**
111      * Called after the session is paused, typically after the activity paused and the
112      * views disappeared.
113      */
114     public static final int TYPE_SESSION_PAUSED = 8;
115 
116     /**
117      * Called when the view's insets are changed. The new insets associated with the
118      * event may then be retrieved by calling {@link #getInsets()}
119      */
120     public static final int TYPE_VIEW_INSETS_CHANGED = 9;
121 
122     /** @hide */
123     @IntDef(prefix = { "TYPE_" }, value = {
124             TYPE_VIEW_APPEARED,
125             TYPE_VIEW_DISAPPEARED,
126             TYPE_VIEW_TEXT_CHANGED,
127             TYPE_VIEW_TREE_APPEARING,
128             TYPE_VIEW_TREE_APPEARED,
129             TYPE_CONTEXT_UPDATED,
130             TYPE_SESSION_PAUSED,
131             TYPE_SESSION_RESUMED,
132             TYPE_VIEW_INSETS_CHANGED
133     })
134     @Retention(RetentionPolicy.SOURCE)
135     public @interface EventType{}
136 
137     private final int mSessionId;
138     private final int mType;
139     private final long mEventTime;
140     private @Nullable AutofillId mId;
141     private @Nullable ArrayList<AutofillId> mIds;
142     private @Nullable ViewNode mNode;
143     private @Nullable CharSequence mText;
144     private int mParentSessionId = NO_SESSION_ID;
145     private @Nullable ContentCaptureContext mClientContext;
146     private @Nullable Insets mInsets;
147 
148     /** @hide */
ContentCaptureEvent(int sessionId, int type, long eventTime)149     public ContentCaptureEvent(int sessionId, int type, long eventTime) {
150         mSessionId = sessionId;
151         mType = type;
152         mEventTime = eventTime;
153     }
154 
155     /** @hide */
ContentCaptureEvent(int sessionId, int type)156     public ContentCaptureEvent(int sessionId, int type) {
157         this(sessionId, type, System.currentTimeMillis());
158     }
159 
160     /** @hide */
setAutofillId(@onNull AutofillId id)161     public ContentCaptureEvent setAutofillId(@NonNull AutofillId id) {
162         mId = Preconditions.checkNotNull(id);
163         return this;
164     }
165 
166     /** @hide */
setAutofillIds(@onNull ArrayList<AutofillId> ids)167     public ContentCaptureEvent setAutofillIds(@NonNull ArrayList<AutofillId> ids) {
168         mIds = Preconditions.checkNotNull(ids);
169         return this;
170     }
171 
172     /**
173      * Adds an autofill id to the this event, merging the single id into a list if necessary.
174      *
175      * @hide
176      */
addAutofillId(@onNull AutofillId id)177     public ContentCaptureEvent addAutofillId(@NonNull AutofillId id) {
178         Preconditions.checkNotNull(id);
179         if (mIds == null) {
180             mIds = new ArrayList<>();
181             if (mId == null) {
182                 Log.w(TAG, "addAutofillId(" + id + ") called without an initial id");
183             } else {
184                 mIds.add(mId);
185                 mId = null;
186             }
187         }
188         mIds.add(id);
189         return this;
190     }
191 
192     /**
193      * Used by {@link #TYPE_SESSION_STARTED} and {@link #TYPE_SESSION_FINISHED}.
194      *
195      * @hide
196      */
setParentSessionId(int parentSessionId)197     public ContentCaptureEvent setParentSessionId(int parentSessionId) {
198         mParentSessionId = parentSessionId;
199         return this;
200     }
201 
202     /**
203      * Used by {@link #TYPE_SESSION_STARTED} and {@link #TYPE_SESSION_FINISHED}.
204      *
205      * @hide
206      */
setClientContext(@onNull ContentCaptureContext clientContext)207     public ContentCaptureEvent setClientContext(@NonNull ContentCaptureContext clientContext) {
208         mClientContext = clientContext;
209         return this;
210     }
211 
212     /** @hide */
213     @NonNull
getSessionId()214     public int getSessionId() {
215         return mSessionId;
216     }
217 
218     /**
219      * Used by {@link #TYPE_SESSION_STARTED} and {@link #TYPE_SESSION_FINISHED}.
220      *
221      * @hide
222      */
223     @Nullable
getParentSessionId()224     public int getParentSessionId() {
225         return mParentSessionId;
226     }
227 
228     /**
229      * Gets the {@link ContentCaptureContext} set calls to
230      * {@link ContentCaptureSession#setContentCaptureContext(ContentCaptureContext)}.
231      *
232      * <p>Only set on {@link #TYPE_CONTEXT_UPDATED} events.
233      */
234     @Nullable
getContentCaptureContext()235     public ContentCaptureContext getContentCaptureContext() {
236         return mClientContext;
237     }
238 
239     /** @hide */
240     @NonNull
setViewNode(@onNull ViewNode node)241     public ContentCaptureEvent setViewNode(@NonNull ViewNode node) {
242         mNode = Preconditions.checkNotNull(node);
243         return this;
244     }
245 
246     /** @hide */
247     @NonNull
setText(@ullable CharSequence text)248     public ContentCaptureEvent setText(@Nullable CharSequence text) {
249         mText = text;
250         return this;
251     }
252 
253     /** @hide */
254     @NonNull
setInsets(@onNull Insets insets)255     public ContentCaptureEvent setInsets(@NonNull Insets insets) {
256         mInsets = insets;
257         return this;
258     }
259 
260     /**
261      * Gets the type of the event.
262      *
263      * @return one of {@link #TYPE_VIEW_APPEARED}, {@link #TYPE_VIEW_DISAPPEARED},
264      * {@link #TYPE_VIEW_TEXT_CHANGED}, {@link #TYPE_VIEW_TREE_APPEARING},
265      * {@link #TYPE_VIEW_TREE_APPEARED}, {@link #TYPE_CONTEXT_UPDATED},
266      * {@link #TYPE_SESSION_RESUMED}, or {@link #TYPE_SESSION_PAUSED}.
267      */
getType()268     public @EventType int getType() {
269         return mType;
270     }
271 
272     /**
273      * Gets when the event was generated, in millis since epoch.
274      */
getEventTime()275     public long getEventTime() {
276         return mEventTime;
277     }
278 
279     /**
280      * Gets the whole metadata of the node associated with the event.
281      *
282      * <p>Only set on {@link #TYPE_VIEW_APPEARED} events.
283      */
284     @Nullable
getViewNode()285     public ViewNode getViewNode() {
286         return mNode;
287     }
288 
289     /**
290      * Gets the {@link AutofillId} of the node associated with the event.
291      *
292      * <p>Only set on {@link #TYPE_VIEW_DISAPPEARED} (when the event contains just one node - if
293      * it contains more than one, this method returns {@code null} and the actual ids should be
294      * retrived by {@link #getIds()}) and {@link #TYPE_VIEW_TEXT_CHANGED} events.
295      */
296     @Nullable
getId()297     public AutofillId getId() {
298         return mId;
299     }
300 
301     /**
302      * Gets the {@link AutofillId AutofillIds} of the nodes associated with the event.
303      *
304      * <p>Only set on {@link #TYPE_VIEW_DISAPPEARED}, when the event contains more than one node
305      * (if it contains just one node, it's returned by {@link #getId()} instead.
306      */
307     @Nullable
getIds()308     public List<AutofillId> getIds() {
309         return mIds;
310     }
311 
312     /**
313      * Gets the current text of the node associated with the event.
314      *
315      * <p>Only set on {@link #TYPE_VIEW_TEXT_CHANGED} events.
316      */
317     @Nullable
getText()318     public CharSequence getText() {
319         return mText;
320     }
321 
322     /**
323      * Gets the rectangle of the insets associated with the event. Valid insets will only be
324      * returned if the type of the event is {@link #TYPE_VIEW_INSETS_CHANGED}, otherwise they
325      * will be null.
326      */
327     @Nullable
getInsets()328     public Insets getInsets() {
329         return mInsets;
330     }
331 
332     /**
333      * Merges event of the same type, either {@link #TYPE_VIEW_TEXT_CHANGED}
334      * or {@link #TYPE_VIEW_DISAPPEARED}.
335      *
336      * @hide
337      */
mergeEvent(@onNull ContentCaptureEvent event)338     public void mergeEvent(@NonNull ContentCaptureEvent event) {
339         Preconditions.checkNotNull(event);
340         final int eventType = event.getType();
341         if (mType != eventType) {
342             Log.e(TAG, "mergeEvent(" + getTypeAsString(eventType) + ") cannot be merged "
343                     + "with different eventType=" + getTypeAsString(mType));
344             return;
345         }
346 
347         if (eventType == TYPE_VIEW_DISAPPEARED) {
348             final List<AutofillId> ids = event.getIds();
349             final AutofillId id = event.getId();
350             if (ids != null) {
351                 if (id != null) {
352                     Log.w(TAG, "got TYPE_VIEW_DISAPPEARED event with both id and ids: " + event);
353                 }
354                 for (int i = 0; i < ids.size(); i++) {
355                     addAutofillId(ids.get(i));
356                 }
357                 return;
358             }
359             if (id != null) {
360                 addAutofillId(id);
361                 return;
362             }
363             throw new IllegalArgumentException("mergeEvent(): got "
364                     + "TYPE_VIEW_DISAPPEARED event with neither id or ids: " + event);
365         } else if (eventType == TYPE_VIEW_TEXT_CHANGED) {
366             setText(event.getText());
367         } else {
368             Log.e(TAG, "mergeEvent(" + getTypeAsString(eventType)
369                     + ") does not support this event type.");
370         }
371     }
372 
373     /** @hide */
dump(@onNull PrintWriter pw)374     public void dump(@NonNull PrintWriter pw) {
375         pw.print("type="); pw.print(getTypeAsString(mType));
376         pw.print(", time="); pw.print(mEventTime);
377         if (mId != null) {
378             pw.print(", id="); pw.print(mId);
379         }
380         if (mIds != null) {
381             pw.print(", ids="); pw.print(mIds);
382         }
383         if (mNode != null) {
384             pw.print(", mNode.id="); pw.print(mNode.getAutofillId());
385         }
386         if (mSessionId != NO_SESSION_ID) {
387             pw.print(", sessionId="); pw.print(mSessionId);
388         }
389         if (mParentSessionId != NO_SESSION_ID) {
390             pw.print(", parentSessionId="); pw.print(mParentSessionId);
391         }
392         if (mText != null) {
393             pw.print(", text="); pw.println(getSanitizedString(mText));
394         }
395         if (mClientContext != null) {
396             pw.print(", context="); mClientContext.dump(pw); pw.println();
397         }
398         if (mInsets != null) {
399             pw.print(", insets="); pw.println(mInsets);
400         }
401     }
402 
403     @NonNull
404     @Override
toString()405     public String toString() {
406         final StringBuilder string = new StringBuilder("ContentCaptureEvent[type=")
407                 .append(getTypeAsString(mType));
408         string.append(", session=").append(mSessionId);
409         if (mType == TYPE_SESSION_STARTED && mParentSessionId != NO_SESSION_ID) {
410             string.append(", parent=").append(mParentSessionId);
411         }
412         if (mId != null) {
413             string.append(", id=").append(mId);
414         }
415         if (mIds != null) {
416             string.append(", ids=").append(mIds);
417         }
418         if (mNode != null) {
419             final String className = mNode.getClassName();
420             if (mNode != null) {
421                 string.append(", class=").append(className);
422             }
423             string.append(", id=").append(mNode.getAutofillId());
424         }
425         if (mText != null) {
426             string.append(", text=").append(getSanitizedString(mText));
427         }
428         if (mClientContext != null) {
429             string.append(", context=").append(mClientContext);
430         }
431         if (mInsets != null) {
432             string.append(", insets=").append(mInsets);
433         }
434         return string.append(']').toString();
435     }
436 
437     @Override
describeContents()438     public int describeContents() {
439         return 0;
440     }
441 
442     @Override
writeToParcel(Parcel parcel, int flags)443     public void writeToParcel(Parcel parcel, int flags) {
444         parcel.writeInt(mSessionId);
445         parcel.writeInt(mType);
446         parcel.writeLong(mEventTime);
447         parcel.writeParcelable(mId, flags);
448         parcel.writeTypedList(mIds);
449         ViewNode.writeToParcel(parcel, mNode, flags);
450         parcel.writeCharSequence(mText);
451         if (mType == TYPE_SESSION_STARTED || mType == TYPE_SESSION_FINISHED) {
452             parcel.writeInt(mParentSessionId);
453         }
454         if (mType == TYPE_SESSION_STARTED || mType == TYPE_CONTEXT_UPDATED) {
455             parcel.writeParcelable(mClientContext, flags);
456         }
457         if (mType == TYPE_VIEW_INSETS_CHANGED) {
458             parcel.writeParcelable(mInsets, flags);
459         }
460     }
461 
462     public static final @android.annotation.NonNull Parcelable.Creator<ContentCaptureEvent> CREATOR =
463             new Parcelable.Creator<ContentCaptureEvent>() {
464 
465         @Override
466         @NonNull
467         public ContentCaptureEvent createFromParcel(Parcel parcel) {
468             final int sessionId = parcel.readInt();
469             final int type = parcel.readInt();
470             final long eventTime  = parcel.readLong();
471             final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, type, eventTime);
472             final AutofillId id = parcel.readParcelable(null);
473             if (id != null) {
474                 event.setAutofillId(id);
475             }
476             final ArrayList<AutofillId> ids = parcel.createTypedArrayList(AutofillId.CREATOR);
477             if (ids != null) {
478                 event.setAutofillIds(ids);
479             }
480             final ViewNode node = ViewNode.readFromParcel(parcel);
481             if (node != null) {
482                 event.setViewNode(node);
483             }
484             event.setText(parcel.readCharSequence());
485             if (type == TYPE_SESSION_STARTED || type == TYPE_SESSION_FINISHED) {
486                 event.setParentSessionId(parcel.readInt());
487             }
488             if (type == TYPE_SESSION_STARTED || type == TYPE_CONTEXT_UPDATED) {
489                 event.setClientContext(parcel.readParcelable(null));
490             }
491             if (type == TYPE_VIEW_INSETS_CHANGED) {
492                 event.setInsets(parcel.readParcelable(null));
493             }
494             return event;
495         }
496 
497         @Override
498         @NonNull
499         public ContentCaptureEvent[] newArray(int size) {
500             return new ContentCaptureEvent[size];
501         }
502     };
503 
504     /** @hide */
getTypeAsString(@ventType int type)505     public static String getTypeAsString(@EventType int type) {
506         switch (type) {
507             case TYPE_SESSION_STARTED:
508                 return "SESSION_STARTED";
509             case TYPE_SESSION_FINISHED:
510                 return "SESSION_FINISHED";
511             case TYPE_SESSION_RESUMED:
512                 return "SESSION_RESUMED";
513             case TYPE_SESSION_PAUSED:
514                 return "SESSION_PAUSED";
515             case TYPE_VIEW_APPEARED:
516                 return "VIEW_APPEARED";
517             case TYPE_VIEW_DISAPPEARED:
518                 return "VIEW_DISAPPEARED";
519             case TYPE_VIEW_TEXT_CHANGED:
520                 return "VIEW_TEXT_CHANGED";
521             case TYPE_VIEW_TREE_APPEARING:
522                 return "VIEW_TREE_APPEARING";
523             case TYPE_VIEW_TREE_APPEARED:
524                 return "VIEW_TREE_APPEARED";
525             case TYPE_CONTEXT_UPDATED:
526                 return "CONTEXT_UPDATED";
527             case TYPE_VIEW_INSETS_CHANGED:
528                 return "VIEW_INSETS_CHANGED";
529             default:
530                 return "UKNOWN_TYPE: " + type;
531         }
532     }
533 }
534