Merge "gtbs: Add Generic Telephone Bearer Service support"
diff --git a/core/api/current.txt b/core/api/current.txt
index 9233941..a87bb4a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -27277,6 +27277,23 @@
 
 package android.net.vcn {
 
+  public final class VcnCellUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate {
+    method @NonNull public java.util.Set<java.lang.String> getOperatorPlmnIds();
+    method public int getOpportunistic();
+    method public int getRoaming();
+    method @NonNull public java.util.Set<java.lang.Integer> getSimSpecificCarrierIds();
+  }
+
+  public static final class VcnCellUnderlyingNetworkTemplate.Builder {
+    ctor public VcnCellUnderlyingNetworkTemplate.Builder();
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate build();
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMetered(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOperatorPlmnIds(@NonNull java.util.Set<java.lang.String>);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOpportunistic(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setRoaming(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setSimSpecificCarrierIds(@NonNull java.util.Set<java.lang.Integer>);
+  }
+
   public final class VcnConfig implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public java.util.Set<android.net.vcn.VcnGatewayConnectionConfig> getGatewayConnectionConfigs();
@@ -27295,6 +27312,7 @@
     method @NonNull public String getGatewayConnectionName();
     method @IntRange(from=0x500) public int getMaxMtu();
     method @NonNull public long[] getRetryIntervalsMillis();
+    method @NonNull public java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities();
   }
 
   public static final class VcnGatewayConnectionConfig.Builder {
@@ -27304,6 +27322,7 @@
     method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int);
     method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=0x500) int);
     method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryIntervalsMillis(@NonNull long[]);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setVcnUnderlyingNetworkPriorities(@NonNull java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate>);
   }
 
   public class VcnManager {
@@ -27327,6 +27346,24 @@
     method public abstract void onStatusChanged(int);
   }
 
+  public abstract class VcnUnderlyingNetworkTemplate {
+    method public int getMetered();
+    field public static final int MATCH_ANY = 0; // 0x0
+    field public static final int MATCH_FORBIDDEN = 2; // 0x2
+    field public static final int MATCH_REQUIRED = 1; // 0x1
+  }
+
+  public final class VcnWifiUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate {
+    method @NonNull public java.util.Set<java.lang.String> getSsids();
+  }
+
+  public static final class VcnWifiUnderlyingNetworkTemplate.Builder {
+    ctor public VcnWifiUnderlyingNetworkTemplate.Builder();
+    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate build();
+    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMetered(int);
+    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setSsids(@NonNull java.util.Set<java.lang.String>);
+  }
+
 }
 
 package android.nfc {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 41ce6f6..b4a82ff 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2264,6 +2264,25 @@
 
 package android.bluetooth.le {
 
+  public final class AdvertiseSettings implements android.os.Parcelable {
+    method public int getOwnAddressType();
+  }
+
+  public static final class AdvertiseSettings.Builder {
+    method @NonNull public android.bluetooth.le.AdvertiseSettings.Builder setOwnAddressType(int);
+  }
+
+  public final class AdvertisingSetParameters implements android.os.Parcelable {
+    method public int getOwnAddressType();
+    field public static final int ADDRESS_TYPE_DEFAULT = -1; // 0xffffffff
+    field public static final int ADDRESS_TYPE_PUBLIC = 0; // 0x0
+    field public static final int ADDRESS_TYPE_RANDOM = 1; // 0x1
+  }
+
+  public static final class AdvertisingSetParameters.Builder {
+    method @NonNull public android.bluetooth.le.AdvertisingSetParameters.Builder setOwnAddressType(int);
+  }
+
   public final class BluetoothLeScanner {
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.UPDATE_DEVICE_STATS}) public void startScanFromSource(android.os.WorkSource, android.bluetooth.le.ScanCallback);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.UPDATE_DEVICE_STATS}) public void startScanFromSource(java.util.List<android.bluetooth.le.ScanFilter>, android.bluetooth.le.ScanSettings, android.os.WorkSource, android.bluetooth.le.ScanCallback);
diff --git a/core/java/android/bluetooth/le/AdvertiseSettings.java b/core/java/android/bluetooth/le/AdvertiseSettings.java
index 7129d76..c52a6ee 100644
--- a/core/java/android/bluetooth/le/AdvertiseSettings.java
+++ b/core/java/android/bluetooth/le/AdvertiseSettings.java
@@ -16,6 +16,9 @@
 
 package android.bluetooth.le;
 
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.bluetooth.le.AdvertisingSetParameters.AddressTypeStatus;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -70,17 +73,21 @@
      */
     private static final int LIMITED_ADVERTISING_MAX_MILLIS = 180 * 1000;
 
+
     private final int mAdvertiseMode;
     private final int mAdvertiseTxPowerLevel;
     private final int mAdvertiseTimeoutMillis;
     private final boolean mAdvertiseConnectable;
+    private final int mOwnAddressType;
 
     private AdvertiseSettings(int advertiseMode, int advertiseTxPowerLevel,
-            boolean advertiseConnectable, int advertiseTimeout) {
+            boolean advertiseConnectable, int advertiseTimeout,
+            @AddressTypeStatus int ownAddressType) {
         mAdvertiseMode = advertiseMode;
         mAdvertiseTxPowerLevel = advertiseTxPowerLevel;
         mAdvertiseConnectable = advertiseConnectable;
         mAdvertiseTimeoutMillis = advertiseTimeout;
+        mOwnAddressType = ownAddressType;
     }
 
     private AdvertiseSettings(Parcel in) {
@@ -88,6 +95,7 @@
         mAdvertiseTxPowerLevel = in.readInt();
         mAdvertiseConnectable = in.readInt() != 0;
         mAdvertiseTimeoutMillis = in.readInt();
+        mOwnAddressType = in.readInt();
     }
 
     /**
@@ -118,12 +126,23 @@
         return mAdvertiseTimeoutMillis;
     }
 
+    /**
+     * @return the own address type for advertising
+     *
+     * @hide
+     */
+    @SystemApi
+    public @AddressTypeStatus int getOwnAddressType() {
+        return mOwnAddressType;
+    }
+
     @Override
     public String toString() {
         return "Settings [mAdvertiseMode=" + mAdvertiseMode
                 + ", mAdvertiseTxPowerLevel=" + mAdvertiseTxPowerLevel
                 + ", mAdvertiseConnectable=" + mAdvertiseConnectable
-                + ", mAdvertiseTimeoutMillis=" + mAdvertiseTimeoutMillis + "]";
+                + ", mAdvertiseTimeoutMillis=" + mAdvertiseTimeoutMillis
+                + ", mOwnAddressType=" + mOwnAddressType + "]";
     }
 
     @Override
@@ -137,6 +156,7 @@
         dest.writeInt(mAdvertiseTxPowerLevel);
         dest.writeInt(mAdvertiseConnectable ? 1 : 0);
         dest.writeInt(mAdvertiseTimeoutMillis);
+        dest.writeInt(mOwnAddressType);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<AdvertiseSettings> CREATOR =
@@ -160,6 +180,7 @@
         private int mTxPowerLevel = ADVERTISE_TX_POWER_MEDIUM;
         private int mTimeoutMillis = 0;
         private boolean mConnectable = true;
+        private int mOwnAddressType = AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT;
 
         /**
          * Set advertise mode to control the advertising power and latency.
@@ -226,10 +247,31 @@
         }
 
         /**
+         * Set own address type for advertising to control public or privacy mode. If used to set
+         * address type anything other than {@link AdvertisingSetParameters#ADDRESS_TYPE_DEFAULT},
+         * then it will require BLUETOOTH_PRIVILEGED permission and will be checked at the
+         * time of starting advertising.
+         *
+         * @throws IllegalArgumentException If the {@code ownAddressType} is invalid
+         *
+         * @hide
+         */
+        @SystemApi
+        public @NonNull Builder setOwnAddressType(@AddressTypeStatus int ownAddressType) {
+            if (ownAddressType < AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT
+                    ||  ownAddressType > AdvertisingSetParameters.ADDRESS_TYPE_RANDOM) {
+                throw new IllegalArgumentException("unknown address type " + ownAddressType);
+            }
+            mOwnAddressType = ownAddressType;
+            return this;
+        }
+
+        /**
          * Build the {@link AdvertiseSettings} object.
          */
         public AdvertiseSettings build() {
-            return new AdvertiseSettings(mMode, mTxPowerLevel, mConnectable, mTimeoutMillis);
+            return new AdvertiseSettings(mMode, mTxPowerLevel, mConnectable, mTimeoutMillis,
+                mOwnAddressType);
         }
     }
 }
diff --git a/core/java/android/bluetooth/le/AdvertisingSetParameters.java b/core/java/android/bluetooth/le/AdvertisingSetParameters.java
index e39b198..5c8fae6 100644
--- a/core/java/android/bluetooth/le/AdvertisingSetParameters.java
+++ b/core/java/android/bluetooth/le/AdvertisingSetParameters.java
@@ -16,11 +16,17 @@
 
 package android.bluetooth.le;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * The {@link AdvertisingSetParameters} provide a way to adjust advertising
  * preferences for each
@@ -97,6 +103,39 @@
      */
     private static final int LIMITED_ADVERTISING_MAX_MILLIS = 180 * 1000;
 
