ContentCapture: add new API to notify views deletion on batch.
Test: atest CtsContentCaptureServiceTestCases
Bug: 121050915
Change-Id: I325cfb2954c7c990c8916fe9b17a2642dcaa1c79
diff --git a/api/system-current.txt b/api/system-current.txt
index eaca6ab..e40a350 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8266,6 +8266,7 @@
method public long getEventTime();
method public int getFlags();
method public android.view.autofill.AutofillId getId();
+ method public java.util.List<android.view.autofill.AutofillId> getIds();
method public java.lang.CharSequence getText();
method public int getType();
method public android.view.contentcapture.ViewNode getViewNode();
diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java
index 0d06430..a3b285d 100644
--- a/core/java/android/view/contentcapture/ContentCaptureEvent.java
+++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java
@@ -21,6 +21,7 @@
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
import android.view.autofill.AutofillId;
import com.android.internal.util.Preconditions;
@@ -28,11 +29,15 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
/** @hide */
@SystemApi
public final class ContentCaptureEvent implements Parcelable {
+ private static final String TAG = ContentCaptureEvent.class.getSimpleName();
+
/** @hide */
public static final int TYPE_SESSION_FINISHED = -2;
/** @hide */
@@ -46,9 +51,11 @@
public static final int TYPE_VIEW_APPEARED = 1;
/**
- * Called when a node has been removed from the screen and is not visible to the user anymore.
+ * Called when one or more nodes have been removed from the screen and is not visible to the
+ * user anymore.
*
- * <p>The id of the node is available through {@link #getId()}.
+ * <p>To get the id(s), first call {@link #getIds()} - if it returns {@code null}, then call
+ * {@link #getId()}.
*/
public static final int TYPE_VIEW_DISAPPEARED = 2;
@@ -76,6 +83,7 @@
private final long mEventTime;
private final int mFlags;
private @Nullable AutofillId mId;
+ private @Nullable ArrayList<AutofillId> mIds;
private @Nullable ViewNode mNode;
private @Nullable CharSequence mText;
private @Nullable String mParentSessionId;
@@ -105,6 +113,27 @@
return this;
}
+ private void setAutofillIds(@NonNull ArrayList<AutofillId> ids) {
+ mIds = Preconditions.checkNotNull(ids);
+ }
+
+ /**
+ * Adds an autofill id to the this event, merging the single id into a list if necessary.
+ * @hide */
+ public ContentCaptureEvent addAutofillId(@NonNull AutofillId id) {
+ if (mIds == null) {
+ mIds = new ArrayList<>();
+ if (mId == null) {
+ Log.w(TAG, "addAutofillId(" + id + ") called without an initial id");
+ } else {
+ mIds.add(mId);
+ mId = null;
+ }
+ }
+ mIds.add(id);
+ return this;
+ }
+
/**
* Used by {@link #TYPE_SESSION_STARTED} and {@link #TYPE_SESSION_FINISHED}.
*
@@ -205,7 +234,9 @@
/**
* Gets the {@link AutofillId} of the node associated with the event.
*
- * <p>Only set on {@link #TYPE_VIEW_DISAPPEARED} and {@link #TYPE_VIEW_TEXT_CHANGED} events.
+ * <p>Only set on {@link #TYPE_VIEW_DISAPPEARED} (when the event contains just one node - if
+ * it contains more than one, this method returns {@code null} and the actual ids should be
+ * retrived by {@link #getIds()}) and {@link #TYPE_VIEW_TEXT_CHANGED} events.
*/
@Nullable
public AutofillId getId() {
@@ -213,6 +244,17 @@
}
/**
+ * Gets the {@link AutofillId AutofillIds} of the nodes associated with the event.
+ *
+ * <p>Only set on {@link #TYPE_VIEW_DISAPPEARED}, when the event contains more than one node
+ * (if it contains just one node, it's returned by {@link #getId()} instead.
+ */
+ @Nullable
+ public List<AutofillId> getIds() {
+ return mIds;
+ }
+
+ /**
* Gets the current text of the node associated with the event.
*
* <p>Only set on {@link #TYPE_VIEW_TEXT_CHANGED} events.
@@ -232,6 +274,9 @@
if (mId != null) {
pw.print(", id="); pw.print(mId);
}
+ if (mIds != null) {
+ pw.print(", ids="); pw.print(mIds);
+ }
if (mNode != null) {
pw.print(", mNode.id="); pw.print(mNode.getAutofillId());
}
@@ -261,6 +306,9 @@
if (mId != null) {
string.append(", id=").append(mId);
}
+ if (mIds != null) {
+ string.append(", ids=").append(mIds);
+ }
if (mNode != null) {
final String className = mNode.getClassName();
if (mNode != null) {
@@ -283,6 +331,7 @@
parcel.writeLong(mEventTime);
parcel.writeInt(mFlags);
parcel.writeParcelable(mId, flags);
+ parcel.writeTypedList(mIds);
ViewNode.writeToParcel(parcel, mNode, flags);
parcel.writeCharSequence(mText);
if (mType == TYPE_SESSION_STARTED || mType == TYPE_SESSION_FINISHED) {
@@ -308,6 +357,10 @@
if (id != null) {
event.setAutofillId(id);
}
+ final ArrayList<AutofillId> ids = parcel.createTypedArrayList(AutofillId.CREATOR);
+ if (ids != null) {
+ event.setAutofillIds(ids);
+ }
final ViewNode node = ViewNode.readFromParcel(parcel);
if (node != null) {
event.setViewNode(node);
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 7fb1fdd..21914e2 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -363,8 +363,8 @@
Preconditions.checkArgument(!ArrayUtils.isEmpty(virtualIds), "virtual ids cannot be empty");
if (!isContentCaptureEnabled()) return;
- // TODO(b/121050915): create a new VIEWS_DISAPPEARED event type, which could also be used
- // to batch delete non-virtual views
+ // TODO(b/123036895): use a internalNotifyViewsDisappeared that optimizes how the event is
+ // parcelized
for (int id : virtualIds) {
internalNotifyViewDisappeared(new AutofillId(hostId, id, getIdAsInt()));
}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index f0778fa..af56af3 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -286,6 +286,9 @@
mEvents = new ArrayList<>(MAX_BUFFER_SIZE);
}
+ // Some type of events can be merged together
+ boolean addEvent = true;
+
if (!mEvents.isEmpty() && eventType == TYPE_VIEW_TEXT_CHANGED) {
final ContentCaptureEvent lastEvent = mEvents.get(mEvents.size() - 1);
@@ -297,10 +300,24 @@
+ event.getText());
}
lastEvent.setText(event.getText());
- } else {
- mEvents.add(event);
+ addEvent = false;
}
- } else {
+ }
+
+ if (!mEvents.isEmpty() && eventType == TYPE_VIEW_DISAPPEARED) {
+ final ContentCaptureEvent lastEvent = mEvents.get(mEvents.size() - 1);
+ if (lastEvent.getType() == TYPE_VIEW_DISAPPEARED
+ && event.getSessionId().equals(lastEvent.getSessionId())) {
+ if (VERBOSE) {
+ Log.v(TAG, "Buffering TYPE_VIEW_DISAPPEARED events for session "
+ + lastEvent.getSessionId());
+ }
+ lastEvent.addAutofillId(event.getId());
+ addEvent = false;
+ }
+ }
+
+ if (addEvent) {
mEvents.add(event);
}