Merge "Added an (optional) session id to Autofill id."
diff --git a/core/java/android/view/autofill/AutofillId.java b/core/java/android/view/autofill/AutofillId.java
index cb1d89c..9c935af 100644
--- a/core/java/android/view/autofill/AutofillId.java
+++ b/core/java/android/view/autofill/AutofillId.java
@@ -15,6 +15,7 @@
*/
package android.view.autofill;
+import android.annotation.NonNull;
import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -25,33 +26,47 @@
*/
public final class AutofillId implements Parcelable {
+ /** @hide */
+ public static final int NO_SESSION = 0;
+
+ private static final int FLAG_IS_VIRTUAL = 0x1;
+ private static final int FLAG_HAS_SESSION = 0x2;
+
private final int mViewId;
- private final boolean mVirtual;
+ private final int mFlags;
private final int mVirtualId;
+ private final int mSessionId;
/** @hide */
@TestApi
public AutofillId(int id) {
- mVirtual = false;
- mViewId = id;
- mVirtualId = View.NO_ID;
+ this(/* flags= */ 0, id, View.NO_ID, NO_SESSION);
}
/** @hide */
@TestApi
- public AutofillId(AutofillId parent, int virtualChildId) {
- mVirtual = true;
- mViewId = parent.mViewId;
- mVirtualId = virtualChildId;
+ public AutofillId(@NonNull AutofillId parent, int virtualChildId) {
+ this(FLAG_IS_VIRTUAL, parent.mViewId, virtualChildId, NO_SESSION);
}
/** @hide */
public AutofillId(int parentId, int virtualChildId) {
- mVirtual = true;
+ this(FLAG_IS_VIRTUAL, parentId, virtualChildId, NO_SESSION);
+ }
+
+ /** @hide */
+ public AutofillId(@NonNull AutofillId parent, int virtualChildId, int sessionId) {
+ this(FLAG_IS_VIRTUAL | FLAG_HAS_SESSION, parent.mViewId, virtualChildId, sessionId);
+ }
+
+ private AutofillId(int flags, int parentId, int virtualChildId, int sessionId) {
+ mFlags = flags;
mViewId = parentId;
mVirtualId = virtualChildId;
+ mSessionId = sessionId;
}
+
/** @hide */
public int getViewId() {
return mViewId;
@@ -64,7 +79,16 @@
/** @hide */
public boolean isVirtual() {
- return mVirtual;
+ return (mFlags & FLAG_IS_VIRTUAL) != 0;
+ }
+
+ private boolean hasSession() {
+ return (mFlags & FLAG_HAS_SESSION) != 0;
+ }
+
+ /** @hide */
+ public int getSessionId() {
+ return mSessionId;
}
/////////////////////////////////
@@ -77,6 +101,7 @@
int result = 1;
result = prime * result + mViewId;
result = prime * result + mVirtualId;
+ result = prime * result + mSessionId;
return result;
}
@@ -88,15 +113,19 @@
final AutofillId other = (AutofillId) obj;
if (mViewId != other.mViewId) return false;
if (mVirtualId != other.mVirtualId) return false;
+ if (mSessionId != other.mSessionId) return false;
return true;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder().append(mViewId);
- if (mVirtual) {
+ if (isVirtual()) {
builder.append(':').append(mVirtualId);
}
+ if (hasSession()) {
+ builder.append('@').append(mSessionId);
+ }
return builder.toString();
}
@@ -108,21 +137,24 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(mViewId);
- parcel.writeInt(mVirtual ? 1 : 0);
- parcel.writeInt(mVirtualId);
- }
-
- private AutofillId(Parcel parcel) {
- mViewId = parcel.readInt();
- mVirtual = parcel.readInt() == 1;
- mVirtualId = parcel.readInt();
+ parcel.writeInt(mFlags);
+ if (isVirtual()) {
+ parcel.writeInt(mVirtualId);
+ }
+ if (hasSession()) {
+ parcel.writeInt(mSessionId);
+ }
}
public static final Parcelable.Creator<AutofillId> CREATOR =
new Parcelable.Creator<AutofillId>() {
@Override
public AutofillId createFromParcel(Parcel source) {
- return new AutofillId(source);
+ final int viewId = source.readInt();
+ final int flags = source.readInt();
+ final int virtualId = (flags & FLAG_IS_VIRTUAL) != 0 ? source.readInt() : View.NO_ID;
+ final int sessionId = (flags & FLAG_HAS_SESSION) != 0 ? source.readInt() : NO_SESSION;
+ return new AutofillId(flags, viewId, virtualId, sessionId);
}
@Override
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 6890beaf..d9a8416 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -28,6 +28,7 @@
import android.view.contentcapture.ViewNode.ViewStructureImpl;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import dalvik.system.CloseGuard;
@@ -107,7 +108,7 @@
/** @hide */
@Nullable
- protected final String mId = UUID.randomUUID().toString();
+ protected final String mId;
private int mState = STATE_UNKNOWN;
@@ -123,6 +124,13 @@
/** @hide */
protected ContentCaptureSession() {
+ this(UUID.randomUUID().toString());
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public ContentCaptureSession(@NonNull String id) {
+ mId = Preconditions.checkNotNull(id);
mCloseGuard.open("destroy");
}
@@ -140,6 +148,13 @@
return mContentCaptureSessionId;
}
+ /** @hide */
+ @VisibleForTesting
+ public int getIdAsInt() {
+ // TODO(b/121197119): use sessionId instead of hashcode once it's changed to int
+ return mId.hashCode();
+ }
+
/**
* Creates a new {@link ContentCaptureSession}.
*
@@ -315,9 +330,7 @@
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);
+ return new AutofillId(parentId, virtualChildId, getIdAsInt());
}
/**
@@ -333,8 +346,7 @@
@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);
+ return new ViewNode.ViewStructureImpl(parentId, virtualId, getIdAsInt());
}
boolean isContentCaptureEnabled() {
diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java
index b7a486a..ddfecb0 100644
--- a/core/java/android/view/contentcapture/ViewNode.java
+++ b/core/java/android/view/contentcapture/ViewNode.java
@@ -672,9 +672,9 @@
}
@VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk.
- public ViewStructureImpl(@NonNull AutofillId parentId, int virtualId) {
+ public ViewStructureImpl(@NonNull AutofillId parentId, int virtualId, int sessionId) {
mNode.mParentAutofillId = Preconditions.checkNotNull(parentId);
- mNode.mAutofillId = new AutofillId(parentId, virtualId);
+ mNode.mAutofillId = new AutofillId(parentId, virtualId, sessionId);
}
@VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk.
diff --git a/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java b/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java
new file mode 100644
index 0000000..7619af2
--- /dev/null
+++ b/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.autofill;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.view.View;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class AutofillIdTest {
+
+ @Test
+ public void testNonVirtual() {
+ final AutofillId id = new AutofillId(42);
+ assertThat(id.getViewId()).isEqualTo(42);
+ assertThat(id.isVirtual()).isFalse();
+ assertThat(id.getVirtualChildId()).isEqualTo(View.NO_ID);
+
+ final AutofillId clone = cloneThroughParcel(id);
+ assertThat(clone.getViewId()).isEqualTo(42);
+ assertThat(clone.isVirtual()).isFalse();
+ assertThat(clone.getVirtualChildId()).isEqualTo(View.NO_ID);
+ }
+
+ @Test
+ public void testVirtual() {
+ final AutofillId id = new AutofillId(42, 108);
+ assertThat(id.getViewId()).isEqualTo(42);
+ assertThat(id.isVirtual()).isTrue();
+ assertThat(id.getVirtualChildId()).isEqualTo(108);
+
+ final AutofillId clone = cloneThroughParcel(id);
+ assertThat(clone.getViewId()).isEqualTo(42);
+ assertThat(clone.isVirtual()).isTrue();
+ assertThat(clone.getVirtualChildId()).isEqualTo(108);
+ }
+
+ @Test
+ public void testVirtual_parentObjectConstructor() {
+ assertThrows(NullPointerException.class, () -> new AutofillId(null, 108));
+
+ final AutofillId id = new AutofillId(new AutofillId(42), 108);
+ assertThat(id.getViewId()).isEqualTo(42);
+ assertThat(id.isVirtual()).isTrue();
+ assertThat(id.getVirtualChildId()).isEqualTo(108);
+
+ final AutofillId clone = cloneThroughParcel(id);
+ assertThat(clone.getViewId()).isEqualTo(42);
+ assertThat(clone.isVirtual()).isTrue();
+ assertThat(clone.getVirtualChildId()).isEqualTo(108);
+ }
+
+ @Test
+ public void testVirtual_withSession() {
+ final AutofillId id = new AutofillId(new AutofillId(42), 108, 666);
+ assertThat(id.getViewId()).isEqualTo(42);
+ assertThat(id.isVirtual()).isTrue();
+ assertThat(id.getVirtualChildId()).isEqualTo(108);
+ assertThat(id.getSessionId()).isEqualTo(666);
+
+ final AutofillId clone = cloneThroughParcel(id);
+ assertThat(clone.getViewId()).isEqualTo(42);
+ assertThat(clone.isVirtual()).isTrue();
+ assertThat(clone.getVirtualChildId()).isEqualTo(108);
+ assertThat(clone.getSessionId()).isEqualTo(666);
+ }
+
+ @Test
+ public void testEqualsHashCode() {
+ final AutofillId realId = new AutofillId(42);
+ final AutofillId realIdSame = new AutofillId(42);
+ assertThat(realId).isEqualTo(realIdSame);
+ assertThat(realIdSame).isEqualTo(realId);
+ assertThat(realId.hashCode()).isEqualTo(realIdSame.hashCode());
+
+ final AutofillId realIdDifferent = new AutofillId(108);
+ assertThat(realId).isNotEqualTo(realIdDifferent);
+ assertThat(realIdDifferent).isNotEqualTo(realId);
+
+ final AutofillId virtualId = new AutofillId(42, 1);
+ final AutofillId virtualIdSame = new AutofillId(42, 1);
+ assertThat(virtualId).isEqualTo(virtualIdSame);
+ assertThat(virtualIdSame).isEqualTo(virtualId);
+ assertThat(virtualId.hashCode()).isEqualTo(virtualIdSame.hashCode());
+ assertThat(virtualId).isNotEqualTo(realId);
+ assertThat(realId).isNotEqualTo(virtualId);
+
+ final AutofillId virtualIdDifferentChild = new AutofillId(42, 2);
+ assertThat(virtualIdDifferentChild).isNotEqualTo(virtualId);
+ assertThat(virtualId).isNotEqualTo(virtualIdDifferentChild);
+ assertThat(virtualIdDifferentChild).isNotEqualTo(realId);
+ assertThat(realId).isNotEqualTo(virtualIdDifferentChild);
+
+ final AutofillId virtualIdDifferentParent = new AutofillId(108, 1);
+ assertThat(virtualIdDifferentParent).isNotEqualTo(virtualId);
+ assertThat(virtualId).isNotEqualTo(virtualIdDifferentParent);
+ assertThat(virtualIdDifferentParent).isNotEqualTo(virtualIdDifferentChild);
+ assertThat(virtualIdDifferentChild).isNotEqualTo(virtualIdDifferentParent);
+
+ final AutofillId virtualIdDifferentSession = new AutofillId(new AutofillId(42), 1, 108);
+ assertThat(virtualIdDifferentSession).isNotEqualTo(virtualId);
+ assertThat(virtualId).isNotEqualTo(virtualIdDifferentSession);
+ assertThat(virtualIdDifferentSession).isNotEqualTo(realId);
+ assertThat(realId).isNotEqualTo(virtualIdDifferentSession);
+
+ final AutofillId sameVirtualIdDifferentSession = new AutofillId(new AutofillId(42), 1, 108);
+ assertThat(sameVirtualIdDifferentSession).isEqualTo(virtualIdDifferentSession);
+ assertThat(virtualIdDifferentSession).isEqualTo(sameVirtualIdDifferentSession);
+ assertThat(sameVirtualIdDifferentSession.hashCode())
+ .isEqualTo(virtualIdDifferentSession.hashCode());
+ }
+
+ private AutofillId cloneThroughParcel(AutofillId id) {
+ Parcel parcel = Parcel.obtain();
+
+ try {
+ // Write to parcel
+ parcel.setDataPosition(0); // Sanity / paranoid check
+ id.writeToParcel(parcel, 0);
+
+ // Read from parcel
+ parcel.setDataPosition(0);
+ AutofillId clone = AutofillId.CREATOR.createFromParcel(parcel);
+ assertThat(clone).isNotNull();
+ return clone;
+ } finally {
+ parcel.recycle();
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
index 59f3a4c..73cceae 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -19,11 +19,14 @@
import static org.testng.Assert.assertThrows;
+import android.view.View;
+import android.view.ViewStructure;
import android.view.autofill.AutofillId;
+import android.view.contentcapture.ViewNode.ViewStructureImpl;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Spy;
+import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
/**
@@ -35,34 +38,104 @@
@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;
+ private ContentCaptureSession mSession1 = new MyContentCaptureSession("111");
+
+ private ContentCaptureSession mSession2 = new MyContentCaptureSession("2222");
+
+ @Mock
+ private View mMockView;
@Test
public void testNewAutofillId_invalid() {
- assertThrows(NullPointerException.class, () -> mMockSession.newAutofillId(null, 42));
+ assertThrows(NullPointerException.class, () -> mSession1.newAutofillId(null, 42));
assertThrows(IllegalArgumentException.class,
- () -> mMockSession.newAutofillId(new AutofillId(42, 42), 42));
+ () -> mSession1.newAutofillId(new AutofillId(42, 42), 42));
}
@Test
public void testNewAutofillId_valid() {
final AutofillId parentId = new AutofillId(42);
- final AutofillId childId = mMockSession.newAutofillId(parentId, 108);
+ final AutofillId childId = mSession1.newAutofillId(parentId, 108);
assertThat(childId.getViewId()).isEqualTo(42);
assertThat(childId.getVirtualChildId()).isEqualTo(108);
- // TODO(b/121197119): assert session id
+ assertThat(childId.getSessionId()).isEqualTo(mSession1.getIdAsInt());
+ }
+
+ @Test
+ public void testNewAutofillId_differentSessions() {
+ assertThat(mSession1.getIdAsInt()).isNotSameAs(mSession2.getIdAsInt()); //sanity check
+ final AutofillId parentId = new AutofillId(42);
+ final AutofillId childId1 = mSession1.newAutofillId(parentId, 108);
+ final AutofillId childId2 = mSession2.newAutofillId(parentId, 108);
+ assertThat(childId1).isNotEqualTo(childId2);
+ assertThat(childId2).isNotEqualTo(childId1);
}
@Test
public void testNotifyXXX_null() {
- assertThrows(NullPointerException.class, () -> mMockSession.notifyViewAppeared(null));
- assertThrows(NullPointerException.class, () -> mMockSession.notifyViewDisappeared(null));
+ assertThrows(NullPointerException.class, () -> mSession1.notifyViewAppeared(null));
+ assertThrows(NullPointerException.class, () -> mSession1.notifyViewDisappeared(null));
assertThrows(NullPointerException.class,
- () -> mMockSession.notifyViewTextChanged(null, "whatever", 0));
+ () -> mSession1.notifyViewTextChanged(null, "whatever", 0));
+ }
+
+ @Test
+ public void testNewViewStructure() {
+ assertThat(mMockView.getAutofillId()).isNotNull(); // sanity check
+ final ViewStructure structure = mSession1.newViewStructure(mMockView);
+ assertThat(structure).isNotNull();
+ assertThat(structure.getAutofillId()).isEqualTo(mMockView.getAutofillId());
+ }
+
+ @Test
+ public void testNewVirtualViewStructure() {
+ final AutofillId parentId = new AutofillId(42);
+ final ViewStructure structure = mSession1.newVirtualViewStructure(parentId, 108);
+ assertThat(structure).isNotNull();
+ final AutofillId childId = mSession1.newAutofillId(parentId, 108);
+ assertThat(structure.getAutofillId()).isEqualTo(childId);
+ }
+
+ // Cannot use @Spy because we need to pass the session id on constructor
+ private class MyContentCaptureSession extends ContentCaptureSession {
+
+ private MyContentCaptureSession(String id) {
+ super(id);
+ }
+
+ @Override
+ MainContentCaptureSession getMainCaptureSession() {
+ throw new UnsupportedOperationException("should not have been called");
+ }
+
+ @Override
+ ContentCaptureSession newChild(ContentCaptureContext context) {
+ throw new UnsupportedOperationException("should not have been called");
+ }
+
+ @Override
+ void flush() {
+ throw new UnsupportedOperationException("should not have been called");
+ }
+
+ @Override
+ void onDestroy() {
+ throw new UnsupportedOperationException("should not have been called");
+ }
+
+ @Override
+ void internalNotifyViewAppeared(ViewStructureImpl node) {
+ throw new UnsupportedOperationException("should not have been called");
+ }
+
+ @Override
+ void internalNotifyViewDisappeared(AutofillId id) {
+ throw new UnsupportedOperationException("should not have been called");
+ }
+
+ @Override
+ void internalNotifyViewTextChanged(AutofillId id, CharSequence text, int flags) {
+ throw new UnsupportedOperationException("should not have been called");
+ }
}
}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
index 995946b..bbfe01c 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
@@ -110,10 +110,10 @@
@Test
public void testAutofillIdMethods_explicitIdsConstructor() {
AutofillId initialParentId = new AutofillId(42);
- ViewStructureImpl structure = new ViewStructureImpl(initialParentId, 108);
+ ViewStructureImpl structure = new ViewStructureImpl(initialParentId, 108, 666);
ViewNode node = structure.getNode();
- assertThat(node.getAutofillId()).isEqualTo(new AutofillId(initialParentId, 108));
+ assertThat(node.getAutofillId()).isEqualTo(new AutofillId(initialParentId, 108, 666));
assertThat(node.getParentAutofillId()).isEqualTo(initialParentId);
AutofillId newChildId = new AutofillId(108);