+    /** @hide */
+    @IntDef(prefix = "ADDRESS_TYPE_", value = {
+        ADDRESS_TYPE_DEFAULT,
+        ADDRESS_TYPE_PUBLIC,
+        ADDRESS_TYPE_RANDOM
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AddressTypeStatus {}
+
+    /**
+     * Advertise own address type that corresponds privacy settings of the device.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ADDRESS_TYPE_DEFAULT = -1;
+
+    /**
+     * Advertise own public address type.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ADDRESS_TYPE_PUBLIC = 0;
+
+    /**
+     * Generate and adverise own resolvable private address.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ADDRESS_TYPE_RANDOM = 1;
+
     private final boolean mIsLegacy;
     private final boolean mIsAnonymous;
     private final boolean mIncludeTxPower;
@@ -106,11 +145,12 @@
     private final boolean mScannable;
     private final int mInterval;
     private final int mTxPowerLevel;
+    private final int mOwnAddressType;
 
     private AdvertisingSetParameters(boolean connectable, boolean scannable, boolean isLegacy,
             boolean isAnonymous, boolean includeTxPower,
             int primaryPhy, int secondaryPhy,
-            int interval, int txPowerLevel) {
+            int interval, int txPowerLevel, @AddressTypeStatus int ownAddressType) {
         mConnectable = connectable;
         mScannable = scannable;
         mIsLegacy = isLegacy;
@@ -120,6 +160,7 @@
         mSecondaryPhy = secondaryPhy;
         mInterval = interval;
         mTxPowerLevel = txPowerLevel;
+        mOwnAddressType = ownAddressType;
     }
 
     private AdvertisingSetParameters(Parcel in) {
@@ -132,6 +173,7 @@
         mSecondaryPhy = in.readInt();
         mInterval = in.readInt();
         mTxPowerLevel = in.readInt();
+        mOwnAddressType = in.readInt();
     }
 
     /**
@@ -197,6 +239,16 @@
         return mTxPowerLevel;
     }
 
+    /**
+     * @return the own address type for advertising
+     *
+     * @hide
+     */
+    @SystemApi
+    public @AddressTypeStatus int getOwnAddressType() {
+        return mOwnAddressType;
+    }
+
     @Override
     public String toString() {
         return "AdvertisingSetParameters [connectable=" + mConnectable
@@ -206,7 +258,8 @@
                 + ", primaryPhy=" + mPrimaryPhy
                 + ", secondaryPhy=" + mSecondaryPhy
                 + ", interval=" + mInterval
-                + ", txPowerLevel=" + mTxPowerLevel + "]";
+                + ", txPowerLevel=" + mTxPowerLevel
+                + ", ownAddressType=" + mOwnAddressType + "]";
     }
 
     @Override
@@ -225,6 +278,7 @@
         dest.writeInt(mSecondaryPhy);
         dest.writeInt(mInterval);
         dest.writeInt(mTxPowerLevel);
+        dest.writeInt(mOwnAddressType);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<AdvertisingSetParameters> CREATOR =
@@ -253,6 +307,7 @@
         private int mSecondaryPhy = BluetoothDevice.PHY_LE_1M;
         private int mInterval = INTERVAL_LOW;
         private int mTxPowerLevel = TX_POWER_MEDIUM;
+        private int mOwnAddressType = ADDRESS_TYPE_DEFAULT;
 
         /**
          * Set whether the advertisement type should be connectable or
@@ -399,6 +454,26 @@
         }
 
         /**
+         * Set own address type for advertising to control public or privacy mode. If used to set
+         * address type anything other than {@link AdvertisingSetParameters#ADDRESS_TYPE_DEFAULT},
+         * then it will require BLUETOOTH_PRIVILEGED permission and will be checked at the
+         * time of starting advertising.
+         *
+         * @throws IllegalArgumentException If the {@code ownAddressType} is invalid
+         *
+         * @hide
+         */
+        @SystemApi
+        public @NonNull Builder setOwnAddressType(@AddressTypeStatus int ownAddressType) {
+            if (ownAddressType < AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT
+                    ||  ownAddressType > AdvertisingSetParameters.ADDRESS_TYPE_RANDOM) {
+                throw new IllegalArgumentException("unknown address type " + ownAddressType);
+            }
+            mOwnAddressType = ownAddressType;
+            return this;
+        }
+
+        /**
          * Build the {@link AdvertisingSetParameters} object.
          *
          * @throws IllegalStateException if invalid combination of parameters is used.
@@ -431,7 +506,8 @@
             }
 
             return new AdvertisingSetParameters(mConnectable, mScannable, mIsLegacy, mIsAnonymous,
-                    mIncludeTxPower, mPrimaryPhy, mSecondaryPhy, mInterval, mTxPowerLevel);
+                    mIncludeTxPower, mPrimaryPhy, mSecondaryPhy, mInterval, mTxPowerLevel,
+                    mOwnAddressType);
         }
     }
 }
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index b9f8a57..879dcee 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -138,6 +138,7 @@
             parameters.setLegacyMode(true);
             parameters.setConnectable(isConnectable);
             parameters.setScannable(true); // legacy advertisements we support are always scannable
+            parameters.setOwnAddressType(settings.getOwnAddressType());
             if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_POWER) {
                 parameters.setInterval(1600); // 1s
             } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_BALANCED) {
diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java
index 1b5ab05..5554137 100644
--- a/core/java/android/net/Ikev2VpnProfile.java
+++ b/core/java/android/net/Ikev2VpnProfile.java
@@ -916,9 +916,23 @@
         }
 
         /**
-         *  Sets whether the local traffic is exempted from the VPN.
+         * Sets whether the local traffic is exempted from the VPN.
          *
-         *  @hide TODO(184750836): unhide once the implementation is completed
+         * When this is set, the system will not use the VPN network when an app
+         * tries to send traffic for an IP address that is on a local network.
+         *
+         * Note that there are important security implications. In particular, the
+         * networks that the device connects to typically decides what IP addresses
+         * are part of the local network. This means that for VPNs setting this
+         * flag, it is possible for anybody to set up a public network in such a
+         * way that traffic to arbitrary IP addresses will bypass the VPN, including
+         * traffic to services like DNS. When using this API, please consider the
+         * security implications for your particular case.
+         *
+         * Note that because the local traffic will always bypass the VPN,
+         * it is not possible to set this flag on a non-bypassable VPN.
+         *
+         * @hide TODO(184750836): unhide once the implementation is completed
          */
         @NonNull
         @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
index 1ac3f0a..125b573 100644
--- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
@@ -15,6 +15,9 @@
  */
 package android.net.vcn;
 
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.getMatchCriteriaString;
+
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
 import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER;
 import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER;
@@ -23,8 +26,12 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.net.NetworkCapabilities;
+import android.net.vcn.VcnUnderlyingNetworkTemplate.MatchCriteria;
 import android.os.PersistableBundle;
 import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.ArraySet;
 
@@ -37,32 +44,36 @@
 import java.util.Objects;
 import java.util.Set;
 
-// TODO: Add documents
-/** @hide */
+/**
+ * This class represents a configuration for a network template class of underlying cellular
+ * networks.
+ *
+ * <p>See {@link VcnUnderlyingNetworkTemplate}
+ */
 public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetworkTemplate {
     private static final String ALLOWED_NETWORK_PLMN_IDS_KEY = "mAllowedNetworkPlmnIds";
     @NonNull private final Set<String> mAllowedNetworkPlmnIds;
     private static final String ALLOWED_SPECIFIC_CARRIER_IDS_KEY = "mAllowedSpecificCarrierIds";
     @NonNull private final Set<Integer> mAllowedSpecificCarrierIds;
 
-    private static final String ALLOW_ROAMING_KEY = "mAllowRoaming";
-    private final boolean mAllowRoaming;
+    private static final String ROAMING_MATCH_KEY = "mRoamingMatchCriteria";
+    private final int mRoamingMatchCriteria;
 
-    private static final String REQUIRE_OPPORTUNISTIC_KEY = "mRequireOpportunistic";
-    private final boolean mRequireOpportunistic;
+    private static final String OPPORTUNISTIC_MATCH_KEY = "mOpportunisticMatchCriteria";
+    private final int mOpportunisticMatchCriteria;
 
     private VcnCellUnderlyingNetworkTemplate(
             int networkQuality,
-            boolean allowMetered,
+            int meteredMatchCriteria,
             Set<String> allowedNetworkPlmnIds,
             Set<Integer> allowedSpecificCarrierIds,
-            boolean allowRoaming,
-            boolean requireOpportunistic) {
-        super(NETWORK_PRIORITY_TYPE_CELL, networkQuality, allowMetered);
+            int roamingMatchCriteria,
+            int opportunisticMatchCriteria) {
+        super(NETWORK_PRIORITY_TYPE_CELL, networkQuality, meteredMatchCriteria);
         mAllowedNetworkPlmnIds = new ArraySet<>(allowedNetworkPlmnIds);
         mAllowedSpecificCarrierIds = new ArraySet<>(allowedSpecificCarrierIds);
-        mAllowRoaming = allowRoaming;
-        mRequireOpportunistic = requireOpportunistic;
+        mRoamingMatchCriteria = roamingMatchCriteria;
+        mOpportunisticMatchCriteria = opportunisticMatchCriteria;
 
         validate();
     }
@@ -72,15 +83,17 @@
     protected void validate() {
         super.validate();
         validatePlmnIds(mAllowedNetworkPlmnIds);
-        Objects.requireNonNull(mAllowedSpecificCarrierIds, "allowedCarrierIds is null");
+        Objects.requireNonNull(mAllowedSpecificCarrierIds, "matchingCarrierIds is null");
+        validateMatchCriteria(mRoamingMatchCriteria, "mRoamingMatchCriteria");
+        validateMatchCriteria(mOpportunisticMatchCriteria, "mOpportunisticMatchCriteria");
     }
 
-    private static void validatePlmnIds(Set<String> allowedNetworkPlmnIds) {
-        Objects.requireNonNull(allowedNetworkPlmnIds, "allowedNetworkPlmnIds is null");
+    private static void validatePlmnIds(Set<String> matchingOperatorPlmnIds) {
+        Objects.requireNonNull(matchingOperatorPlmnIds, "matchingOperatorPlmnIds is null");
 
         // A valid PLMN is a concatenation of MNC and MCC, and thus consists of 5 or 6 decimal
         // digits.
-        for (String id : allowedNetworkPlmnIds) {
+        for (String id : matchingOperatorPlmnIds) {
             if ((id.length() == 5 || id.length() == 6) && id.matches("[0-9]+")) {
                 continue;
             } else {
@@ -97,7 +110,7 @@
         Objects.requireNonNull(in, "PersistableBundle is null");
 
         final int networkQuality = in.getInt(NETWORK_QUALITY_KEY);
-        final boolean allowMetered = in.getBoolean(ALLOW_METERED_KEY);
+        final int meteredMatchCriteria = in.getInt(METERED_MATCH_KEY);
 
         final PersistableBundle plmnIdsBundle =
                 in.getPersistableBundle(ALLOWED_NETWORK_PLMN_IDS_KEY);
@@ -114,16 +127,16 @@
                         PersistableBundleUtils.toList(
                                 specificCarrierIdsBundle, INTEGER_DESERIALIZER));
 
-        final boolean allowRoaming = in.getBoolean(ALLOW_ROAMING_KEY);
-        final boolean requireOpportunistic = in.getBoolean(REQUIRE_OPPORTUNISTIC_KEY);
+        final int roamingMatchCriteria = in.getInt(ROAMING_MATCH_KEY);
+        final int opportunisticMatchCriteria = in.getInt(OPPORTUNISTIC_MATCH_KEY);
 
         return new VcnCellUnderlyingNetworkTemplate(
                 networkQuality,
-                allowMetered,
+                meteredMatchCriteria,
                 allowedNetworkPlmnIds,
                 allowedSpecificCarrierIds,
-                allowRoaming,
-                requireOpportunistic);
+                roamingMatchCriteria,
+                opportunisticMatchCriteria);
     }
 
     /** @hide */
@@ -143,35 +156,51 @@
                         new ArrayList<>(mAllowedSpecificCarrierIds), INTEGER_SERIALIZER);
         result.putPersistableBundle(ALLOWED_SPECIFIC_CARRIER_IDS_KEY, specificCarrierIdsBundle);
 
-        result.putBoolean(ALLOW_ROAMING_KEY, mAllowRoaming);
-        result.putBoolean(REQUIRE_OPPORTUNISTIC_KEY, mRequireOpportunistic);
+        result.putInt(ROAMING_MATCH_KEY, mRoamingMatchCriteria);
+        result.putInt(OPPORTUNISTIC_MATCH_KEY, mOpportunisticMatchCriteria);
 
         return result;
     }
 
-    /** Retrieve the allowed PLMN IDs, or an empty set if any PLMN ID is acceptable. */
+    /**
+     * Retrieve the matching operator PLMN IDs, or an empty set if any PLMN ID is acceptable.
+     *
+     * @see Builder#setOperatorPlmnIds(Set)
+     */
     @NonNull
-    public Set<String> getAllowedOperatorPlmnIds() {
+    public Set<String> getOperatorPlmnIds() {
         return Collections.unmodifiableSet(mAllowedNetworkPlmnIds);
     }
 
     /**
-     * Retrieve the allowed specific carrier IDs, or an empty set if any specific carrier ID is
-     * acceptable.
+     * Retrieve the matching sim specific carrier IDs, or an empty set if any sim specific carrier
+     * ID is acceptable.
+     *
+     * @see Builder#setSimSpecificCarrierIds(Set)
      */
     @NonNull
-    public Set<Integer> getAllowedSpecificCarrierIds() {
+    public Set<Integer> getSimSpecificCarrierIds() {
         return Collections.unmodifiableSet(mAllowedSpecificCarrierIds);
     }
 
-    /** Return if roaming is allowed. */
-    public boolean allowRoaming() {
-        return mAllowRoaming;
+    /**
+     * Return the matching criteria for roaming networks.
+     *
+     * @see Builder#setRoaming(int)
+     */
+    @MatchCriteria
+    public int getRoaming() {
+        return mRoamingMatchCriteria;
     }
 
-    /** Return if requiring an opportunistic network. */
-    public boolean requireOpportunistic() {
-        return mRequireOpportunistic;
+    /**
+     * Return the matching criteria for opportunistic cellular subscriptions.
+     *
+     * @see Builder#setOpportunistic(int)
+     */
+    @MatchCriteria
+    public int getOpportunistic() {
+        return mOpportunisticMatchCriteria;
     }
 
     @Override
@@ -180,8 +209,8 @@
                 super.hashCode(),
                 mAllowedNetworkPlmnIds,
                 mAllowedSpecificCarrierIds,
-                mAllowRoaming,
-                mRequireOpportunistic);
+                mRoamingMatchCriteria,
+                mOpportunisticMatchCriteria);
     }
 
     @Override
@@ -197,8 +226,8 @@
         final VcnCellUnderlyingNetworkTemplate rhs = (VcnCellUnderlyingNetworkTemplate) other;
         return Objects.equals(mAllowedNetworkPlmnIds, rhs.mAllowedNetworkPlmnIds)
                 && Objects.equals(mAllowedSpecificCarrierIds, rhs.mAllowedSpecificCarrierIds)
-                && mAllowRoaming == rhs.mAllowRoaming
-                && mRequireOpportunistic == rhs.mRequireOpportunistic;
+                && mRoamingMatchCriteria == rhs.mRoamingMatchCriteria
+                && mOpportunisticMatchCriteria == rhs.mOpportunisticMatchCriteria;
     }
 
     /** @hide */
@@ -206,77 +235,137 @@
     void dumpTransportSpecificFields(IndentingPrintWriter pw) {
         pw.println("mAllowedNetworkPlmnIds: " + mAllowedNetworkPlmnIds.toString());
         pw.println("mAllowedSpecificCarrierIds: " + mAllowedSpecificCarrierIds.toString());
-        pw.println("mAllowRoaming: " + mAllowRoaming);
-        pw.println("mRequireOpportunistic: " + mRequireOpportunistic);
+        pw.println("mRoamingMatchCriteria: " + getMatchCriteriaString(mRoamingMatchCriteria));
+        pw.println(
+                "mOpportunisticMatchCriteria: "
+                        + getMatchCriteriaString(mOpportunisticMatchCriteria));
     }
 
-    /** This class is used to incrementally build WifiNetworkPriority objects. */
-    public static final class Builder extends VcnUnderlyingNetworkTemplate.Builder<Builder> {
+    /** This class is used to incrementally build VcnCellUnderlyingNetworkTemplate objects. */
+    public static final class Builder {
+        private int mNetworkQuality = NETWORK_QUALITY_ANY;
+        private int mMeteredMatchCriteria = MATCH_ANY;
+
         @NonNull private final Set<String> mAllowedNetworkPlmnIds = new ArraySet<>();
         @NonNull private final Set<Integer> mAllowedSpecificCarrierIds = new ArraySet<>();
 
-        private boolean mAllowRoaming = false;
-        private boolean mRequireOpportunistic = false;
+        private int mRoamingMatchCriteria = MATCH_ANY;
+        private int mOpportunisticMatchCriteria = MATCH_ANY;
 
         /** Construct a Builder object. */
         public Builder() {}
 
         /**
-         * Set allowed operator PLMN IDs.
+         * Set the required network quality to match this template.
+         *
+         * <p>Network quality is a aggregation of multiple signals that reflect the network link
+         * metrics. For example, the network validation bit (see {@link
+         * NetworkCapabilities#NET_CAPABILITY_VALIDATED}), estimated first hop transport bandwidth
+         * and signal strength.
+         *
+         * @param networkQuality the required network quality. Defaults to NETWORK_QUALITY_ANY
+         * @hide
+         */
+        @NonNull
+        public Builder setNetworkQuality(@NetworkQuality int networkQuality) {
+            validateNetworkQuality(networkQuality);
+
+            mNetworkQuality = networkQuality;
+            return this;
+        }
+
+        /**
+         * Set the matching criteria for metered networks.
+         *
+         * <p>A template where setMetered(MATCH_REQUIRED) will only match metered networks (one
+         * without NET_CAPABILITY_NOT_METERED). A template where setMetered(MATCH_FORBIDDEN) will
+         * only match a network that is not metered (one with NET_CAPABILITY_NOT_METERED).
+         *
+         * @param matchCriteria the matching criteria for metered networks. Defaults to {@link
+         *     #MATCH_ANY}.
+         * @see NetworkCapabilities#NET_CAPABILITY_NOT_METERED
+         */
+        // The matching getter is defined in the super class. Please see {@link
+        // VcnUnderlyingNetworkTemplate#getMetered()}
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public Builder setMetered(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setMetered");
+
+            mMeteredMatchCriteria = matchCriteria;
+            return this;
+        }
+
+        /**
+         * Set operator PLMN IDs with which a network can match this template.
          *
          * <p>This is used to distinguish cases where roaming agreements may dictate a different
          * priority from a partner's networks.
          *
-         * @param allowedNetworkPlmnIds the allowed operator PLMN IDs in String. Defaults to an
-         *     empty set, allowing ANY PLMN ID. A valid PLMN is a concatenation of MNC and MCC, and
-         *     thus consists of 5 or 6 decimal digits. See {@link SubscriptionInfo#getMccString()}
-         *     and {@link SubscriptionInfo#getMncString()}.
+         * @param operatorPlmnIds the matching operator PLMN IDs in String. Network with one of the
+         *     matching PLMN IDs can match this template. If the set is empty, any PLMN ID will
+         *     match. The default is an empty set. A valid PLMN is a concatenation of MNC and MCC,
+         *     and thus consists of 5 or 6 decimal digits.
+         * @see SubscriptionInfo#getMccString()
+         * @see SubscriptionInfo#getMncString()
          */
         @NonNull
-        public Builder setAllowedOperatorPlmnIds(@NonNull Set<String> allowedNetworkPlmnIds) {
-            validatePlmnIds(allowedNetworkPlmnIds);
+        public Builder setOperatorPlmnIds(@NonNull Set<String> operatorPlmnIds) {
+            validatePlmnIds(operatorPlmnIds);
 
             mAllowedNetworkPlmnIds.clear();
-            mAllowedNetworkPlmnIds.addAll(allowedNetworkPlmnIds);
+            mAllowedNetworkPlmnIds.addAll(operatorPlmnIds);
             return this;
         }
 
         /**
-         * Set allowed specific carrier IDs.
+         * Set sim specific carrier IDs with which a network can match this template.
          *
-         * @param allowedSpecificCarrierIds the allowed specific carrier IDs. Defaults to an empty
-         *     set, allowing ANY carrier ID. See {@link TelephonyManager#getSimSpecificCarrierId()}.
+         * @param simSpecificCarrierIds the matching sim specific carrier IDs. Network with one of
+         *     the sim specific carrier IDs can match this template. If the set is empty, any
+         *     carrier ID will match. The default is an empty set.
+         * @see TelephonyManager#getSimSpecificCarrierId()
          */
         @NonNull
-        public Builder setAllowedSpecificCarrierIds(
-                @NonNull Set<Integer> allowedSpecificCarrierIds) {
-            Objects.requireNonNull(allowedSpecificCarrierIds, "allowedCarrierIds is null");
+        public Builder setSimSpecificCarrierIds(@NonNull Set<Integer> simSpecificCarrierIds) {
+            Objects.requireNonNull(simSpecificCarrierIds, "simSpecificCarrierIds is null");
+
             mAllowedSpecificCarrierIds.clear();
-            mAllowedSpecificCarrierIds.addAll(allowedSpecificCarrierIds);
+            mAllowedSpecificCarrierIds.addAll(simSpecificCarrierIds);
             return this;
         }
 
         /**
-         * Set if roaming is allowed.
+         * Set the matching criteria for roaming networks.
          *
-         * @param allowRoaming the flag to indicate if roaming is allowed. Defaults to {@code
-         *     false}.
+         * <p>A template where setRoaming(MATCH_REQUIRED) will only match roaming networks (one
+         * without NET_CAPABILITY_NOT_ROAMING). A template where setRoaming(MATCH_FORBIDDEN) will
+         * only match a network that is not roaming (one with NET_CAPABILITY_NOT_ROAMING).
+         *
+         * @param matchCriteria the matching criteria for roaming networks. Defaults to {@link
+         *     #MATCH_ANY}.
+         * @see NetworkCapabilities#NET_CAPABILITY_NOT_ROAMING
          */
         @NonNull
-        public Builder setAllowRoaming(boolean allowRoaming) {
-            mAllowRoaming = allowRoaming;
+        public Builder setRoaming(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setRoaming");
+
+            mRoamingMatchCriteria = matchCriteria;
             return this;
         }
 
         /**
-         * Set if requiring an opportunistic network.
+         * Set the matching criteria for opportunistic cellular subscriptions.
          *
-         * @param requireOpportunistic the flag to indicate if caller requires an opportunistic
-         *     network. Defaults to {@code false}.
+         * @param matchCriteria the matching criteria for opportunistic cellular subscriptions.
+         *     Defaults to {@link #MATCH_ANY}.
+         * @see SubscriptionManager#setOpportunistic(boolean, int)
          */
         @NonNull
-        public Builder setRequireOpportunistic(boolean requireOpportunistic) {
-            mRequireOpportunistic = requireOpportunistic;
+        public Builder setOpportunistic(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setOpportunistic");
+
+            mOpportunisticMatchCriteria = matchCriteria;
             return this;
         }
 
@@ -285,17 +374,11 @@
         public VcnCellUnderlyingNetworkTemplate build() {
             return new VcnCellUnderlyingNetworkTemplate(
                     mNetworkQuality,
-                    mAllowMetered,
+                    mMeteredMatchCriteria,
                     mAllowedNetworkPlmnIds,
                     mAllowedSpecificCarrierIds,
-                    mAllowRoaming,
-                    mRequireOpportunistic);
-        }
-
-        /** @hide */
-        @Override
-        Builder self() {
-            return this;
+                    mRoamingMatchCriteria,
+                    mOpportunisticMatchCriteria);
         }
     }
 }
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index d07c24a..92956e8 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -16,6 +16,7 @@
 package android.net.vcn;
 
 import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
@@ -42,7 +43,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 import java.util.SortedSet;
@@ -162,30 +163,24 @@
 
     /** @hide */
     @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static final LinkedHashSet<VcnUnderlyingNetworkTemplate>
-            DEFAULT_UNDERLYING_NETWORK_PRIORITIES = new LinkedHashSet<>();
+    public static final List<VcnUnderlyingNetworkTemplate> DEFAULT_UNDERLYING_NETWORK_TEMPLATES =
+            new ArrayList<>();
 
     static {
-        DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add(
+        DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add(
                 new VcnCellUnderlyingNetworkTemplate.Builder()
                         .setNetworkQuality(NETWORK_QUALITY_OK)
-                        .setAllowMetered(true /* allowMetered */)
-                        .setAllowRoaming(true /* allowRoaming */)
-                        .setRequireOpportunistic(true /* requireOpportunistic */)
+                        .setOpportunistic(MATCH_REQUIRED)
                         .build());
 
-        DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add(
+        DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add(
                 new VcnWifiUnderlyingNetworkTemplate.Builder()
                         .setNetworkQuality(NETWORK_QUALITY_OK)
-                        .setAllowMetered(true /* allowMetered */)
                         .build());
 
-        DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add(
+        DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add(
                 new VcnCellUnderlyingNetworkTemplate.Builder()
                         .setNetworkQuality(NETWORK_QUALITY_OK)
-                        .setAllowMetered(true /* allowMetered */)
-                        .setAllowRoaming(true /* allowRoaming */)
-                        .setRequireOpportunistic(false /* requireOpportunistic */)
                         .build());
     }
 
@@ -200,9 +195,9 @@
 
     /** @hide */
     @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static final String UNDERLYING_NETWORK_PRIORITIES_KEY = "mUnderlyingNetworkPriorities";
+    public static final String UNDERLYING_NETWORK_TEMPLATES_KEY = "mUnderlyingNetworkTemplates";
 
-    @NonNull private final LinkedHashSet<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkPriorities;
+    @NonNull private final List<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkTemplates;
 
     private static final String MAX_MTU_KEY = "mMaxMtu";
     private final int mMaxMtu;
@@ -215,7 +210,7 @@
             @NonNull String gatewayConnectionName,
             @NonNull IkeTunnelConnectionParams tunnelConnectionParams,
             @NonNull Set<Integer> exposedCapabilities,
-            @NonNull LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
             @NonNull long[] retryIntervalsMs,
             @IntRange(from = MIN_MTU_V6) int maxMtu) {
         mGatewayConnectionName = gatewayConnectionName;
@@ -224,9 +219,9 @@
         mRetryIntervalsMs = retryIntervalsMs;
         mMaxMtu = maxMtu;
 
-        mUnderlyingNetworkPriorities = new LinkedHashSet<>(underlyingNetworkPriorities);
-        if (mUnderlyingNetworkPriorities.isEmpty()) {
-            mUnderlyingNetworkPriorities.addAll(DEFAULT_UNDERLYING_NETWORK_PRIORITIES);
+        mUnderlyingNetworkTemplates = new ArrayList<>(underlyingNetworkTemplates);
+        if (mUnderlyingNetworkTemplates.isEmpty()) {
+            mUnderlyingNetworkTemplates.addAll(DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
         }
 
         validate();
@@ -250,22 +245,19 @@
         mExposedCapabilities = new TreeSet<>(PersistableBundleUtils.toList(
                 exposedCapsBundle, PersistableBundleUtils.INTEGER_DESERIALIZER));
 
-        final PersistableBundle networkPrioritiesBundle =
-                in.getPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY);
+        final PersistableBundle networkTemplatesBundle =
+                in.getPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY);
 
-        if (networkPrioritiesBundle == null) {
-            // UNDERLYING_NETWORK_PRIORITIES_KEY was added in Android T. Thus
+        if (networkTemplatesBundle == null) {
+            // UNDERLYING_NETWORK_TEMPLATES_KEY was added in Android T. Thus
             // VcnGatewayConnectionConfig created on old platforms will not have this data and will
             // be assigned with the default value
-            mUnderlyingNetworkPriorities =
-                    new LinkedHashSet<>(DEFAULT_UNDERLYING_NETWORK_PRIORITIES);
-
+            mUnderlyingNetworkTemplates = new ArrayList<>(DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
         } else {
-            mUnderlyingNetworkPriorities =
-                    new LinkedHashSet<>(
-                            PersistableBundleUtils.toList(
-                                    networkPrioritiesBundle,
-                                    VcnUnderlyingNetworkTemplate::fromPersistableBundle));
+            mUnderlyingNetworkTemplates =
+                    PersistableBundleUtils.toList(
+                            networkTemplatesBundle,
+                            VcnUnderlyingNetworkTemplate::fromPersistableBundle);
         }
 
         mRetryIntervalsMs = in.getLongArray(RETRY_INTERVAL_MS_KEY);
@@ -285,7 +277,7 @@
             checkValidCapability(cap);
         }
 
-        Objects.requireNonNull(mUnderlyingNetworkPriorities, "underlyingNetworkPriorities is null");
+        validateNetworkTemplateList(mUnderlyingNetworkTemplates);
         Objects.requireNonNull(mRetryIntervalsMs, "retryIntervalsMs was null");
         validateRetryInterval(mRetryIntervalsMs);
 
@@ -314,6 +306,19 @@
         }
     }
 
+    private static void validateNetworkTemplateList(
+            List<VcnUnderlyingNetworkTemplate> networkPriorityRules) {
+        Objects.requireNonNull(networkPriorityRules, "networkPriorityRules is null");
+
+        Set<VcnUnderlyingNetworkTemplate> existingRules = new ArraySet<>();
+        for (VcnUnderlyingNetworkTemplate rule : networkPriorityRules) {
+            Objects.requireNonNull(rule, "Found null value VcnUnderlyingNetworkTemplate");
+            if (!existingRules.add(rule)) {
+                throw new IllegalArgumentException("Found duplicate VcnUnderlyingNetworkTemplate");
+            }
+        }
+    }
+
     /**
      * Returns the configured Gateway Connection name.
      *
@@ -368,15 +373,13 @@
     }
 
     /**
-     * Retrieve the configured VcnUnderlyingNetworkTemplate list, or a default list if it is not
-     * configured.
+     * Retrieve the VcnUnderlyingNetworkTemplate list, or a default list if it is not configured.
      *
-     * @see Builder#setVcnUnderlyingNetworkPriorities(LinkedHashSet<VcnUnderlyingNetworkTemplate>)
-     * @hide
+     * @see Builder#setVcnUnderlyingNetworkPriorities(List)
      */
     @NonNull
-    public LinkedHashSet<VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities() {
-        return new LinkedHashSet<>(mUnderlyingNetworkPriorities);
+    public List<VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities() {
+        return new ArrayList<>(mUnderlyingNetworkTemplates);
     }
 
     /**
@@ -415,15 +418,15 @@
                 PersistableBundleUtils.fromList(
                         new ArrayList<>(mExposedCapabilities),
                         PersistableBundleUtils.INTEGER_SERIALIZER);
-        final PersistableBundle networkPrioritiesBundle =
+        final PersistableBundle networkTemplatesBundle =
                 PersistableBundleUtils.fromList(
-                        new ArrayList<>(mUnderlyingNetworkPriorities),
+                        mUnderlyingNetworkTemplates,
                         VcnUnderlyingNetworkTemplate::toPersistableBundle);
 
         result.putString(GATEWAY_CONNECTION_NAME_KEY, mGatewayConnectionName);
         result.putPersistableBundle(TUNNEL_CONNECTION_PARAMS_KEY, tunnelConnectionParamsBundle);
         result.putPersistableBundle(EXPOSED_CAPABILITIES_KEY, exposedCapsBundle);
-        result.putPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY, networkPrioritiesBundle);
+        result.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, networkTemplatesBundle);
         result.putLongArray(RETRY_INTERVAL_MS_KEY, mRetryIntervalsMs);
         result.putInt(MAX_MTU_KEY, mMaxMtu);
 
@@ -436,7 +439,7 @@
                 mGatewayConnectionName,
                 mTunnelConnectionParams,
                 mExposedCapabilities,
-                mUnderlyingNetworkPriorities,
+                mUnderlyingNetworkTemplates,
                 Arrays.hashCode(mRetryIntervalsMs),
                 mMaxMtu);
     }
@@ -451,7 +454,7 @@
         return mGatewayConnectionName.equals(rhs.mGatewayConnectionName)
                 && mTunnelConnectionParams.equals(rhs.mTunnelConnectionParams)
                 && mExposedCapabilities.equals(rhs.mExposedCapabilities)
-                && mUnderlyingNetworkPriorities.equals(rhs.mUnderlyingNetworkPriorities)
+                && mUnderlyingNetworkTemplates.equals(rhs.mUnderlyingNetworkTemplates)
                 && Arrays.equals(mRetryIntervalsMs, rhs.mRetryIntervalsMs)
                 && mMaxMtu == rhs.mMaxMtu;
     }
@@ -465,8 +468,8 @@
         @NonNull private final Set<Integer> mExposedCapabilities = new ArraySet();
 
         @NonNull
-        private final LinkedHashSet<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkPriorities =
-                new LinkedHashSet<>(DEFAULT_UNDERLYING_NETWORK_PRIORITIES);
+        private final List<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkTemplates =
+                new ArrayList<>(DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
 
         @NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS;
         private int mMaxMtu = DEFAULT_MAX_MTU;
@@ -539,27 +542,37 @@
         }
 
         /**
-         * Set the VcnUnderlyingNetworkTemplate list.
+         * Set the list of templates to match underlying networks against, in high-to-low priority
+         * order.
          *
-         * @param underlyingNetworkPriorities a list of unique VcnUnderlyingNetworkPriorities that
-         *     are ordered from most to least preferred, or an empty list to use the default
-         *     prioritization. The default network prioritization is Opportunistic cellular, Carrier
-         *     WiFi and Macro cellular
-         * @return
+         * <p>To select the VCN underlying network, the VCN connection will go through all the
+         * network candidates and return a network matching the highest priority rule.
+         *
+         * <p>If multiple networks match the same rule, the VCN will prefer an already-selected
+         * network as opposed to a new/unselected network. However, if both are new/unselected
+         * networks, a network will be chosen arbitrarily amongst the networks matching the highest
+         * priority rule.
+         *
+         * <p>If all networks fail to match the rules provided, an underlying network will still be
+         * selected (at random if necessary).
+         *
+         * @param underlyingNetworkTemplates a list of unique VcnUnderlyingNetworkTemplates that are
+         *     ordered from most to least preferred, or an empty list to use the default
+         *     prioritization. The default network prioritization order is Opportunistic cellular,
+         *     Carrier WiFi and then Macro cellular.
+         * @return this {@link Builder} instance, for chaining
          */
-        /** @hide */
         @NonNull
         public Builder setVcnUnderlyingNetworkPriorities(
-                @NonNull LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities) {
-            Objects.requireNonNull(
-                    mUnderlyingNetworkPriorities, "underlyingNetworkPriorities is null");
+                @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates) {
+            validateNetworkTemplateList(underlyingNetworkTemplates);
 
-            mUnderlyingNetworkPriorities.clear();
+            mUnderlyingNetworkTemplates.clear();
 
-            if (underlyingNetworkPriorities.isEmpty()) {
-                mUnderlyingNetworkPriorities.addAll(DEFAULT_UNDERLYING_NETWORK_PRIORITIES);
+            if (underlyingNetworkTemplates.isEmpty()) {
+                mUnderlyingNetworkTemplates.addAll(DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
             } else {
-                mUnderlyingNetworkPriorities.addAll(underlyingNetworkPriorities);
+                mUnderlyingNetworkTemplates.addAll(underlyingNetworkTemplates);
             }
 
             return this;
@@ -629,7 +642,7 @@
                     mGatewayConnectionName,
                     mTunnelConnectionParams,
                     mExposedCapabilities,
-                    mUnderlyingNetworkPriorities,
+                    mUnderlyingNetworkTemplates,
                     mRetryIntervalsMs,
                     mMaxMtu);
         }
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
index d306d5c..60fc936 100644
--- a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
@@ -15,6 +15,8 @@
  */
 package android.net.vcn;
 
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
+
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
 
 import android.annotation.IntDef;
@@ -31,17 +33,24 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
-// TODO: Add documents
-/** @hide */
+/**
+ * This class represents a template containing set of underlying network requirements for doing
+ * route selection.
+ *
+ * <p>Apps provisioning a VCN can configure the underlying network priority for each Gateway
+ * Connection by setting a list (in priority order, most to least preferred) of the appropriate
+ * subclasses in the VcnGatewayConnectionConfig. See {@link
+ * VcnGatewayConnectionConfig.Builder#setVcnUnderlyingNetworkPriorities}
+ */
 public abstract class VcnUnderlyingNetworkTemplate {
     /** @hide */
-    protected static final int NETWORK_PRIORITY_TYPE_WIFI = 1;
+    static final int NETWORK_PRIORITY_TYPE_WIFI = 1;
     /** @hide */
-    protected static final int NETWORK_PRIORITY_TYPE_CELL = 2;
+    static final int NETWORK_PRIORITY_TYPE_CELL = 2;
 
-    /** Denotes that any network quality is acceptable */
+    /** Denotes that any network quality is acceptable. @hide */
     public static final int NETWORK_QUALITY_ANY = 0;
-    /** Denotes that network quality needs to be OK */
+    /** Denotes that network quality needs to be OK. @hide */
     public static final int NETWORK_QUALITY_OK = 100000;
 
     private static final SparseArray<String> NETWORK_QUALITY_TO_STRING_MAP = new SparseArray<>();
@@ -56,34 +65,82 @@
     @IntDef({NETWORK_QUALITY_OK, NETWORK_QUALITY_ANY})
     public @interface NetworkQuality {}
 
+    /**
+     * Used to configure the matching criteria of a network characteristic. This may include network
+     * capabilities, or cellular subscription information. Denotes that networks with or without the
+     * characteristic are both acceptable to match this template.
+     */
+    public static final int MATCH_ANY = 0;
+
+    /**
+     * Used to configure the matching criteria of a network characteristic. This may include network
+     * capabilities, or cellular subscription information. Denotes that a network MUST have the
+     * capability in order to match this template.
+     */
+    public static final int MATCH_REQUIRED = 1;
+
+    /**
+     * Used to configure the matching criteria of a network characteristic. This may include network
+     * capabilities, or cellular subscription information. Denotes that a network MUST NOT have the
+     * capability in order to match this template.
+     */
+    public static final int MATCH_FORBIDDEN = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({MATCH_ANY, MATCH_REQUIRED, MATCH_FORBIDDEN})
+    public @interface MatchCriteria {}
+
+    private static final SparseArray<String> MATCH_CRITERIA_TO_STRING_MAP = new SparseArray<>();
+
+    static {
+        MATCH_CRITERIA_TO_STRING_MAP.put(MATCH_ANY, "MATCH_ANY");
+        MATCH_CRITERIA_TO_STRING_MAP.put(MATCH_REQUIRED, "MATCH_REQUIRED");
+        MATCH_CRITERIA_TO_STRING_MAP.put(MATCH_FORBIDDEN, "MATCH_FORBIDDEN");
+    }
+
     private static final String NETWORK_PRIORITY_TYPE_KEY = "mNetworkPriorityType";
     private final int mNetworkPriorityType;
 
     /** @hide */
-    protected static final String NETWORK_QUALITY_KEY = "mNetworkQuality";
+    static final String NETWORK_QUALITY_KEY = "mNetworkQuality";
+
     private final int mNetworkQuality;
 
     /** @hide */
-    protected static final String ALLOW_METERED_KEY = "mAllowMetered";
-    private final boolean mAllowMetered;
+    static final String METERED_MATCH_KEY = "mMeteredMatchCriteria";
+
+    private final int mMeteredMatchCriteria;
 
     /** @hide */
-    protected VcnUnderlyingNetworkTemplate(
-            int networkPriorityType, int networkQuality, boolean allowMetered) {
+    VcnUnderlyingNetworkTemplate(
+            int networkPriorityType, int networkQuality, int meteredMatchCriteria) {
         mNetworkPriorityType = networkPriorityType;
         mNetworkQuality = networkQuality;
-        mAllowMetered = allowMetered;
+        mMeteredMatchCriteria = meteredMatchCriteria;
     }
 
-    private static void validateNetworkQuality(int networkQuality) {
+    /** @hide */
+    static void validateNetworkQuality(int networkQuality) {
         Preconditions.checkArgument(
                 networkQuality == NETWORK_QUALITY_ANY || networkQuality == NETWORK_QUALITY_OK,
                 "Invalid networkQuality:" + networkQuality);
     }
 
     /** @hide */
+    static void validateMatchCriteria(int meteredMatchCriteria, String matchingCapability) {
+        Preconditions.checkArgument(
+                MATCH_CRITERIA_TO_STRING_MAP.contains(meteredMatchCriteria),
+                "Invalid matching criteria: "
+                        + meteredMatchCriteria
+                        + " for "
+                        + matchingCapability);
+    }
+
+    /** @hide */
     protected void validate() {
         validateNetworkQuality(mNetworkQuality);
+        validateMatchCriteria(mMeteredMatchCriteria, "mMeteredMatchCriteria");
     }
 
     /** @hide */
@@ -112,14 +169,14 @@
 
         result.putInt(NETWORK_PRIORITY_TYPE_KEY, mNetworkPriorityType);
         result.putInt(NETWORK_QUALITY_KEY, mNetworkQuality);
-        result.putBoolean(ALLOW_METERED_KEY, mAllowMetered);
+        result.putInt(METERED_MATCH_KEY, mMeteredMatchCriteria);
 
         return result;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mNetworkPriorityType, mNetworkQuality, mAllowMetered);
+        return Objects.hash(mNetworkPriorityType, mNetworkQuality, mMeteredMatchCriteria);
     }
 
     @Override
@@ -131,7 +188,17 @@
         final VcnUnderlyingNetworkTemplate rhs = (VcnUnderlyingNetworkTemplate) other;
         return mNetworkPriorityType == rhs.mNetworkPriorityType
                 && mNetworkQuality == rhs.mNetworkQuality
-                && mAllowMetered == rhs.mAllowMetered;
+                && mMeteredMatchCriteria == rhs.mMeteredMatchCriteria;
+    }
+
+    /** @hide */
+    static String getNameString(SparseArray<String> toStringMap, int key) {
+        return toStringMap.get(key, "Invalid value " + key);
+    }
+
+    /** @hide */
+    static String getMatchCriteriaString(int meteredMatchCriteria) {
+        return getNameString(MATCH_CRITERIA_TO_STRING_MAP, meteredMatchCriteria);
     }
 
     /** @hide */
@@ -148,65 +215,32 @@
 
         pw.println(
                 "mNetworkQuality: "
-                        + NETWORK_QUALITY_TO_STRING_MAP.get(
-                                mNetworkQuality, "Invalid value " + mNetworkQuality));
-        pw.println("mAllowMetered: " + mAllowMetered);
+                        + getNameString(NETWORK_QUALITY_TO_STRING_MAP, mNetworkQuality));
+        pw.println("mMeteredMatchCriteria: " + getMatchCriteriaString(mMeteredMatchCriteria));
         dumpTransportSpecificFields(pw);
 
         pw.decreaseIndent();
     }
 
-    /** Retrieve the required network quality. */
+    /**
+     * Retrieve the required network quality to match this template.
+     *
+     * @see Builder#setNetworkQuality(int)
+     * @hide
+     */
     @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 VcnUnderlyingNetworkTemplate objects.
+     * Return the matching criteria for metered networks.
      *
-     * @param <T> The subclass to be built.
+     * @see VcnWifiUnderlyingNetworkTemplate.Builder#setMetered(int)
+     * @see VcnCellUnderlyingNetworkTemplate.Builder#setMetered(int)
      */
-    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();
+    @MatchCriteria
+    public int getMetered() {
+        return mMeteredMatchCriteria;
     }
 }
diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
index 6bbb2bf..272ca9d 100644
--- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
@@ -16,31 +16,59 @@
 package android.net.vcn;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
+import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
+import static com.android.server.vcn.util.PersistableBundleUtils.STRING_SERIALIZER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.net.NetworkCapabilities;
 import android.os.PersistableBundle;
+import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.vcn.util.PersistableBundleUtils;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Objects;
+import java.util.Set;
 
-// TODO: Add documents
-/** @hide */
+/**
+ * This class represents a configuration for a network template class of underlying Carrier WiFi
+ * networks.
+ *
+ * <p>See {@link VcnUnderlyingNetworkTemplate}
+ */
 public final class VcnWifiUnderlyingNetworkTemplate extends VcnUnderlyingNetworkTemplate {
-    private static final String SSID_KEY = "mSsid";
-    @Nullable private final String mSsid;
+    private static final String SSIDS_KEY = "mSsids";
+    @Nullable private final Set<String> mSsids;
 
     private VcnWifiUnderlyingNetworkTemplate(
-            int networkQuality, boolean allowMetered, String ssid) {
-        super(NETWORK_PRIORITY_TYPE_WIFI, networkQuality, allowMetered);
-        mSsid = ssid;
+            int networkQuality, int meteredMatchCriteria, Set<String> ssids) {
+        super(NETWORK_PRIORITY_TYPE_WIFI, networkQuality, meteredMatchCriteria);
+        mSsids = new ArraySet<>(ssids);
 
         validate();
     }
 
     /** @hide */
+    @Override
+    protected void validate() {
+        super.validate();
+        validateSsids(mSsids);
+    }
+
+    private static void validateSsids(Set<String> ssids) {
+        Objects.requireNonNull(ssids, "ssids is null");
+
+        for (String ssid : ssids) {
+            Objects.requireNonNull(ssid, "found null value ssid");
+        }
+    }
+
+    /** @hide */
     @NonNull
     @VisibleForTesting(visibility = Visibility.PROTECTED)
     public static VcnWifiUnderlyingNetworkTemplate fromPersistableBundle(
@@ -48,9 +76,14 @@
         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 VcnWifiUnderlyingNetworkTemplate(networkQuality, allowMetered, ssid);
+        final int meteredMatchCriteria = in.getInt(METERED_MATCH_KEY);
+
+        final PersistableBundle ssidsBundle = in.getPersistableBundle(SSIDS_KEY);
+        Objects.requireNonNull(ssidsBundle, "ssidsBundle is null");
+        final Set<String> ssids =
+                new ArraySet<String>(
+                        PersistableBundleUtils.toList(ssidsBundle, STRING_DESERIALIZER));
+        return new VcnWifiUnderlyingNetworkTemplate(networkQuality, meteredMatchCriteria, ssids);
     }
 
     /** @hide */
@@ -59,13 +92,17 @@
     @VisibleForTesting(visibility = Visibility.PROTECTED)
     public PersistableBundle toPersistableBundle() {
         final PersistableBundle result = super.toPersistableBundle();
-        result.putString(SSID_KEY, mSsid);
+
+        final PersistableBundle ssidsBundle =
+                PersistableBundleUtils.fromList(new ArrayList<>(mSsids), STRING_SERIALIZER);
+        result.putPersistableBundle(SSIDS_KEY, ssidsBundle);
+
         return result;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(super.hashCode(), mSsid);
+        return Objects.hash(super.hashCode(), mSsids);
     }
 
     @Override
@@ -79,49 +116,95 @@
         }
 
         final VcnWifiUnderlyingNetworkTemplate rhs = (VcnWifiUnderlyingNetworkTemplate) other;
-        return mSsid.equals(rhs.mSsid);
+        return mSsids.equals(rhs.mSsids);
     }
 
     /** @hide */
     @Override
     void dumpTransportSpecificFields(IndentingPrintWriter pw) {
-        pw.println("mSsid: " + mSsid);
+        pw.println("mSsids: " + mSsids);
     }
 
-    /** Retrieve the required SSID, or {@code null} if there is no requirement on SSID. */
-    @Nullable
-    public String getSsid() {
-        return mSsid;
+    /**
+     * Retrieve the matching SSIDs, or an empty set if any SSID is acceptable.
+     *
+     * @see Builder#setSsids(Set)
+     */
+    @NonNull
+    public Set<String> getSsids() {
+        return Collections.unmodifiableSet(mSsids);
     }
 
     /** This class is used to incrementally build VcnWifiUnderlyingNetworkTemplate objects. */
-    public static class Builder extends VcnUnderlyingNetworkTemplate.Builder<Builder> {
-        @Nullable private String mSsid;
+    public static final class Builder {
+        private int mNetworkQuality = NETWORK_QUALITY_ANY;
+        private int mMeteredMatchCriteria = MATCH_ANY;
+        @NonNull private final Set<String> mSsids = new ArraySet<>();
 
         /** Construct a Builder object. */
         public Builder() {}
 
         /**
-         * Set the required SSID.
+         * Set the required network quality to match this template.
          *
-         * @param ssid the required SSID, or {@code null} if any SSID is acceptable.
+         * <p>Network quality is a aggregation of multiple signals that reflect the network link
+         * metrics. For example, the network validation bit (see {@link
+         * NetworkCapabilities#NET_CAPABILITY_VALIDATED}), estimated first hop transport bandwidth
+         * and signal strength.
+         *
+         * @param networkQuality the required network quality. Defaults to NETWORK_QUALITY_ANY
+         * @hide
          */
         @NonNull
-        public Builder setSsid(@Nullable String ssid) {
-            mSsid = ssid;
+        public Builder setNetworkQuality(@NetworkQuality int networkQuality) {
+            validateNetworkQuality(networkQuality);
+
+            mNetworkQuality = networkQuality;
+            return this;
+        }
+
+        /**
+         * Set the matching criteria for metered networks.
+         *
+         * <p>A template where setMetered(MATCH_REQUIRED) will only match metered networks (one
+         * without NET_CAPABILITY_NOT_METERED). A template where setMetered(MATCH_FORBIDDEN) will
+         * only match a network that is not metered (one with NET_CAPABILITY_NOT_METERED).
+         *
+         * @param matchCriteria the matching criteria for metered networks. Defaults to {@link
+         *     #MATCH_ANY}.
+         * @see NetworkCapabilities#NET_CAPABILITY_NOT_METERED
+         */
+        // The matching getter is defined in the super class. Please see {@link
+        // VcnUnderlyingNetworkTemplate#getMetered()}
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public Builder setMetered(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setMetered");
+
+            mMeteredMatchCriteria = matchCriteria;
+            return this;
+        }
+
+        /**
+         * Set the SSIDs with which a network can match this priority rule.
+         *
+         * @param ssids the matching SSIDs. Network with one of the matching SSIDs can match this
+         *     priority rule. If the set is empty, any SSID will match. The default is an empty set.
+         */
+        @NonNull
+        public Builder setSsids(@NonNull Set<String> ssids) {
+            validateSsids(ssids);
+
+            mSsids.clear();
+            mSsids.addAll(ssids);
             return this;
         }
 
         /** Build the VcnWifiUnderlyingNetworkTemplate. */
         @NonNull
         public VcnWifiUnderlyingNetworkTemplate build() {
-            return new VcnWifiUnderlyingNetworkTemplate(mNetworkQuality, mAllowMetered, mSsid);
-        }
-
-        /** @hide */
-        @Override
-        Builder self() {
-            return this;
+            return new VcnWifiUnderlyingNetworkTemplate(
+                    mNetworkQuality, mMeteredMatchCriteria, mSsids);
         }
     }
 }
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
index bd8d13b..6db25b7 100644
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
@@ -20,6 +20,8 @@
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
 
@@ -44,7 +46,7 @@
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.VcnContext;
 
-import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Set;
 
 /** @hide */
@@ -76,7 +78,7 @@
     public static int calculatePriorityClass(
             VcnContext vcnContext,
             UnderlyingNetworkRecord networkRecord,
-            LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities,
+            List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
             ParcelUuid subscriptionGroup,
             TelephonySubscriptionSnapshot snapshot,
             UnderlyingNetworkRecord currentlySelected,
@@ -94,7 +96,7 @@
         }
 
         int priorityIndex = 0;
-        for (VcnUnderlyingNetworkTemplate nwPriority : underlyingNetworkPriorities) {
+        for (VcnUnderlyingNetworkTemplate nwPriority : underlyingNetworkTemplates) {
             if (checkMatchesPriorityRule(
                     vcnContext,
                     nwPriority,
@@ -122,7 +124,11 @@
         // TODO: Check Network Quality reported by metric monitors/probers.
 
         final NetworkCapabilities caps = networkRecord.networkCapabilities;
-        if (!networkPriority.allowMetered() && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)) {
+
+        final int meteredMatch = networkPriority.getMetered();
+        final boolean isMetered = !caps.hasCapability(NET_CAPABILITY_NOT_METERED);
+        if (meteredMatch == MATCH_REQUIRED && !isMetered
+                || meteredMatch == MATCH_FORBIDDEN && isMetered) {
             return false;
         }
 
@@ -171,7 +177,8 @@
             return false;
         }
 
-        if (networkPriority.getSsid() != null && networkPriority.getSsid() != caps.getSsid()) {
+        if (!networkPriority.getSsids().isEmpty()
+                && !networkPriority.getSsids().contains(caps.getSsid())) {
             return false;
         }
 
@@ -226,26 +233,31 @@
                         .getSystemService(TelephonyManager.class)
                         .createForSubscriptionId(subId);
 
-        if (!networkPriority.getAllowedOperatorPlmnIds().isEmpty()) {
+        if (!networkPriority.getOperatorPlmnIds().isEmpty()) {
             final String plmnId = subIdSpecificTelephonyMgr.getNetworkOperator();
-            if (!networkPriority.getAllowedOperatorPlmnIds().contains(plmnId)) {
+            if (!networkPriority.getOperatorPlmnIds().contains(plmnId)) {
                 return false;
             }
         }
 
-        if (!networkPriority.getAllowedSpecificCarrierIds().isEmpty()) {
+        if (!networkPriority.getSimSpecificCarrierIds().isEmpty()) {
             final int carrierId = subIdSpecificTelephonyMgr.getSimSpecificCarrierId();
-            if (!networkPriority.getAllowedSpecificCarrierIds().contains(carrierId)) {
+            if (!networkPriority.getSimSpecificCarrierIds().contains(carrierId)) {
                 return false;
             }
         }
 
-        if (!networkPriority.allowRoaming() && !caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)) {
+        final int roamingMatch = networkPriority.getRoaming();
+        final boolean isRoaming = !caps.hasCapability(NET_CAPABILITY_NOT_ROAMING);
+        if (roamingMatch == MATCH_REQUIRED && !isRoaming
+                || roamingMatch == MATCH_FORBIDDEN && isRoaming) {
             return false;
         }
 
-        if (networkPriority.requireOpportunistic()) {
-            if (!isOpportunistic(snapshot, caps.getSubscriptionIds())) {
+        final int opportunisticMatch = networkPriority.getOpportunistic();
+        final boolean isOpportunistic = isOpportunistic(snapshot, caps.getSubscriptionIds());
+        if (opportunisticMatch == MATCH_REQUIRED) {
+            if (!isOpportunistic) {
                 return false;
             }
 
@@ -265,6 +277,8 @@
                             .contains(SubscriptionManager.getActiveDataSubscriptionId())) {
                 return false;
             }
+        } else if (opportunisticMatch == MATCH_FORBIDDEN && !isOpportunistic) {
+            return false;
         }
 
         return true;
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
index df2f0d5..c0488b1 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
@@ -32,7 +32,7 @@
 import com.android.server.vcn.VcnContext;
 
 import java.util.Comparator;
-import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -77,7 +77,7 @@
 
     static Comparator<UnderlyingNetworkRecord> getComparator(
             VcnContext vcnContext,
-            LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities,
+            List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
             ParcelUuid subscriptionGroup,
             TelephonySubscriptionSnapshot snapshot,
             UnderlyingNetworkRecord currentlySelected,
@@ -87,7 +87,7 @@
                     NetworkPriorityClassifier.calculatePriorityClass(
                             vcnContext,
                             left,
-                            underlyingNetworkPriorities,
+                            underlyingNetworkTemplates,
                             subscriptionGroup,
                             snapshot,
                             currentlySelected,
@@ -96,7 +96,7 @@
                     NetworkPriorityClassifier.calculatePriorityClass(
                             vcnContext,
                             right,
-                            underlyingNetworkPriorities,
+                            underlyingNetworkTemplates,
                             subscriptionGroup,
                             snapshot,
                             currentlySelected,
@@ -133,7 +133,7 @@
     void dump(
             VcnContext vcnContext,
             IndentingPrintWriter pw,
-            LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities,
+            List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
             ParcelUuid subscriptionGroup,
             TelephonySubscriptionSnapshot snapshot,
             UnderlyingNetworkRecord currentlySelected,
@@ -145,7 +145,7 @@
                 NetworkPriorityClassifier.calculatePriorityClass(
                         vcnContext,
                         this,
-                        underlyingNetworkPriorities,
+                        underlyingNetworkTemplates,
                         subscriptionGroup,
                         snapshot,
                         currentlySelected,
diff --git a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java
index d03aee2..4a724b7 100644
--- a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java
@@ -15,12 +15,13 @@
  */
 package android.net.vcn;
 
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 
 import org.junit.Test;
 
@@ -32,26 +33,26 @@
     private static final Set<Integer> ALLOWED_CARRIER_IDS = new HashSet<>();
 
     // Package private for use in VcnGatewayConnectionConfigTest
-    static VcnCellUnderlyingNetworkTemplate getTestNetworkPriority() {
+    static VcnCellUnderlyingNetworkTemplate getTestNetworkTemplate() {
         return new VcnCellUnderlyingNetworkTemplate.Builder()
                 .setNetworkQuality(NETWORK_QUALITY_OK)
-                .setAllowMetered(true /* allowMetered */)
-                .setAllowedOperatorPlmnIds(ALLOWED_PLMN_IDS)
-                .setAllowedSpecificCarrierIds(ALLOWED_CARRIER_IDS)
-                .setAllowRoaming(true /* allowRoaming */)
-                .setRequireOpportunistic(true /* requireOpportunistic */)
+                .setMetered(MATCH_FORBIDDEN)
+                .setOperatorPlmnIds(ALLOWED_PLMN_IDS)
+                .setSimSpecificCarrierIds(ALLOWED_CARRIER_IDS)
+                .setRoaming(MATCH_FORBIDDEN)
+                .setOpportunistic(MATCH_REQUIRED)
                 .build();
     }
 
     @Test
     public void testBuilderAndGetters() {
-        final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority();
+        final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate();
         assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality());
-        assertTrue(networkPriority.allowMetered());
-        assertEquals(ALLOWED_PLMN_IDS, networkPriority.getAllowedOperatorPlmnIds());
-        assertEquals(ALLOWED_CARRIER_IDS, networkPriority.getAllowedSpecificCarrierIds());
-        assertTrue(networkPriority.allowRoaming());
-        assertTrue(networkPriority.requireOpportunistic());
+        assertEquals(MATCH_FORBIDDEN, networkPriority.getMetered());
+        assertEquals(ALLOWED_PLMN_IDS, networkPriority.getOperatorPlmnIds());
+        assertEquals(ALLOWED_CARRIER_IDS, networkPriority.getSimSpecificCarrierIds());
+        assertEquals(MATCH_FORBIDDEN, networkPriority.getRoaming());
+        assertEquals(MATCH_REQUIRED, networkPriority.getOpportunistic());
     }
 
     @Test
@@ -59,16 +60,16 @@
         final VcnCellUnderlyingNetworkTemplate networkPriority =
                 new VcnCellUnderlyingNetworkTemplate.Builder().build();
         assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality());
-        assertFalse(networkPriority.allowMetered());
-        assertEquals(new HashSet<String>(), networkPriority.getAllowedOperatorPlmnIds());
-        assertEquals(new HashSet<Integer>(), networkPriority.getAllowedSpecificCarrierIds());
-        assertFalse(networkPriority.allowRoaming());
-        assertFalse(networkPriority.requireOpportunistic());
+        assertEquals(MATCH_ANY, networkPriority.getMetered());
+        assertEquals(new HashSet<String>(), networkPriority.getOperatorPlmnIds());
+        assertEquals(new HashSet<Integer>(), networkPriority.getSimSpecificCarrierIds());
+        assertEquals(MATCH_ANY, networkPriority.getRoaming());
+        assertEquals(MATCH_ANY, networkPriority.getOpportunistic());
     }
 
     @Test
     public void testPersistableBundle() {
-        final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority();
+        final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate();
         assertEquals(
                 networkPriority,
                 VcnUnderlyingNetworkTemplate.fromPersistableBundle(
diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
index 1f2905d..2aef9ae 100644
--- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
@@ -17,8 +17,8 @@
 package android.net.vcn;
 
 import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
-import static android.net.vcn.VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_PRIORITIES;
-import static android.net.vcn.VcnGatewayConnectionConfig.UNDERLYING_NETWORK_PRIORITIES_KEY;
+import static android.net.vcn.VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES;
+import static android.net.vcn.VcnGatewayConnectionConfig.UNDERLYING_NETWORK_TEMPLATES_KEY;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -40,8 +40,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
@@ -54,17 +55,17 @@
             };
     public static final int[] UNDERLYING_CAPS = new int[] {NetworkCapabilities.NET_CAPABILITY_DUN};
 
-    private static final LinkedHashSet<VcnUnderlyingNetworkTemplate> UNDERLYING_NETWORK_PRIORITIES =
-            new LinkedHashSet();
+    private static final List<VcnUnderlyingNetworkTemplate> UNDERLYING_NETWORK_TEMPLATES =
+            new ArrayList();
 
     static {
         Arrays.sort(EXPOSED_CAPS);
         Arrays.sort(UNDERLYING_CAPS);
 
-        UNDERLYING_NETWORK_PRIORITIES.add(
-                VcnCellUnderlyingNetworkTemplateTest.getTestNetworkPriority());
-        UNDERLYING_NETWORK_PRIORITIES.add(
-                VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkPriority());
+        UNDERLYING_NETWORK_TEMPLATES.add(
+                VcnCellUnderlyingNetworkTemplateTest.getTestNetworkTemplate());
+        UNDERLYING_NETWORK_TEMPLATES.add(
+                VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkTemplate());
     }
 
     public static final long[] RETRY_INTERVALS_MS =
@@ -95,7 +96,7 @@
     // Public for use in VcnGatewayConnectionTest
     public static VcnGatewayConnectionConfig buildTestConfig() {
         final VcnGatewayConnectionConfig.Builder builder =
-                newBuilder().setVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_PRIORITIES);
+                newBuilder().setVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_TEMPLATES);
 
         return buildTestConfigWithExposedCaps(builder, EXPOSED_CAPS);
     }
@@ -174,10 +175,10 @@
     }
 
     @Test
-    public void testBuilderRequiresNonNullNetworkPriorities() {
+    public void testBuilderRequiresNonNullNetworkTemplates() {
         try {
             newBuilder().setVcnUnderlyingNetworkPriorities(null);
-            fail("Expected exception due to invalid underlyingNetworkPriorities");
+            fail("Expected exception due to invalid underlyingNetworkTemplates");
         } catch (NullPointerException e) {
         }
     }
@@ -219,7 +220,7 @@
         Arrays.sort(exposedCaps);
         assertArrayEquals(EXPOSED_CAPS, exposedCaps);
 
-        assertEquals(UNDERLYING_NETWORK_PRIORITIES, config.getVcnUnderlyingNetworkPriorities());
+        assertEquals(UNDERLYING_NETWORK_TEMPLATES, config.getVcnUnderlyingNetworkPriorities());
         assertEquals(TUNNEL_CONNECTION_PARAMS, config.getTunnelConnectionParams());
 
         assertArrayEquals(RETRY_INTERVALS_MS, config.getRetryIntervalsMillis());
@@ -234,13 +235,13 @@
     }
 
     @Test
-    public void testParsePersistableBundleWithoutVcnUnderlyingNetworkPriorities() {
+    public void testParsePersistableBundleWithoutVcnUnderlyingNetworkTemplates() {
         PersistableBundle configBundle = buildTestConfig().toPersistableBundle();
-        configBundle.putPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY, null);
+        configBundle.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, null);
 
         final VcnGatewayConnectionConfig config = new VcnGatewayConnectionConfig(configBundle);
         assertEquals(
-                DEFAULT_UNDERLYING_NETWORK_PRIORITIES, config.getVcnUnderlyingNetworkPriorities());
+                DEFAULT_UNDERLYING_NETWORK_TEMPLATES, config.getVcnUnderlyingNetworkPriorities());
     }
 
     private static IkeTunnelConnectionParams buildTunnelConnectionParams(String ikePsk) {
@@ -285,39 +286,36 @@
         assertNotEquals(config, anotherConfig);
     }
 
-    private static VcnGatewayConnectionConfig buildTestConfigWithVcnUnderlyingNetworkPriorities(
-            LinkedHashSet<VcnUnderlyingNetworkTemplate> networkPriorities) {
+    private static VcnGatewayConnectionConfig buildTestConfigWithVcnUnderlyingNetworkTemplates(
+            List<VcnUnderlyingNetworkTemplate> networkTemplates) {
         return buildTestConfigWithExposedCaps(
                 new VcnGatewayConnectionConfig.Builder(
-                                "buildTestConfigWithVcnUnderlyingNetworkPriorities",
+                                "buildTestConfigWithVcnUnderlyingNetworkTemplates",
                                 TUNNEL_CONNECTION_PARAMS)
-                        .setVcnUnderlyingNetworkPriorities(networkPriorities),
+                        .setVcnUnderlyingNetworkPriorities(networkTemplates),
                 EXPOSED_CAPS);
     }
 
     @Test
-    public void testVcnUnderlyingNetworkPrioritiesEquality() throws Exception {
+    public void testVcnUnderlyingNetworkTemplatesEquality() throws Exception {
         final VcnGatewayConnectionConfig config =
-                buildTestConfigWithVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_PRIORITIES);
+                buildTestConfigWithVcnUnderlyingNetworkTemplates(UNDERLYING_NETWORK_TEMPLATES);
 
-        final LinkedHashSet<VcnUnderlyingNetworkTemplate> networkPrioritiesEqual =
-                new LinkedHashSet();
-        networkPrioritiesEqual.add(VcnCellUnderlyingNetworkTemplateTest.getTestNetworkPriority());
-        networkPrioritiesEqual.add(VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkPriority());
+        final List<VcnUnderlyingNetworkTemplate> networkTemplatesEqual = new ArrayList();
+        networkTemplatesEqual.add(VcnCellUnderlyingNetworkTemplateTest.getTestNetworkTemplate());
+        networkTemplatesEqual.add(VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkTemplate());
         final VcnGatewayConnectionConfig configEqual =
-                buildTestConfigWithVcnUnderlyingNetworkPriorities(networkPrioritiesEqual);
+                buildTestConfigWithVcnUnderlyingNetworkTemplates(networkTemplatesEqual);
 
-        final LinkedHashSet<VcnUnderlyingNetworkTemplate> networkPrioritiesNotEqual =
-                new LinkedHashSet();
-        networkPrioritiesNotEqual.add(
-                VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkPriority());
+        final List<VcnUnderlyingNetworkTemplate> networkTemplatesNotEqual = new ArrayList();
+        networkTemplatesNotEqual.add(VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkTemplate());
         final VcnGatewayConnectionConfig configNotEqual =
-                buildTestConfigWithVcnUnderlyingNetworkPriorities(networkPrioritiesNotEqual);
+                buildTestConfigWithVcnUnderlyingNetworkTemplates(networkTemplatesNotEqual);
 
-        assertEquals(UNDERLYING_NETWORK_PRIORITIES, networkPrioritiesEqual);
+        assertEquals(UNDERLYING_NETWORK_TEMPLATES, networkTemplatesEqual);
         assertEquals(config, configEqual);
 
-        assertNotEquals(UNDERLYING_NETWORK_PRIORITIES, networkPrioritiesNotEqual);
+        assertNotEquals(UNDERLYING_NETWORK_TEMPLATES, networkTemplatesNotEqual);
         assertNotEquals(config, configNotEqual);
     }
 }
diff --git a/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java
index 652057f..cb5b47b 100644
--- a/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java
@@ -15,36 +15,38 @@
  */
 package android.net.vcn;
 
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.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;
 
+import java.util.Set;
+
 public class VcnWifiUnderlyingNetworkTemplateTest {
     private static final String SSID = "TestWifi";
     private static final int INVALID_NETWORK_QUALITY = -1;
 
     // Package private for use in VcnGatewayConnectionConfigTest
-    static VcnWifiUnderlyingNetworkTemplate getTestNetworkPriority() {
+    static VcnWifiUnderlyingNetworkTemplate getTestNetworkTemplate() {
         return new VcnWifiUnderlyingNetworkTemplate.Builder()
                 .setNetworkQuality(NETWORK_QUALITY_OK)
-                .setAllowMetered(true /* allowMetered */)
-                .setSsid(SSID)
+                .setMetered(MATCH_FORBIDDEN)
+                .setSsids(Set.of(SSID))
                 .build();
     }
 
     @Test
     public void testBuilderAndGetters() {
-        final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority();
+        final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate();
         assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality());
-        assertTrue(networkPriority.allowMetered());
-        assertEquals(SSID, networkPriority.getSsid());
+        assertEquals(MATCH_FORBIDDEN, networkPriority.getMetered());
+        assertEquals(Set.of(SSID), networkPriority.getSsids());
     }
 
     @Test
@@ -52,8 +54,8 @@
         final VcnWifiUnderlyingNetworkTemplate networkPriority =
                 new VcnWifiUnderlyingNetworkTemplate.Builder().build();
         assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality());
-        assertFalse(networkPriority.allowMetered());
-        assertNull(SSID, networkPriority.getSsid());
+        assertEquals(MATCH_ANY, networkPriority.getMetered());
+        assertTrue(networkPriority.getSsids().isEmpty());
     }
 
     @Test
@@ -68,7 +70,7 @@
 
     @Test
     public void testPersistableBundle() {
-        final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority();
+        final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate();
         assertEquals(
                 networkPriority,
                 VcnUnderlyingNetworkTemplate.fromPersistableBundle(
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
index f23d5bf..4bb7de8 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.vcn.routeselection;
 
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
 
 import static com.android.server.vcn.VcnTestUtils.setupSystemService;
@@ -145,7 +147,7 @@
         final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
                 new VcnWifiUnderlyingNetworkTemplate.Builder()
                         .setNetworkQuality(NETWORK_QUALITY_OK)
-                        .setAllowMetered(false /* allowMetered */)
+                        .setMetered(MATCH_FORBIDDEN)
                         .build();
 
         assertFalse(
@@ -164,7 +166,6 @@
         final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
                 new VcnWifiUnderlyingNetworkTemplate.Builder()
                         .setNetworkQuality(NETWORK_QUALITY_OK)
-                        .setAllowMetered(true /* allowMetered */)
                         .build();
         final UnderlyingNetworkRecord selectedNetworkRecord =
                 isSelectedNetwork ? mWifiNetworkRecord : null;
@@ -214,8 +215,7 @@
         final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
                 new VcnWifiUnderlyingNetworkTemplate.Builder()
                         .setNetworkQuality(NETWORK_QUALITY_OK)
-                        .setAllowMetered(true /* allowMetered */)
-                        .setSsid(nwPrioritySsid)
+                        .setSsids(Set.of(nwPrioritySsid))
                         .build();
 
         assertEquals(
@@ -238,10 +238,7 @@
     }
 
     private static VcnCellUnderlyingNetworkTemplate.Builder getCellNetworkPriorityBuilder() {
-        return new VcnCellUnderlyingNetworkTemplate.Builder()
-                .setNetworkQuality(NETWORK_QUALITY_OK)
-                .setAllowMetered(true /* allowMetered */)
-                .setAllowRoaming(true /* allowRoaming */);
+        return new VcnCellUnderlyingNetworkTemplate.Builder().setNetworkQuality(NETWORK_QUALITY_OK);
     }
 
     @Test
@@ -258,9 +255,7 @@
     @Test
     public void testMatchOpportunisticCell() {
         final VcnCellUnderlyingNetworkTemplate opportunisticCellNetworkPriority =
-                getCellNetworkPriorityBuilder()
-                        .setRequireOpportunistic(true /* requireOpportunistic */)
-                        .build();
+                getCellNetworkPriorityBuilder().setOpportunistic(MATCH_REQUIRED).build();
 
         when(mSubscriptionSnapshot.isOpportunistic(SUB_ID)).thenReturn(true);
         when(mSubscriptionSnapshot.getAllSubIdsInGroup(SUB_GROUP)).thenReturn(new ArraySet<>());
@@ -279,7 +274,7 @@
         final String networkPriorityPlmnId = useMatchedPlmnId ? PLMN_ID : PLMN_ID_OTHER;
         final VcnCellUnderlyingNetworkTemplate networkPriority =
                 getCellNetworkPriorityBuilder()
-                        .setAllowedOperatorPlmnIds(Set.of(networkPriorityPlmnId))
+                        .setOperatorPlmnIds(Set.of(networkPriorityPlmnId))
                         .build();
 
         assertEquals(
@@ -308,7 +303,7 @@
         final int networkPriorityCarrierId = useMatchedCarrierId ? CARRIER_ID : CARRIER_ID_OTHER;
         final VcnCellUnderlyingNetworkTemplate networkPriority =
                 getCellNetworkPriorityBuilder()
-                        .setAllowedSpecificCarrierIds(Set.of(networkPriorityCarrierId))
+                        .setSimSpecificCarrierIds(Set.of(networkPriorityCarrierId))
                         .build();
 
         assertEquals(
@@ -336,7 +331,7 @@
     @Test
     public void testMatchWifiFailWithoutNotRoamingBit() {
         final VcnCellUnderlyingNetworkTemplate networkPriority =
-                getCellNetworkPriorityBuilder().setAllowRoaming(false /* allowRoaming */).build();
+                getCellNetworkPriorityBuilder().setRoaming(MATCH_FORBIDDEN).build();
 
         assertFalse(
                 checkMatchesCellPriorityRule(
@@ -353,7 +348,7 @@
                 calculatePriorityClass(
                         mVcnContext,
                         networkRecord,
-                        VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_PRIORITIES,
+                        VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
                         SUB_GROUP,
                         mSubscriptionSnapshot,
                         null /* currentlySelected */,