From 5148e340bbe50912fe7bb9b04d4a7232dec690b8 Mon Sep 17 00:00:00 2001 From: Kai Li Date: Mon, 30 Jan 2023 16:29:15 +0800 Subject: [PATCH] Add notifyViewsAppeared API. Also update notifyViewsDisappeared API to send view tree events. Bug: 258825825 Change-Id: Ibd17f2170791c9c04282df9e8e096399b351bc74 Test: atest CtsContentCaptureServiceTestCases passed. --- core/api/current.txt | 1 + .../contentcapture/ContentCaptureSession.java | 54 ++++++++++++++++++- .../ContentCaptureSessionTest.java | 42 +++++++++++++-- 3 files changed, 92 insertions(+), 5 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index bec2559bb4610..ef43bf45100f6 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -54302,6 +54302,7 @@ package android.view.contentcapture { method public final void notifyViewDisappeared(@NonNull android.view.autofill.AutofillId); method public final void notifyViewInsetsChanged(@NonNull android.graphics.Insets); method public final void notifyViewTextChanged(@NonNull android.view.autofill.AutofillId, @Nullable CharSequence); + method public final void notifyViewsAppeared(@NonNull java.util.List); method public final void notifyViewsDisappeared(@NonNull android.view.autofill.AutofillId, @NonNull long[]); method public final void setContentCaptureContext(@Nullable android.view.contentcapture.ContentCaptureContext); } diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index 2134d819943e3..ee86fc1414906 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -15,6 +15,7 @@ */ package android.view.contentcapture; +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static android.view.contentcapture.ContentCaptureHelper.sDebug; import static android.view.contentcapture.ContentCaptureHelper.sVerbose; import static android.view.contentcapture.ContentCaptureManager.NO_SESSION_ID; @@ -23,6 +24,9 @@ import android.annotation.CallSuper; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.graphics.Insets; import android.util.DebugUtils; import android.util.Log; @@ -41,6 +45,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.security.SecureRandom; import java.util.ArrayList; +import java.util.List; import java.util.Objects; /** @@ -171,6 +176,10 @@ public abstract class ContentCaptureSession implements AutoCloseable { /** @hide */ public static final int FLUSH_REASON_SESSION_CONNECTED = 7; + @ChangeId + @EnabledSince(targetSdkVersion = UPSIDE_DOWN_CAKE) + static final long NOTIFY_NODES_DISAPPEAR_NOW_SENDS_TREE_EVENTS = 258825825L; + /** @hide */ @IntDef(prefix = { "FLUSH_REASON_" }, value = { FLUSH_REASON_FULL, @@ -229,7 +238,7 @@ public abstract class ContentCaptureSession implements AutoCloseable { mId = id; } - // Used by ChildCOntentCaptureSession + // Used by ChildContentCaptureSession ContentCaptureSession(@NonNull ContentCaptureContext initialContext) { this(); mClientContext = Objects.requireNonNull(initialContext); @@ -362,6 +371,9 @@ public abstract class ContentCaptureSession implements AutoCloseable { * automatically by the Android System for views that return {@code true} on * {@link View#onProvideContentCaptureStructure(ViewStructure, int)}. * + *

Consider use {@link #notifyViewsAppeared} which has a better performance when notifying + * a list of nodes has appeared. + * * @param node node that has been added. */ public final void notifyViewAppeared(@NonNull ViewStructure node) { @@ -383,6 +395,9 @@ public abstract class ContentCaptureSession implements AutoCloseable { *

Typically called "manually" by views that handle their own virtual view hierarchy, or * automatically by the Android System for standard views. * + *

Consider use {@link #notifyViewsDisappeared} which has a better performance when notifying + * a list of nodes has disappeared. + * * @param id id of the node that has been removed. */ public final void notifyViewDisappeared(@NonNull AutofillId id) { @@ -394,12 +409,43 @@ public abstract class ContentCaptureSession implements AutoCloseable { abstract void internalNotifyViewDisappeared(@NonNull AutofillId id); + /** + * Notifies the Content Capture Service that a list of nodes has appeared in the view structure. + * + *

Typically called manually by views that handle their own virtual view hierarchy. + * + * @param appearedNodes nodes that have appeared. Each element represents a view node that has + * been added to the view structure. The order of the elements is important, which should be + * preserved as the attached order of when the node is attached to the virtual view hierarchy. + */ + public final void notifyViewsAppeared(@NonNull List appearedNodes) { + Preconditions.checkCollectionElementsNotNull(appearedNodes, "appearedNodes"); + if (!isContentCaptureEnabled()) return; + + for (int i = 0; i < appearedNodes.size(); i++) { + ViewStructure v = appearedNodes.get(i); + if (!(v instanceof ViewNode.ViewStructureImpl)) { + throw new IllegalArgumentException("Invalid class: " + v.getClass()); + } + } + + internalNotifyViewTreeEvent(/* started= */ true); + for (int i = 0; i < appearedNodes.size(); i++) { + ViewStructure v = appearedNodes.get(i); + internalNotifyViewAppeared((ViewStructureImpl) v); + } + internalNotifyViewTreeEvent(/* started= */ false); + } + /** * Notifies the Content Capture Service that many nodes has been removed from a virtual view * structure. * *

Should only be called by views that handle their own virtual view hierarchy. * + *

After UPSIDE_DOWN_CAKE, this method wraps the virtual children with a pair of view tree + * appearing and view tree appeared events. + * * @param hostId id of the non-virtual view hosting the virtual view hierarchy (it can be * obtained by calling {@link ViewStructure#getAutofillId()}). * @param virtualIds ids of the virtual children. @@ -413,11 +459,17 @@ public abstract class ContentCaptureSession implements AutoCloseable { Preconditions.checkArgument(!ArrayUtils.isEmpty(virtualIds), "virtual ids cannot be empty"); if (!isContentCaptureEnabled()) return; + if (CompatChanges.isChangeEnabled(NOTIFY_NODES_DISAPPEAR_NOW_SENDS_TREE_EVENTS)) { + internalNotifyViewTreeEvent(/* started= */ true); + } // TODO(b/123036895): use a internalNotifyViewsDisappeared that optimizes how the event is // parcelized for (long id : virtualIds) { internalNotifyViewDisappeared(new AutofillId(hostId, id, mId)); } + if (CompatChanges.isChangeEnabled(NOTIFY_NODES_DISAPPEAR_NOW_SENDS_TREE_EVENTS)) { + internalNotifyViewTreeEvent(/* started= */ false); + } } /** diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java index eb58c6300fca8..27d58b8cdcb7d 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java @@ -20,13 +20,19 @@ import static com.google.common.truth.Truth.assertThat; import static org.testng.Assert.assertThrows; +import android.compat.testing.PlatformCompatChangeRule; import android.graphics.Insets; import android.view.View; import android.view.ViewStructure; import android.view.autofill.AutofillId; import android.view.contentcapture.ViewNode.ViewStructureImpl; +import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; @@ -39,6 +45,8 @@ import org.mockito.junit.MockitoJUnitRunner; */ @RunWith(MockitoJUnitRunner.class) public class ContentCaptureSessionTest { + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); private ContentCaptureSession mSession1 = new MyContentCaptureSession(111); @@ -47,6 +55,7 @@ public class ContentCaptureSessionTest { @Mock private View mMockView; + @DisableCompatChanges({ContentCaptureSession.NOTIFY_NODES_DISAPPEAR_NOW_SENDS_TREE_EVENTS}) @Test public void testNewAutofillId_invalid() { assertThrows(NullPointerException.class, () -> mSession1.newAutofillId(null, 42L)); @@ -78,6 +87,8 @@ public class ContentCaptureSessionTest { public void testNotifyXXX_null() { assertThrows(NullPointerException.class, () -> mSession1.notifyViewAppeared(null)); assertThrows(NullPointerException.class, () -> mSession1.notifyViewDisappeared(null)); + assertThrows(NullPointerException.class, + () -> mSession1.notifyViewsAppeared(null)); assertThrows(NullPointerException.class, () -> mSession1.notifyViewTextChanged(null, "whatever")); } @@ -115,8 +126,29 @@ public class ContentCaptureSessionTest { () -> mSession1.notifyViewsDisappeared(new AutofillId(42, 108), new long[] {666})); } + @Test + public void testNotifyViewsDisappeared_noSendTreeEventBeforeU() { + MyContentCaptureSession session = new MyContentCaptureSession(121); + session.notifyViewsDisappeared(new AutofillId(42), new long[] {42}); + + assertThat(session.mInternalNotifyViewTreeEventStartedCount).isEqualTo(0); + assertThat(session.mInternalNotifyViewTreeEventFinishedCount).isEqualTo(0); + } + + @EnableCompatChanges({ContentCaptureSession.NOTIFY_NODES_DISAPPEAR_NOW_SENDS_TREE_EVENTS}) + @Test + public void testNotifyViewsDisappeared_sendTreeEventSinceU() { + MyContentCaptureSession session = new MyContentCaptureSession(122); + session.notifyViewsDisappeared(new AutofillId(42), new long[] {42}); + + assertThat(session.mInternalNotifyViewTreeEventStartedCount).isEqualTo(1); + assertThat(session.mInternalNotifyViewTreeEventFinishedCount).isEqualTo(1); + } + // Cannot use @Spy because we need to pass the session id on constructor private class MyContentCaptureSession extends ContentCaptureSession { + int mInternalNotifyViewTreeEventStartedCount = 0; + int mInternalNotifyViewTreeEventFinishedCount = 0; private MyContentCaptureSession(int id) { super(id); @@ -148,9 +180,7 @@ public class ContentCaptureSessionTest { } @Override - void internalNotifyViewDisappeared(AutofillId id) { - throw new UnsupportedOperationException("should not have been called"); - } + void internalNotifyViewDisappeared(AutofillId id) {} @Override void internalNotifyViewTextChanged(AutofillId id, CharSequence text) { @@ -159,7 +189,11 @@ public class ContentCaptureSessionTest { @Override public void internalNotifyViewTreeEvent(boolean started) { - throw new UnsupportedOperationException("should not have been called"); + if (started) { + mInternalNotifyViewTreeEventStartedCount += 1; + } else { + mInternalNotifyViewTreeEventFinishedCount += 1; + } } @Override