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

Commit a508d8ce authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Added an (optional) session id to Autofill id."

parents 1e09345b 739b98fe
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