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