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