Create VcnUnderlyingNetworkPriority and the subclass for Wifi

Create VcnUnderlyingNetworkPriority and VcnWifiUnderlyingNetworkPriority
to allow VCN callers to configure network prioritization.

Bug: 206044122
Test: atest FrameworksVcnTests(new tests)
Test: atest CtsVcnTestCases
Change-Id: I540f826176c7085780be93e42fdbc3ff0ad537d7
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java
new file mode 100644
index 0000000..27750c6
--- /dev/null
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2021 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.net.vcn;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.PersistableBundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+// TODO: Add documents
+/** @hide */
+public abstract class VcnUnderlyingNetworkPriority {
+    /** @hide */
+    protected static final int NETWORK_PRIORITY_TYPE_WIFI = 1;
+    /** @hide */
+    protected static final int NETWORK_PRIORITY_TYPE_CELL = 2;
+
+    /** Denotes that network quality needs to be OK */
+    public static final int NETWORK_QUALITY_OK = 10000;
+    /** Denotes that any network quality is acceptable */
+    public static final int NETWORK_QUALITY_ANY = Integer.MAX_VALUE;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({NETWORK_QUALITY_OK, NETWORK_QUALITY_ANY})
+    public @interface NetworkQuality {}
+
+    private static final String NETWORK_PRIORITY_TYPE_KEY = "mNetworkPriorityType";
+    private final int mNetworkPriorityType;
+
+    /** @hide */
+    protected static final String NETWORK_QUALITY_KEY = "mNetworkQuality";
+    private final int mNetworkQuality;
+
+    /** @hide */
+    protected static final String ALLOW_METERED_KEY = "mAllowMetered";
+    private final boolean mAllowMetered;
+
+    /** @hide */
+    protected VcnUnderlyingNetworkPriority(
+            int networkPriorityType, int networkQuality, boolean allowMetered) {
+        mNetworkPriorityType = networkPriorityType;
+        mNetworkQuality = networkQuality;
+        mAllowMetered = allowMetered;
+    }
+
+    private static void validateNetworkQuality(int networkQuality) {
+        Preconditions.checkArgument(
+                networkQuality == NETWORK_QUALITY_ANY || networkQuality == NETWORK_QUALITY_OK,
+                "Invalid networkQuality:" + networkQuality);
+    }
+
+    /** @hide */
+    protected void validate() {
+        validateNetworkQuality(mNetworkQuality);
+    }
+
+    /** @hide */
+    @NonNull
+    @VisibleForTesting(visibility = Visibility.PROTECTED)
+    public static VcnUnderlyingNetworkPriority fromPersistableBundle(
+            @NonNull PersistableBundle in) {
+        Objects.requireNonNull(in, "PersistableBundle is null");
+
+        final int networkPriorityType = in.getInt(NETWORK_PRIORITY_TYPE_KEY);
+        switch (networkPriorityType) {
+            case NETWORK_PRIORITY_TYPE_WIFI:
+                return VcnWifiUnderlyingNetworkPriority.fromPersistableBundle(in);
+            case NETWORK_PRIORITY_TYPE_CELL:
+                throw new UnsupportedOperationException("Not implemented");
+            default:
+                throw new IllegalArgumentException(
+                        "Invalid networkPriorityType:" + networkPriorityType);
+        }
+    }
+
+    /** @hide */
+    @NonNull
+    PersistableBundle toPersistableBundle() {
+        final PersistableBundle result = new PersistableBundle();
+
+        result.putInt(NETWORK_PRIORITY_TYPE_KEY, mNetworkPriorityType);
+        result.putInt(NETWORK_QUALITY_KEY, mNetworkQuality);
+        result.putBoolean(ALLOW_METERED_KEY, mAllowMetered);
+
+        return result;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mNetworkPriorityType, mNetworkQuality, mAllowMetered);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object other) {
+        if (!(other instanceof VcnUnderlyingNetworkPriority)) {
+            return false;
+        }
+
+        final VcnUnderlyingNetworkPriority rhs = (VcnUnderlyingNetworkPriority) other;
+        return mNetworkPriorityType == rhs.mNetworkPriorityType
+                && mNetworkQuality == rhs.mNetworkQuality
+                && mAllowMetered == rhs.mAllowMetered;
+    }
+
+    /** Retrieve the required network quality. */
+    @NetworkQuality
+    public int getNetworkQuality() {
+        return mNetworkQuality;
+    }
+
+    /** Return if a metered network is allowed. */
+    public boolean allowMetered() {
+        return mAllowMetered;
+    }
+
+    /**
+     * This class is used to incrementally build VcnUnderlyingNetworkPriority objects.
+     *
+     * @param <T> The subclass to be built.
+     */
+    public abstract static class Builder<T extends Builder<T>> {
+        /** @hide */
+        protected int mNetworkQuality = NETWORK_QUALITY_ANY;
+        /** @hide */
+        protected boolean mAllowMetered = false;
+
+        /** @hide */
+        protected Builder() {}
+
+        /**
+         * Set the required network quality.
+         *
+         * @param networkQuality the required network quality. Defaults to NETWORK_QUALITY_ANY
+         */
+        @NonNull
+        public T setNetworkQuality(@NetworkQuality int networkQuality) {
+            validateNetworkQuality(networkQuality);
+
+            mNetworkQuality = networkQuality;
+            return self();
+        }
+
+        /**
+         * Set if a metered network is allowed.
+         *
+         * @param allowMetered the flag to indicate if a metered network is allowed, defaults to
+         *     {@code false}
+         */
+        @NonNull
+        public T setAllowMetered(boolean allowMetered) {
+            mAllowMetered = allowMetered;
+            return self();
+        }
+
+        /** @hide */
+        abstract T self();
+    }
+}
diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java
new file mode 100644
index 0000000..fc7e7e2
--- /dev/null
+++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2021 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.net.vcn;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.PersistableBundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+// TODO: Add documents
+/** @hide */
+public final class VcnWifiUnderlyingNetworkPriority extends VcnUnderlyingNetworkPriority {
+    private static final String SSID_KEY = "mSsid";
+    @Nullable private final String mSsid;
+
+    private VcnWifiUnderlyingNetworkPriority(
+            int networkQuality, boolean allowMetered, String ssid) {
+        super(NETWORK_PRIORITY_TYPE_WIFI, networkQuality, allowMetered);
+        mSsid = ssid;
+
+        validate();
+    }
+
+    /** @hide */
+    @NonNull
+    @VisibleForTesting(visibility = Visibility.PROTECTED)
+    public static VcnWifiUnderlyingNetworkPriority fromPersistableBundle(
+            @NonNull PersistableBundle in) {
+        Objects.requireNonNull(in, "PersistableBundle is null");
+
+        final int networkQuality = in.getInt(NETWORK_QUALITY_KEY);
+        final boolean allowMetered = in.getBoolean(ALLOW_METERED_KEY);
+        final String ssid = in.getString(SSID_KEY);
+        return new VcnWifiUnderlyingNetworkPriority(networkQuality, allowMetered, ssid);
+    }
+
+    /** @hide */
+    @Override
+    @NonNull
+    @VisibleForTesting(visibility = Visibility.PROTECTED)
+    public PersistableBundle toPersistableBundle() {
+        final PersistableBundle result = super.toPersistableBundle();
+        result.putString(SSID_KEY, mSsid);
+        return result;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), mSsid);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object other) {
+        if (!super.equals(other)) {
+            return false;
+        }
+
+        if (!(other instanceof VcnWifiUnderlyingNetworkPriority)) {
+            return false;
+        }
+
+        final VcnWifiUnderlyingNetworkPriority rhs = (VcnWifiUnderlyingNetworkPriority) other;
+        return mSsid == rhs.mSsid;
+    }
+
+    /** Retrieve the required SSID, or {@code null} if there is no requirement on SSID. */
+    @Nullable
+    public String getSsid() {
+        return mSsid;
+    }
+
+    /** This class is used to incrementally build VcnWifiUnderlyingNetworkPriority objects. */
+    public static class Builder extends VcnUnderlyingNetworkPriority.Builder<Builder> {
+        @Nullable private String mSsid;
+
+        /** Construct a Builder object. */
+        public Builder() {}
+
+        /**
+         * Set the required SSID.
+         *
+         * @param ssid the required SSID, or {@code null} if any SSID is acceptable.
+         */
+        @NonNull
+        public Builder setSsid(@Nullable String ssid) {
+            mSsid = ssid;
+            return this;
+        }
+
+        /** Build the VcnWifiUnderlyingNetworkPriority. */
+        @NonNull
+        public VcnWifiUnderlyingNetworkPriority build() {
+            return new VcnWifiUnderlyingNetworkPriority(mNetworkQuality, mAllowMetered, mSsid);
+        }
+
+        /** @hide */
+        @Override
+        Builder self() {
+            return this;
+        }
+    }
+}
diff --git a/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkPriorityTest.java b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkPriorityTest.java
new file mode 100644
index 0000000..69ffead
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkPriorityTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 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.net.vcn;
+
+import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_ANY;
+import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_OK;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+public class VcnWifiUnderlyingNetworkPriorityTest {
+    private static final String SSID = "TestWifi";
+    private static final int INVALID_NETWORK_QUALITY = -1;
+
+    private static VcnWifiUnderlyingNetworkPriority getTestNetworkPriority() {
+        return new VcnWifiUnderlyingNetworkPriority.Builder()
+                .setNetworkQuality(NETWORK_QUALITY_OK)
+                .setAllowMetered(true /* allowMetered */)
+                .setSsid(SSID)
+                .build();
+    }
+
+    @Test
+    public void testBuilderAndGetters() {
+        final VcnWifiUnderlyingNetworkPriority networkPriority = getTestNetworkPriority();
+        assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality());
+        assertTrue(networkPriority.allowMetered());
+        assertEquals(SSID, networkPriority.getSsid());
+    }
+
+    @Test
+    public void testBuilderAndGettersForDefaultValues() {
+        final VcnWifiUnderlyingNetworkPriority networkPriority =
+                new VcnWifiUnderlyingNetworkPriority.Builder().build();
+        assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality());
+        assertFalse(networkPriority.allowMetered());
+        assertNull(SSID, networkPriority.getSsid());
+    }
+
+    @Test
+    public void testBuildWithInvalidNetworkQuality() {
+        try {
+            new VcnWifiUnderlyingNetworkPriority.Builder()
+                    .setNetworkQuality(INVALID_NETWORK_QUALITY);
+            fail("Expected to fail due to the invalid network quality");
+        } catch (Exception expected) {
+        }
+    }
+
+    @Test
+    public void testPersistableBundle() {
+        final VcnWifiUnderlyingNetworkPriority networkPriority = getTestNetworkPriority();
+        assertEquals(
+                networkPriority,
+                VcnUnderlyingNetworkPriority.fromPersistableBundle(
+                        networkPriority.toPersistableBundle()));
+    }
+}