New, minor ContentCapture APIs for virtual views management.
Test: atest CtsContentCaptureServiceTestCases:android.contentcaptureservice.cts.CustomViewActivityTest
Test: atest CtsContentCaptureServiceTestCases # sanity check
Test: atest FrameworksCoreTests:android.view.contentcapture.ContentCaptureSessionTest
Test: m update-api
Bug: 117944706
Change-Id: I08bed441c6cc15673296f392e333c78018b14f65
diff --git a/api/current.txt b/api/current.txt
index d3aa606..a0cebe3 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -52313,6 +52313,8 @@
method public final android.view.contentcapture.ContentCaptureSession createContentCaptureSession(android.view.contentcapture.ContentCaptureContext);
method public final void destroy();
method public final android.view.contentcapture.ContentCaptureSessionId getContentCaptureSessionId();
+ method public android.view.autofill.AutofillId newAutofillId(android.view.autofill.AutofillId, int);
+ method public final android.view.ViewStructure newVirtualViewStructure(android.view.autofill.AutofillId, int);
method public final void notifyViewAppeared(android.view.ViewStructure);
method public final void notifyViewDisappeared(android.view.autofill.AutofillId);
method public final void notifyViewTextChanged(android.view.autofill.AutofillId, java.lang.CharSequence, int);
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index 26bf361..cefca8f 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -165,7 +165,7 @@
*/
public final void setContentCaptureWhitelist(@Nullable List<String> packages,
@Nullable List<ComponentName> activities) {
- //TODO(b/111276913): implement
+ //TODO(b/122595322): implement
}
/**
@@ -176,7 +176,7 @@
*/
public final void setActivityContentCaptureEnabled(@NonNull ComponentName activity,
boolean enabled) {
- //TODO(b/111276913): implement
+ //TODO(b/122595322): implement
}
/**
@@ -187,7 +187,7 @@
*/
public final void setPackageContentCaptureEnabled(@NonNull String packageName,
boolean enabled) {
- //TODO(b/111276913): implement
+ //TODO(b/122595322): implement
}
/**
@@ -196,7 +196,7 @@
*/
@NonNull
public final Set<ComponentName> getContentCaptureDisabledActivities() {
- //TODO(b/111276913): implement
+ //TODO(b/122595322): implement
return null;
}
@@ -206,7 +206,7 @@
*/
@NonNull
public final Set<String> getContentCaptureDisabledPackages() {
- //TODO(b/111276913): implement
+ //TODO(b/122595322): implement
return null;
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e2c6252..cd0e579 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8199,6 +8199,19 @@
* <p>The populated structure is then passed to the service through
* {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)}.
*
+ * <p><b>Note: </b>views that manage a virtual structure under this view must populate just
+ * the node representing this view and return right away, then asynchronously report (not
+ * necessarily in the UI thread) when the children nodes appear, disappear or have their text
+ * changed by calling
+ * {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)},
+ * {@link ContentCaptureSession#notifyViewDisappeared(AutofillId)}, and
+ * {@link ContentCaptureSession#notifyViewTextChanged(AutofillId, CharSequence, int)}
+ * respectively. The structure for the a child must be created using
+ * {@link ContentCaptureSession#newVirtualViewStructure(AutofillId, int)}, and the
+ * {@code autofillId} for a child can be obtained either through
+ * {@code childStructure.getAutofillId()} or
+ * {@link ContentCaptureSession#newAutofillId(AutofillId, int)}.
+ *
* <p><b>Note: </b>the following methods of the {@code structure} will be ignored:
* <ul>
* <li>{@link ViewStructure#setChildCount(int)}
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 344b9973..a96bf3b 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -46,8 +46,7 @@
/**
* Used on {@link #notifyViewTextChanged(AutofillId, CharSequence, int)} to indicate that the
- *
- * thext change was caused by user input (for example, through IME).
+ * text change was caused by user input (for example, through IME).
*/
public static final int FLAG_USER_INPUT = 0x1;
@@ -178,7 +177,7 @@
mCloseGuard.close();
- //TODO(b/111276913): check state (for example, how to handle if it's waiting for remote
+ // TODO(b/111276913): check state (for example, how to handle if it's waiting for remote
// id) and send it to the cache of batched commands
if (VERBOSE) {
Log.v(TAG, "destroy(): state=" + getStateAsString(mState) + ", mId=" + mId);
@@ -295,6 +294,26 @@
}
/**
+ * Creates a new {@link AutofillId} for a virtual child, so it can be used to uniquely identify
+ * the children in the session.
+ *
+ * @param parentId id of the virtual view parent (it can be obtained by calling
+ * {@link ViewStructure#getAutofillId()} on the parent).
+ * @param virtualChildId id of the virtual child, relative to the parent.
+ *
+ * @return if for the virtual child
+ *
+ * @throws IllegalArgumentException if the {@code parentId} is a virtual child id.
+ */
+ public @NonNull AutofillId newAutofillId(@NonNull AutofillId parentId, int virtualChildId) {
+ Preconditions.checkNotNull(parentId);
+ Preconditions.checkArgument(!parentId.isVirtual(), "virtual ids cannot have children");
+ // TODO(b/121197119): we need to add the session id to the AutofillId to make them unique
+ // per session
+ return new AutofillId(parentId, virtualChildId);
+ }
+
+ /**
* Creates a {@link ViewStructure} for a "virtual" view, so it can be passed to
* {@link #notifyViewAppeared(ViewStructure)} by the view managing the virtual view hierarchy.
*
@@ -303,12 +322,11 @@
* @param virtualId id of the virtual child, relative to the parent.
*
* @return a new {@link ViewStructure} that can be used for Content Capture purposes.
- *
- * @hide
*/
@NonNull
public final ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId,
int virtualId) {
+ // TODO(b/121197119): use the constructor that takes a session id / assert on unit test.
return new ViewNode.ViewStructureImpl(parentId, virtualId);
}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
new file mode 100644
index 0000000..59f3a4c
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.contentcapture;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.view.autofill.AutofillId;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+/**
+ * Unit test for {@link ContentCaptureSessionTest}.
+ *
+ * <p>To run it:
+ * {@code atest FrameworksCoreTests:android.view.contentcapture.ContentCaptureSessionTest}
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ContentCaptureSessionTest {
+
+ /**
+ * Uses a spy as ContentCaptureSession is abstract but (so far) we're testing its concrete
+ * methods.
+ */
+ @Spy
+ private ContentCaptureSession mMockSession;
+
+ @Test
+ public void testNewAutofillId_invalid() {
+ assertThrows(NullPointerException.class, () -> mMockSession.newAutofillId(null, 42));
+ assertThrows(IllegalArgumentException.class,
+ () -> mMockSession.newAutofillId(new AutofillId(42, 42), 42));
+ }
+
+ @Test
+ public void testNewAutofillId_valid() {
+ final AutofillId parentId = new AutofillId(42);
+ final AutofillId childId = mMockSession.newAutofillId(parentId, 108);
+ assertThat(childId.getViewId()).isEqualTo(42);
+ assertThat(childId.getVirtualChildId()).isEqualTo(108);
+ // TODO(b/121197119): assert session id
+ }
+
+ @Test
+ public void testNotifyXXX_null() {
+ assertThrows(NullPointerException.class, () -> mMockSession.notifyViewAppeared(null));
+ assertThrows(NullPointerException.class, () -> mMockSession.notifyViewDisappeared(null));
+ assertThrows(NullPointerException.class,
+ () -> mMockSession.notifyViewTextChanged(null, "whatever", 0));
+ }
+}