Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 739b98fe authored by Felipe Leme's avatar Felipe Leme
Browse files

Added an (optional) session id to Autofill id.

Test: atest FrameworksCoreTests:android.view.autofill.AutofillIdTest \
            FrameworksCoreTests:android.view.contentcapture.ContentCaptureSessionTest \
            FrameworksCoreTests:android.view.contentcapture.ViewNodeTest \
	    CtsContentCaptureServiceTestCases \
            CtsAutoFillServiceTestCases
Bug: 121197119

Change-Id: I7572de182d9bf1c3862997766423bd5742f3ce2a
parent a26b9b94
Loading
Loading
Loading
Loading
+52 −20
Original line number Diff line number Diff line
@@ -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 @@ import android.view.View;
 */
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 @@ public final class AutofillId implements Parcelable {

    /** @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 @@ public final class AutofillId implements Parcelable {
        int result = 1;
        result = prime * result + mViewId;
        result = prime * result + mVirtualId;
        result = prime * result + mSessionId;
        return result;
    }

@@ -88,15 +113,19 @@ public final class AutofillId implements Parcelable {
        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 @@ public final class AutofillId implements Parcelable {
    @Override
    public void writeToParcel(Parcel parcel, int flags) {
        parcel.writeInt(mViewId);
        parcel.writeInt(mVirtual ? 1 : 0);
        parcel.writeInt(mFlags);
        if (isVirtual()) {
            parcel.writeInt(mVirtualId);
        }

    private AutofillId(Parcel parcel) {
        mViewId = parcel.readInt();
        mVirtual = parcel.readInt() == 1;
        mVirtualId = parcel.readInt();
        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
+18 −6
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.view.autofill.AutofillId;
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 @@ public abstract class ContentCaptureSession implements AutoCloseable {

    /** @hide */
    @Nullable
    protected final String mId = UUID.randomUUID().toString();
    protected final String mId;

    private int mState = STATE_UNKNOWN;

@@ -123,6 +124,13 @@ public abstract class ContentCaptureSession implements AutoCloseable {

    /** @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 @@ public abstract class ContentCaptureSession implements AutoCloseable {
        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 abstract class ContentCaptureSession implements AutoCloseable {
    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 @@ public abstract class ContentCaptureSession implements AutoCloseable {
    @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() {
+2 −2
Original line number Diff line number Diff line
@@ -672,9 +672,9 @@ public final class ViewNode extends AssistStructure.ViewNode {
        }

        @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.
+150 −0
Original line number Diff line number Diff line
/*
 * 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();
        }
    }
}
+87 −14
Original line number Diff line number Diff line
@@ -19,11 +19,14 @@ import static com.google.common.truth.Truth.assertThat;

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 @@ import org.mockito.junit.MockitoJUnitRunner;
@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");
        }
    }
}
Loading