Merge "Remove 1.3s "Waiting for Debugger" delay"
diff --git a/core/api/current.txt b/core/api/current.txt
index 2ba2f5b..c9b5af5 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -16888,6 +16888,7 @@
     method public int describeContents();
     method public int getFormat();
     method public int getHeight();
+    method public long getId();
     method public int getLayers();
     method public long getUsage();
     method public int getWidth();
@@ -21303,7 +21304,7 @@
     field public static final int ERROR_RECLAIMED = 1101; // 0x44d
   }
 
-  public static final class MediaCodec.CryptoException extends java.lang.RuntimeException {
+  public static final class MediaCodec.CryptoException extends java.lang.RuntimeException implements android.media.MediaDrmThrowable {
     ctor public MediaCodec.CryptoException(int, @Nullable String);
     method public int getErrorCode();
     field @Deprecated public static final int ERROR_FRAME_TOO_LARGE = 8; // 0x8
@@ -21803,7 +21804,7 @@
     method public void setMediaDrmSession(@NonNull byte[]) throws android.media.MediaCryptoException;
   }
 
-  public final class MediaCryptoException extends java.lang.Exception {
+  public final class MediaCryptoException extends java.lang.Exception implements android.media.MediaDrmThrowable {
     ctor public MediaCryptoException(@Nullable String);
   }
 
@@ -22026,7 +22027,7 @@
     method public long getTimestampMillis();
   }
 
-  public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException {
+  public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException implements android.media.MediaDrmThrowable {
     method @NonNull public String getDiagnosticInfo();
     method public int getErrorCode();
     method public boolean isTransient();
@@ -22099,7 +22100,7 @@
   @Deprecated @IntDef({android.media.MediaDrm.SECURITY_LEVEL_UNKNOWN, android.media.MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO, android.media.MediaDrm.SECURITY_LEVEL_SW_SECURE_DECODE, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_CRYPTO, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_DECODE, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_ALL}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaDrm.SecurityLevel {
   }
 
-  public static final class MediaDrm.SessionException extends java.lang.RuntimeException {
+  public static final class MediaDrm.SessionException extends java.lang.RuntimeException implements android.media.MediaDrmThrowable {
     ctor public MediaDrm.SessionException(int, @Nullable String);
     method @Deprecated public int getErrorCode();
     method public boolean isTransient();
@@ -22107,14 +22108,20 @@
     field @Deprecated public static final int ERROR_UNKNOWN = 0; // 0x0
   }
 
-  public class MediaDrmException extends java.lang.Exception {
+  public class MediaDrmException extends java.lang.Exception implements android.media.MediaDrmThrowable {
     ctor public MediaDrmException(String);
   }
 
-  public class MediaDrmResetException extends java.lang.IllegalStateException {
+  public class MediaDrmResetException extends java.lang.IllegalStateException implements android.media.MediaDrmThrowable {
     ctor public MediaDrmResetException(String);
   }
 
+  public interface MediaDrmThrowable {
+    method public default int getErrorContext();
+    method public default int getOemError();
+    method public default int getVendorError();
+  }
+
   public final class MediaExtractor {
     ctor public MediaExtractor();
     method public boolean advance();
@@ -27264,8 +27271,14 @@
 package android.net.vcn {
 
   public final class VcnCellUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate {
+    method public int getCbs();
+    method public int getDun();
+    method public int getIms();
+    method public int getInternet();
+    method public int getMms();
     method @NonNull public java.util.Set<java.lang.String> getOperatorPlmnIds();
     method public int getOpportunistic();
+    method public int getRcs();
     method public int getRoaming();
     method @NonNull public java.util.Set<java.lang.Integer> getSimSpecificCarrierIds();
   }
@@ -27273,11 +27286,17 @@
   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 setCbs(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setDun(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setIms(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setInternet(int);
     method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMetered(int);
     method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMinDownstreamBandwidthKbps(int, int);
     method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMinUpstreamBandwidthKbps(int, int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMms(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 setRcs(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>);
   }
@@ -27285,6 +27304,7 @@
   public final class VcnConfig implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public java.util.Set<android.net.vcn.VcnGatewayConnectionConfig> getGatewayConnectionConfigs();
+    method @NonNull public java.util.Set<java.lang.Integer> getRestrictedUnderlyingNetworkTransports();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnConfig> CREATOR;
   }
@@ -27293,6 +27313,7 @@
     ctor public VcnConfig.Builder(@NonNull android.content.Context);
     method @NonNull public android.net.vcn.VcnConfig.Builder addGatewayConnectionConfig(@NonNull android.net.vcn.VcnGatewayConnectionConfig);
     method @NonNull public android.net.vcn.VcnConfig build();
+    method @NonNull public android.net.vcn.VcnConfig.Builder setRestrictedUnderlyingNetworkTransports(@NonNull java.util.Set<java.lang.Integer>);
   }
 
   public final class VcnGatewayConnectionConfig {
@@ -27301,13 +27322,17 @@
     method @IntRange(from=0x500) public int getMaxMtu();
     method @NonNull public long[] getRetryIntervalsMillis();
     method @NonNull public java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities();
+    method public boolean hasGatewayOption(int);
+    field public static final int VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY = 0; // 0x0
   }
 
   public static final class VcnGatewayConnectionConfig.Builder {
     ctor public VcnGatewayConnectionConfig.Builder(@NonNull String, @NonNull android.net.ipsec.ike.IkeTunnelConnectionParams);
     method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addExposedCapability(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addGatewayOption(int);
     method @NonNull public android.net.vcn.VcnGatewayConnectionConfig build();
     method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeGatewayOption(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>);
@@ -37652,6 +37677,11 @@
     ctor public AlreadyPersonalizedException(@NonNull String, @NonNull Throwable);
   }
 
+  public final class AuthenticationKeyMetadata {
+    method @NonNull public java.time.Instant getExpirationDate();
+    method @IntRange(from=0) public int getUsageCount();
+  }
+
   public class CipherSuiteNotSupportedException extends android.security.identity.IdentityCredentialException {
     ctor public CipherSuiteNotSupportedException(@NonNull String);
     ctor public CipherSuiteNotSupportedException(@NonNull String, @NonNull Throwable);
@@ -37682,6 +37712,7 @@
   public abstract class CredentialDataResult {
     method @Nullable public abstract byte[] getDeviceMac();
     method @NonNull public abstract byte[] getDeviceNameSpaces();
+    method @Nullable public byte[] getDeviceSignature();
     method @NonNull public abstract android.security.identity.CredentialDataResult.Entries getDeviceSignedEntries();
     method @NonNull public abstract android.security.identity.CredentialDataResult.Entries getIssuerSignedEntries();
     method @NonNull public abstract byte[] getStaticAuthenticationData();
@@ -37718,13 +37749,15 @@
     method @NonNull public byte[] delete(@NonNull byte[]);
     method @Deprecated @NonNull public abstract byte[] encryptMessageToReader(@NonNull byte[]);
     method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getAuthKeysNeedingCertification();
-    method @NonNull public abstract int[] getAuthenticationDataUsageCount();
+    method @Deprecated @NonNull public abstract int[] getAuthenticationDataUsageCount();
+    method @NonNull public java.util.List<android.security.identity.AuthenticationKeyMetadata> getAuthenticationKeyMetadata();
     method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getCredentialKeyCertificateChain();
     method @Deprecated @NonNull public abstract android.security.identity.ResultData getEntries(@Nullable byte[], @NonNull java.util.Map<java.lang.String,java.util.Collection<java.lang.String>>, @Nullable byte[], @Nullable byte[]) throws android.security.identity.EphemeralPublicKeyNotFoundException, android.security.identity.InvalidReaderSignatureException, android.security.identity.InvalidRequestMessageException, android.security.identity.NoAuthenticationKeyAvailableException, android.security.identity.SessionTranscriptMismatchException;
     method @NonNull public byte[] proveOwnership(@NonNull byte[]);
     method @Deprecated public abstract void setAllowUsingExhaustedKeys(boolean);
     method @Deprecated public void setAllowUsingExpiredKeys(boolean);
-    method public abstract void setAvailableAuthenticationKeys(int, int);
+    method @Deprecated public abstract void setAvailableAuthenticationKeys(int, int);
+    method public void setAvailableAuthenticationKeys(@IntRange(from=0) int, @IntRange(from=1) int, @IntRange(from=0) long);
     method @Deprecated public abstract void setReaderEphemeralPublicKey(@NonNull java.security.PublicKey) throws java.security.InvalidKeyException;
     method @Deprecated public abstract void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException;
     method public void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull java.time.Instant, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException;
@@ -41804,6 +41837,9 @@
     field public static final int AUTHENTICATION_METHOD_CERT = 1; // 0x1
     field public static final int AUTHENTICATION_METHOD_EAP_ONLY = 0; // 0x0
     field public static final int EPDG_ADDRESS_CELLULAR_LOC = 3; // 0x3
+    field public static final int EPDG_ADDRESS_IPV4_ONLY = 2; // 0x2
+    field public static final int EPDG_ADDRESS_IPV4_PREFERRED = 0; // 0x0
+    field public static final int EPDG_ADDRESS_IPV6_PREFERRED = 1; // 0x1
     field public static final int EPDG_ADDRESS_PCO = 2; // 0x2
     field public static final int EPDG_ADDRESS_PLMN = 1; // 0x1
     field public static final int EPDG_ADDRESS_STATIC = 0; // 0x0
@@ -41818,6 +41854,7 @@
     field public static final String KEY_CHILD_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_ctr_key_size_int_array";
     field public static final String KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY = "iwlan.diffie_hellman_groups_int_array";
     field public static final String KEY_DPD_TIMER_SEC_INT = "iwlan.dpd_timer_sec_int";
+    field public static final String KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT = "iwlan.epdg_address_ip_type_preference_int";
     field public static final String KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY = "iwlan.epdg_address_priority_int_array";
     field public static final String KEY_EPDG_AUTHENTICATION_METHOD_INT = "iwlan.epdg_authentication_method_int";
     field public static final String KEY_EPDG_PCO_ID_IPV4_INT = "iwlan.epdg_pco_id_ipv4_int";
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index ed2c8eb..8d53959 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -446,6 +446,10 @@
 
 package android.telephony {
 
+  public class CarrierConfigManager {
+    field public static final String KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT = "min_udp_port_4500_nat_timeout_sec_int";
+  }
+
   public abstract class CellSignalStrength {
     method public static int getNumSignalStrengthLevels();
   }
diff --git a/core/java/Android.bp b/core/java/Android.bp
index d2e7536..3921408 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -516,3 +516,8 @@
     name: "framework-configinfrastructure",
     sdk_version: "module_current",
 }
+
+java_library {
+    name: "framework-configinfrastructure.stubs.module_lib",
+    sdk_version: "module_current",
+}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index f494fa6..db1db91 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -1019,9 +1019,10 @@
         registerService(Context.APPWIDGET_SERVICE, AppWidgetManager.class,
                 new CachedServiceFetcher<AppWidgetManager>() {
             @Override
-            public AppWidgetManager createService(ContextImpl ctx) throws ServiceNotFoundException {
-                IBinder b = ServiceManager.getServiceOrThrow(Context.APPWIDGET_SERVICE);
-                return new AppWidgetManager(ctx, IAppWidgetService.Stub.asInterface(b));
+            public AppWidgetManager createService(ContextImpl ctx) {
+                IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
+                return b == null ? null : new AppWidgetManager(ctx,
+                        IAppWidgetService.Stub.asInterface(b));
             }});
 
         registerService(Context.MIDI_SERVICE, MidiManager.class,
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index 92f7dee..cb6aa09 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -31,6 +31,7 @@
 import android.app.ClientTransactionHandler;
 import android.os.IBinder;
 import android.util.IntArray;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -43,6 +44,7 @@
  * @hide
  */
 public class TransactionExecutorHelper {
+    private static final String TAG = TransactionExecutorHelper.class.getSimpleName();
     // A penalty applied to path with destruction when looking for the shortest one.
     private static final int DESTRUCTION_PENALTY = 10;
 
@@ -162,6 +164,11 @@
         if (finalStates == null || finalStates.length == 0) {
             return UNDEFINED;
         }
+        if (r == null) {
+            // Early return because the ActivityClientRecord hasn't been created or cannot be found.
+            Log.w(TAG, "ActivityClientRecord was null");
+            return UNDEFINED;
+        }
 
         final int currentState = r.getLifecycleState();
         int closestState = UNDEFINED;
diff --git a/core/java/android/hardware/HardwareBuffer.aidl b/core/java/android/hardware/HardwareBuffer.aidl
index 5bdd966..1333f0d 100644
--- a/core/java/android/hardware/HardwareBuffer.aidl
+++ b/core/java/android/hardware/HardwareBuffer.aidl
@@ -16,4 +16,4 @@
 
 package android.hardware;
 
-parcelable HardwareBuffer;
+@JavaOnlyStableParcelable @NdkOnlyStableParcelable parcelable HardwareBuffer ndk_header "android/hardware_buffer_aidl.h";
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index 1e4c9501..11892fe 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -188,9 +188,6 @@
     public static HardwareBuffer create(
             @IntRange(from = 1) int width, @IntRange(from = 1) int height,
             @Format int format, @IntRange(from = 1) int layers, @Usage long usage) {
-        if (!HardwareBuffer.isSupportedFormat(format)) {
-            throw new IllegalArgumentException("Invalid pixel format " + format);
-        }
         if (width <= 0) {
             throw new IllegalArgumentException("Invalid width " + width);
         }
@@ -226,9 +223,6 @@
      */
     public static boolean isSupported(@IntRange(from = 1) int width, @IntRange(from = 1) int height,
             @Format int format, @IntRange(from = 1) int layers, @Usage long usage) {
-        if (!HardwareBuffer.isSupportedFormat(format)) {
-            throw new IllegalArgumentException("Invalid pixel format " + format);
-        }
         if (width <= 0) {
             throw new IllegalArgumentException("Invalid width " + width);
         }
@@ -286,10 +280,7 @@
      * Returns the width of this buffer in pixels.
      */
     public int getWidth() {
-        if (isClosed()) {
-            throw new IllegalStateException("This HardwareBuffer has been closed and its width "
-                    + "cannot be obtained.");
-        }
+        checkClosed("width");
         return nGetWidth(mNativeObject);
     }
 
@@ -297,10 +288,7 @@
      * Returns the height of this buffer in pixels.
      */
     public int getHeight() {
-        if (isClosed()) {
-            throw new IllegalStateException("This HardwareBuffer has been closed and its height "
-                    + "cannot be obtained.");
-        }
+        checkClosed("height");
         return nGetHeight(mNativeObject);
     }
 
@@ -309,10 +297,7 @@
      */
     @Format
     public int getFormat() {
-        if (isClosed()) {
-            throw new IllegalStateException("This HardwareBuffer has been closed and its format "
-                    + "cannot be obtained.");
-        }
+        checkClosed("format");
         return nGetFormat(mNativeObject);
     }
 
@@ -320,10 +305,7 @@
      * Returns the number of layers in this buffer.
      */
     public int getLayers() {
-        if (isClosed()) {
-            throw new IllegalStateException("This HardwareBuffer has been closed and its layer "
-                    + "count cannot be obtained.");
-        }
+        checkClosed("layer count");
         return nGetLayers(mNativeObject);
     }
 
@@ -331,14 +313,27 @@
      * Returns the usage flags of the usage hints set on this buffer.
      */
     public long getUsage() {
-        if (isClosed()) {
-            throw new IllegalStateException("This HardwareBuffer has been closed and its usage "
-                    + "cannot be obtained.");
-        }
+        checkClosed("usage");
         return nGetUsage(mNativeObject);
     }
 
     /**
+     * Returns the system-wide unique id for this buffer
+     *
+     */
+    public long getId() {
+        checkClosed("id");
+        return nGetId(mNativeObject);
+    }
+
+    private void checkClosed(String name) {
+        if (isClosed()) {
+            throw new IllegalStateException("This HardwareBuffer has been closed and its "
+                    + name + " cannot be obtained.");
+        }
+    }
+
+    /**
      * Destroys this buffer immediately. Calling this method frees up any
      * underlying native resources. After calling this method, this buffer
      * must not be used in any way.
@@ -407,36 +402,6 @@
         }
     };
 
-    /**
-     * Validates whether a particular format is supported by HardwareBuffer.
-     *
-     * @param format The format to validate.
-     *
-     * @return True if <code>format</code> is a supported format. false otherwise.
-     * See {@link #create(int, int, int, int, long)}.
-     */
-    private static boolean isSupportedFormat(@Format int format) {
-        switch(format) {
-            case RGBA_8888:
-            case RGBA_FP16:
-            case RGBA_1010102:
-            case RGBX_8888:
-            case RGB_565:
-            case RGB_888:
-            case BLOB:
-            case YCBCR_420_888:
-            case D_16:
-            case D_24:
-            case DS_24UI8:
-            case D_FP32:
-            case DS_FP32UI8:
-            case S_UI8:
-            case YCBCR_P010:
-                return true;
-        }
-        return false;
-    }
-
     private static native long nCreateHardwareBuffer(int width, int height, int format, int layers,
             long usage);
     private static native long nCreateFromGraphicBuffer(GraphicBuffer graphicBuffer);
@@ -457,4 +422,6 @@
             long usage);
     @CriticalNative
     private static native long nEstimateSize(long nativeObject);
+    @CriticalNative
+    private static native long nGetId(long nativeObject);
 }
diff --git a/core/java/android/hardware/display/AmbientBrightnessDayStats.java b/core/java/android/hardware/display/AmbientBrightnessDayStats.java
index 8aff911..2816706 100644
--- a/core/java/android/hardware/display/AmbientBrightnessDayStats.java
+++ b/core/java/android/hardware/display/AmbientBrightnessDayStats.java
@@ -208,7 +208,7 @@
     }
 
     private int getBucketIndex(float ambientBrightness) {
-        if (ambientBrightness < mBucketBoundaries[0]) {
+        if (ambientBrightness < mBucketBoundaries[0] || Float.isNaN(ambientBrightness)) {
             return -1;
         }
         int low = 0;
diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
index 2d1a3fe..38b3174 100644
--- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
@@ -15,6 +15,12 @@
  */
 package android.net.vcn;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.getMatchCriteriaString;
 
@@ -37,10 +43,13 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
 import com.android.server.vcn.util.PersistableBundleUtils;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -64,6 +73,23 @@
     private static final int DEFAULT_OPPORTUNISTIC_MATCH_CRITERIA = MATCH_ANY;
     private final int mOpportunisticMatchCriteria;
 
+    private static final String CAPABILITIES_MATCH_CRITERIA_KEY = "mCapabilitiesMatchCriteria";
+    @NonNull private final Map<Integer, Integer> mCapabilitiesMatchCriteria;
+
+    private static final Map<Integer, Integer> CAPABILITIES_MATCH_CRITERIA_DEFAULT;
+
+    static {
+        Map<Integer, Integer> capsMatchCriteria = new HashMap<>();
+        capsMatchCriteria.put(NET_CAPABILITY_CBS, MATCH_ANY);
+        capsMatchCriteria.put(NET_CAPABILITY_DUN, MATCH_ANY);
+        capsMatchCriteria.put(NET_CAPABILITY_IMS, MATCH_ANY);
+        capsMatchCriteria.put(NET_CAPABILITY_INTERNET, MATCH_REQUIRED);
+        capsMatchCriteria.put(NET_CAPABILITY_MMS, MATCH_ANY);
+        capsMatchCriteria.put(NET_CAPABILITY_RCS, MATCH_ANY);
+
+        CAPABILITIES_MATCH_CRITERIA_DEFAULT = Collections.unmodifiableMap(capsMatchCriteria);
+    }
+
     private VcnCellUnderlyingNetworkTemplate(
             int meteredMatchCriteria,
             int minEntryUpstreamBandwidthKbps,
@@ -73,7 +99,8 @@
             Set<String> allowedNetworkPlmnIds,
             Set<Integer> allowedSpecificCarrierIds,
             int roamingMatchCriteria,
-            int opportunisticMatchCriteria) {
+            int opportunisticMatchCriteria,
+            Map<Integer, Integer> capabilitiesMatchCriteria) {
         super(
                 NETWORK_PRIORITY_TYPE_CELL,
                 meteredMatchCriteria,
@@ -85,6 +112,7 @@
         mAllowedSpecificCarrierIds = new ArraySet<>(allowedSpecificCarrierIds);
         mRoamingMatchCriteria = roamingMatchCriteria;
         mOpportunisticMatchCriteria = opportunisticMatchCriteria;
+        mCapabilitiesMatchCriteria = new HashMap<>(capabilitiesMatchCriteria);
 
         validate();
     }
@@ -94,6 +122,7 @@
     protected void validate() {
         super.validate();
         validatePlmnIds(mAllowedNetworkPlmnIds);
+        validateCapabilitiesMatchCriteria(mCapabilitiesMatchCriteria);
         Objects.requireNonNull(mAllowedSpecificCarrierIds, "matchingCarrierIds is null");
         validateMatchCriteria(mRoamingMatchCriteria, "mRoamingMatchCriteria");
         validateMatchCriteria(mOpportunisticMatchCriteria, "mOpportunisticMatchCriteria");
@@ -113,6 +142,28 @@
         }
     }
 
+    private static void validateCapabilitiesMatchCriteria(
+            Map<Integer, Integer> capabilitiesMatchCriteria) {
+        Objects.requireNonNull(capabilitiesMatchCriteria, "capabilitiesMatchCriteria is null");
+
+        boolean requiredCapabilityFound = false;
+        for (Map.Entry<Integer, Integer> entry : capabilitiesMatchCriteria.entrySet()) {
+            final int capability = entry.getKey();
+            final int matchCriteria = entry.getValue();
+
+            Preconditions.checkArgument(
+                    CAPABILITIES_MATCH_CRITERIA_DEFAULT.containsKey(capability),
+                    "NetworkCapability " + capability + "out of range");
+            validateMatchCriteria(matchCriteria, "capability " + capability);
+
+            requiredCapabilityFound |= (matchCriteria == MATCH_REQUIRED);
+        }
+
+        if (!requiredCapabilityFound) {
+            throw new IllegalArgumentException("No required capabilities found");
+        }
+    }
+
     /** @hide */
     @NonNull
     @VisibleForTesting(visibility = Visibility.PROTECTED)
@@ -146,6 +197,19 @@
                         PersistableBundleUtils.toList(
                                 specificCarrierIdsBundle, INTEGER_DESERIALIZER));
 
+        final PersistableBundle capabilitiesMatchCriteriaBundle =
+                in.getPersistableBundle(CAPABILITIES_MATCH_CRITERIA_KEY);
+        final Map<Integer, Integer> capabilitiesMatchCriteria;
+        if (capabilitiesMatchCriteriaBundle == null) {
+            capabilitiesMatchCriteria = CAPABILITIES_MATCH_CRITERIA_DEFAULT;
+        } else {
+            capabilitiesMatchCriteria =
+                    PersistableBundleUtils.toMap(
+                            capabilitiesMatchCriteriaBundle,
+                            INTEGER_DESERIALIZER,
+                            INTEGER_DESERIALIZER);
+        }
+
         final int roamingMatchCriteria = in.getInt(ROAMING_MATCH_KEY);
         final int opportunisticMatchCriteria = in.getInt(OPPORTUNISTIC_MATCH_KEY);
 
@@ -158,7 +222,8 @@
                 allowedNetworkPlmnIds,
                 allowedSpecificCarrierIds,
                 roamingMatchCriteria,
-                opportunisticMatchCriteria);
+                opportunisticMatchCriteria,
+                capabilitiesMatchCriteria);
     }
 
     /** @hide */
@@ -178,6 +243,12 @@
                         new ArrayList<>(mAllowedSpecificCarrierIds), INTEGER_SERIALIZER);
         result.putPersistableBundle(ALLOWED_SPECIFIC_CARRIER_IDS_KEY, specificCarrierIdsBundle);
 
+        final PersistableBundle capabilitiesMatchCriteriaBundle =
+                PersistableBundleUtils.fromMap(
+                        mCapabilitiesMatchCriteria, INTEGER_SERIALIZER, INTEGER_SERIALIZER);
+        result.putPersistableBundle(
+                CAPABILITIES_MATCH_CRITERIA_KEY, capabilitiesMatchCriteriaBundle);
+
         result.putInt(ROAMING_MATCH_KEY, mRoamingMatchCriteria);
         result.putInt(OPPORTUNISTIC_MATCH_KEY, mOpportunisticMatchCriteria);
 
@@ -225,12 +296,75 @@
         return mOpportunisticMatchCriteria;
     }
 
+    /**
+     * Returns the matching criteria for CBS networks.
+     *
+     * @see Builder#setCbs(int)
+     */
+    @MatchCriteria
+    public int getCbs() {
+        return mCapabilitiesMatchCriteria.get(NET_CAPABILITY_CBS);
+    }
+
+    /**
+     * Returns the matching criteria for DUN networks.
+     *
+     * @see Builder#setDun(int)
+     */
+    @MatchCriteria
+    public int getDun() {
+        return mCapabilitiesMatchCriteria.get(NET_CAPABILITY_DUN);
+    }
+    /**
+     * Returns the matching criteria for IMS networks.
+     *
+     * @see Builder#setIms(int)
+     */
+    @MatchCriteria
+    public int getIms() {
+        return mCapabilitiesMatchCriteria.get(NET_CAPABILITY_IMS);
+    }
+    /**
+     * Returns the matching criteria for INTERNET networks.
+     *
+     * @see Builder#setInternet(int)
+     */
+    @MatchCriteria
+    public int getInternet() {
+        return mCapabilitiesMatchCriteria.get(NET_CAPABILITY_INTERNET);
+    }
+    /**
+     * Returns the matching criteria for MMS networks.
+     *
+     * @see Builder#setMms(int)
+     */
+    @MatchCriteria
+    public int getMms() {
+        return mCapabilitiesMatchCriteria.get(NET_CAPABILITY_MMS);
+    }
+
+    /**
+     * Returns the matching criteria for RCS networks.
+     *
+     * @see Builder#setRcs(int)
+     */
+    @MatchCriteria
+    public int getRcs() {
+        return mCapabilitiesMatchCriteria.get(NET_CAPABILITY_RCS);
+    }
+
+    /** @hide */
+    public Map<Integer, Integer> getCapabilitiesMatchCriteria() {
+        return Collections.unmodifiableMap(new HashMap<>(mCapabilitiesMatchCriteria));
+    }
+
     @Override
     public int hashCode() {
         return Objects.hash(
                 super.hashCode(),
                 mAllowedNetworkPlmnIds,
                 mAllowedSpecificCarrierIds,
+                mCapabilitiesMatchCriteria,
                 mRoamingMatchCriteria,
                 mOpportunisticMatchCriteria);
     }
@@ -248,6 +382,7 @@
         final VcnCellUnderlyingNetworkTemplate rhs = (VcnCellUnderlyingNetworkTemplate) other;
         return Objects.equals(mAllowedNetworkPlmnIds, rhs.mAllowedNetworkPlmnIds)
                 && Objects.equals(mAllowedSpecificCarrierIds, rhs.mAllowedSpecificCarrierIds)
+                && Objects.equals(mCapabilitiesMatchCriteria, rhs.mCapabilitiesMatchCriteria)
                 && mRoamingMatchCriteria == rhs.mRoamingMatchCriteria
                 && mOpportunisticMatchCriteria == rhs.mOpportunisticMatchCriteria;
     }
@@ -261,6 +396,7 @@
         if (!mAllowedNetworkPlmnIds.isEmpty()) {
             pw.println("mAllowedSpecificCarrierIds: " + mAllowedSpecificCarrierIds);
         }
+        pw.println("mCapabilitiesMatchCriteria: " + mCapabilitiesMatchCriteria);
         if (mRoamingMatchCriteria != DEFAULT_ROAMING_MATCH_CRITERIA) {
             pw.println("mRoamingMatchCriteria: " + getMatchCriteriaString(mRoamingMatchCriteria));
         }
@@ -277,6 +413,7 @@
 
         @NonNull private final Set<String> mAllowedNetworkPlmnIds = new ArraySet<>();
         @NonNull private final Set<Integer> mAllowedSpecificCarrierIds = new ArraySet<>();
+        @NonNull private final Map<Integer, Integer> mCapabilitiesMatchCriteria = new HashMap<>();
 
         private int mRoamingMatchCriteria = DEFAULT_ROAMING_MATCH_CRITERIA;
         private int mOpportunisticMatchCriteria = DEFAULT_OPPORTUNISTIC_MATCH_CRITERIA;
@@ -287,7 +424,9 @@
         private int mMinExitDownstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
 
         /** Construct a Builder object. */
-        public Builder() {}
+        public Builder() {
+            mCapabilitiesMatchCriteria.putAll(CAPABILITIES_MATCH_CRITERIA_DEFAULT);
+        }
 
         /**
          * Set the matching criteria for metered networks.
@@ -461,6 +600,126 @@
             return this;
         }
 
+        /**
+         * Sets the matching criteria for CBS networks.
+         *
+         * <p>A template where {@code setCbs(MATCH_REQUIRED)} is called will only match CBS networks
+         * (ones with NET_CAPABILITY_CBS). A template where {@code setCbs(MATCH_FORBIDDEN)} is
+         * called will only match networks that do not support CBS (ones without
+         * NET_CAPABILITY_CBS).
+         *
+         * @param matchCriteria the matching criteria for CBS networks. Defaults to {@link
+         *     #MATCH_ANY}.
+         * @see NetworkCapabilities#NET_CAPABILITY_CBS
+         */
+        @NonNull
+        public Builder setCbs(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setCbs");
+
+            mCapabilitiesMatchCriteria.put(NET_CAPABILITY_CBS, matchCriteria);
+            return this;
+        }
+
+        /**
+         * Sets the matching criteria for DUN networks.
+         *
+         * <p>A template where {@code setDun(MATCH_REQUIRED)} is called will only match DUN networks
+         * (ones with NET_CAPABILITY_DUN). A template where {@code setDun(MATCH_FORBIDDEN)} is
+         * called will only match networks that do not support DUN (ones without
+         * NET_CAPABILITY_DUN).
+         *
+         * @param matchCriteria the matching criteria for DUN networks. Defaults to {@link
+         *     #MATCH_ANY}.
+         * @see NetworkCapabilities#NET_CAPABILITY_DUN
+         */
+        @NonNull
+        public Builder setDun(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setDun");
+
+            mCapabilitiesMatchCriteria.put(NET_CAPABILITY_DUN, matchCriteria);
+            return this;
+        }
+
+        /**
+         * Sets the matching criteria for IMS networks.
+         *
+         * <p>A template where {@code setIms(MATCH_REQUIRED)} is called will only match IMS networks
+         * (ones with NET_CAPABILITY_IMS). A template where {@code setIms(MATCH_FORBIDDEN)} is
+         * called will only match networks that do not support IMS (ones without
+         * NET_CAPABILITY_IMS).
+         *
+         * @param matchCriteria the matching criteria for IMS networks. Defaults to {@link
+         *     #MATCH_ANY}.
+         * @see NetworkCapabilities#NET_CAPABILITY_IMS
+         */
+        @NonNull
+        public Builder setIms(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setIms");
+
+            mCapabilitiesMatchCriteria.put(NET_CAPABILITY_IMS, matchCriteria);
+            return this;
+        }
+
+        /**
+         * Sets the matching criteria for INTERNET networks.
+         *
+         * <p>A template where {@code setInternet(MATCH_REQUIRED)} is called will only match
+         * INTERNET networks (ones with NET_CAPABILITY_INTERNET). A template where {@code
+         * setInternet(MATCH_FORBIDDEN)} is called will only match networks that do not support
+         * INTERNET (ones without NET_CAPABILITY_INTERNET).
+         *
+         * @param matchCriteria the matching criteria for INTERNET networks. Defaults to {@link
+         *     #MATCH_REQUIRED}.
+         * @see NetworkCapabilities#NET_CAPABILITY_INTERNET
+         */
+        @NonNull
+        public Builder setInternet(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setInternet");
+
+            mCapabilitiesMatchCriteria.put(NET_CAPABILITY_INTERNET, matchCriteria);
+            return this;
+        }
+
+        /**
+         * Sets the matching criteria for MMS networks.
+         *
+         * <p>A template where {@code setMms(MATCH_REQUIRED)} is called will only match MMS networks
+         * (ones with NET_CAPABILITY_MMS). A template where {@code setMms(MATCH_FORBIDDEN)} is
+         * called will only match networks that do not support MMS (ones without
+         * NET_CAPABILITY_MMS).
+         *
+         * @param matchCriteria the matching criteria for MMS networks. Defaults to {@link
+         *     #MATCH_ANY}.
+         * @see NetworkCapabilities#NET_CAPABILITY_MMS
+         */
+        @NonNull
+        public Builder setMms(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setMms");
+
+            mCapabilitiesMatchCriteria.put(NET_CAPABILITY_MMS, matchCriteria);
+            return this;
+        }
+
+        /**
+         * Sets the matching criteria for RCS networks.
+         *
+         * <p>A template where {@code setRcs(MATCH_REQUIRED)} is called will only match RCS networks
+         * (ones with NET_CAPABILITY_RCS). A template where {@code setRcs(MATCH_FORBIDDEN)} is
+         * called will only match networks that do not support RCS (ones without
+         * NET_CAPABILITY_RCS).
+         *
+         * @param matchCriteria the matching criteria for RCS networks. Defaults to {@link
+         *     #MATCH_ANY}.
+         * @see NetworkCapabilities#NET_CAPABILITY_RCS
+         */
+        @NonNull
+        public Builder setRcs(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setRcs");
+
+            mCapabilitiesMatchCriteria.put(NET_CAPABILITY_RCS, matchCriteria);
+            return this;
+        }
+
         /** Build the VcnCellUnderlyingNetworkTemplate. */
         @NonNull
         public VcnCellUnderlyingNetworkTemplate build() {
@@ -473,7 +732,8 @@
                     mAllowedNetworkPlmnIds,
                     mAllowedSpecificCarrierIds,
                     mRoamingMatchCriteria,
-                    mOpportunisticMatchCriteria);
+                    mOpportunisticMatchCriteria,
+                    mCapabilitiesMatchCriteria);
         }
     }
 }
diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java
index fd3fe37..dcf0026 100644
--- a/core/java/android/net/vcn/VcnConfig.java
+++ b/core/java/android/net/vcn/VcnConfig.java
@@ -15,16 +15,23 @@
  */
 package android.net.vcn;
 
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
 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;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
 import android.util.ArraySet;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
@@ -32,6 +39,7 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.Objects;
 import java.util.Set;
 
@@ -46,22 +54,36 @@
 public final class VcnConfig implements Parcelable {
     @NonNull private static final String TAG = VcnConfig.class.getSimpleName();
 
+    private static final Set<Integer> ALLOWED_TRANSPORTS = new ArraySet<>();
+
+    static {
+        ALLOWED_TRANSPORTS.add(TRANSPORT_WIFI);
+        ALLOWED_TRANSPORTS.add(TRANSPORT_CELLULAR);
+    }
+
     private static final String PACKAGE_NAME_KEY = "mPackageName";
     @NonNull private final String mPackageName;
 
     private static final String GATEWAY_CONNECTION_CONFIGS_KEY = "mGatewayConnectionConfigs";
     @NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs;
 
+    private static final Set<Integer> RESTRICTED_TRANSPORTS_DEFAULT =
+            Collections.singleton(TRANSPORT_WIFI);
+    private static final String RESTRICTED_TRANSPORTS_KEY = "mRestrictedTransports";
+    @NonNull private final Set<Integer> mRestrictedTransports;
+
     private static final String IS_TEST_MODE_PROFILE_KEY = "mIsTestModeProfile";
     private final boolean mIsTestModeProfile;
 
     private VcnConfig(
             @NonNull String packageName,
             @NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs,
+            @NonNull Set<Integer> restrictedTransports,
             boolean isTestModeProfile) {
         mPackageName = packageName;
         mGatewayConnectionConfigs =
                 Collections.unmodifiableSet(new ArraySet<>(gatewayConnectionConfigs));
+        mRestrictedTransports = Collections.unmodifiableSet(new ArraySet<>(restrictedTransports));
         mIsTestModeProfile = isTestModeProfile;
 
         validate();
@@ -82,6 +104,20 @@
                 new ArraySet<>(
                         PersistableBundleUtils.toList(
                                 gatewayConnectionConfigsBundle, VcnGatewayConnectionConfig::new));
+
+        final PersistableBundle restrictedTransportsBundle =
+                in.getPersistableBundle(RESTRICTED_TRANSPORTS_KEY);
+        if (restrictedTransportsBundle == null) {
+            // RESTRICTED_TRANSPORTS_KEY was added in U and does not exist in VcnConfigs created in
+            // older platforms
+            mRestrictedTransports = RESTRICTED_TRANSPORTS_DEFAULT;
+        } else {
+            mRestrictedTransports =
+                    new ArraySet<Integer>(
+                            PersistableBundleUtils.toList(
+                                    restrictedTransportsBundle, INTEGER_DESERIALIZER));
+        }
+
         mIsTestModeProfile = in.getBoolean(IS_TEST_MODE_PROFILE_KEY);
 
         validate();
@@ -91,6 +127,19 @@
         Objects.requireNonNull(mPackageName, "packageName was null");
         Preconditions.checkCollectionNotEmpty(
                 mGatewayConnectionConfigs, "gatewayConnectionConfigs was empty");
+
+        final Iterator<Integer> iterator = mRestrictedTransports.iterator();
+        while (iterator.hasNext()) {
+            final int transport = iterator.next();
+            if (!ALLOWED_TRANSPORTS.contains(transport)) {
+                iterator.remove();
+                Log.w(
+                        TAG,
+                        "Found invalid transport "
+                                + transport
+                                + " which might be from a new version of VcnConfig");
+            }
+        }
     }
 
     /**
@@ -110,6 +159,16 @@
     }
 
     /**
+     * Retrieve the transports that will be restricted by the VCN.
+     *
+     * @see Builder#setRestrictedUnderlyingNetworkTransports(Set)
+     */
+    @NonNull
+    public Set<Integer> getRestrictedUnderlyingNetworkTransports() {
+        return Collections.unmodifiableSet(mRestrictedTransports);
+    }
+
+    /**
      * Returns whether or not this VcnConfig is restricted to test networks.
      *
      * @hide
@@ -134,6 +193,12 @@
                         new ArrayList<>(mGatewayConnectionConfigs),
                         VcnGatewayConnectionConfig::toPersistableBundle);
         result.putPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY, gatewayConnectionConfigsBundle);
+
+        final PersistableBundle restrictedTransportsBundle =
+                PersistableBundleUtils.fromList(
+                        new ArrayList<>(mRestrictedTransports), INTEGER_SERIALIZER);
+        result.putPersistableBundle(RESTRICTED_TRANSPORTS_KEY, restrictedTransportsBundle);
+
         result.putBoolean(IS_TEST_MODE_PROFILE_KEY, mIsTestModeProfile);
 
         return result;
@@ -141,7 +206,8 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mPackageName, mGatewayConnectionConfigs, mIsTestModeProfile);
+        return Objects.hash(
+                mPackageName, mGatewayConnectionConfigs, mRestrictedTransports, mIsTestModeProfile);
     }
 
     @Override
@@ -153,6 +219,7 @@
         final VcnConfig rhs = (VcnConfig) other;
         return mPackageName.equals(rhs.mPackageName)
                 && mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs)
+                && mRestrictedTransports.equals(rhs.mRestrictedTransports)
                 && mIsTestModeProfile == rhs.mIsTestModeProfile;
     }
 
@@ -189,12 +256,15 @@
         @NonNull
         private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs = new ArraySet<>();
 
+        @NonNull private final Set<Integer> mRestrictedTransports = new ArraySet<>();
+
         private boolean mIsTestModeProfile = false;
 
         public Builder(@NonNull Context context) {
             Objects.requireNonNull(context, "context was null");
 
             mPackageName = context.getOpPackageName();
+            mRestrictedTransports.addAll(RESTRICTED_TRANSPORTS_DEFAULT);
         }
 
         /**
@@ -225,6 +295,36 @@
             return this;
         }
 
+        private void validateRestrictedTransportsOrThrow(Set<Integer> restrictedTransports) {
+            Objects.requireNonNull(restrictedTransports, "transports was null");
+
+            for (int transport : restrictedTransports) {
+                if (!ALLOWED_TRANSPORTS.contains(transport)) {
+                    throw new IllegalArgumentException("Invalid transport " + transport);
+                }
+            }
+        }
+
+        /**
+         * Sets transports that will be restricted by the VCN.
+         *
+         * @param transports transports that will be restricted by VCN. Networks that include any
+         *     of the transports will be marked as restricted. Only {@link
+         *     NetworkCapabilities#TRANSPORT_WIFI} and {@link
+         *     NetworkCapabilities#TRANSPORT_CELLULAR} are allowed. {@link
+         *     NetworkCapabilities#TRANSPORT_WIFI} is marked restricted by default.
+         * @return this {@link Builder} instance, for chaining
+         * @throws IllegalArgumentException if the input contains unsupported transport types.
+         */
+        @NonNull
+        public Builder setRestrictedUnderlyingNetworkTransports(@NonNull Set<Integer> transports) {
+            validateRestrictedTransportsOrThrow(transports);
+
+            mRestrictedTransports.clear();
+            mRestrictedTransports.addAll(transports);
+            return this;
+        }
+
         /**
          * Restricts this VcnConfig to matching with test networks (only).
          *
@@ -248,7 +348,11 @@
          */
         @NonNull
         public VcnConfig build() {
-            return new VcnConfig(mPackageName, mGatewayConnectionConfigs, mIsTestModeProfile);
+            return new VcnConfig(
+                    mPackageName,
+                    mGatewayConnectionConfigs,
+                    mRestrictedTransports,
+                    mIsTestModeProfile);
         }
     }
 }
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index 2339656..4c9d150 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -130,6 +130,30 @@
             })
     public @interface VcnSupportedCapability {}
 
+    /**
+     * Perform mobility update to attempt recovery from suspected data stalls.
+     *
+     * <p>If set, the gateway connection will monitor the data stall detection of the VCN network.
+     * When there is a suspected data stall, the gateway connection will attempt recovery by
+     * performing a mobility update on the underlying IKE session.
+     */
+    public static final int VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY = 0;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            prefix = {"VCN_GATEWAY_OPTION_"},
+            value = {
+                VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY,
+            })
+    public @interface VcnGatewayOption {}
+
+    private static final Set<Integer> ALLOWED_GATEWAY_OPTIONS = new ArraySet<>();
+
+    static {
+        ALLOWED_GATEWAY_OPTIONS.add(VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY);
+    }
+
     private static final int DEFAULT_MAX_MTU = 1500;
 
     /**
@@ -201,6 +225,9 @@
     private static final String RETRY_INTERVAL_MS_KEY = "mRetryIntervalsMs";
     @NonNull private final long[] mRetryIntervalsMs;
 
+    private static final String GATEWAY_OPTIONS_KEY = "mGatewayOptions";
+    @NonNull private final Set<Integer> mGatewayOptions;
+
     /** Builds a VcnGatewayConnectionConfig with the specified parameters. */
     private VcnGatewayConnectionConfig(
             @NonNull String gatewayConnectionName,
@@ -208,12 +235,14 @@
             @NonNull Set<Integer> exposedCapabilities,
             @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
             @NonNull long[] retryIntervalsMs,
-            @IntRange(from = MIN_MTU_V6) int maxMtu) {
+            @IntRange(from = MIN_MTU_V6) int maxMtu,
+            @NonNull Set<Integer> gatewayOptions) {
         mGatewayConnectionName = gatewayConnectionName;
         mTunnelConnectionParams = tunnelConnectionParams;
         mExposedCapabilities = new TreeSet(exposedCapabilities);
         mRetryIntervalsMs = retryIntervalsMs;
         mMaxMtu = maxMtu;
+        mGatewayOptions = Collections.unmodifiableSet(new ArraySet(gatewayOptions));
 
         mUnderlyingNetworkTemplates = new ArrayList<>(underlyingNetworkTemplates);
         if (mUnderlyingNetworkTemplates.isEmpty()) {
@@ -256,6 +285,20 @@
                             VcnUnderlyingNetworkTemplate::fromPersistableBundle);
         }
 
+        final PersistableBundle gatewayOptionsBundle = in.getPersistableBundle(GATEWAY_OPTIONS_KEY);
+
+        if (gatewayOptionsBundle == null) {
+            // GATEWAY_OPTIONS_KEY was added in Android U. Thus VcnGatewayConnectionConfig created
+            // on old platforms will not have this data and will be assigned with the default value
+            mGatewayOptions = Collections.emptySet();
+        } else {
+            mGatewayOptions =
+                    new ArraySet<>(
+                            PersistableBundleUtils.toList(
+                                    gatewayOptionsBundle,
+                                    PersistableBundleUtils.INTEGER_DESERIALIZER));
+        }
+
         mRetryIntervalsMs = in.getLongArray(RETRY_INTERVAL_MS_KEY);
         mMaxMtu = in.getInt(MAX_MTU_KEY);
 
@@ -279,6 +322,10 @@
 
         Preconditions.checkArgument(
                 mMaxMtu >= MIN_MTU_V6, "maxMtu must be at least IPv6 min MTU (1280)");
+
+        for (int option : mGatewayOptions) {
+            validateGatewayOption(option);
+        }
     }
 
     private static void checkValidCapability(int capability) {
@@ -315,6 +362,12 @@
         }
     }
 
+    private static void validateGatewayOption(int option) {
+        if (!ALLOWED_GATEWAY_OPTIONS.contains(option)) {
+            throw new IllegalArgumentException("Invalid vcn gateway option: " + option);
+        }
+    }
+
     /**
      * Returns the configured Gateway Connection name.
      *
@@ -399,6 +452,19 @@
     }
 
     /**
+     * Checks if the given VCN gateway option is enabled.
+     *
+     * @param option the option to check.
+     * @throws IllegalArgumentException if the provided option is invalid.
+     * @see Builder#addGatewayOption(int)
+     * @see Builder#removeGatewayOption(int)
+     */
+    public boolean hasGatewayOption(@VcnGatewayOption int option) {
+        validateGatewayOption(option);
+        return mGatewayOptions.contains(option);
+    }
+
+    /**
      * Converts this config to a PersistableBundle.
      *
      * @hide
@@ -418,11 +484,16 @@
                 PersistableBundleUtils.fromList(
                         mUnderlyingNetworkTemplates,
                         VcnUnderlyingNetworkTemplate::toPersistableBundle);
+        final PersistableBundle gatewayOptionsBundle =
+                PersistableBundleUtils.fromList(
+                        new ArrayList<>(mGatewayOptions),
+                        PersistableBundleUtils.INTEGER_SERIALIZER);
 
         result.putString(GATEWAY_CONNECTION_NAME_KEY, mGatewayConnectionName);
         result.putPersistableBundle(TUNNEL_CONNECTION_PARAMS_KEY, tunnelConnectionParamsBundle);
         result.putPersistableBundle(EXPOSED_CAPABILITIES_KEY, exposedCapsBundle);
         result.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, networkTemplatesBundle);
+        result.putPersistableBundle(GATEWAY_OPTIONS_KEY, gatewayOptionsBundle);
         result.putLongArray(RETRY_INTERVAL_MS_KEY, mRetryIntervalsMs);
         result.putInt(MAX_MTU_KEY, mMaxMtu);
 
@@ -437,7 +508,8 @@
                 mExposedCapabilities,
                 mUnderlyingNetworkTemplates,
                 Arrays.hashCode(mRetryIntervalsMs),
-                mMaxMtu);
+                mMaxMtu,
+                mGatewayOptions);
     }
 
     @Override
@@ -452,7 +524,8 @@
                 && mExposedCapabilities.equals(rhs.mExposedCapabilities)
                 && mUnderlyingNetworkTemplates.equals(rhs.mUnderlyingNetworkTemplates)
                 && Arrays.equals(mRetryIntervalsMs, rhs.mRetryIntervalsMs)
-                && mMaxMtu == rhs.mMaxMtu;
+                && mMaxMtu == rhs.mMaxMtu
+                && mGatewayOptions.equals(rhs.mGatewayOptions);
     }
 
     /**
@@ -470,6 +543,8 @@
         @NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS;
         private int mMaxMtu = DEFAULT_MAX_MTU;
 
+        @NonNull private final Set<Integer> mGatewayOptions = new ArraySet<>();
+
         // TODO: (b/175829816) Consider VCN-exposed capabilities that may be transport dependent.
         //       Consider the case where the VCN might only expose MMS on WiFi, but defer to MMS
         //       when on Cell.
@@ -628,6 +703,34 @@
         }
 
         /**
+         * Enables the specified VCN gateway option.
+         *
+         * @param option the option to be enabled
+         * @return this {@link Builder} instance, for chaining
+         * @throws IllegalArgumentException if the provided option is invalid
+         */
+        @NonNull
+        public Builder addGatewayOption(@VcnGatewayOption int option) {
+            validateGatewayOption(option);
+            mGatewayOptions.add(option);
+            return this;
+        }
+
+        /**
+         * Resets (disables) the specified VCN gateway option.
+         *
+         * @param option the option to be disabled
+         * @return this {@link Builder} instance, for chaining
+         * @throws IllegalArgumentException if the provided option is invalid
+         */
+        @NonNull
+        public Builder removeGatewayOption(@VcnGatewayOption int option) {
+            validateGatewayOption(option);
+            mGatewayOptions.remove(option);
+            return this;
+        }
+
+        /**
          * Builds and validates the VcnGatewayConnectionConfig.
          *
          * @return an immutable VcnGatewayConnectionConfig instance
@@ -640,7 +743,8 @@
                     mExposedCapabilities,
                     mUnderlyingNetworkTemplates,
                     mRetryIntervalsMs,
-                    mMaxMtu);
+                    mMaxMtu,
+                    mGatewayOptions);
         }
     }
 }
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 3a7aea5..70cf973 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -114,13 +114,28 @@
     public static final String VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY =
             "vcn_restricted_transports";
 
+    /**
+     * Key for maximum number of parallel SAs for tunnel aggregation
+     *
+     * <p>If set to a value > 1, multiple tunnels will be set up, and inbound traffic will be
+     * aggregated over the various tunnels.
+     *
+     * <p>Defaults to 1, unless overridden by carrier config
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY =
+            "vcn_tunnel_aggregation_sa_count_max";
+
     /** List of Carrier Config options to extract from Carrier Config bundles. @hide */
     @NonNull
     public static final String[] VCN_RELATED_CARRIER_CONFIG_KEYS =
             new String[] {
                 VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY,
                 VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
-                VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY
+                VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
+                VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY,
             };
 
     private static final Map<
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index f545c30..0c7f529 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -30,6 +30,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.IntentFilter;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.nfc.tech.MifareClassic;
@@ -525,6 +526,66 @@
     }
 
     /**
+     * Helper to check if this device has FEATURE_NFC_BEAM, but without using
+     * a context.
+     * Equivalent to
+     * context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC_BEAM)
+     */
+    private static boolean hasBeamFeature() {
+        IPackageManager pm = ActivityThread.getPackageManager();
+        if (pm == null) {
+            Log.e(TAG, "Cannot get package manager, assuming no Android Beam feature");
+            return false;
+        }
+        try {
+            return pm.hasSystemFeature(PackageManager.FEATURE_NFC_BEAM, 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Package manager query failed, assuming no Android Beam feature", e);
+            return false;
+        }
+    }
+
+    /**
+     * Helper to check if this device has FEATURE_NFC, but without using
+     * a context.
+     * Equivalent to
+     * context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)
+     */
+    private static boolean hasNfcFeature() {
+        IPackageManager pm = ActivityThread.getPackageManager();
+        if (pm == null) {
+            Log.e(TAG, "Cannot get package manager, assuming no NFC feature");
+            return false;
+        }
+        try {
+            return pm.hasSystemFeature(PackageManager.FEATURE_NFC, 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Package manager query failed, assuming no NFC feature", e);
+            return false;
+        }
+    }
+
+    /**
+     * Helper to check if this device is NFC HCE capable, by checking for
+     * FEATURE_NFC_HOST_CARD_EMULATION and/or FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+     * but without using a context.
+     */
+    private static boolean hasNfcHceFeature() {
+        IPackageManager pm = ActivityThread.getPackageManager();
+        if (pm == null) {
+            Log.e(TAG, "Cannot get package manager, assuming no NFC feature");
+            return false;
+        }
+        try {
+            return pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION, 0)
+                || pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF, 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Package manager query failed, assuming no NFC feature", e);
+            return false;
+        }
+    }
+
+    /**
      * Return list of Secure Elements which support off host card emulation.
      *
      * @return List<String> containing secure elements on the device which supports
@@ -533,21 +594,23 @@
      * @hide
      */
     public @NonNull List<String> getSupportedOffHostSecureElements() {
-        if (mContext == null) {
-            throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
-                    + " getSupportedOffHostSecureElements APIs");
-        }
         List<String> offHostSE = new ArrayList<String>();
-        PackageManager pm = mContext.getPackageManager();
+        IPackageManager pm = ActivityThread.getPackageManager();
         if (pm == null) {
             Log.e(TAG, "Cannot get package manager, assuming no off-host CE feature");
             return offHostSE;
         }
-        if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC)) {
-            offHostSE.add("SIM");
-        }
-        if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE)) {
-            offHostSE.add("eSE");
+        try {
+            if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC, 0)) {
+                offHostSE.add("SIM");
+            }
+            if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE, 0)) {
+                offHostSE.add("eSE");
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Package manager query failed, assuming no off-host CE feature", e);
+            offHostSE.clear();
+            return offHostSE;
         }
         return offHostSE;
     }
@@ -559,19 +622,10 @@
      */
     @UnsupportedAppUsage
     public static synchronized NfcAdapter getNfcAdapter(Context context) {
-        if (context == null) {
-            if (sNullContextNfcAdapter == null) {
-                sNullContextNfcAdapter = new NfcAdapter(null);
-            }
-            return sNullContextNfcAdapter;
-        }
         if (!sIsInitialized) {
-            PackageManager pm = context.getPackageManager();
-            sHasNfcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC);
-            sHasBeamFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC_BEAM);
-            boolean hasHceFeature =
-                    pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)
-                    || pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF);
+            sHasNfcFeature = hasNfcFeature();
+            sHasBeamFeature = hasBeamFeature();
+            boolean hasHceFeature = hasNfcHceFeature();
             /* is this device meant to have NFC */
             if (!sHasNfcFeature && !hasHceFeature) {
                 Log.v(TAG, "this device does not have NFC support");
@@ -607,6 +661,12 @@
 
             sIsInitialized = true;
         }
+        if (context == null) {
+            if (sNullContextNfcAdapter == null) {
+                sNullContextNfcAdapter = new NfcAdapter(null);
+            }
+            return sNullContextNfcAdapter;
+        }
         NfcAdapter adapter = sNfcAdapters.get(context);
         if (adapter == null) {
             adapter = new NfcAdapter(context);
@@ -617,12 +677,8 @@
 
     /** get handle to NFC service interface */
     private static INfcAdapter getServiceInterface() {
-        if (!sHasNfcFeature) {
-            /* NFC is not supported */
-            return null;
-        }
         /* get a handle to NFC service */
-        IBinder b = ServiceManager.waitForService("nfc");
+        IBinder b = ServiceManager.getService("nfc");
         if (b == null) {
             return null;
         }
@@ -652,15 +708,6 @@
                     "context not associated with any application (using a mock context?)");
         }
 
-        synchronized (NfcAdapter.class) {
-            if (!sIsInitialized) {
-                PackageManager pm = context.getPackageManager();
-                sHasNfcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC);
-            }
-            if (!sHasNfcFeature) {
-                return null;
-            }
-        }
         if (getServiceInterface() == null) {
             // NFC is not available
             return null;
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index 6a42091..0b56d19 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -22,9 +22,11 @@
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.app.Activity;
+import android.app.ActivityThread;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.nfc.INfcCardEmulation;
 import android.nfc.NfcAdapter;
@@ -156,13 +158,18 @@
             throw new UnsupportedOperationException();
         }
         if (!sIsInitialized) {
-            PackageManager pm = context.getPackageManager();
+            IPackageManager pm = ActivityThread.getPackageManager();
             if (pm == null) {
                 Log.e(TAG, "Cannot get PackageManager");
                 throw new UnsupportedOperationException();
             }
-            if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
-                Log.e(TAG, "This device does not support card emulation");
+            try {
+                if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION, 0)) {
+                    Log.e(TAG, "This device does not support card emulation");
+                    throw new UnsupportedOperationException();
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "PackageManager query failed.");
                 throw new UnsupportedOperationException();
             }
             sIsInitialized = true;
diff --git a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
index 48bbf5b6..3c92455 100644
--- a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
+++ b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
@@ -17,8 +17,10 @@
 package android.nfc.cardemulation;
 
 import android.app.Activity;
+import android.app.ActivityThread;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.nfc.INfcFCardEmulation;
 import android.nfc.NfcAdapter;
@@ -68,13 +70,18 @@
             throw new UnsupportedOperationException();
         }
         if (!sIsInitialized) {
-            PackageManager pm = context.getPackageManager();
+            IPackageManager pm = ActivityThread.getPackageManager();
             if (pm == null) {
                 Log.e(TAG, "Cannot get PackageManager");
                 throw new UnsupportedOperationException();
             }
-            if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF)) {
-                Log.e(TAG, "This device does not support NFC-F card emulation");
+            try {
+                if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF, 0)) {
+                    Log.e(TAG, "This device does not support NFC-F card emulation");
+                    throw new UnsupportedOperationException();
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "PackageManager query failed.");
                 throw new UnsupportedOperationException();
             }
             sIsInitialized = true;
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index 394927e..b210c46 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -292,8 +292,10 @@
      * If the service is not running, servicemanager will attempt to start it, and this function
      * will wait for it to be ready.
      *
-     * @return {@code null} if the service is not declared in the manifest, or if there are
-     * permission problems, or if there are fatal errors.
+     * @throws SecurityException if the process does not have the permissions to check
+     * isDeclared() for the service.
+     * @return {@code null} if the service is not declared in the manifest, or if there
+     * are fatal errors.
      * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
diff --git a/core/java/android/os/ThreadLocalWorkSource.java b/core/java/android/os/ThreadLocalWorkSource.java
index 894b1cc4..e9adb20 100644
--- a/core/java/android/os/ThreadLocalWorkSource.java
+++ b/core/java/android/os/ThreadLocalWorkSource.java
@@ -39,8 +39,8 @@
  */
 public final class ThreadLocalWorkSource {
     public static final int UID_NONE = Message.UID_NONE;
-    private static final ThreadLocal<Integer> sWorkSourceUid =
-            ThreadLocal.withInitial(() -> UID_NONE);
+    private static final ThreadLocal<int []> sWorkSourceUid =
+            ThreadLocal.withInitial(() -> new int[] {UID_NONE});
 
     /**
      * Returns the UID to blame for the code currently executed on this thread.
@@ -50,7 +50,7 @@
      * <p>It can also be set manually using {@link #setUid(int)}.
      */
     public static int getUid() {
-        return sWorkSourceUid.get();
+        return sWorkSourceUid.get()[0];
     }
 
     /**
@@ -65,7 +65,7 @@
      */
     public static long setUid(int uid) {
         final long token = getToken();
-        sWorkSourceUid.set(uid);
+        sWorkSourceUid.get()[0] = uid;
         return token;
     }
 
@@ -73,7 +73,7 @@
      * Restores the state using the provided token.
      */
     public static void restore(long token) {
-        sWorkSourceUid.set(parseUidFromToken(token));
+        sWorkSourceUid.get()[0] = parseUidFromToken(token);
     }
 
     /**
@@ -88,7 +88,7 @@
      * </pre>
      *
      * @return a token that can be used to restore the state.
-     **/
+     */
     public static long clear() {
         return setUid(UID_NONE);
     }
@@ -98,7 +98,7 @@
     }
 
     private static long getToken() {
-        return sWorkSourceUid.get();
+        return sWorkSourceUid.get()[0];
     }
 
     private ThreadLocalWorkSource() {
diff --git a/core/java/android/security/rkp/IRemoteProvisioning.aidl b/core/java/android/security/rkp/IRemoteProvisioning.aidl
index 23d8159..0efaa89 100644
--- a/core/java/android/security/rkp/IRemoteProvisioning.aidl
+++ b/core/java/android/security/rkp/IRemoteProvisioning.aidl
@@ -58,11 +58,4 @@
      *
      */
     void getRegistration(String irpcName, IGetRegistrationCallback callback);
-
-    /**
-     * Cancel any active {@link getRegistration} call associated with the given
-     * callback. If no getRegistration call is currently active, this function is
-     * a noop.
-     */
-    void cancelGetRegistration(IGetRegistrationCallback callback);
 }
diff --git a/core/java/android/service/controls/OWNERS b/core/java/android/service/controls/OWNERS
new file mode 100644
index 0000000..4bb78c7
--- /dev/null
+++ b/core/java/android/service/controls/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 802726
+asc@google.com
+kozynski@google.com
+juliacr@google.com
\ No newline at end of file
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index a24c1f9..492c938 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -77,7 +77,7 @@
         mInputChannel = inputChannel;
         mMessageQueue = looper.getQueue();
         mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
-                inputChannel, mMessageQueue);
+                mInputChannel, mMessageQueue);
 
         mCloseGuard.open("InputEventReceiver.dispose");
     }
diff --git a/core/java/android/view/InputEventSender.java b/core/java/android/view/InputEventSender.java
index 9035f3f..64f62c7 100644
--- a/core/java/android/view/InputEventSender.java
+++ b/core/java/android/view/InputEventSender.java
@@ -65,7 +65,7 @@
         mInputChannel = inputChannel;
         mMessageQueue = looper.getQueue();
         mSenderPtr = nativeInit(new WeakReference<InputEventSender>(this),
-                inputChannel, mMessageQueue);
+                mInputChannel, mMessageQueue);
 
         mCloseGuard.open("InputEventSender.dispose");
     }
diff --git a/core/java/android/view/autofill/OWNERS b/core/java/android/view/autofill/OWNERS
index 108c42c..26c59a6 100644
--- a/core/java/android/view/autofill/OWNERS
+++ b/core/java/android/view/autofill/OWNERS
@@ -1,7 +1,10 @@
 # Bug component: 351486
 
 augale@google.com
+haoranzhang@google.com
 joannechung@google.com
 markpun@google.com
 lpeter@google.com
+simranjit@google.com
 tymtsai@google.com
+yunicorn@google.com
diff --git a/core/java/com/android/internal/security/VerityUtils.java b/core/java/com/android/internal/security/VerityUtils.java
index 7f45c09..3ab11a8 100644
--- a/core/java/com/android/internal/security/VerityUtils.java
+++ b/core/java/com/android/internal/security/VerityUtils.java
@@ -17,6 +17,7 @@
 package com.android.internal.security;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Build;
 import android.os.SystemProperties;
 import android.system.Os;
@@ -41,6 +42,7 @@
 import java.nio.ByteOrder;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
+import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
@@ -77,17 +79,23 @@
         return filePath + FSVERITY_SIGNATURE_FILE_EXTENSION;
     }
 
-    /** Enables fs-verity for the file with a PKCS#7 detached signature file. */
-    public static void setUpFsverity(@NonNull String filePath, @NonNull String signaturePath)
+    /** Enables fs-verity for the file with an optional PKCS#7 detached signature file. */
+    public static void setUpFsverity(@NonNull String filePath, @Nullable String signaturePath)
             throws IOException {
-        if (Files.size(Paths.get(signaturePath)) > MAX_SIGNATURE_FILE_SIZE_BYTES) {
-            throw new SecurityException("Signature file is unexpectedly large: " + signaturePath);
+        byte[] rawSignature = null;
+        if (signaturePath != null) {
+            Path path = Paths.get(signaturePath);
+            if (Files.size(path) > MAX_SIGNATURE_FILE_SIZE_BYTES) {
+                throw new SecurityException("Signature file is unexpectedly large: "
+                        + signaturePath);
+            }
+            rawSignature = Files.readAllBytes(path);
         }
-        setUpFsverity(filePath, Files.readAllBytes(Paths.get(signaturePath)));
+        setUpFsverity(filePath, rawSignature);
     }
 
-    /** Enables fs-verity for the file with a PKCS#7 detached signature bytes. */
-    public static void setUpFsverity(@NonNull String filePath, @NonNull byte[] pkcs7Signature)
+    /** Enables fs-verity for the file with an optional PKCS#7 detached signature bytes. */
+    public static void setUpFsverity(@NonNull String filePath, @Nullable byte[] pkcs7Signature)
             throws IOException {
         // This will fail if the public key is not already in .fs-verity kernel keyring.
         int errno = enableFsverityNative(filePath, pkcs7Signature);
@@ -227,7 +235,7 @@
     }
 
     private static native int enableFsverityNative(@NonNull String filePath,
-            @NonNull byte[] pkcs7Signature);
+            @Nullable byte[] pkcs7Signature);
     private static native int measureFsverityNative(@NonNull String filePath,
             @NonNull byte[] digest);
     private static native int statxForFsverityNative(@NonNull String filePath);
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 949f363..7dd46a6 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -640,7 +640,6 @@
     char jitmaxsizeOptsBuf[sizeof("-Xjitmaxsize:")-1 + PROPERTY_VALUE_MAX];
     char jitinitialsizeOptsBuf[sizeof("-Xjitinitialsize:")-1 + PROPERTY_VALUE_MAX];
     char jitthresholdOptsBuf[sizeof("-Xjitthreshold:")-1 + PROPERTY_VALUE_MAX];
-    char useJitProfilesOptsBuf[sizeof("-Xjitsaveprofilinginfo:")-1 + PROPERTY_VALUE_MAX];
     char jitprithreadweightOptBuf[sizeof("-Xjitprithreadweight:")-1 + PROPERTY_VALUE_MAX];
     char jittransitionweightOptBuf[sizeof("-Xjittransitionweight:")-1 + PROPERTY_VALUE_MAX];
     char hotstartupsamplesOptsBuf[sizeof("-Xps-hot-startup-method-samples:")-1 + PROPERTY_VALUE_MAX];
@@ -856,10 +855,7 @@
     parseRuntimeOption("dalvik.vm.jitpthreadpriority",
                        jitpthreadpriorityOptsBuf,
                        "-Xjitpthreadpriority:");
-    property_get("dalvik.vm.usejitprofiles", useJitProfilesOptsBuf, "");
-    if (strcmp(useJitProfilesOptsBuf, "true") == 0) {
-        addOption("-Xjitsaveprofilinginfo");
-    }
+    addOption("-Xjitsaveprofilinginfo");
 
     parseRuntimeOption("dalvik.vm.jitprithreadweight",
                        jitprithreadweightOptBuf,
diff --git a/core/jni/android_hardware_HardwareBuffer.cpp b/core/jni/android_hardware_HardwareBuffer.cpp
index f462523..5fcc46e 100644
--- a/core/jni/android_hardware_HardwareBuffer.cpp
+++ b/core/jni/android_hardware_HardwareBuffer.cpp
@@ -163,7 +163,7 @@
 static jlong android_hardware_HardwareBuffer_getUsage(JNIEnv* env,
     jobject clazz, jlong nativeObject) {
     GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(nativeObject);
-    return AHardwareBuffer_convertFromGrallocUsageBits(buffer->getUsage());
+    return static_cast<jlong>(AHardwareBuffer_convertFromGrallocUsageBits(buffer->getUsage()));
 }
 
 static jlong android_hardware_HardwareBuffer_estimateSize(jlong nativeObject) {
@@ -177,7 +177,12 @@
 
     const uint32_t bufferStride =
             buffer->getStride() > 0 ? buffer->getStride() : buffer->getWidth();
-    return static_cast<jlong>(buffer->getHeight() * bufferStride * bpp);
+    return static_cast<jlong>(static_cast<uint64_t>(buffer->getHeight() * bufferStride * bpp));
+}
+
+static jlong android_hardware_HardwareBuffer_getId(jlong nativeObject) {
+    GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(nativeObject);
+    return static_cast<jlong>(buffer->getId());
 }
 
 // ----------------------------------------------------------------------------
@@ -223,16 +228,6 @@
     }
 }
 
-GraphicBuffer* android_hardware_HardwareBuffer_getNativeGraphicBuffer(
-        JNIEnv* env, jobject hardwareBufferObj) {
-    if (env->IsInstanceOf(hardwareBufferObj, gHardwareBufferClassInfo.clazz)) {
-        return GraphicBufferWrapper_to_GraphicBuffer(
-                env->GetLongField(hardwareBufferObj, gHardwareBufferClassInfo.mNativeObject));
-    } else {
-        return nullptr;
-    }
-}
-
 jobject android_hardware_HardwareBuffer_createFromAHardwareBuffer(
         JNIEnv* env, AHardwareBuffer* hardwareBuffer) {
     GraphicBuffer* buffer = AHardwareBuffer_to_GraphicBuffer(hardwareBuffer);
@@ -295,6 +290,7 @@
 
     // --------------- @CriticalNative ----------------------
     { "nEstimateSize", "(J)J",  (void*) android_hardware_HardwareBuffer_estimateSize },
+    { "nGetId", "(J)J",  (void*) android_hardware_HardwareBuffer_getId },
 };
 // clang-format on
 
diff --git a/core/jni/com_android_internal_security_VerityUtils.cpp b/core/jni/com_android_internal_security_VerityUtils.cpp
index c5b3d8a..5553d28 100644
--- a/core/jni/com_android_internal_security_VerityUtils.cpp
+++ b/core/jni/com_android_internal_security_VerityUtils.cpp
@@ -48,10 +48,6 @@
     if (rfd.get() < 0) {
         return errno;
     }
-    ScopedByteArrayRO signature_bytes(env, signature);
-    if (signature_bytes.get() == nullptr) {
-        return EINVAL;
-    }
 
     fsverity_enable_arg arg = {};
     arg.version = 1;
@@ -59,8 +55,18 @@
     arg.block_size = 4096;
     arg.salt_size = 0;
     arg.salt_ptr = reinterpret_cast<uintptr_t>(nullptr);
-    arg.sig_size = signature_bytes.size();
-    arg.sig_ptr = reinterpret_cast<uintptr_t>(signature_bytes.get());
+
+    if (signature != nullptr) {
+        ScopedByteArrayRO signature_bytes(env, signature);
+        if (signature_bytes.get() == nullptr) {
+            return EINVAL;
+        }
+        arg.sig_size = signature_bytes.size();
+        arg.sig_ptr = reinterpret_cast<uintptr_t>(signature_bytes.get());
+    } else {
+        arg.sig_size = 0;
+        arg.sig_ptr = reinterpret_cast<uintptr_t>(nullptr);
+    }
 
     if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, &arg) < 0) {
         return errno;
diff --git a/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h b/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h
index dfd8035..964c28f 100644
--- a/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h
+++ b/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h
@@ -28,10 +28,6 @@
 extern AHardwareBuffer* android_hardware_HardwareBuffer_getNativeHardwareBuffer(
         JNIEnv* env, jobject hardwareBufferObj);
 
-/* Gets the underlying GraphicBuffer for a HardwareBuffer. */
-extern GraphicBuffer* android_hardware_HardwareBuffer_getNativeGraphicBuffer(
-        JNIEnv* env, jobject hardwareBufferObj);
-
 /* Returns a HardwareBuffer wrapper for the underlying AHardwareBuffer. */
 extern jobject android_hardware_HardwareBuffer_createFromAHardwareBuffer(
         JNIEnv* env, AHardwareBuffer* hardwareBuffer);
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index a4463e4..db391f7 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -16,6 +16,8 @@
 per-file package_item_info.proto = toddke@google.com,patb@google.com
 per-file usagestatsservice.proto, usagestatsservice_v2.proto = file:/core/java/android/app/usage/OWNERS
 per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS
+per-file android/hardware/sensorprivacy.proto = ntmyren@google.com,evanseverson@google.com
+per-file background_install_control.proto = wenhaowang@google.com,georgechan@google.com,billylau@google.com
 
 # Biometrics
 jaggies@google.com
diff --git a/core/res/OWNERS b/core/res/OWNERS
index a2ef400..b878189 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -21,6 +21,11 @@
 tsuji@google.com
 yamasani@google.com
 
+# WindowManager team
+# TODO(262451702): Move WindowManager configs out of config.xml in a separate file
+per-file core/res/res/values/config.xml = file:/services/core/java/com/android/server/wm/OWNERS
+per-file core/res/res/values/symbols.xml = file:/services/core/java/com/android/server/wm/OWNERS
+
 # Resources finalization
 per-file res/xml/public-staging.xml = file:/tools/aapt2/OWNERS
 per-file res/xml/public-final.xml = file:/tools/aapt2/OWNERS
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 1327d96..ea2b988 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -17,12 +17,6 @@
 <resources>
     <!-- This file defines Android telephony related resources -->
 
-    <!-- Whether force disabling telephony new data stack or not.
-         This flag and the old data stack code will be deleted in Android 14.
-    -->
-    <bool name="config_force_disable_telephony_new_data_stack">false</bool>
-    <java-symbol type="bool" name="config_force_disable_telephony_new_data_stack" />
-
     <!-- Configure tcp buffer sizes per network type in the form:
          network-type:rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max
 
diff --git a/core/tests/coretests/src/android/companion/virtual/OWNERS b/core/tests/coretests/src/android/companion/virtual/OWNERS
new file mode 100644
index 0000000..1a3e927
--- /dev/null
+++ b/core/tests/coretests/src/android/companion/virtual/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+
+include /services/companion/java/com/android/server/companion/virtual/OWNERS
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/service/controls/OWNERS b/core/tests/coretests/src/android/service/controls/OWNERS
new file mode 100644
index 0000000..bf67034
--- /dev/null
+++ b/core/tests/coretests/src/android/service/controls/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/core/java/android/service/controls/OWNERS
\ No newline at end of file
diff --git a/identity/java/android/security/identity/AuthenticationKeyMetadata.java b/identity/java/android/security/identity/AuthenticationKeyMetadata.java
new file mode 100644
index 0000000..c6abc22
--- /dev/null
+++ b/identity/java/android/security/identity/AuthenticationKeyMetadata.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.identity;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+
+import java.time.Instant;
+
+/**
+ * Data about authentication keys.
+ */
+public final class AuthenticationKeyMetadata {
+    private int mUsageCount;
+    private Instant mExpirationDate;
+
+    AuthenticationKeyMetadata(int usageCount, @NonNull Instant expirationDate) {
+        mUsageCount = usageCount;
+        mExpirationDate = expirationDate;
+    }
+
+    /**
+     * Gets usage count for the authentication key.
+     *
+     * @return the usage count
+     */
+    public @IntRange(from = 0) int getUsageCount() {
+        return mUsageCount;
+    }
+
+    /**
+     * Gets expiration date for the authentication key.
+     *
+     * @return the expiration date of the authentication key.
+     */
+    public @NonNull Instant getExpirationDate() {
+        return mExpirationDate;
+    }
+}
diff --git a/identity/java/android/security/identity/CredentialDataResult.java b/identity/java/android/security/identity/CredentialDataResult.java
index beb03af..dca039a 100644
--- a/identity/java/android/security/identity/CredentialDataResult.java
+++ b/identity/java/android/security/identity/CredentialDataResult.java
@@ -106,6 +106,30 @@
     public abstract @Nullable byte[] getDeviceMac();
 
     /**
+     * Returns a signature over the {@code DeviceAuthenticationBytes} CBOR
+     * specified in {@link #getDeviceNameSpaces()}, to prove to the reader that the data
+     * is from a trusted credential.
+     *
+     * <p>The signature is made using the authentication private key. See section 9.1.3.4 of
+     * ISO/IEC 18013-5:2021 for details of this operation.
+     *
+     * <p>If the session transcript or reader ephemeral key wasn't set on the {@link
+     * PresentationSession} used to obtain this data no signature will be produced and this method
+     * will return {@code null}.
+     *
+     * <p>This is only implemented in feature version 202301 or later. If not implemented, the call
+     * fails with {@link UnsupportedOperationException}. See
+     * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known
+     * feature versions.
+     *
+     * @return A COSE_Sign1 structure as described above or {@code null} if the conditions
+     *     specified above are not met.
+     */
+    public @Nullable byte[] getDeviceSignature() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Returns the static authentication data associated with the dynamic authentication
      * key used to MAC the data returned by {@link #getDeviceNameSpaces()}.
      *
diff --git a/identity/java/android/security/identity/CredstoreCredentialDataResult.java b/identity/java/android/security/identity/CredstoreCredentialDataResult.java
index 7afe3d4..b4fd5d3 100644
--- a/identity/java/android/security/identity/CredstoreCredentialDataResult.java
+++ b/identity/java/android/security/identity/CredstoreCredentialDataResult.java
@@ -47,6 +47,11 @@
     }
 
     @Override
+    public @Nullable byte[] getDeviceSignature() {
+        return mDeviceSignedResult.getSignature();
+    }
+
+    @Override
     public @NonNull byte[] getStaticAuthenticationData() {
         return mDeviceSignedResult.getStaticAuthenticationData();
     }
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredential.java b/identity/java/android/security/identity/CredstoreIdentityCredential.java
index 8e01105..449c7a7 100644
--- a/identity/java/android/security/identity/CredstoreIdentityCredential.java
+++ b/identity/java/android/security/identity/CredstoreIdentityCredential.java
@@ -38,8 +38,9 @@
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.Collection;
-import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 
 import javax.crypto.BadPaddingException;
@@ -59,16 +60,19 @@
     private Context mContext;
     private ICredential mBinder;
     private CredstorePresentationSession mSession;
+    private int mFeatureVersion;
 
     CredstoreIdentityCredential(Context context, String credentialName,
             @IdentityCredentialStore.Ciphersuite int cipherSuite,
             ICredential binder,
-            @Nullable CredstorePresentationSession session) {
+            @Nullable CredstorePresentationSession session,
+            int featureVersion) {
         mContext = context;
         mCredentialName = credentialName;
         mCipherSuite = cipherSuite;
         mBinder = binder;
         mSession = session;
+        mFeatureVersion = featureVersion;
     }
 
     private KeyPair mEphemeralKeyPair = null;
@@ -227,7 +231,7 @@
                 throw new RuntimeException("Error decoding certificates", e);
             }
 
-            LinkedList<X509Certificate> x509Certs = new LinkedList<>();
+            ArrayList<X509Certificate> x509Certs = new ArrayList<>();
             for (Certificate cert : certs) {
                 x509Certs.add((X509Certificate) cert);
             }
@@ -346,12 +350,18 @@
             }
         }
 
+        byte[] signature = resultParcel.signature;
+        if (signature != null && signature.length == 0) {
+            signature = null;
+        }
+
         byte[] mac = resultParcel.mac;
         if (mac != null && mac.length == 0) {
             mac = null;
         }
         CredstoreResultData.Builder resultDataBuilder = new CredstoreResultData.Builder(
-                resultParcel.staticAuthenticationData, resultParcel.deviceNameSpaces, mac);
+                mFeatureVersion, resultParcel.staticAuthenticationData,
+                resultParcel.deviceNameSpaces, mac, signature);
 
         for (ResultNamespaceParcel resultNamespaceParcel : resultParcel.resultNamespaces) {
             for (ResultEntryParcel resultEntryParcel : resultNamespaceParcel.entries) {
@@ -370,8 +380,14 @@
 
     @Override
     public void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey) {
+        setAvailableAuthenticationKeys(keyCount, maxUsesPerKey, 0);
+    }
+
+    @Override
+    public void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey,
+                                               long minValidTimeMillis) {
         try {
-            mBinder.setAvailableAuthenticationKeys(keyCount, maxUsesPerKey);
+            mBinder.setAvailableAuthenticationKeys(keyCount, maxUsesPerKey, minValidTimeMillis);
         } catch (android.os.RemoteException e) {
             throw new RuntimeException("Unexpected RemoteException ", e);
         } catch (android.os.ServiceSpecificException e) {
@@ -384,7 +400,7 @@
     public @NonNull Collection<X509Certificate> getAuthKeysNeedingCertification() {
         try {
             AuthKeyParcel[] authKeyParcels = mBinder.getAuthKeysNeedingCertification();
-            LinkedList<X509Certificate> x509Certs = new LinkedList<>();
+            ArrayList<X509Certificate> x509Certs = new ArrayList<>();
             CertificateFactory factory = CertificateFactory.getInstance("X.509");
             for (AuthKeyParcel authKeyParcel : authKeyParcels) {
                 Collection<? extends Certificate> certs = null;
@@ -471,6 +487,34 @@
     }
 
     @Override
+    public @NonNull List<AuthenticationKeyMetadata> getAuthenticationKeyMetadata() {
+        try {
+            int[] usageCount = mBinder.getAuthenticationDataUsageCount();
+            long[] expirationsMillis = mBinder.getAuthenticationDataExpirations();
+            if (usageCount.length != expirationsMillis.length) {
+                throw new IllegalStateException("Size og usageCount and expirationMillis differ");
+            }
+            List<AuthenticationKeyMetadata> mds = new ArrayList<>();
+            for (int n = 0; n < expirationsMillis.length; n++) {
+                AuthenticationKeyMetadata md = null;
+                long expirationMillis = expirationsMillis[n];
+                if (expirationMillis != Long.MAX_VALUE) {
+                    md = new AuthenticationKeyMetadata(
+                        usageCount[n],
+                        Instant.ofEpochMilli(expirationMillis));
+                }
+                mds.add(md);
+            }
+            return mds;
+        } catch (android.os.RemoteException e) {
+            throw new IllegalStateException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new IllegalStateException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+    }
+
+    @Override
     public @NonNull byte[] proveOwnership(@NonNull byte[] challenge) {
         try {
             byte[] proofOfOwnership = mBinder.proveOwnership(challenge);
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
index bbaf086..d785c3c 100644
--- a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
+++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
@@ -19,6 +19,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.security.GenerateRkpKey;
@@ -30,10 +32,28 @@
 
     private Context mContext = null;
     private ICredentialStore mStore = null;
+    private int mFeatureVersion;
+
+    static int getFeatureVersion(@NonNull Context context) {
+        PackageManager pm = context.getPackageManager();
+        if (pm.hasSystemFeature(PackageManager.FEATURE_IDENTITY_CREDENTIAL_HARDWARE)) {
+            FeatureInfo[] infos = pm.getSystemAvailableFeatures();
+            for (int n = 0; n < infos.length; n++) {
+                FeatureInfo info = infos[n];
+                if (info.name.equals(PackageManager.FEATURE_IDENTITY_CREDENTIAL_HARDWARE)) {
+                    return info.version;
+                }
+            }
+        }
+        // Use of the system feature is not required since Android 12. So for Android 11
+        // return 202009 which is the feature version shipped with Android 11.
+        return 202009;
+    }
 
     private CredstoreIdentityCredentialStore(@NonNull Context context, ICredentialStore store) {
         mContext = context;
         mStore = store;
+        mFeatureVersion = getFeatureVersion(mContext);
     }
 
     static CredstoreIdentityCredentialStore getInstanceForType(@NonNull Context context,
@@ -139,8 +159,7 @@
             ICredential credstoreCredential;
             credstoreCredential = mStore.getCredentialByName(credentialName, cipherSuite);
             return new CredstoreIdentityCredential(mContext, credentialName, cipherSuite,
-                    credstoreCredential,
-                    null);
+                    credstoreCredential, null, mFeatureVersion);
         } catch (android.os.RemoteException e) {
             throw new RuntimeException("Unexpected RemoteException ", e);
         } catch (android.os.ServiceSpecificException e) {
@@ -182,7 +201,8 @@
             throws CipherSuiteNotSupportedException {
         try {
             ISession credstoreSession = mStore.createPresentationSession(cipherSuite);
-            return new CredstorePresentationSession(mContext, cipherSuite, this, credstoreSession);
+            return new CredstorePresentationSession(mContext, cipherSuite, this, credstoreSession,
+                                                    mFeatureVersion);
         } catch (android.os.RemoteException e) {
             throw new RuntimeException("Unexpected RemoteException ", e);
         } catch (android.os.ServiceSpecificException e) {
diff --git a/identity/java/android/security/identity/CredstorePresentationSession.java b/identity/java/android/security/identity/CredstorePresentationSession.java
index e3c6689..96bda52 100644
--- a/identity/java/android/security/identity/CredstorePresentationSession.java
+++ b/identity/java/android/security/identity/CredstorePresentationSession.java
@@ -48,15 +48,18 @@
     private byte[] mSessionTranscript = null;
     private boolean mOperationHandleSet = false;
     private long mOperationHandle = 0;
+    private int mFeatureVersion = 0;
 
     CredstorePresentationSession(Context context,
             @IdentityCredentialStore.Ciphersuite int cipherSuite,
             CredstoreIdentityCredentialStore store,
-            ISession binder) {
+            ISession binder,
+            int featureVersion) {
         mContext = context;
         mCipherSuite = cipherSuite;
         mStore = store;
         mBinder = binder;
+        mFeatureVersion = featureVersion;
     }
 
     private void ensureEphemeralKeyPair() {
@@ -147,7 +150,7 @@
                     mBinder.getCredentialForPresentation(credentialName);
                 credential = new CredstoreIdentityCredential(mContext, credentialName,
                                                              mCipherSuite, credstoreCredential,
-                                                             this);
+                                                             this, mFeatureVersion);
                 mCredentialCache.put(credentialName, credential);
 
                 credential.setAllowUsingExhaustedKeys(request.isAllowUsingExhaustedKeys());
diff --git a/identity/java/android/security/identity/CredstoreResultData.java b/identity/java/android/security/identity/CredstoreResultData.java
index 2ef735e..57c0436 100644
--- a/identity/java/android/security/identity/CredstoreResultData.java
+++ b/identity/java/android/security/identity/CredstoreResultData.java
@@ -30,10 +30,11 @@
  * data requested from a {@link IdentityCredential}.
  */
 class CredstoreResultData extends ResultData {
-
+    int mFeatureVersion = 0;
     byte[] mStaticAuthenticationData = null;
     byte[] mAuthenticatedData = null;
     byte[] mMessageAuthenticationCode = null;
+    byte[] mSignature = null;
 
     private Map<String, Map<String, EntryData>> mData = new LinkedHashMap<>();
 
@@ -61,6 +62,14 @@
     }
 
     @Override
+    @Nullable byte[] getSignature() {
+        if (mFeatureVersion < 202301) {
+            throw new UnsupportedOperationException();
+        }
+        return mSignature;
+    }
+
+    @Override
     public @NonNull byte[] getStaticAuthenticationData() {
         return mStaticAuthenticationData;
     }
@@ -124,13 +133,17 @@
     static class Builder {
         private CredstoreResultData mResultData;
 
-        Builder(byte[] staticAuthenticationData,
+        Builder(int featureVersion,
+                byte[] staticAuthenticationData,
                 byte[] authenticatedData,
-                byte[] messageAuthenticationCode) {
+                byte[] messageAuthenticationCode,
+                byte[] signature) {
             this.mResultData = new CredstoreResultData();
+            this.mResultData.mFeatureVersion = featureVersion;
             this.mResultData.mStaticAuthenticationData = staticAuthenticationData;
             this.mResultData.mAuthenticatedData = authenticatedData;
             this.mResultData.mMessageAuthenticationCode = messageAuthenticationCode;
+            this.mResultData.mSignature = signature;
         }
 
         private Map<String, EntryData> getOrCreateInnerMap(String namespaceName) {
diff --git a/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java b/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java
index d2e7984..1ad70ed 100644
--- a/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java
+++ b/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java
@@ -25,8 +25,9 @@
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
 import java.util.Collection;
-import java.util.LinkedList;
+import java.util.List;
 
 class CredstoreWritableIdentityCredential extends WritableIdentityCredential {
 
@@ -61,7 +62,7 @@
                 throw new RuntimeException("Error decoding certificates", e);
             }
 
-            LinkedList<X509Certificate> x509Certs = new LinkedList<>();
+            ArrayList<X509Certificate> x509Certs = new ArrayList<>();
             for (Certificate cert : certs) {
                 x509Certs.add((X509Certificate) cert);
             }
diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java
index f440b69..2dd9778 100644
--- a/identity/java/android/security/identity/IdentityCredential.java
+++ b/identity/java/android/security/identity/IdentityCredential.java
@@ -16,6 +16,7 @@
 
 package android.security.identity;
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
@@ -25,6 +26,7 @@
 import java.security.cert.X509Certificate;
 import java.time.Instant;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -236,14 +238,15 @@
      *   IntentToRetain = bool
      * </pre>
      *
-     * <p>If the {@code sessionTranscript} parameter is not {@code null}, the X and Y coordinates
-     * of the public part of the key-pair previously generated by {@link #createEphemeralKeyPair()}
-     * must appear somewhere in the bytes of the CBOR. Each of these coordinates must appear
-     * encoded with the most significant bits first and use the exact amount of bits indicated by
-     * the key size of the ephemeral keys. For example, if the ephemeral key is using the P-256
-     * curve then the 32 bytes for the X coordinate encoded with the most significant bits first
-     * must appear somewhere in {@code sessionTranscript} and ditto for the 32 bytes for the Y
-     * coordinate.
+     * <p>If mdoc session encryption is used (e.g. if {@link #createEphemeralKeyPair()} has been
+     * called) and if the {@code sessionTranscript} parameter is not {@code null}, the X and Y
+     * coordinates of the public part of the key-pair previously generated by
+     * {@link #createEphemeralKeyPair()} must appear somewhere in the bytes of the CBOR. Each of
+     * these coordinates must appear encoded with the most significant bits first and use the
+     * exact amount of bits indicated by the key size of the ephemeral keys. For example, if the
+     * ephemeral key is using the P-256 curve then the 32 bytes for the X coordinate encoded with
+     * the most significant bits first must appear somewhere in {@code sessionTranscript} and
+     * ditto for the 32 bytes for the Y coordinate.
      *
      * <p>If {@code readerSignature} is not {@code null} it must be the bytes of a
      * {@code COSE_Sign1} structure as defined in RFC 8152. For the payload nil shall be used and
@@ -296,7 +299,7 @@
      *                                                session transcripts.
      * @throws NoAuthenticationKeyAvailableException  if authentication keys were never
      *                                                provisioned, the method
-     *                                             {@link #setAvailableAuthenticationKeys(int, int)}
+     *                                      {@link #setAvailableAuthenticationKeys(int, int, long)}
      *                                                was called with {@code keyCount} set to 0,
      *                                                the method
      *                                                {@link #setAllowUsingExhaustedKeys(boolean)}
@@ -330,19 +333,25 @@
      * for which this method has not been called behave as though it had been called wit
      * {@code keyCount} 0 and {@code maxUsesPerKey} 1.
      *
+     * <p>The effect of this method is like calling
+     * {@link #setAvailableAuthenticationKeys(int, int, long)} with the last parameter is set to 0.
+     *
      * @param keyCount      The number of active, certified dynamic authentication keys the
      *                      {@code IdentityCredential} will try to keep available. This value
      *                      must be non-negative.
      * @param maxUsesPerKey The maximum number of times each of the keys will be used before it's
      *                      eligible for replacement. This value must be greater than zero.
+     * @deprecated Use {@link #setAvailableAuthenticationKeys(int, int, long)} instead.
      */
+    @Deprecated
     public abstract void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey);
 
     /**
      * Gets a collection of dynamic authentication keys that need certification.
      *
      * <p>When there aren't enough certified dynamic authentication keys, either because the key
-     * count has been increased or because one or more keys have reached their usage count, this
+     * count has been increased or because one or more keys have reached their usage count or
+     * it if a key is too close to its expiration date, this
      * method will generate replacement keys and certificates and return them for issuer
      * certification.  The issuer certificates and associated static authentication data must then
      * be provided back to the Identity Credential using
@@ -400,11 +409,6 @@
      * This should only be called for an authenticated key returned by
      * {@link #getAuthKeysNeedingCertification()}.
      *
-     * <p>This is only implemented in feature version 202101 or later. If not implemented, the call
-     * fails with {@link UnsupportedOperationException}. See
-     * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known
-     * feature versions.
-     *
      * @param authenticationKey The dynamic authentication key for which certification and
      *                          associated static
      *                          authentication data is being provided.
@@ -426,7 +430,9 @@
      * Get the number of times the dynamic authentication keys have been used.
      *
      * @return int array of dynamic authentication key usage counts.
+     * @deprecated Use {@link #getAuthenticationKeyMetadata()} instead.
      */
+    @Deprecated
     public @NonNull abstract int[] getAuthenticationDataUsageCount();
 
     /**
@@ -519,4 +525,47 @@
     public @NonNull byte[] update(@NonNull PersonalizationData personalizationData) {
         throw new UnsupportedOperationException();
     }
+
+    /**
+     * Sets the number of dynamic authentication keys the {@code IdentityCredential} will maintain,
+     * the number of times each should be used, and the minimum amount of time it's valid for.
+     *
+     * <p>The Identity Credential system will select the least-used dynamic authentication key each
+     * time {@link #getEntries(byte[], Map, byte[], byte[])} is called. Identity Credentials
+     * for which this method has not been called behave as though it had been called wit
+     * {@code keyCount} 0, {@code maxUsesPerKey} 1, and {@code minValidTimeMillis} 0.
+     *
+     * <p>Applications can use {@link #getAuthenticationKeyMetadata()} to get a picture of the
+     * usage andtime left of each configured authentication key. This can be used to determine
+     * how urgent it is recertify new authentication keys via the
+     * {@link #getAuthKeysNeedingCertification()} method.
+     *
+     * @param keyCount      The number of active, certified dynamic authentication keys the
+     *                      {@code IdentityCredential} will try to keep available. This value
+     *                      must be non-negative.
+     * @param maxUsesPerKey The maximum number of times each of the keys will be used before it's
+     *                      eligible for replacement. This value must be greater than zero.
+     * @param minValidTimeMillis If a key has less time left than this value it will be eliglible
+     *                           for replacement. This value must be non-negative.
+     */
+    public void setAvailableAuthenticationKeys(
+            @IntRange(from = 0) int keyCount,
+            @IntRange(from = 1) int maxUsesPerKey,
+            @IntRange(from = 0) long minValidTimeMillis) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Get information about dynamic authentication keys.
+     *
+     * <p>The returned list may have <code>null</code> values if certification for the dynamic
+     * authentication key is pending.
+     *
+     * <p>The list is always <code>keyCount</code> elements long.
+     *
+     * @return list of authentication key metadata objects.
+     */
+    public @NonNull List<AuthenticationKeyMetadata> getAuthenticationKeyMetadata() {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/identity/java/android/security/identity/PersonalizationData.java b/identity/java/android/security/identity/PersonalizationData.java
index b34f250..bdb00fdf 100644
--- a/identity/java/android/security/identity/PersonalizationData.java
+++ b/identity/java/android/security/identity/PersonalizationData.java
@@ -18,10 +18,11 @@
 
 import android.annotation.NonNull;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashMap;
-import java.util.LinkedList;
+import java.util.List;
 
 /**
  * An object that holds personalization data.
@@ -38,7 +39,7 @@
     private PersonalizationData() {
     }
 
-    private LinkedList<AccessControlProfile> mProfiles = new LinkedList<>();
+    private ArrayList<AccessControlProfile> mProfiles = new ArrayList<>();
 
     private LinkedHashMap<String, NamespaceData> mNamespaces = new LinkedHashMap<>();
 
diff --git a/identity/java/android/security/identity/PresentationSession.java b/identity/java/android/security/identity/PresentationSession.java
index 6cde611..f392e12 100644
--- a/identity/java/android/security/identity/PresentationSession.java
+++ b/identity/java/android/security/identity/PresentationSession.java
@@ -73,7 +73,8 @@
      * <p>If called, this must be called before any calls to
      * {@link #getCredentialData(String, CredentialDataRequest)}.
      *
-     * <p>The X and Y coordinates of the public part of the key-pair returned by {@link
+     * <p>If mdoc session encryption is used (e.g. if {@link #getEphemeralKeyPair()} has been
+     * called) then the X and Y coordinates of the public part of the key-pair returned by {@link
      * #getEphemeralKeyPair()} must appear somewhere in the bytes of the passed in CBOR.  Each of
      * these coordinates must appear encoded with the most significant bits first and use the exact
      * amount of bits indicated by the key size of the ephemeral keys. For example, if the
diff --git a/identity/java/android/security/identity/ResultData.java b/identity/java/android/security/identity/ResultData.java
index d46f985..8a0e56e 100644
--- a/identity/java/android/security/identity/ResultData.java
+++ b/identity/java/android/security/identity/ResultData.java
@@ -134,6 +134,10 @@
      */
     public abstract @Nullable byte[] getMessageAuthenticationCode();
 
+    @Nullable byte[] getSignature() {
+        throw new UnsupportedOperationException();
+    }
+
     /**
      * Returns the static authentication data associated with the dynamic authentication
      * key used to sign or MAC the data returned by {@link #getAuthenticatedData()}.
diff --git a/media/java/android/media/DeniedByServerException.java b/media/java/android/media/DeniedByServerException.java
index 9c1633a..98903ec 100644
--- a/media/java/android/media/DeniedByServerException.java
+++ b/media/java/android/media/DeniedByServerException.java
@@ -24,4 +24,11 @@
     public DeniedByServerException(String detailMessage) {
         super(detailMessage);
     }
+
+    /**
+     * @hide
+     */
+    public DeniedByServerException(String message, int vendorError, int oemError, int context) {
+        super(message, vendorError, oemError, context);
+    }
 }
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 220232d..8e8ed6c 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -2472,10 +2472,22 @@
     /**
      * Thrown when a crypto error occurs while queueing a secure input buffer.
      */
-    public final static class CryptoException extends RuntimeException {
+    public final static class CryptoException extends RuntimeException
+            implements MediaDrmThrowable {
         public CryptoException(int errorCode, @Nullable String detailMessage) {
-            super(detailMessage);
+            this(detailMessage, errorCode, 0, 0, 0);
+        }
+
+        /**
+         * @hide
+         */
+        public CryptoException(String message, int errorCode, int vendorError, int oemError,
+                int errorContext) {
+            super(message);
             mErrorCode = errorCode;
+            mVendorError = vendorError;
+            mOemError = oemError;
+            mErrorContext = errorContext;
         }
 
         /**
@@ -2594,7 +2606,22 @@
             return mErrorCode;
         }
 
-        private int mErrorCode;
+        @Override
+        public int getVendorError() {
+            return mVendorError;
+        }
+
+        @Override
+        public int getOemError() {
+            return mOemError;
+        }
+
+        @Override
+        public int getErrorContext() {
+            return mErrorContext;
+        }
+
+        private final int mErrorCode, mVendorError, mOemError, mErrorContext;
     }
 
     /**
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 30d90a8..55de0b3 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -981,8 +981,20 @@
                     continue;
                 }
 
-                // AAC does not use levels
-                if (level == null || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) {
+                // No specific level requested
+                if (level == null) {
+                    return true;
+                }
+
+                // AAC doesn't use levels
+                if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) {
+                    return true;
+                }
+
+                // DTS doesn't use levels
+                if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS)
+                        || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_HD)
+                        || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_UHD)) {
                     return true;
                 }
 
@@ -1410,6 +1422,7 @@
             int[] sampleRates = null;
             Range<Integer> sampleRateRange = null, bitRates = null;
             int maxChannels = MAX_INPUT_CHANNEL_COUNT;
+            CodecProfileLevel[] profileLevels = mParent.profileLevels;
             String mime = mParent.getMimeType();
 
             if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG)) {
@@ -1473,6 +1486,53 @@
                 sampleRates = new int[] { 44100, 48000, 96000, 192000 };
                 bitRates = Range.create(16000, 2688000);
                 maxChannels = 24;
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS)) {
+                sampleRates = new int[] { 44100, 48000 };
+                bitRates = Range.create(96000, 1524000);
+                maxChannels = 6;
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_HD)) {
+                for (CodecProfileLevel profileLevel: profileLevels) {
+                    switch (profileLevel.profile) {
+                        case CodecProfileLevel.DTS_HDProfileLBR:
+                            sampleRates = new int[]{ 22050, 24000, 44100, 48000 };
+                            bitRates = Range.create(32000, 768000);
+                            break;
+                        case CodecProfileLevel.DTS_HDProfileHRA:
+                        case CodecProfileLevel.DTS_HDProfileMA:
+                            sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
+                            bitRates = Range.create(96000, 24500000);
+                            break;
+                        default:
+                            Log.w(TAG, "Unrecognized profile "
+                                    + profileLevel.profile + " for " + mime);
+                            mParent.mError |= ERROR_UNRECOGNIZED;
+                            sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
+                            bitRates = Range.create(96000, 24500000);
+                    }
+                }
+                maxChannels = 8;
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_UHD)) {
+                for (CodecProfileLevel profileLevel: profileLevels) {
+                    switch (profileLevel.profile) {
+                        case CodecProfileLevel.DTS_UHDProfileP2:
+                            sampleRates = new int[]{ 48000 };
+                            bitRates = Range.create(96000, 768000);
+                            maxChannels = 10;
+                            break;
+                        case CodecProfileLevel.DTS_UHDProfileP1:
+                            sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
+                            bitRates = Range.create(96000, 24500000);
+                            maxChannels = 32;
+                            break;
+                        default:
+                            Log.w(TAG, "Unrecognized profile "
+                                    + profileLevel.profile + " for " + mime);
+                            mParent.mError |= ERROR_UNRECOGNIZED;
+                            sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
+                            bitRates = Range.create(96000, 24500000);
+                            maxChannels = 32;
+                    }
+                }
             } else {
                 Log.w(TAG, "Unsupported mime " + mime);
                 mParent.mError |= ERROR_UNSUPPORTED;
diff --git a/media/java/android/media/MediaCryptoException.java b/media/java/android/media/MediaCryptoException.java
index 32ddf47..2a472fb 100644
--- a/media/java/android/media/MediaCryptoException.java
+++ b/media/java/android/media/MediaCryptoException.java
@@ -22,8 +22,35 @@
  * Exception thrown if MediaCrypto object could not be instantiated or
  * if unable to perform an operation on the MediaCrypto object.
  */
-public final class MediaCryptoException extends Exception {
+public final class MediaCryptoException extends Exception implements MediaDrmThrowable {
     public MediaCryptoException(@Nullable String detailMessage) {
-        super(detailMessage);
+        this(detailMessage, 0, 0, 0);
     }
+
+    /**
+     * @hide
+     */
+    public MediaCryptoException(String message, int vendorError, int oemError, int errorContext) {
+        super(message);
+        mVendorError = vendorError;
+        mOemError = oemError;
+        mErrorContext = errorContext;
+    }
+
+    @Override
+    public int getVendorError() {
+        return mVendorError;
+    }
+
+    @Override
+    public int getOemError() {
+        return mOemError;
+    }
+
+    @Override
+    public int getErrorContext() {
+        return mErrorContext;
+    }
+
+    private final int mVendorError, mOemError, mErrorContext;
 }
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 2a04ebb..a3fa43d 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -664,21 +664,33 @@
      * strategy and details about each possible return value from {@link
      * MediaDrmStateException#getErrorCode()}.
      */
-    public static final class MediaDrmStateException extends java.lang.IllegalStateException {
-        private final int mErrorCode;
+    public static final class MediaDrmStateException extends java.lang.IllegalStateException
+            implements MediaDrmThrowable {
+        private final int mErrorCode, mVendorError, mOemError, mErrorContext;
         private final String mDiagnosticInfo;
 
         /**
          * @hide
          */
         public MediaDrmStateException(int errorCode, @Nullable String detailMessage) {
+            this(detailMessage, errorCode, 0, 0, 0);
+        }
+
+        /**
+         * @hide
+         */
+        public MediaDrmStateException(String detailMessage, int errorCode,
+                int vendorError, int oemError, int errorContext) {
             super(detailMessage);
             mErrorCode = errorCode;
+            mVendorError = vendorError;
+            mOemError = oemError;
+            mErrorContext = errorContext;
 
             // TODO get this from DRM session
             final String sign = errorCode < 0 ? "neg_" : "";
             mDiagnosticInfo =
-                "android.media.MediaDrm.error_" + sign + Math.abs(errorCode);
+                    "android.media.MediaDrm.error_" + sign + Math.abs(errorCode);
 
         }
 
@@ -696,6 +708,21 @@
             return mErrorCode;
         }
 
+        @Override
+        public int getVendorError() {
+            return mVendorError;
+        }
+
+        @Override
+        public int getOemError() {
+            return mOemError;
+        }
+
+        @Override
+        public int getErrorContext() {
+            return mErrorContext;
+        }
+
         /**
          * Returns true if the {@link MediaDrmStateException} is a transient
          * issue, perhaps due to resource constraints, and that the operation
@@ -727,10 +754,22 @@
      * {@link #isTransient()} to determine whether the app should retry the
      * failing operation.
      */
-    public static final class SessionException extends RuntimeException {
+    public static final class SessionException extends RuntimeException
+            implements MediaDrmThrowable {
         public SessionException(int errorCode, @Nullable String detailMessage) {
+            this(detailMessage, errorCode, 0, 0, 0);
+        }
+
+        /**
+         * @hide
+         */
+        public SessionException(String detailMessage, int errorCode, int vendorError, int oemError,
+                int errorContext) {
             super(detailMessage);
             mErrorCode = errorCode;
+            mVendorError = vendorError;
+            mOemError = oemError;
+            mErrorContext = errorContext;
         }
 
         /**
@@ -769,6 +808,21 @@
             return mErrorCode;
         }
 
+        @Override
+        public int getVendorError() {
+            return mVendorError;
+        }
+
+        @Override
+        public int getOemError() {
+            return mOemError;
+        }
+
+        @Override
+        public int getErrorContext() {
+            return mErrorContext;
+        }
+
         /**
          * Returns true if the {@link SessionException} is a transient
          * issue, perhaps due to resource constraints, and that the operation
@@ -779,7 +833,7 @@
             return mErrorCode == ERROR_RESOURCE_CONTENTION;
         }
 
-        private final int mErrorCode;
+        private final int mErrorCode, mVendorError, mOemError, mErrorContext;
     }
 
     /**
diff --git a/media/java/android/media/MediaDrmException.java b/media/java/android/media/MediaDrmException.java
index d547574..58c5dd0 100644
--- a/media/java/android/media/MediaDrmException.java
+++ b/media/java/android/media/MediaDrmException.java
@@ -19,8 +19,35 @@
 /**
  * Base class for MediaDrm exceptions
  */
-public class MediaDrmException extends Exception {
+public class MediaDrmException extends Exception implements MediaDrmThrowable {
     public MediaDrmException(String detailMessage) {
-        super(detailMessage);
+        this(detailMessage, 0, 0, 0);
     }
+
+    /**
+     * @hide
+     */
+    public MediaDrmException(String message, int vendorError, int oemError, int errorContext) {
+        super(message);
+        mVendorError = vendorError;
+        mOemError = oemError;
+        mErrorContext = errorContext;
+    }
+
+    @Override
+    public int getVendorError() {
+        return mVendorError;
+    }
+
+    @Override
+    public int getOemError() {
+        return mOemError;
+    }
+
+    @Override
+    public int getErrorContext() {
+        return mErrorContext;
+    }
+
+    private final int mVendorError, mOemError, mErrorContext;
 }
diff --git a/media/java/android/media/MediaDrmResetException.java b/media/java/android/media/MediaDrmResetException.java
index 3b2da1e..ccd723b 100644
--- a/media/java/android/media/MediaDrmResetException.java
+++ b/media/java/android/media/MediaDrmResetException.java
@@ -21,7 +21,7 @@
  * due to a restart of the mediaserver process.  To continue, the app must
  * release the MediaDrm object, then create and initialize a new one.
  */
-public class MediaDrmResetException extends IllegalStateException {
+public class MediaDrmResetException extends IllegalStateException implements MediaDrmThrowable {
     public MediaDrmResetException(String detailMessage) {
         super(detailMessage);
     }
diff --git a/media/java/android/media/MediaDrmThrowable.java b/media/java/android/media/MediaDrmThrowable.java
new file mode 100644
index 0000000..38480d7
--- /dev/null
+++ b/media/java/android/media/MediaDrmThrowable.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+/**
+ * A @{@link Throwable} thrown from {@link MediaDrm} or @{@link MediaCrypto} APIs
+ */
+public interface MediaDrmThrowable {
+    /**
+     * Returns {@link MediaDrm} plugin vendor defined error code associated with this {@link
+     * MediaDrmThrowable}.
+     * <p>
+     * Please consult the {@link MediaDrm} plugin vendor for details on the error code.
+     *
+     * @return an error code defined by the {@link MediaDrm} plugin vendor if available,
+     * otherwise 0.
+     */
+    public default int getVendorError() {
+        return 0;
+    }
+
+    /**
+     * Returns OEM or SOC specific error code associated with this {@link
+     * MediaDrmThrowable}.
+     * <p>
+     * Please consult the {@link MediaDrm} plugin, chip, or device vendor for details on the
+     * error code.
+     *
+     * @return an OEM or SOC specific error code if available, otherwise 0.
+     */
+    public default int getOemError() {
+        return 0;
+    }
+
+    /**
+     * Returns {@link MediaDrm} plugin vendor defined error context associated with this {@link
+     * MediaDrmThrowable}.
+     * <p>
+     * Please consult the {@link MediaDrm} plugin vendor for details on the error context.
+     *
+     * @return an opaque integer that would help the @{@link MediaDrm} vendor locate the
+     * source of the error if available, otherwise 0.
+     */
+    public default int getErrorContext() {
+        return 0;
+    }
+
+}
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index be9862b..b51c018 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -174,11 +174,20 @@
     public static final String MIMETYPE_AUDIO_MPEGH_MHA1 = "audio/mha1";
     /** MIME type for MPEG-H Audio single stream, encapsulated in MHAS */
     public static final String MIMETYPE_AUDIO_MPEGH_MHM1 = "audio/mhm1";
-    /** MIME type for DTS (up to 5.1 channels) audio stream. */
+    /** MIME type for DTS Digital Surround (up to 5.1 channels) audio stream, aka DTS-CA. */
     public static final String MIMETYPE_AUDIO_DTS = "audio/vnd.dts";
-    /** MIME type for DTS HD (up to 7.1 channels) audio stream. */
+    /**
+     * MIME type for DTS HD (up to 7.1 channels) audio stream.
+     * With codec profile DTS_HDProfileHRA represents DTS HD High Resolution Audio.
+     * With codec profile DTS_HDProfileMA represents DTS HD Master Audio.
+     * With codec profile DTS_HDProfileLBR represents DTS Express.
+     */
     public static final String MIMETYPE_AUDIO_DTS_HD = "audio/vnd.dts.hd";
-    /** MIME type for DTS UHD (object-based) audio stream. */
+    /**
+     * MIME type for DTS UHD (object-based) audio stream, aka DTS:X.
+     * With codec profile DTS_UHDProfileP1 represents DTS-UHD P1.
+     * With codec profile DTS_UHDProfileP2 represents DTS-UHD P2.
+     */
     public static final String MIMETYPE_AUDIO_DTS_UHD = "audio/vnd.dts.uhd";
     /** MIME type for Dynamic Resolution Adaptation (DRA) audio stream. */
     public static final String MIMETYPE_AUDIO_DRA = "audio/vnd.dra";
diff --git a/media/java/android/media/NotProvisionedException.java b/media/java/android/media/NotProvisionedException.java
index 32b8151..4b5a816 100644
--- a/media/java/android/media/NotProvisionedException.java
+++ b/media/java/android/media/NotProvisionedException.java
@@ -26,4 +26,11 @@
     public NotProvisionedException(String detailMessage) {
         super(detailMessage);
     }
+
+    /**
+     * @hide
+     */
+    public NotProvisionedException(String message, int vendorError, int oemError, int context) {
+        super(message, vendorError, oemError, context);
+    }
 }
diff --git a/media/java/android/media/ResourceBusyException.java b/media/java/android/media/ResourceBusyException.java
index a5abe21..7aaf7eb 100644
--- a/media/java/android/media/ResourceBusyException.java
+++ b/media/java/android/media/ResourceBusyException.java
@@ -24,4 +24,11 @@
     public ResourceBusyException(String detailMessage) {
         super(detailMessage);
     }
+
+    /**
+     * @hide
+     */
+    public ResourceBusyException(String message, int vendorError, int oemError, int context) {
+        super(message, vendorError, oemError, context);
+    }
 }
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 1183ca3..d8705a7 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -1285,7 +1285,7 @@
     CHECK(clazz.get() != NULL);
 
     jmethodID constructID =
-        env->GetMethodID(clazz.get(), "<init>", "(ILjava/lang/String;)V");
+        env->GetMethodID(clazz.get(), "<init>", "(Ljava/lang/String;IIII)V");
     CHECK(constructID != NULL);
 
     std::string defaultMsg = "Unknown Error";
@@ -1335,14 +1335,14 @@
             break;
     }
 
-    std::string msgStr(msg != NULL ? msg : defaultMsg.c_str());
-    if (crypto != NULL) {
-        msgStr = DrmUtils::GetExceptionMessage(err, msgStr.c_str(), crypto);
-    }
-    jstring msgObj = env->NewStringUTF(msgStr.c_str());
+    std::string originalMsg(msg != NULL ? msg : defaultMsg.c_str());
+    DrmStatus dStatus(err, originalMsg.c_str());
+    std::string detailedMsg(DrmUtils::GetExceptionMessage(dStatus, defaultMsg.c_str(), crypto));
+    jstring msgObj = env->NewStringUTF(detailedMsg.c_str());
 
     jthrowable exception =
-        (jthrowable)env->NewObject(clazz.get(), constructID, jerr, msgObj);
+        (jthrowable)env->NewObject(clazz.get(), constructID, msgObj, jerr,
+                                   dStatus.getCdmErr(), dStatus.getOemErr(), dStatus.getContext());
 
     env->Throw(exception);
 }
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 2f4dd8f..681d76a 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -244,6 +244,20 @@
     }
     return arrayList;
 }
+
+int drmThrowException(JNIEnv* env, const char *className, const DrmStatus &err, const char *msg) {
+    using namespace android::jnihelp;
+    jstring _detailMessage = CreateExceptionMsg(env, msg);
+    int _status = ThrowException(env, className, "(Ljava/lang/String;III)V",
+                                 _detailMessage,
+                                 err.getCdmErr(),
+                                 err.getOemErr(),
+                                 err.getContext());
+    if (_detailMessage != NULL) {
+        env->DeleteLocalRef(_detailMessage);
+    }
+    return _status;
+}
 }  // namespace anonymous
 
 // ----------------------------------------------------------------------------
@@ -388,18 +402,18 @@
     return static_cast<jint>(err);
 }
 
-static void throwStateException(JNIEnv *env, const char *msg, status_t err) {
-    ALOGE("Illegal state exception: %s (%d)", msg, err);
+static void throwStateException(JNIEnv *env, const char *msg, const DrmStatus &err) {
+    ALOGE("Illegal state exception: %s (%d)", msg, static_cast<status_t>(err));
 
     jint jerr = MediaErrorToJavaError(err);
     jobject exception = env->NewObject(gFields.stateException.classId,
-            gFields.stateException.init, static_cast<int>(jerr),
-            env->NewStringUTF(msg));
+            gFields.stateException.init, env->NewStringUTF(msg), static_cast<int>(jerr),
+            err.getCdmErr(), err.getOemErr(), err.getContext());
     env->Throw(static_cast<jthrowable>(exception));
 }
 
-static void throwSessionException(JNIEnv *env, const char *msg, status_t err) {
-    ALOGE("Session exception: %s (%d)", msg, err);
+static void throwSessionException(JNIEnv *env, const char *msg, const DrmStatus &err) {
+    ALOGE("Session exception: %s (%d)", msg, static_cast<status_t>(err));
 
     jint jErrorCode = 0;
     switch(err) {
@@ -411,10 +425,13 @@
     }
 
     jobject exception = env->NewObject(gFields.sessionException.classId,
-            gFields.sessionException.init, static_cast<int>(err),
-            env->NewStringUTF(msg));
+            gFields.sessionException.init,
+            env->NewStringUTF(msg),
+            jErrorCode,
+            err.getCdmErr(),
+            err.getOemErr(),
+            err.getContext());
 
-    env->SetIntField(exception, gFields.sessionException.errorCode, jErrorCode);
     env->Throw(static_cast<jthrowable>(exception));
 }
 
@@ -423,7 +440,7 @@
 }
 
 static bool throwExceptionAsNecessary(
-        JNIEnv *env, const sp<IDrm> &drm, status_t err, const char *msg = NULL) {
+        JNIEnv *env, const sp<IDrm> &drm, const DrmStatus &err, const char *msg = NULL) {
     std::string msgStr;
     if (drm != NULL && err != OK) {
         msgStr = DrmUtils::GetExceptionMessage(err, msg, drm);
@@ -437,13 +454,13 @@
         jniThrowException(env, "java/lang/UnsupportedOperationException", msg);
         return true;
     } else if (err == ERROR_DRM_NOT_PROVISIONED) {
-        jniThrowException(env, "android/media/NotProvisionedException", msg);
+        drmThrowException(env, "android/media/NotProvisionedException", err, msg);
         return true;
     } else if (err == ERROR_DRM_RESOURCE_BUSY) {
-        jniThrowException(env, "android/media/ResourceBusyException", msg);
+        drmThrowException(env, "android/media/ResourceBusyException", err, msg);
         return true;
     } else if (err == ERROR_DRM_DEVICE_REVOKED) {
-        jniThrowException(env, "android/media/DeniedByServerException", msg);
+        drmThrowException(env, "android/media/DeniedByServerException", err, msg);
         return true;
     } else if (err == DEAD_OBJECT) {
         jniThrowException(env, "android/media/MediaDrmResetException", msg);
@@ -493,7 +510,7 @@
         return NULL;
     }
 
-    status_t err = drm->createPlugin(uuid, appPackageName);
+    DrmStatus err = drm->createPlugin(uuid, appPackageName);
 
     if (err != OK) {
         return NULL;
@@ -915,11 +932,11 @@
     gFields.arraylistClassId = static_cast<jclass>(env->NewGlobalRef(clazz));
 
     FIND_CLASS(clazz, "android/media/MediaDrm$MediaDrmStateException");
-    GET_METHOD_ID(gFields.stateException.init, clazz, "<init>", "(ILjava/lang/String;)V");
+    GET_METHOD_ID(gFields.stateException.init, clazz, "<init>", "(Ljava/lang/String;IIII)V");
     gFields.stateException.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
 
     FIND_CLASS(clazz, "android/media/MediaDrm$SessionException");
-    GET_METHOD_ID(gFields.sessionException.init, clazz, "<init>", "(ILjava/lang/String;)V");
+    GET_METHOD_ID(gFields.sessionException.init, clazz, "<init>", "(Ljava/lang/String;IIII)V");
     gFields.sessionException.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
     GET_FIELD_ID(gFields.sessionException.errorCode, clazz, "mErrorCode", "I");
 
@@ -1062,7 +1079,7 @@
         return NULL;
     }
 
-    status_t err = drm->openSession(level, sessionId);
+    DrmStatus err = drm->openSession(level, sessionId);
 
     if (throwExceptionAsNecessary(env, drm, err, "Failed to open session")) {
         return NULL;
@@ -1081,7 +1098,7 @@
 
     Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId));
 
-    status_t err = drm->closeSession(sessionId);
+    DrmStatus err = drm->closeSession(sessionId);
 
     throwExceptionAsNecessary(env, drm, err, "Failed to close session");
 }
@@ -1133,8 +1150,8 @@
     String8 defaultUrl;
     DrmPlugin::KeyRequestType keyRequestType;
 
-    status_t err = drm->getKeyRequest(sessionId, initData, mimeType,
-            keyType, optParams, request, defaultUrl, &keyRequestType);
+    DrmStatus err = drm->getKeyRequest(sessionId, initData, mimeType, keyType, optParams, request,
+                                       defaultUrl, &keyRequestType);
 
     if (throwExceptionAsNecessary(env, drm, err, "Failed to get key request")) {
         return NULL;
@@ -1204,7 +1221,7 @@
     Vector<uint8_t> response(JByteArrayToVector(env, jresponse));
     Vector<uint8_t> keySetId;
 
-    status_t err = drm->provideKeyResponse(sessionId, response, keySetId);
+    DrmStatus err = drm->provideKeyResponse(sessionId, response, keySetId);
 
     if (throwExceptionAsNecessary(env, drm, err, "Failed to handle key response")) {
         return NULL;
@@ -1228,7 +1245,7 @@
 
     Vector<uint8_t> keySetId(JByteArrayToVector(env, jkeysetId));
 
-    status_t err = drm->removeKeys(keySetId);
+    DrmStatus err = drm->removeKeys(keySetId);
 
     throwExceptionAsNecessary(env, drm, err, "Failed to remove keys");
 }
@@ -1251,7 +1268,7 @@
     Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId));
     Vector<uint8_t> keySetId(JByteArrayToVector(env, jkeysetId));
 
-    status_t err = drm->restoreKeys(sessionId, keySetId);
+    DrmStatus err = drm->restoreKeys(sessionId, keySetId);
 
     throwExceptionAsNecessary(env, drm, err, "Failed to restore keys");
 }
@@ -1267,7 +1284,7 @@
 
     KeyedVector<String8, String8> infoMap;
 
-    status_t err = drm->queryKeyStatus(sessionId, infoMap);
+    DrmStatus err = drm->queryKeyStatus(sessionId, infoMap);
 
     if (throwExceptionAsNecessary(env, drm, err, "Failed to query key status")) {
         return NULL;
@@ -1297,7 +1314,7 @@
     }
 
     String8 certAuthority = JStringToString8(env, jcertAuthority);
-    status_t err = drm->getProvisionRequest(certType, certAuthority, request, defaultUrl);
+    DrmStatus err = drm->getProvisionRequest(certType, certAuthority, request, defaultUrl);
 
     if (throwExceptionAsNecessary(env, drm, err, "Failed to get provision request")) {
         return NULL;
@@ -1338,7 +1355,7 @@
     Vector<uint8_t> response(JByteArrayToVector(env, jresponse));
     Vector<uint8_t> certificate, wrappedKey;
 
-    status_t err = drm->provideProvisionResponse(response, certificate, wrappedKey);
+    DrmStatus err = drm->provideProvisionResponse(response, certificate, wrappedKey);
 
     // Fill out return obj
     jclass clazz = gFields.certificateClassId;
@@ -1368,7 +1385,7 @@
 
     List<Vector<uint8_t>> secureStops;
 
-    status_t err = drm->getSecureStops(secureStops);
+    DrmStatus err = drm->getSecureStops(secureStops);
 
     if (throwExceptionAsNecessary(env, drm, err, "Failed to get secure stops")) {
         return NULL;
@@ -1387,7 +1404,7 @@
 
     List<Vector<uint8_t>> secureStopIds;
 
-    status_t err = drm->getSecureStopIds(secureStopIds);
+    DrmStatus err = drm->getSecureStopIds(secureStopIds);
 
     if (throwExceptionAsNecessary(env, drm, err, "Failed to get secure stop Ids")) {
         return NULL;
@@ -1406,7 +1423,7 @@
 
     Vector<uint8_t> secureStop;
 
-    status_t err = drm->getSecureStop(JByteArrayToVector(env, ssid), secureStop);
+    DrmStatus err = drm->getSecureStop(JByteArrayToVector(env, ssid), secureStop);
 
     if (throwExceptionAsNecessary(env, drm, err, "Failed to get secure stop")) {
         return NULL;
@@ -1425,7 +1442,7 @@
 
     Vector<uint8_t> ssRelease(JByteArrayToVector(env, jssRelease));
 
-    status_t err = drm->releaseSecureStops(ssRelease);
+    DrmStatus err = drm->releaseSecureStops(ssRelease);
 
     throwExceptionAsNecessary(env, drm, err, "Failed to release secure stops");
 }
@@ -1438,7 +1455,7 @@
         return;
     }
 
-    status_t err = drm->removeSecureStop(JByteArrayToVector(env, ssid));
+    DrmStatus err = drm->removeSecureStop(JByteArrayToVector(env, ssid));
 
     throwExceptionAsNecessary(env, drm, err, "Failed to remove secure stop");
 }
@@ -1451,7 +1468,7 @@
         return;
     }
 
-    status_t err = drm->removeAllSecureStops();
+    DrmStatus err = drm->removeAllSecureStops();
 
     throwExceptionAsNecessary(env, drm, err, "Failed to remove all secure stops");
 }
@@ -1490,7 +1507,7 @@
     DrmPlugin::HdcpLevel connected = DrmPlugin::kHdcpNone;
     DrmPlugin::HdcpLevel max = DrmPlugin::kHdcpNone;
 
-    status_t err = drm->getHdcpLevels(&connected, &max);
+    DrmStatus err = drm->getHdcpLevels(&connected, &max);
 
     if (throwExceptionAsNecessary(env, drm, err, "Failed to get HDCP levels")) {
         return gHdcpLevels.kHdcpLevelUnknown;
@@ -1509,7 +1526,7 @@
     DrmPlugin::HdcpLevel connected = DrmPlugin::kHdcpLevelUnknown;
     DrmPlugin::HdcpLevel max = DrmPlugin::kHdcpLevelUnknown;
 
-    status_t err = drm->getHdcpLevels(&connected, &max);
+    DrmStatus err = drm->getHdcpLevels(&connected, &max);
 
     if (throwExceptionAsNecessary(env, drm, err, "Failed to get HDCP levels")) {
         return gHdcpLevels.kHdcpLevelUnknown;
@@ -1526,7 +1543,7 @@
     }
 
     uint32_t open = 0, max = 0;
-    status_t err = drm->getNumberOfSessions(&open, &max);
+    DrmStatus err = drm->getNumberOfSessions(&open, &max);
 
     if (throwExceptionAsNecessary(env, drm, err, "Failed to get number of sessions")) {
         return 0;
@@ -1543,7 +1560,7 @@
     }
 
     uint32_t open = 0, max = 0;
-    status_t err = drm->getNumberOfSessions(&open, &max);
+    DrmStatus err = drm->getNumberOfSessions(&open, &max);
 
     if (throwExceptionAsNecessary(env, drm, err, "Failed to get number of sessions")) {
         return 0;
@@ -1563,7 +1580,7 @@
 
     DrmPlugin::SecurityLevel level = DrmPlugin::kSecurityLevelUnknown;
 
-    status_t err = drm->getSecurityLevel(sessionId, &level);
+    DrmStatus err = drm->getSecurityLevel(sessionId, &level);
 
     if (throwExceptionAsNecessary(env, drm, err, "Failed to get security level")) {
         return gSecurityLevels.kSecurityLevelUnknown;
@@ -1595,7 +1612,7 @@
 
     List<Vector<uint8_t> > keySetIds;
 
-    status_t err = drm->getOfflineLicenseKeySetIds(keySetIds);
+    DrmStatus err = drm->getOfflineLicenseKeySetIds(keySetIds);
 
     if (throwExceptionAsNecessary(env, drm, err, "Failed to get offline key set Ids")) {
         return NULL;
@@ -1612,7 +1629,7 @@
         return;
     }
 
-    status_t err = drm->removeOfflineLicense(JByteArrayToVector(env, keySetId));
+    DrmStatus err = drm->removeOfflineLicense(JByteArrayToVector(env, keySetId));
 
     throwExceptionAsNecessary(env, drm, err, "Failed to remove offline license");
 }
@@ -1629,7 +1646,7 @@
 
     DrmPlugin::OfflineLicenseState state = DrmPlugin::kOfflineLicenseStateUnknown;
 
-    status_t err = drm->getOfflineLicenseState(keySetId, &state);
+    DrmStatus err = drm->getOfflineLicenseState(keySetId, &state);
 
     if (throwExceptionAsNecessary(env, drm, err, "Failed to get offline license state")) {
         return gOfflineLicenseStates.kOfflineLicenseStateUnknown;
@@ -1662,7 +1679,7 @@
     String8 name = JStringToString8(env, jname);
     String8 value;
 
-    status_t err = drm->getPropertyString(name, value);
+    DrmStatus err = drm->getPropertyString(name, value);
 
     if (throwExceptionAsNecessary(env, drm, err, "Failed to get property")) {
         return NULL;
@@ -1688,7 +1705,7 @@
     String8 name = JStringToString8(env, jname);
     Vector<uint8_t> value;
 
-    status_t err = drm->getPropertyByteArray(name, value);
+    DrmStatus err = drm->getPropertyByteArray(name, value);
 
     if (throwExceptionAsNecessary(env, drm, err, "Failed to get property")) {
         return NULL;
@@ -1720,7 +1737,7 @@
     String8 name = JStringToString8(env, jname);
     String8 value = JStringToString8(env, jvalue);
 
-    status_t err = drm->setPropertyString(name, value);
+    DrmStatus err = drm->setPropertyString(name, value);
 
     throwExceptionAsNecessary(env, drm, err, "Failed to set property");
 }
@@ -1748,7 +1765,7 @@
     String8 name = JStringToString8(env, jname);
     Vector<uint8_t> value = JByteArrayToVector(env, jvalue);
 
-    status_t err = drm->setPropertyByteArray(name, value);
+    DrmStatus err = drm->setPropertyByteArray(name, value);
 
     throwExceptionAsNecessary(env, drm, err, "Failed to set property");
 }
@@ -1772,7 +1789,7 @@
     Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId));
     String8 algorithm = JStringToString8(env, jalgorithm);
 
-    status_t err = drm->setCipherAlgorithm(sessionId, algorithm);
+    DrmStatus err = drm->setCipherAlgorithm(sessionId, algorithm);
 
     throwExceptionAsNecessary(env, drm, err, "Failed to set cipher algorithm");
 }
@@ -1796,7 +1813,7 @@
     Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId));
     String8 algorithm = JStringToString8(env, jalgorithm);
 
-    status_t err = drm->setMacAlgorithm(sessionId, algorithm);
+    DrmStatus err = drm->setMacAlgorithm(sessionId, algorithm);
 
     throwExceptionAsNecessary(env, drm, err, "Failed to set mac algorithm");
 }
@@ -1824,7 +1841,7 @@
     Vector<uint8_t> iv(JByteArrayToVector(env, jiv));
     Vector<uint8_t> output;
 
-    status_t err = drm->encrypt(sessionId, keyId, input, iv, output);
+    DrmStatus err = drm->encrypt(sessionId, keyId, input, iv, output);
 
     if (throwExceptionAsNecessary(env, drm, err, "Failed to encrypt")) {
         return NULL;
@@ -1855,7 +1872,7 @@
     Vector<uint8_t> iv(JByteArrayToVector(env, jiv));
     Vector<uint8_t> output;
 
-    status_t err = drm->decrypt(sessionId, keyId, input, iv, output);
+    DrmStatus err = drm->decrypt(sessionId, keyId, input, iv, output);
     if (throwExceptionAsNecessary(env, drm, err, "Failed to decrypt")) {
         return NULL;
     }
@@ -1884,7 +1901,7 @@
     Vector<uint8_t> message(JByteArrayToVector(env, jmessage));
     Vector<uint8_t> signature;
 
-    status_t err = drm->sign(sessionId, keyId, message, signature);
+    DrmStatus err = drm->sign(sessionId, keyId, message, signature);
 
     if (throwExceptionAsNecessary(env, drm, err, "Failed to sign")) {
         return NULL;
@@ -1915,7 +1932,7 @@
     Vector<uint8_t> signature(JByteArrayToVector(env, jsignature));
     bool match;
 
-    status_t err = drm->verify(sessionId, keyId, message, signature, match);
+    DrmStatus err = drm->verify(sessionId, keyId, message, signature, match);
 
     throwExceptionAsNecessary(env, drm, err, "Failed to verify");
     return match;
@@ -1933,7 +1950,7 @@
     // Retrieve current metrics snapshot from drm.
     PersistableBundle metrics;
     sp<IDrmMetricsConsumer> consumer(new DrmMetricsConsumer(&metrics));
-    status_t err = drm->getMetrics(consumer);
+    DrmStatus err = drm->getMetrics(consumer);
     if (err != OK) {
         ALOGE("getMetrics failed: %d", (int)err);
         return (jobject) NULL;
@@ -1964,7 +1981,7 @@
     Vector<uint8_t> message(JByteArrayToVector(env, jmessage));
     Vector<uint8_t> signature;
 
-    status_t err = drm->signRSA(sessionId, algorithm, message, wrappedKey, signature);
+    DrmStatus err = drm->signRSA(sessionId, algorithm, message, wrappedKey, signature);
 
     if (throwExceptionAsNecessary(env, drm, err, "Failed to sign")) {
         return NULL;
@@ -1993,7 +2010,7 @@
     }
 
     bool required = false;
-    status_t err = OK;
+    DrmStatus err = OK;
     if (securityLevel == DrmPlugin::kSecurityLevelMax) {
         err = drm->requiresSecureDecoder(mimeType.c_str(), &required);
     } else {
@@ -2019,7 +2036,7 @@
     if (jplaybackId != NULL) {
         playbackId = JStringToString8(env, jplaybackId);
     }
-    status_t err = drm->setPlaybackId(sessionId, playbackId.c_str());
+    DrmStatus err = drm->setPlaybackId(sessionId, playbackId.c_str());
     throwExceptionAsNecessary(env, drm, err, "Failed to set playbackId");
 }
 
@@ -2031,7 +2048,7 @@
     }
 
     Vector<drm::V1_4::LogMessage> logs;
-    status_t err = drm->getLogMessages(logs);
+    DrmStatus err = drm->getLogMessages(logs);
     ALOGI("drm->getLogMessages %zu logs", logs.size());
     if (throwExceptionAsNecessary(env, drm, err, "Failed to get log messages")) {
         return NULL;
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppIconCacheManager.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppIconCacheManager.java
index 9dfc8ea..c0117b9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/AppIconCacheManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppIconCacheManager.java
@@ -22,13 +22,16 @@
 import android.util.Log;
 import android.util.LruCache;
 
+import androidx.annotation.VisibleForTesting;
+
 /**
  * Cache app icon for management.
  */
 public class AppIconCacheManager {
     private static final String TAG = "AppIconCacheManager";
     private static final float CACHE_RATIO = 0.1f;
-    private static final int MAX_CACHE_SIZE_IN_KB = getMaxCacheInKb();
+    @VisibleForTesting
+    static final int MAX_CACHE_SIZE_IN_KB = getMaxCacheInKb();
     private static final String DELIMITER = ":";
     private static AppIconCacheManager sAppIconCacheManager;
     private final LruCache<String, Drawable> mDrawableCache;
@@ -109,4 +112,25 @@
     private static int getMaxCacheInKb() {
         return Math.round(CACHE_RATIO * Runtime.getRuntime().maxMemory() / 1024);
     }
+
+    /**
+     * Clears as much memory as possible.
+     *
+     * @see android.content.ComponentCallbacks2#onTrimMemory(int)
+     */
+    public void trimMemory(int level) {
+        if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
+            // Time to clear everything
+            if (sAppIconCacheManager != null) {
+                sAppIconCacheManager.mDrawableCache.trimToSize(0);
+            }
+        } else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
+                || level == android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
+            // Tough time but still affordable, clear half of the cache
+            if (sAppIconCacheManager != null) {
+                final int maxSize = sAppIconCacheManager.mDrawableCache.maxSize();
+                sAppIconCacheManager.mDrawableCache.trimToSize(maxSize / 2);
+            }
+        }
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppIconCacheManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppIconCacheManagerTest.java
index 64f8bef..1b0e1f1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppIconCacheManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppIconCacheManagerTest.java
@@ -34,11 +34,21 @@
 public class AppIconCacheManagerTest {
 
     private static final String APP_PACKAGE_NAME = "com.test.app";
+    private static final String APP_PACKAGE_NAME1 = "com.test.app1";
+    private static final String APP_PACKAGE_NAME2 = "com.test.app2";
+    private static final String APP_PACKAGE_NAME3 = "com.test.app3";
     private static final int APP_UID = 9999;
 
     @Mock
     private Drawable mIcon;
 
+    @Mock
+    private Drawable mIcon1;
+    @Mock
+    private Drawable mIcon2;
+    @Mock
+    private Drawable mIcon3;
+
     private AppIconCacheManager mAppIconCacheManager;
 
     @Before
@@ -48,6 +58,29 @@
         doReturn(10).when(mIcon).getIntrinsicHeight();
         doReturn(10).when(mIcon).getIntrinsicWidth();
         doReturn(mIcon).when(mIcon).mutate();
+
+        // Algorithm for trim memory test:
+        // The real maxsize is defined by AppIconCacheManager.MAX_CACHE_SIZE_IN_KB, and the size
+        // of each element is calculated as following:
+        // n * n * 4 / 1024
+        // In the testcase, we want to mock the maxsize of LruCache is 3, so the formula calculating
+        // the size of each element will be like:
+        // n * n * 4 / 1024 = maxsize / 3
+        // Thus, n = square_root(maxsize / 3 * 1024 / 4), which can be used as an icon size.
+        final int iconSize =
+                (int) Math.sqrt(AppIconCacheManager.MAX_CACHE_SIZE_IN_KB / 3f * 1024f / 4f);
+
+        doReturn(iconSize).when(mIcon1).getIntrinsicHeight();
+        doReturn(iconSize).when(mIcon1).getIntrinsicWidth();
+        doReturn(mIcon1).when(mIcon1).mutate();
+
+        doReturn(iconSize).when(mIcon2).getIntrinsicHeight();
+        doReturn(iconSize).when(mIcon2).getIntrinsicWidth();
+        doReturn(mIcon2).when(mIcon2).mutate();
+
+        doReturn(iconSize).when(mIcon3).getIntrinsicHeight();
+        doReturn(iconSize).when(mIcon3).getIntrinsicWidth();
+        doReturn(mIcon3).when(mIcon3).mutate();
     }
 
     @After
@@ -106,4 +139,41 @@
 
         assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
     }
+
+    @Test
+    public void trimMemory_levelSatisfied_shouldNotCacheIcon() {
+
+        mAppIconCacheManager.put(APP_PACKAGE_NAME1, APP_UID, mIcon1);
+        mAppIconCacheManager.put(APP_PACKAGE_NAME2, APP_UID, mIcon2);
+        mAppIconCacheManager.put(APP_PACKAGE_NAME3, APP_UID, mIcon3);
+
+        // Expected to trim size to 0
+        final int level = android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
+        mAppIconCacheManager.trimMemory(level);
+
+        // None of the elements should be cached
+        assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME1, APP_UID)).isNull();
+        assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME2, APP_UID)).isNull();
+        assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME3, APP_UID)).isNull();
+    }
+
+    @Test
+    public void trimMemory_levelSatisfied_shouldCacheAtLeastHalf() {
+
+        mAppIconCacheManager.put(APP_PACKAGE_NAME1, APP_UID, mIcon1);
+        mAppIconCacheManager.put(APP_PACKAGE_NAME2, APP_UID, mIcon2);
+        mAppIconCacheManager.put(APP_PACKAGE_NAME3, APP_UID, mIcon3);
+
+        // Get the last element
+        mAppIconCacheManager.get(APP_PACKAGE_NAME1, APP_UID);
+
+        // Expected to trim size to half of it, which is int( 3 / 2 ) = 1
+        final int level = android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;
+        mAppIconCacheManager.trimMemory(level);
+
+        // There should be only one cached element, which is the last recently used one
+        assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME1, APP_UID)).isNotNull();
+        assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME2, APP_UID)).isNull();
+        assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME3, APP_UID)).isNull();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/OWNERS b/packages/SystemUI/src/com/android/systemui/notetask/OWNERS
new file mode 100644
index 0000000..7ccb316
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/OWNERS
@@ -0,0 +1,8 @@
+# Bug component: 1254381
+azappone@google.com
+achalke@google.com
+juliacr@google.com
+madym@google.com
+mgalhardo@google.com
+petrcermak@google.com
+vanjan@google.com
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/OWNERS b/packages/SystemUI/src/com/android/systemui/stylus/OWNERS
new file mode 100644
index 0000000..7ccb316
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stylus/OWNERS
@@ -0,0 +1,8 @@
+# Bug component: 1254381
+azappone@google.com
+achalke@google.com
+juliacr@google.com
+madym@google.com
+mgalhardo@google.com
+petrcermak@google.com
+vanjan@google.com
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index bf7c459..55fa4c0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -429,6 +429,11 @@
                             AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER |
                             AudioManager.DEVICE_OUT_BLE_HEADSET)) != 0;
             changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth);
+        } else if (stream == AudioManager.STREAM_VOICE_CALL) {
+            final boolean routedToBluetooth =
+                    (mAudio.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)
+                            & AudioManager.DEVICE_OUT_BLE_HEADSET) != 0;
+            changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth);
         }
         return changed;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 903aba1..198d022 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1677,6 +1677,7 @@
         if (ss.level == row.requestedLevel) {
             row.requestedLevel = -1;
         }
+        final boolean isVoiceCallStream = row.stream == AudioManager.STREAM_VOICE_CALL;
         final boolean isA11yStream = row.stream == STREAM_ACCESSIBILITY;
         final boolean isRingStream = row.stream == AudioManager.STREAM_RING;
         final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM;
@@ -1721,8 +1722,12 @@
         } else if (isRingSilent || zenMuted) {
             iconRes = row.iconMuteRes;
         } else if (ss.routedToBluetooth) {
-            iconRes = isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute
-                                        : R.drawable.ic_volume_media_bt;
+            if (isVoiceCallStream) {
+                iconRes = R.drawable.ic_volume_bt_sco;
+            } else {
+                iconRes = isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute
+                                            : R.drawable.ic_volume_media_bt;
+            }
         } else if (isStreamMuted(ss)) {
             iconRes = ss.muted ? R.drawable.ic_volume_media_off : row.iconMuteRes;
         } else {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/OWNERS b/packages/SystemUI/tests/src/com/android/systemui/notetask/OWNERS
new file mode 100644
index 0000000..7ccb316
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/OWNERS
@@ -0,0 +1,8 @@
+# Bug component: 1254381
+azappone@google.com
+achalke@google.com
+juliacr@google.com
+madym@google.com
+mgalhardo@google.com
+petrcermak@google.com
+vanjan@google.com
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/OWNERS b/packages/SystemUI/tests/src/com/android/systemui/stylus/OWNERS
new file mode 100644
index 0000000..7ccb316
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/OWNERS
@@ -0,0 +1,8 @@
+# Bug component: 1254381
+azappone@google.com
+achalke@google.com
+juliacr@google.com
+madym@google.com
+mgalhardo@google.com
+petrcermak@google.com
+vanjan@google.com
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index aaf2188..acf1088 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -167,6 +167,34 @@
     }
 
     @Test
+    public void testVolumeChangeW_deviceOutFromBLEHeadset_doStateChanged() {
+        mVolumeController.setDeviceInteractive(false);
+        when(mWakefullnessLifcycle.getWakefulness()).thenReturn(
+                WakefulnessLifecycle.WAKEFULNESS_AWAKE);
+        when(mAudioManager.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)).thenReturn(
+                AudioManager.DEVICE_OUT_BLE_HEADSET);
+
+        mVolumeController.onVolumeChangedW(
+                AudioManager.STREAM_VOICE_CALL, AudioManager.FLAG_SHOW_UI);
+
+        verify(mCallback, times(1)).onStateChanged(any());
+    }
+
+    @Test
+    public void testVolumeChangeW_deviceOutFromA2DP_doStateChanged() {
+        mVolumeController.setDeviceInteractive(false);
+        when(mWakefullnessLifcycle.getWakefulness()).thenReturn(
+                WakefulnessLifecycle.WAKEFULNESS_AWAKE);
+        when(mAudioManager.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)).thenReturn(
+                AudioManager.DEVICE_OUT_BLUETOOTH_A2DP);
+
+        mVolumeController.onVolumeChangedW(
+                AudioManager.STREAM_VOICE_CALL, AudioManager.FLAG_SHOW_UI);
+
+        verify(mCallback, never()).onStateChanged(any());
+    }
+
+    @Test
     public void testOnRemoteVolumeChanged_newStream_noNullPointer() {
         MediaSession.Token token = new MediaSession.Token(Process.myUid(), null);
         mVolumeController.mMediaSessionsCallbacksW.onRemoteVolumeChanged(token, 0);
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 61f7f30..f652cb0 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -371,8 +371,9 @@
             return new LocationPermissionChecker(context);
         }
 
-        /** Gets the transports that need to be marked as restricted by the VCN */
-        public Set<Integer> getRestrictedTransports(
+        /** Gets transports that need to be marked as restricted by the VCN from CarrierConfig */
+        @VisibleForTesting(visibility = Visibility.PRIVATE)
+        public Set<Integer> getRestrictedTransportsFromCarrierConfig(
                 ParcelUuid subGrp, TelephonySubscriptionSnapshot lastSnapshot) {
             if (!Build.IS_ENG && !Build.IS_USERDEBUG) {
                 return RESTRICTED_TRANSPORTS_DEFAULT;
@@ -398,6 +399,22 @@
             }
             return restrictedTransports;
         }
+
+        /** Gets the transports that need to be marked as restricted by the VCN */
+        public Set<Integer> getRestrictedTransports(
+                ParcelUuid subGrp,
+                TelephonySubscriptionSnapshot lastSnapshot,
+                VcnConfig vcnConfig) {
+            final Set<Integer> restrictedTransports = new ArraySet<>();
+            restrictedTransports.addAll(vcnConfig.getRestrictedUnderlyingNetworkTransports());
+
+            // TODO: b/262269892 Remove the ability to configure restricted transports
+            // via CarrierConfig
+            restrictedTransports.addAll(
+                    getRestrictedTransportsFromCarrierConfig(subGrp, lastSnapshot));
+
+            return restrictedTransports;
+        }
     }
 
     /** Notifies the VcnManagementService that external dependencies can be set up. */
@@ -719,6 +736,7 @@
         if (mVcns.containsKey(subscriptionGroup)) {
             final Vcn vcn = mVcns.get(subscriptionGroup);
             vcn.updateConfig(config);
+            notifyAllPolicyListenersLocked();
         } else {
             // TODO(b/193687515): Support multiple VCNs active at the same time
             if (isActiveSubGroup(subscriptionGroup, mLastSnapshot)) {
@@ -936,7 +954,6 @@
     }
 
     /** Adds the provided listener for receiving VcnUnderlyingNetworkPolicy updates. */
-    @GuardedBy("mLock")
     @Override
     public void addVcnUnderlyingNetworkPolicyListener(
             @NonNull IVcnUnderlyingNetworkPolicyListener listener) {
@@ -963,16 +980,7 @@
         });
     }
 
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    void addVcnUnderlyingNetworkPolicyListenerForTest(
-            @NonNull IVcnUnderlyingNetworkPolicyListener listener) {
-        synchronized (mLock) {
-            addVcnUnderlyingNetworkPolicyListener(listener);
-        }
-    }
-
     /** Removes the provided listener from receiving VcnUnderlyingNetworkPolicy updates. */
-    @GuardedBy("mLock")
     @Override
     public void removeVcnUnderlyingNetworkPolicyListener(
             @NonNull IVcnUnderlyingNetworkPolicyListener listener) {
@@ -1062,8 +1070,8 @@
                         isVcnManagedNetwork = true;
                     }
 
-                    final Set<Integer> restrictedTransports =
-                            mDeps.getRestrictedTransports(subGrp, mLastSnapshot);
+                    final Set<Integer> restrictedTransports = mDeps.getRestrictedTransports(
+                            subGrp, mLastSnapshot, mConfigs.get(subGrp));
                     for (int restrictedTransport : restrictedTransports) {
                         if (ncCopy.hasTransport(restrictedTransport)) {
                             if (restrictedTransport == TRANSPORT_CELLULAR) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 430186a..6503029 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2797,7 +2797,11 @@
         try {
             pvr = verifyAndGetBypass(uid, packageName, null);
         } catch (SecurityException e) {
-            Slog.e(TAG, "Cannot setMode", e);
+            if (Process.isIsolated(uid)) {
+                Slog.e(TAG, "Cannot setMode: isolated process");
+            } else {
+                Slog.e(TAG, "Cannot setMode", e);
+            }
             return;
         }
 
@@ -3252,7 +3256,11 @@
         try {
             pvr = verifyAndGetBypass(uid, packageName, null);
         } catch (SecurityException e) {
-            Slog.e(TAG, "checkOperation", e);
+            if (Process.isIsolated(uid)) {
+                Slog.e(TAG, "Cannot checkOperation: isolated process");
+            } else {
+                Slog.e(TAG, "Cannot checkOperation", e);
+            }
             return AppOpsManager.opToDefaultMode(code);
         }
 
@@ -3458,7 +3466,11 @@
                 attributionTag = null;
             }
         } catch (SecurityException e) {
-            Slog.e(TAG, "noteOperation", e);
+            if (Process.isIsolated(uid)) {
+                Slog.e(TAG, "Cannot noteOperation: isolated process");
+            } else {
+                Slog.e(TAG, "Cannot noteOperation", e);
+            }
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
@@ -3974,7 +3986,11 @@
                 attributionTag = null;
             }
         } catch (SecurityException e) {
-            Slog.e(TAG, "startOperation", e);
+            if (Process.isIsolated(uid)) {
+                Slog.e(TAG, "Cannot startOperation: isolated process");
+            } else {
+                Slog.e(TAG, "Cannot startOperation", e);
+            }
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
@@ -4148,7 +4164,11 @@
                 attributionTag = null;
             }
         } catch (SecurityException e) {
-            Slog.e(TAG, "Cannot finishOperation", e);
+            if (Process.isIsolated(uid)) {
+                Slog.e(TAG, "Cannot finishOperation: isolated process");
+            } else {
+                Slog.e(TAG, "Cannot finishOperation", e);
+            }
             return;
         }
 
diff --git a/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java b/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java
index ab3b250..dce1c96 100644
--- a/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java
+++ b/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java
@@ -29,6 +29,7 @@
 import java.io.EOFException;
 import java.io.FileDescriptor;
 import java.io.InterruptedIOException;
+import java.net.ProtocolException;
 import java.net.SocketException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -39,12 +40,16 @@
 // write contents of the host system's clipboard.
 class EmulatorClipboardMonitor implements Consumer<ClipData> {
     private static final String TAG = "EmulatorClipboardMonitor";
+
     private static final String PIPE_NAME = "pipe:clipboard";
     private static final int HOST_PORT = 5000;
-    private final Thread mHostMonitorThread;
+
     private static final boolean LOG_CLIBOARD_ACCESS =
             SystemProperties.getBoolean("ro.boot.qemu.log_clipboard_access", false);
+    private static final int MAX_CLIPBOARD_BYTES = 128 << 20;
+
     private FileDescriptor mPipe = null;
+    private final Thread mHostMonitorThread;
 
     private static byte[] createOpenHandshake() {
         // String.getBytes doesn't include the null terminator,
@@ -97,8 +102,8 @@
         return fd;
     }
 
-    private static byte[] receiveMessage(final FileDescriptor fd) throws ErrnoException,
-            InterruptedIOException, EOFException {
+    private byte[] receiveMessage(final FileDescriptor fd) throws ErrnoException,
+            InterruptedIOException, EOFException, ProtocolException {
         final byte[] lengthBits = new byte[4];
         readFully(fd, lengthBits, 0, lengthBits.length);
 
@@ -106,6 +111,10 @@
         bb.order(ByteOrder.LITTLE_ENDIAN);
         final int msgLen = bb.getInt();
 
+        if (msgLen <= 0 || msgLen > MAX_CLIPBOARD_BYTES) {
+            throw new ProtocolException("Clipboard message length: " + msgLen + " out of bounds.");
+        }
+
         final byte[] msg = new byte[msgLen];
         readFully(fd, msg, 0, msg.length);
 
@@ -150,7 +159,8 @@
                     }
                     setAndroidClipboard.accept(clip);
                 } catch (ErrnoException | EOFException | InterruptedIOException
-                         | InterruptedException e) {
+                         | InterruptedException | ProtocolException | OutOfMemoryError e) {
+                    Slog.w(TAG, "Failure to read from host clipboard", e);
                     setPipeFD(null);
 
                     try {
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 4fcde97..c15e419 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -140,6 +140,7 @@
 import com.android.internal.net.VpnConfig;
 import com.android.internal.net.VpnProfile;
 import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.BinderUtils;
 import com.android.net.module.util.NetdUtils;
 import com.android.net.module.util.NetworkStackConstants;
 import com.android.server.DeviceIdleInternal;
@@ -646,7 +647,10 @@
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
                 .setTransportInfo(new VpnTransportInfo(
-                        VpnManager.TYPE_VPN_NONE, null /* sessionId */, false /* bypassable */))
+                        VpnManager.TYPE_VPN_NONE,
+                        null /* sessionId */,
+                        false /* bypassable */,
+                        false /* longLivedTcpConnectionsExpensive */))
                 .build();
 
         loadAlwaysOnPackage();
@@ -711,7 +715,10 @@
         mNetworkCapabilities = new NetworkCapabilities.Builder(mNetworkCapabilities)
                 .setUids(null)
                 .setTransportInfo(new VpnTransportInfo(
-                        VpnManager.TYPE_VPN_NONE, null /* sessionId */, false /* bypassable */))
+                        VpnManager.TYPE_VPN_NONE,
+                        null /* sessionId */,
+                        false /* bypassable */,
+                        false /* longLivedTcpConnectionsExpensive */))
                 .build();
     }
 
@@ -1570,7 +1577,8 @@
                 mConfig.allowedApplications, mConfig.disallowedApplications));
 
         capsBuilder.setTransportInfo(
-                new VpnTransportInfo(getActiveVpnType(), mConfig.session, mConfig.allowBypass));
+                new VpnTransportInfo(getActiveVpnType(), mConfig.session, mConfig.allowBypass,
+                        false /* longLivedTcpConnectionsExpensive */));
 
         // Only apps targeting Q and above can explicitly declare themselves as metered.
         // These VPNs are assumed metered unless they state otherwise.
@@ -2776,6 +2784,16 @@
         return hasIPV6 && !hasIPV4;
     }
 
+    private void setVpnNetworkPreference(String session, Set<Range<Integer>> ranges) {
+        BinderUtils.withCleanCallingIdentity(
+                () -> mConnectivityManager.setVpnDefaultForUids(session, ranges));
+    }
+
+    private void clearVpnNetworkPreference(String session) {
+        BinderUtils.withCleanCallingIdentity(
+                () -> mConnectivityManager.setVpnDefaultForUids(session, Collections.EMPTY_LIST));
+    }
+
     /**
      * Internal class managing IKEv2/IPsec VPN connectivity
      *
@@ -2887,6 +2905,9 @@
                     (r, exe) -> {
                         Log.d(TAG, "Runnable " + r + " rejected by the mExecutor");
                     });
+            setVpnNetworkPreference(mSessionKey,
+                    createUserAndRestrictedProfilesRanges(mUserId,
+                            mConfig.allowedApplications, mConfig.disallowedApplications));
         }
 
         @Override
@@ -3040,7 +3061,6 @@
                     mConfig.dnsServers.addAll(dnsAddrStrings);
 
                     mConfig.underlyingNetworks = new Network[] {network};
-                    mConfig.disallowedApplications = getAppExclusionList(mPackage);
 
                     networkAgent = mNetworkAgent;
 
@@ -3743,6 +3763,7 @@
             mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
             mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback(
                     mDiagnosticsCallback);
+            clearVpnNetworkPreference(mSessionKey);
 
             mExecutor.shutdown();
         }
@@ -4303,6 +4324,7 @@
             mConfig.requiresInternetValidation = profile.requiresInternetValidation;
             mConfig.excludeLocalRoutes = profile.excludeLocalRoutes;
             mConfig.allowBypass = profile.isBypassable;
+            mConfig.disallowedApplications = getAppExclusionList(mPackage);
 
             switch (profile.type) {
                 case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
@@ -4455,6 +4477,9 @@
                         .setUids(createUserAndRestrictedProfilesRanges(
                                 mUserId, null /* allowedApplications */, excludedApps))
                         .build();
+                setVpnNetworkPreference(getSessionKeyLocked(),
+                        createUserAndRestrictedProfilesRanges(mUserId,
+                                mConfig.allowedApplications, mConfig.disallowedApplications));
                 doSendNetworkCapabilities(mNetworkAgent, mNetworkCapabilities);
             }
         }
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index ad27c45..4a5b7e8 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -186,8 +186,8 @@
         }
 
         @Override
-        public void setUpFsverity(String filePath, byte[] pkcs7Signature) throws IOException {
-            VerityUtils.setUpFsverity(filePath, pkcs7Signature);
+        public void setUpFsverity(String filePath) throws IOException {
+            VerityUtils.setUpFsverity(filePath, /* signature */ (byte[]) null);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index 457d5b7..6f93608 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -78,7 +78,7 @@
     interface FsverityUtil {
         boolean isFromTrustedProvider(String path, byte[] pkcs7Signature);
 
-        void setUpFsverity(String path, byte[] pkcs7Signature) throws IOException;
+        void setUpFsverity(String path) throws IOException;
 
         boolean rename(File src, File dest);
     }
@@ -354,8 +354,7 @@
             try {
                 // Do not parse font file before setting up fs-verity.
                 // setUpFsverity throws IOException if failed.
-                mFsverityUtil.setUpFsverity(tempNewFontFile.getAbsolutePath(),
-                        pkcs7Signature);
+                mFsverityUtil.setUpFsverity(tempNewFontFile.getAbsolutePath());
             } catch (IOException e) {
                 throw new SystemFontException(
                         FontManager.RESULT_ERROR_VERIFICATION_FAILURE,
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 1e64701..89719ce 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1772,7 +1772,7 @@
 
     private int runCompile() throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
-        boolean checkProfiles = SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
+        boolean checkProfiles = true;
         boolean forceCompilation = false;
         boolean allPackages = false;
         boolean clearProfileData = false;
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index af507cd..50253ea 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -320,15 +320,13 @@
 
         switch (profileType) {
             case ArtManager.PROFILE_APPS :
-                return SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
+                return true;
             case ArtManager.PROFILE_BOOT_IMAGE:
                 // The device config property overrides the system property version.
                 boolean profileBootClassPath = SystemProperties.getBoolean(
                         "persist.device_config.runtime_native_boot.profilebootclasspath",
                         SystemProperties.getBoolean("dalvik.vm.profilebootclasspath", false));
-                return (Build.IS_USERDEBUG || Build.IS_ENG) &&
-                        SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false) &&
-                        profileBootClassPath;
+                return (Build.IS_USERDEBUG || Build.IS_ENG) && profileBootClassPath;
             default:
                 throw new IllegalArgumentException("Invalid profile type:" + profileType);
         }
diff --git a/services/core/java/com/android/server/power/OWNERS b/services/core/java/com/android/server/power/OWNERS
index 5cbe74c..a0e91ad 100644
--- a/services/core/java/com/android/server/power/OWNERS
+++ b/services/core/java/com/android/server/power/OWNERS
@@ -1,4 +1,5 @@
 michaelwr@google.com
 santoscordon@google.com
+philipjunker@google.com
 
 per-file ThermalManagerService.java=wvw@google.com
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index dbf05f1..ed4ba0d 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -4212,8 +4212,8 @@
     }
 
     private boolean forceSuspendInternal(int uid) {
-        try {
-            synchronized (mLock) {
+        synchronized (mLock) {
+            try {
                 mForceSuspendActive = true;
                 // Place the system in an non-interactive state
                 for (int idx = 0; idx < mPowerGroups.size(); idx++) {
@@ -4223,16 +4223,14 @@
 
                 // Disable all the partial wake locks as well
                 updateWakeLockDisabledStatesLocked();
-            }
 
-            Slog.i(TAG, "Force-Suspending (uid " + uid + ")...");
-            boolean success = mNativeWrapper.nativeForceSuspend();
-            if (!success) {
-                Slog.i(TAG, "Force-Suspending failed in native.");
-            }
-            return success;
-        } finally {
-            synchronized (mLock) {
+                Slog.i(TAG, "Force-Suspending (uid " + uid + ")...");
+                boolean success = mNativeWrapper.nativeForceSuspend();
+                if (!success) {
+                    Slog.i(TAG, "Force-Suspending failed in native.");
+                }
+                return success;
+            } finally {
                 mForceSuspendActive = false;
                 // Re-enable wake locks once again.
                 updateWakeLockDisabledStatesLocked();
diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java
new file mode 100644
index 0000000..868f34b
--- /dev/null
+++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.rkp;
+
+import android.os.CancellationSignal;
+import android.os.OperationCanceledException;
+import android.os.OutcomeReceiver;
+import android.security.rkp.IGetKeyCallback;
+import android.security.rkp.IRegistration;
+import android.security.rkp.service.RegistrationProxy;
+import android.security.rkp.service.RemotelyProvisionedKey;
+import android.util.Log;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+
+/**
+ * Implements android.security.rkp.IRegistration as a thin wrapper around the java code
+ * exported by com.android.rkp.
+ *
+ * @hide
+ */
+final class RemoteProvisioningRegistration extends IRegistration.Stub {
+    static final String TAG = RemoteProvisioningService.TAG;
+    private final ConcurrentHashMap<IGetKeyCallback, CancellationSignal> mOperations =
+            new ConcurrentHashMap<>();
+    private final RegistrationProxy mRegistration;
+    private final Executor mExecutor;
+
+    private class GetKeyReceiver implements OutcomeReceiver<RemotelyProvisionedKey, Exception> {
+        IGetKeyCallback mCallback;
+        GetKeyReceiver(IGetKeyCallback callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void onResult(RemotelyProvisionedKey result) {
+            mOperations.remove(mCallback);
+            Log.i(TAG, "Successfully fetched key for client " + mCallback.hashCode());
+            android.security.rkp.RemotelyProvisionedKey parcelable =
+                    new android.security.rkp.RemotelyProvisionedKey();
+            parcelable.keyBlob = result.getKeyBlob();
+            parcelable.encodedCertChain = result.getEncodedCertChain();
+            wrapCallback(() -> mCallback.onSuccess(parcelable));
+        }
+
+        @Override
+        public void onError(Exception e) {
+            mOperations.remove(mCallback);
+            if (e instanceof OperationCanceledException) {
+                Log.i(TAG, "Operation cancelled for client " + mCallback.hashCode());
+                wrapCallback(mCallback::onCancel);
+            } else {
+                Log.e(TAG, "Error fetching key for client " + mCallback.hashCode(), e);
+                wrapCallback(() -> mCallback.onError(e.getMessage()));
+            }
+        }
+    }
+
+    RemoteProvisioningRegistration(RegistrationProxy registration, Executor executor) {
+        mRegistration = registration;
+        mExecutor = executor;
+    }
+
+    @Override
+    public void getKey(int keyId, IGetKeyCallback callback) {
+        CancellationSignal cancellationSignal = new CancellationSignal();
+        if (mOperations.putIfAbsent(callback, cancellationSignal) != null) {
+            Log.e(TAG, "Client can only request one call at a time " + callback.hashCode());
+            throw new IllegalArgumentException(
+                    "Callback is already associated with an existing operation: "
+                            + callback.hashCode());
+        }
+
+        try {
+            Log.i(TAG, "Fetching key " + keyId + " for client " + callback.hashCode());
+            mRegistration.getKeyAsync(keyId, cancellationSignal, mExecutor,
+                    new GetKeyReceiver(callback));
+        } catch (Exception e) {
+            Log.e(TAG, "getKeyAsync threw an exception for client " + callback.hashCode(), e);
+            mOperations.remove(callback);
+            wrapCallback(() -> callback.onError(e.getMessage()));
+        }
+    }
+
+    @Override
+    public void cancelGetKey(IGetKeyCallback callback) {
+        CancellationSignal cancellationSignal = mOperations.remove(callback);
+        if (cancellationSignal == null) {
+            throw new IllegalArgumentException(
+                    "Invalid client in cancelGetKey: " + callback.hashCode());
+        }
+
+        Log.i(TAG, "Requesting cancellation for client " + callback.hashCode());
+        cancellationSignal.cancel();
+    }
+
+    @Override
+    public void storeUpgradedKey(byte[] oldKeyBlob, byte[] newKeyBlob) {
+        // TODO(b/262748535)
+        Log.e(TAG, "RegistrationBinder.storeUpgradedKey NOT YET IMPLEMENTED");
+    }
+
+    interface CallbackRunner {
+        void run() throws Exception;
+    }
+
+    private void wrapCallback(CallbackRunner callback) {
+        // Exceptions resulting from notifications to IGetKeyCallback objects can only be logged,
+        // since getKey execution is asynchronous, and there's no way for an exception to be
+        // properly handled up the stack.
+        try {
+            callback.run();
+        } catch (Exception e) {
+            Log.e(TAG, "Error invoking callback on client binder", e);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java
index 65a4b38..cd1a968 100644
--- a/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java
+++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java
@@ -20,9 +20,7 @@
 import android.os.Binder;
 import android.os.OutcomeReceiver;
 import android.os.RemoteException;
-import android.security.rkp.IGetKeyCallback;
 import android.security.rkp.IGetRegistrationCallback;
-import android.security.rkp.IRegistration;
 import android.security.rkp.IRemoteProvisioning;
 import android.security.rkp.service.RegistrationProxy;
 import android.util.Log;
@@ -30,6 +28,7 @@
 import com.android.server.SystemService;
 
 import java.time.Duration;
+import java.util.concurrent.Executor;
 
 /**
  * Implements the remote provisioning system service. This service is backed by a mainline
@@ -43,6 +42,35 @@
     private static final Duration CREATE_REGISTRATION_TIMEOUT = Duration.ofSeconds(10);
     private final RemoteProvisioningImpl mBinderImpl = new RemoteProvisioningImpl();
 
+    private static class RegistrationReceiver implements
+            OutcomeReceiver<RegistrationProxy, Exception> {
+        private final Executor mExecutor;
+        private final IGetRegistrationCallback mCallback;
+
+        RegistrationReceiver(Executor executor, IGetRegistrationCallback callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onResult(RegistrationProxy registration) {
+            try {
+                mCallback.onSuccess(new RemoteProvisioningRegistration(registration, mExecutor));
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling success callback " + mCallback.hashCode(), e);
+            }
+        }
+
+        @Override
+        public void onError(Exception error) {
+            try {
+                mCallback.onError(error.toString());
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling error callback " + mCallback.hashCode(), e);
+            }
+        }
+    }
+
     /** @hide */
     public RemoteProvisioningService(Context context) {
         super(context);
@@ -54,73 +82,20 @@
     }
 
     private final class RemoteProvisioningImpl extends IRemoteProvisioning.Stub {
-
-        final class RegistrationBinder extends IRegistration.Stub {
-            static final String TAG = RemoteProvisioningService.TAG;
-            private final RegistrationProxy mRegistration;
-
-            RegistrationBinder(RegistrationProxy registration) {
-                mRegistration = registration;
-            }
-
-            @Override
-            public void getKey(int keyId, IGetKeyCallback callback) {
-                Log.e(TAG, "RegistrationBinder.getKey NOT YET IMPLEMENTED");
-            }
-
-            @Override
-            public void cancelGetKey(IGetKeyCallback callback) {
-                Log.e(TAG, "RegistrationBinder.cancelGetKey NOT YET IMPLEMENTED");
-            }
-
-            @Override
-            public void storeUpgradedKey(byte[] oldKeyBlob, byte[] newKeyBlob) {
-                Log.e(TAG, "RegistrationBinder.storeUpgradedKey NOT YET IMPLEMENTED");
-            }
-        }
-
         @Override
         public void getRegistration(String irpcName, IGetRegistrationCallback callback)
                 throws RemoteException {
             final int callerUid = Binder.getCallingUidOrThrow();
             final long callingIdentity = Binder.clearCallingIdentity();
+            final Executor executor = getContext().getMainExecutor();
             try {
                 Log.i(TAG, "getRegistration(" + irpcName + ")");
-                RegistrationProxy.createAsync(
-                        getContext(),
-                        callerUid,
-                        irpcName,
-                        CREATE_REGISTRATION_TIMEOUT,
-                        getContext().getMainExecutor(),
-                        new OutcomeReceiver<>() {
-                            @Override
-                            public void onResult(RegistrationProxy registration) {
-                                try {
-                                    callback.onSuccess(new RegistrationBinder(registration));
-                                } catch (RemoteException e) {
-                                    Log.e(TAG, "Error calling success callback", e);
-                                }
-                            }
-
-                            @Override
-                            public void onError(Exception error) {
-                                try {
-                                    callback.onError(error.toString());
-                                } catch (RemoteException e) {
-                                    Log.e(TAG, "Error calling error callback", e);
-                                }
-                            }
-                        });
+                RegistrationProxy.createAsync(getContext(), callerUid, irpcName,
+                        CREATE_REGISTRATION_TIMEOUT, executor,
+                        new RegistrationReceiver(executor, callback));
             } finally {
                 Binder.restoreCallingIdentity(callingIdentity);
             }
         }
-
-        @Override
-        public void cancelGetRegistration(IGetRegistrationCallback callback)
-                throws RemoteException {
-            Log.i(TAG, "cancelGetRegistration()");
-            callback.onError("cancelGetRegistration not yet implemented");
-        }
     }
 }
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 05df22f..739aff7 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -26,16 +26,20 @@
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
+import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY;
 import static android.net.vcn.VcnManager.VCN_ERROR_CODE_CONFIG_ERROR;
 import static android.net.vcn.VcnManager.VCN_ERROR_CODE_INTERNAL_ERROR;
 import static android.net.vcn.VcnManager.VCN_ERROR_CODE_NETWORK_ERROR;
 
 import static com.android.server.VcnManagementService.LOCAL_LOG;
 import static com.android.server.VcnManagementService.VDBG;
+import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.net.ConnectivityDiagnosticsManager;
+import android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
 import android.net.ConnectivityManager;
 import android.net.InetAddresses;
 import android.net.IpPrefix;
@@ -50,11 +54,13 @@
 import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkProvider;
+import android.net.NetworkRequest;
 import android.net.NetworkScore;
 import android.net.RouteInfo;
 import android.net.TelephonyNetworkSpecifier;
 import android.net.Uri;
 import android.net.annotations.PolicyDirection;
+import android.net.ipsec.ike.ChildSaProposal;
 import android.net.ipsec.ike.ChildSessionCallback;
 import android.net.ipsec.ike.ChildSessionConfiguration;
 import android.net.ipsec.ike.ChildSessionParams;
@@ -63,11 +69,14 @@
 import android.net.ipsec.ike.IkeSessionConfiguration;
 import android.net.ipsec.ike.IkeSessionConnectionInfo;
 import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.IkeTrafficSelector;
 import android.net.ipsec.ike.IkeTunnelConnectionParams;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
 import android.net.ipsec.ike.exceptions.IkeException;
 import android.net.ipsec.ike.exceptions.IkeInternalException;
 import android.net.ipsec.ike.exceptions.IkeProtocolException;
 import android.net.vcn.VcnGatewayConnectionConfig;
+import android.net.vcn.VcnManager;
 import android.net.vcn.VcnTransportInfo;
 import android.net.wifi.WifiInfo;
 import android.os.Handler;
@@ -165,6 +174,9 @@
 public class VcnGatewayConnection extends StateMachine {
     private static final String TAG = VcnGatewayConnection.class.getSimpleName();
 
+    /** Default number of parallel SAs requested */
+    static final int TUNNEL_AGGREGATION_SA_COUNT_MAX_DEFAULT = 1;
+
     // Matches DataConnection.NETWORK_TYPE private constant, and magic string from
     // ConnectivityManager#getNetworkTypeName()
     @VisibleForTesting(visibility = Visibility.PRIVATE)
@@ -546,6 +558,39 @@
         }
     }
 
+    /**
+     * Sent when there is a suspected data stall on a network
+     *
+     * <p>Only relevant in the Connected state.
+     *
+     * @param arg1 The "all" token; this signal is always honored.
+     * @param obj @NonNull An EventDataStallSuspectedInfo instance with relevant data.
+     */
+    private static final int EVENT_DATA_STALL_SUSPECTED = 13;
+
+    private static class EventDataStallSuspectedInfo implements EventInfo {
+        @NonNull public final Network network;
+
+        EventDataStallSuspectedInfo(@NonNull Network network) {
+            this.network = network;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(network);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            if (!(other instanceof EventDataStallSuspectedInfo)) {
+                return false;
+            }
+
+            final EventDataStallSuspectedInfo rhs = (EventDataStallSuspectedInfo) other;
+            return Objects.equals(network, rhs.network);
+        }
+    }
+
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     @NonNull
     final DisconnectedState mDisconnectedState = new DisconnectedState();
@@ -578,10 +623,13 @@
     @NonNull
     private final VcnUnderlyingNetworkControllerCallback mUnderlyingNetworkControllerCallback;
 
+    @NonNull private final VcnConnectivityDiagnosticsCallback mConnectivityDiagnosticsCallback;
+
     private final boolean mIsMobileDataEnabled;
 
     @NonNull private final IpSecManager mIpSecManager;
     @NonNull private final ConnectivityManager mConnectivityManager;
+    @NonNull private final ConnectivityDiagnosticsManager mConnectivityDiagnosticsManager;
 
     @Nullable private IpSecTunnelInterface mTunnelIface = null;
 
@@ -748,6 +796,20 @@
                         mUnderlyingNetworkControllerCallback);
         mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class);
         mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class);
+        mConnectivityDiagnosticsManager =
+                mVcnContext.getContext().getSystemService(ConnectivityDiagnosticsManager.class);
+
+        mConnectivityDiagnosticsCallback = new VcnConnectivityDiagnosticsCallback();
+
+        if (mConnectionConfig.hasGatewayOption(
+                VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY)) {
+            final NetworkRequest diagRequest =
+                    new NetworkRequest.Builder().addTransportType(TRANSPORT_CELLULAR).build();
+            mConnectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback(
+                    diagRequest,
+                    new HandlerExecutor(new Handler(vcnContext.getLooper())),
+                    mConnectivityDiagnosticsCallback);
+        }
 
         addState(mDisconnectedState);
         addState(mDisconnectingState);
@@ -810,6 +872,9 @@
         mUnderlyingNetworkController.teardown();
 
         mGatewayStatusCallback.onQuit();
+
+        mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback(
+                mConnectivityDiagnosticsCallback);
     }
 
     /**
@@ -828,6 +893,20 @@
         sendMessageAndAcquireWakeLock(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL);
     }
 
+    private class VcnConnectivityDiagnosticsCallback extends ConnectivityDiagnosticsCallback {
+        @Override
+        public void onDataStallSuspected(ConnectivityDiagnosticsManager.DataStallReport report) {
+            mVcnContext.ensureRunningOnLooperThread();
+
+            final Network network = report.getNetwork();
+            logInfo("Data stall suspected on " + network);
+            sendMessageAndAcquireWakeLock(
+                    EVENT_DATA_STALL_SUSPECTED,
+                    TOKEN_ALL,
+                    new EventDataStallSuspectedInfo(network));
+        }
+    }
+
     private class VcnUnderlyingNetworkControllerCallback
             implements UnderlyingNetworkControllerCallback {
         @Override
@@ -1367,7 +1446,8 @@
                 case EVENT_SUBSCRIPTIONS_CHANGED: // Fallthrough
                 case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: // Fallthrough
                 case EVENT_MIGRATION_COMPLETED: // Fallthrough
-                case EVENT_IKE_CONNECTION_INFO_CHANGED:
+                case EVENT_IKE_CONNECTION_INFO_CHANGED: // Fallthrough
+                case EVENT_DATA_STALL_SUSPECTED:
                     logUnexpectedEvent(msg.what);
                     break;
                 default:
@@ -1908,6 +1988,22 @@
                             mChildConfig,
                             oldChildConfig,
                             mIkeConnectionInfo);
+
+                    // Create opportunistic child SAs; this allows SA aggregation in the downlink,
+                    // reducing lock/atomic contention in high throughput scenarios. All SAs will
+                    // share the same UDP encap socket (and keepalives) as necessary, and are
+                    // effectively free.
+                    final int parallelTunnelCount =
+                            mDeps.getParallelTunnelCount(mLastSnapshot, mSubscriptionGroup);
+                    logInfo("Parallel tunnel count: " + parallelTunnelCount);
+
+                    for (int i = 0; i < parallelTunnelCount - 1; i++) {
+                        mIkeSession.openChildSession(
+                                buildOpportunisticChildParams(),
+                                new VcnChildSessionCallback(
+                                        mCurrentToken, true /* isOpportunistic */));
+                    }
+
                     break;
                 case EVENT_DISCONNECT_REQUESTED:
                     handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
@@ -1925,6 +2021,11 @@
                     mIkeConnectionInfo =
                             ((EventIkeConnectionInfoChangedInfo) msg.obj).ikeConnectionInfo;
                     break;
+                case EVENT_DATA_STALL_SUSPECTED:
+                    final Network networkWithDataStall =
+                            ((EventDataStallSuspectedInfo) msg.obj).network;
+                    handleDataStallSuspected(networkWithDataStall);
+                    break;
                 default:
                     logUnhandledMessage(msg);
                     break;
@@ -1985,6 +2086,15 @@
             }
         }
 
+        private void handleDataStallSuspected(Network networkWithDataStall) {
+            if (mUnderlying != null
+                    && mNetworkAgent != null
+                    && mNetworkAgent.getNetwork().equals(networkWithDataStall)) {
+                logInfo("Perform Mobility update to recover from suspected data stall");
+                mIkeSession.setNetwork(mUnderlying.network);
+            }
+        }
+
         protected void setupInterfaceAndNetworkAgent(
                 int token,
                 @NonNull IpSecTunnelInterface tunnelIface,
@@ -2264,15 +2374,44 @@
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     public class VcnChildSessionCallback implements ChildSessionCallback {
         private final int mToken;
+        private final boolean mIsOpportunistic;
+
+        private boolean mIsChildOpened = false;
 
         VcnChildSessionCallback(int token) {
+            this(token, false /* isOpportunistic */);
+        }
+
+        /**
+         * Creates a ChildSessionCallback
+         *
+         * <p>If configured as opportunistic, transforms will not report initial startup, or
+         * associated startup failures. This serves the dual purposes of ensuring that if the server
+         * does not support connection multiplexing, new child SA negotiations will be ignored, and
+         * at the same time, will notify the VCN session if a successfully negotiated opportunistic
+         * child SA is subsequently torn down, which could impact uplink traffic if the SA in use
+         * for outbound/uplink traffic is this opportunistic SA.
+         *
+         * <p>While inbound SAs can be used in parallel, the IPsec stack explicitly selects the last
+         * applied outbound transform for outbound traffic. This means that unlike inbound traffic,
+         * outbound does not benefit from these parallel SAs in the same manner.
+         */
+        VcnChildSessionCallback(int token, boolean isOpportunistic) {
             mToken = token;
+            mIsOpportunistic = isOpportunistic;
         }
 
         /** Internal proxy method for injecting of mocked ChildSessionConfiguration */
         @VisibleForTesting(visibility = Visibility.PRIVATE)
         void onOpened(@NonNull VcnChildSessionConfiguration childConfig) {
             logDbg("ChildOpened for token " + mToken);
+
+            if (mIsOpportunistic) {
+                logDbg("ChildOpened for opportunistic child; suppressing event message");
+                mIsChildOpened = true;
+                return;
+            }
+
             childOpened(mToken, childConfig);
         }
 
@@ -2284,12 +2423,24 @@
         @Override
         public void onClosed() {
             logDbg("ChildClosed for token " + mToken);
+
+            if (mIsOpportunistic && !mIsChildOpened) {
+                logDbg("ChildClosed for unopened opportunistic child; ignoring");
+                return;
+            }
+
             sessionLost(mToken, null);
         }
 
         @Override
         public void onClosedExceptionally(@NonNull IkeException exception) {
             logInfo("ChildClosedExceptionally for token " + mToken, exception);
+
+            if (mIsOpportunistic && !mIsChildOpened) {
+                logInfo("ChildClosedExceptionally for unopened opportunistic child; ignoring");
+                return;
+            }
+
             sessionLost(mToken, exception);
         }
 
@@ -2424,6 +2575,11 @@
     }
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
+    ConnectivityDiagnosticsCallback getConnectivityDiagnosticsCallback() {
+        return mConnectivityDiagnosticsCallback;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
     UnderlyingNetworkRecord getUnderlyingNetwork() {
         return mUnderlying;
     }
@@ -2489,6 +2645,30 @@
         return mConnectionConfig.getTunnelConnectionParams().getTunnelModeChildSessionParams();
     }
 
+    private ChildSessionParams buildOpportunisticChildParams() {
+        final ChildSessionParams baseParams =
+                mConnectionConfig.getTunnelConnectionParams().getTunnelModeChildSessionParams();
+
+        final TunnelModeChildSessionParams.Builder builder =
+                new TunnelModeChildSessionParams.Builder();
+        for (ChildSaProposal proposal : baseParams.getChildSaProposals()) {
+            builder.addChildSaProposal(proposal);
+        }
+
+        for (IkeTrafficSelector inboundSelector : baseParams.getInboundTrafficSelectors()) {
+            builder.addInboundTrafficSelectors(inboundSelector);
+        }
+
+        for (IkeTrafficSelector outboundSelector : baseParams.getOutboundTrafficSelectors()) {
+            builder.addOutboundTrafficSelectors(outboundSelector);
+        }
+
+        builder.setLifetimeSeconds(
+                baseParams.getHardLifetimeSeconds(), baseParams.getSoftLifetimeSeconds());
+
+        return builder.build();
+    }
+
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     VcnIkeSession buildIkeSession(@NonNull Network network) {
         final int token = ++mCurrentToken;
@@ -2589,6 +2769,23 @@
                 return 0;
             }
         }
+
+        /** Gets the max number of parallel tunnels allowed for tunnel aggregation. */
+        public int getParallelTunnelCount(
+                TelephonySubscriptionSnapshot snapshot, ParcelUuid subGrp) {
+            PersistableBundleWrapper carrierConfig = snapshot.getCarrierConfigForSubGrp(subGrp);
+            int result = TUNNEL_AGGREGATION_SA_COUNT_MAX_DEFAULT;
+
+            if (carrierConfig != null) {
+                result =
+                        carrierConfig.getInt(
+                                VcnManager.VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY,
+                                TUNNEL_AGGREGATION_SA_COUNT_MAX_DEFAULT);
+            }
+
+            // Guard against tunnel count < 1
+            return Math.max(1, result);
+        }
     }
 
     /**
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 2f84fdd..2141eba 100644
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.vcn.routeselection;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -45,6 +46,7 @@
 import com.android.server.vcn.VcnContext;
 
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -69,9 +71,23 @@
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     static final int WIFI_EXIT_RSSI_THRESHOLD_DEFAULT = -74;
 
-    /** Priority for any other networks (including unvalidated, etc) */
+    /**
+     * Priority for networks that VCN can fall back to.
+     *
+     * <p>If none of the network candidates are validated or match any template, VCN will fall back
+     * to any INTERNET network.
+     */
     @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int PRIORITY_ANY = Integer.MAX_VALUE;
+    static final int PRIORITY_FALLBACK = Integer.MAX_VALUE;
+
+    /**
+     * Priority for networks that cannot be selected as VCN's underlying networks.
+     *
+     * <p>VCN MUST never select a non-INTERNET network that are unvalidated or fail to match any
+     * template as the underlying network.
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int PRIORITY_INVALID = -1;
 
     /** Gives networks a priority class, based on configured VcnGatewayConnectionConfig */
     public static int calculatePriorityClass(
@@ -86,12 +102,12 @@
 
         if (networkRecord.isBlocked) {
             logWtf("Network blocked for System Server: " + networkRecord.network);
-            return PRIORITY_ANY;
+            return PRIORITY_INVALID;
         }
 
         if (snapshot == null) {
             logWtf("Got null snapshot");
-            return PRIORITY_ANY;
+            return PRIORITY_INVALID;
         }
 
         int priorityIndex = 0;
@@ -108,7 +124,13 @@
             }
             priorityIndex++;
         }
-        return PRIORITY_ANY;
+
+        final NetworkCapabilities caps = networkRecord.networkCapabilities;
+        if (caps.hasCapability(NET_CAPABILITY_INTERNET)
+                || (vcnContext.isInTestMode() && caps.hasTransport(TRANSPORT_TEST))) {
+            return PRIORITY_FALLBACK;
+        }
+        return PRIORITY_INVALID;
     }
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
@@ -297,6 +319,18 @@
             return false;
         }
 
+        for (Map.Entry<Integer, Integer> entry :
+                networkPriority.getCapabilitiesMatchCriteria().entrySet()) {
+            final int cap = entry.getKey();
+            final int matchCriteria = entry.getValue();
+
+            if (matchCriteria == MATCH_REQUIRED && !caps.hasCapability(cap)) {
+                return false;
+            } else if (matchCriteria == MATCH_FORBIDDEN && caps.hasCapability(cap)) {
+                return false;
+            }
+        }
+
         return true;
     }
 
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
index d474c5d..6afa795 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
@@ -16,6 +16,9 @@
 
 package com.android.server.vcn.routeselection;
 
+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.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
 
 import static com.android.server.VcnManagementService.LOCAL_LOG;
@@ -32,6 +35,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.VcnCellUnderlyingNetworkTemplate;
 import android.net.vcn.VcnGatewayConnectionConfig;
 import android.net.vcn.VcnUnderlyingNetworkTemplate;
 import android.os.Handler;
@@ -40,6 +44,7 @@
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -49,6 +54,7 @@
 import com.android.server.vcn.util.LogUtils;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -126,6 +132,63 @@
         registerOrUpdateNetworkRequests();
     }
 
+    private static class CapabilityMatchCriteria {
+        public final int capability;
+        public final int matchCriteria;
+
+        CapabilityMatchCriteria(int capability, int matchCriteria) {
+            this.capability = capability;
+            this.matchCriteria = matchCriteria;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(capability, matchCriteria);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            if (!(other instanceof CapabilityMatchCriteria)) {
+                return false;
+            }
+
+            final CapabilityMatchCriteria rhs = (CapabilityMatchCriteria) other;
+            return capability == rhs.capability && matchCriteria == rhs.matchCriteria;
+        }
+    }
+
+    private static Set<Set<CapabilityMatchCriteria>> dedupAndGetCapRequirementsForCell(
+            VcnGatewayConnectionConfig connectionConfig) {
+        final Set<Set<CapabilityMatchCriteria>> dedupedCapsMatchSets = new ArraySet<>();
+
+        for (VcnUnderlyingNetworkTemplate template :
+                connectionConfig.getVcnUnderlyingNetworkPriorities()) {
+            if (template instanceof VcnCellUnderlyingNetworkTemplate) {
+                final Set<CapabilityMatchCriteria> capsMatchSet = new ArraySet<>();
+
+                for (Map.Entry<Integer, Integer> entry :
+                        ((VcnCellUnderlyingNetworkTemplate) template)
+                                .getCapabilitiesMatchCriteria()
+                                .entrySet()) {
+
+                    final int capability = entry.getKey();
+                    final int matchCriteria = entry.getValue();
+                    if (matchCriteria != MATCH_ANY) {
+                        capsMatchSet.add(new CapabilityMatchCriteria(capability, matchCriteria));
+                    }
+                }
+
+                dedupedCapsMatchSets.add(capsMatchSet);
+            }
+        }
+
+        dedupedCapsMatchSets.add(
+                Collections.singleton(
+                        new CapabilityMatchCriteria(
+                                NetworkCapabilities.NET_CAPABILITY_INTERNET, MATCH_REQUIRED)));
+        return dedupedCapsMatchSets;
+    }
+
     private void registerOrUpdateNetworkRequests() {
         NetworkCallback oldRouteSelectionCallback = mRouteSelectionCallback;
         NetworkCallback oldWifiCallback = mWifiBringupCallback;
@@ -158,11 +221,14 @@
                     getWifiNetworkRequest(), mWifiBringupCallback, mHandler);
 
             for (final int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) {
-                final NetworkBringupCallback cb = new NetworkBringupCallback();
-                mCellBringupCallbacks.add(cb);
+                for (Set<CapabilityMatchCriteria> capsMatchCriteria :
+                        dedupAndGetCapRequirementsForCell(mConnectionConfig)) {
+                    final NetworkBringupCallback cb = new NetworkBringupCallback();
+                    mCellBringupCallbacks.add(cb);
 
-                mConnectivityManager.requestBackgroundNetwork(
-                        getCellNetworkRequestForSubId(subId), cb, mHandler);
+                    mConnectivityManager.requestBackgroundNetwork(
+                            getCellNetworkRequestForSubId(subId, capsMatchCriteria), cb, mHandler);
+                }
             }
         } else {
             mRouteSelectionCallback = null;
@@ -214,6 +280,13 @@
                 .build();
     }
 
+    private NetworkRequest.Builder getBaseWifiNetworkRequestBuilder() {
+        return getBaseNetworkRequestBuilder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup));
+    }
+
     /**
      * Builds the WiFi bringup request
      *
@@ -224,10 +297,7 @@
      * but will NEVER bring up a Carrier WiFi network itself.
      */
     private NetworkRequest getWifiNetworkRequest() {
-        return getBaseNetworkRequestBuilder()
-                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
-                .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))
-                .build();
+        return getBaseWifiNetworkRequestBuilder().build();
     }
 
     /**
@@ -238,9 +308,7 @@
      * pace to effectively select a short-lived WiFi offload network.
      */
     private NetworkRequest getWifiEntryRssiThresholdNetworkRequest() {
-        return getBaseNetworkRequestBuilder()
-                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
-                .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))
+        return getBaseWifiNetworkRequestBuilder()
                 // Ensure wifi updates signal strengths when crossing this threshold.
                 .setSignalStrength(getWifiEntryRssiThreshold(mCarrierConfig))
                 .build();
@@ -254,9 +322,7 @@
      * pace to effectively select away from a failing WiFi network.
      */
     private NetworkRequest getWifiExitRssiThresholdNetworkRequest() {
-        return getBaseNetworkRequestBuilder()
-                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
-                .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))
+        return getBaseWifiNetworkRequestBuilder()
                 // Ensure wifi updates signal strengths when crossing this threshold.
                 .setSignalStrength(getWifiExitRssiThreshold(mCarrierConfig))
                 .build();
@@ -273,11 +339,25 @@
      * <p>Since this request MUST make it to the TelephonyNetworkFactory, subIds are not specified
      * in the NetworkCapabilities, but rather in the TelephonyNetworkSpecifier.
      */
-    private NetworkRequest getCellNetworkRequestForSubId(int subId) {
-        return getBaseNetworkRequestBuilder()
-                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId))
-                .build();
+    private NetworkRequest getCellNetworkRequestForSubId(
+            int subId, Set<CapabilityMatchCriteria> capsMatchCriteria) {
+        final NetworkRequest.Builder nrBuilder =
+                getBaseNetworkRequestBuilder()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                        .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId));
+
+        for (CapabilityMatchCriteria capMatchCriteria : capsMatchCriteria) {
+            final int cap = capMatchCriteria.capability;
+            final int matchCriteria = capMatchCriteria.matchCriteria;
+
+            if (matchCriteria == MATCH_REQUIRED) {
+                nrBuilder.addCapability(cap);
+            } else if (matchCriteria == MATCH_FORBIDDEN) {
+                nrBuilder.addForbiddenCapability(cap);
+            }
+        }
+
+        return nrBuilder.build();
     }
 
     /**
@@ -285,7 +365,6 @@
      */
     private NetworkRequest.Builder getBaseNetworkRequestBuilder() {
         return new NetworkRequest.Builder()
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
@@ -356,7 +435,7 @@
             if (!allNetworkPriorities.isEmpty()) {
                 allNetworkPriorities += ", ";
             }
-            allNetworkPriorities += record.network + ": " + record.getPriorityClass();
+            allNetworkPriorities += record.network + ": " + record.priorityClass;
         }
         logInfo(
                 "Selected network changed to "
@@ -393,19 +472,22 @@
 
         private TreeSet<UnderlyingNetworkRecord> getSortedUnderlyingNetworks() {
             TreeSet<UnderlyingNetworkRecord> sorted =
-                    new TreeSet<>(
-                            UnderlyingNetworkRecord.getComparator(
+                    new TreeSet<>(UnderlyingNetworkRecord.getComparator());
+
+            for (UnderlyingNetworkRecord.Builder builder :
+                    mUnderlyingNetworkRecordBuilders.values()) {
+                if (builder.isValid()) {
+                    final UnderlyingNetworkRecord record =
+                            builder.build(
                                     mVcnContext,
                                     mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
                                     mSubscriptionGroup,
                                     mLastSnapshot,
                                     mCurrentRecord,
-                                    mCarrierConfig));
-
-            for (UnderlyingNetworkRecord.Builder builder :
-                    mUnderlyingNetworkRecordBuilders.values()) {
-                if (builder.isValid()) {
-                    sorted.add(builder.build());
+                                    mCarrierConfig);
+                    if (record.priorityClass != NetworkPriorityClassifier.PRIORITY_INVALID) {
+                        sorted.add(record);
+                    }
                 }
             }
 
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 319680e..aea9f4d 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
@@ -42,53 +42,58 @@
  * @hide
  */
 public class UnderlyingNetworkRecord {
-    private static final int PRIORITY_CLASS_INVALID = Integer.MAX_VALUE;
-
     @NonNull public final Network network;
     @NonNull public final NetworkCapabilities networkCapabilities;
     @NonNull public final LinkProperties linkProperties;
     public final boolean isBlocked;
-
-    private int mPriorityClass = PRIORITY_CLASS_INVALID;
+    public final boolean isSelected;
+    public final int priorityClass;
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     public UnderlyingNetworkRecord(
             @NonNull Network network,
             @NonNull NetworkCapabilities networkCapabilities,
             @NonNull LinkProperties linkProperties,
-            boolean isBlocked) {
-        this.network = network;
-        this.networkCapabilities = networkCapabilities;
-        this.linkProperties = linkProperties;
-        this.isBlocked = isBlocked;
-    }
-
-    private int getOrCalculatePriorityClass(
+            boolean isBlocked,
             VcnContext vcnContext,
             List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
             ParcelUuid subscriptionGroup,
             TelephonySubscriptionSnapshot snapshot,
             UnderlyingNetworkRecord currentlySelected,
             PersistableBundleWrapper carrierConfig) {
-        // Never changes after the underlying network record is created.
-        if (mPriorityClass == PRIORITY_CLASS_INVALID) {
-            mPriorityClass =
-                    NetworkPriorityClassifier.calculatePriorityClass(
-                            vcnContext,
-                            this,
-                            underlyingNetworkTemplates,
-                            subscriptionGroup,
-                            snapshot,
-                            currentlySelected,
-                            carrierConfig);
-        }
+        this.network = network;
+        this.networkCapabilities = networkCapabilities;
+        this.linkProperties = linkProperties;
+        this.isBlocked = isBlocked;
 
-        return mPriorityClass;
+        this.isSelected = isSelected(this.network, currentlySelected);
+
+        priorityClass =
+                NetworkPriorityClassifier.calculatePriorityClass(
+                        vcnContext,
+                        this,
+                        underlyingNetworkTemplates,
+                        subscriptionGroup,
+                        snapshot,
+                        currentlySelected,
+                        carrierConfig);
     }
 
-    // Used in UnderlyingNetworkController
-    int getPriorityClass() {
-        return mPriorityClass;
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public UnderlyingNetworkRecord(
+            @NonNull Network network,
+            @NonNull NetworkCapabilities networkCapabilities,
+            @NonNull LinkProperties linkProperties,
+            boolean isBlocked,
+            boolean isSelected,
+            int priorityClass) {
+        this.network = network;
+        this.networkCapabilities = networkCapabilities;
+        this.linkProperties = linkProperties;
+        this.isBlocked = isBlocked;
+        this.isSelected = isSelected;
+
+        this.priorityClass = priorityClass;
     }
 
     @Override
@@ -108,40 +113,32 @@
         return Objects.hash(network, networkCapabilities, linkProperties, isBlocked);
     }
 
-    static Comparator<UnderlyingNetworkRecord> getComparator(
-            VcnContext vcnContext,
-            List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
-            ParcelUuid subscriptionGroup,
-            TelephonySubscriptionSnapshot snapshot,
-            UnderlyingNetworkRecord currentlySelected,
-            PersistableBundleWrapper carrierConfig) {
+    /** Returns if two records are equal including their priority classes. */
+    public static boolean isEqualIncludingPriorities(
+            UnderlyingNetworkRecord left, UnderlyingNetworkRecord right) {
+        if (left != null && right != null) {
+            return left.equals(right)
+                    && left.isSelected == right.isSelected
+                    && left.priorityClass == right.priorityClass;
+        }
+
+        return left == right;
+    }
+
+    static Comparator<UnderlyingNetworkRecord> getComparator() {
         return (left, right) -> {
-            final int leftIndex =
-                    left.getOrCalculatePriorityClass(
-                            vcnContext,
-                            underlyingNetworkTemplates,
-                            subscriptionGroup,
-                            snapshot,
-                            currentlySelected,
-                            carrierConfig);
-            final int rightIndex =
-                    right.getOrCalculatePriorityClass(
-                            vcnContext,
-                            underlyingNetworkTemplates,
-                            subscriptionGroup,
-                            snapshot,
-                            currentlySelected,
-                            carrierConfig);
+            final int leftIndex = left.priorityClass;
+            final int rightIndex = right.priorityClass;
 
             // In the case of networks in the same priority class, prioritize based on other
             // criteria (eg. actively selected network, link metrics, etc)
             if (leftIndex == rightIndex) {
                 // TODO: Improve the strategy of network selection when both UnderlyingNetworkRecord
                 // fall into the same priority class.
-                if (isSelected(left, currentlySelected)) {
+                if (left.isSelected) {
                     return -1;
                 }
-                if (isSelected(left, currentlySelected)) {
+                if (right.isSelected) {
                     return 1;
                 }
             }
@@ -150,11 +147,11 @@
     }
 
     private static boolean isSelected(
-            UnderlyingNetworkRecord recordToCheck, UnderlyingNetworkRecord currentlySelected) {
+            Network networkToCheck, UnderlyingNetworkRecord currentlySelected) {
         if (currentlySelected == null) {
             return false;
         }
-        if (currentlySelected.network == recordToCheck.network) {
+        if (currentlySelected.network.equals(networkToCheck)) {
             return true;
         }
         return false;
@@ -172,16 +169,8 @@
         pw.println("UnderlyingNetworkRecord:");
         pw.increaseIndent();
 
-        final int priorityIndex =
-                getOrCalculatePriorityClass(
-                        vcnContext,
-                        underlyingNetworkTemplates,
-                        subscriptionGroup,
-                        snapshot,
-                        currentlySelected,
-                        carrierConfig);
-
-        pw.println("Priority index: " + priorityIndex);
+        pw.println("priorityClass: " + priorityClass);
+        pw.println("isSelected: " + isSelected);
         pw.println("mNetwork: " + network);
         pw.println("mNetworkCapabilities: " + networkCapabilities);
         pw.println("mLinkProperties: " + linkProperties);
@@ -198,8 +187,6 @@
         boolean mIsBlocked;
         boolean mWasIsBlockedSet;
 
-        @Nullable private UnderlyingNetworkRecord mCached;
-
         Builder(@NonNull Network network) {
             mNetwork = network;
         }
@@ -211,7 +198,6 @@
 
         void setNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
             mNetworkCapabilities = networkCapabilities;
-            mCached = null;
         }
 
         @Nullable
@@ -221,32 +207,40 @@
 
         void setLinkProperties(@NonNull LinkProperties linkProperties) {
             mLinkProperties = linkProperties;
-            mCached = null;
         }
 
         void setIsBlocked(boolean isBlocked) {
             mIsBlocked = isBlocked;
             mWasIsBlockedSet = true;
-            mCached = null;
         }
 
         boolean isValid() {
             return mNetworkCapabilities != null && mLinkProperties != null && mWasIsBlockedSet;
         }
 
-        UnderlyingNetworkRecord build() {
+        UnderlyingNetworkRecord build(
+                VcnContext vcnContext,
+                List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+                ParcelUuid subscriptionGroup,
+                TelephonySubscriptionSnapshot snapshot,
+                UnderlyingNetworkRecord currentlySelected,
+                PersistableBundleWrapper carrierConfig) {
             if (!isValid()) {
                 throw new IllegalArgumentException(
                         "Called build before UnderlyingNetworkRecord was valid");
             }
 
-            if (mCached == null) {
-                mCached =
-                        new UnderlyingNetworkRecord(
-                                mNetwork, mNetworkCapabilities, mLinkProperties, mIsBlocked);
-            }
-
-            return mCached;
+            return new UnderlyingNetworkRecord(
+                    mNetwork,
+                    mNetworkCapabilities,
+                    mLinkProperties,
+                    mIsBlocked,
+                    vcnContext,
+                    underlyingNetworkTemplates,
+                    subscriptionGroup,
+                    snapshot,
+                    currentlySelected,
+                    carrierConfig);
         }
     }
 }
diff --git a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java b/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java
index d22ec0a..d6761a2 100644
--- a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java
+++ b/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java
@@ -573,5 +573,10 @@
 
             return isEqual(mBundle, other.mBundle);
         }
+
+        @Override
+        public String toString() {
+            return mBundle.toString();
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index ff09163..330c098 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1085,10 +1085,14 @@
         // Use launch-adjacent-flag-root if launching with launch-adjacent flag.
         if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0
                 && mLaunchAdjacentFlagRootTask != null) {
-            // If the adjacent launch is coming from the same root, launch to adjacent root instead.
-            if (sourceTask != null && mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment() != null
+            if (sourceTask != null && sourceTask == candidateTask) {
+                // Do nothing when task that is getting opened is same as the source.
+            } else if (sourceTask != null
+                    && mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment() != null
                     && (sourceTask == mLaunchAdjacentFlagRootTask
                     || sourceTask.isDescendantOf(mLaunchAdjacentFlagRootTask))) {
+                // If the adjacent launch is coming from the same root, launch to
+                // adjacent root instead.
                 return mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment().asTask();
             } else {
                 return mLaunchAdjacentFlagRootTask;
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 202fe55..2b15a79 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -63,6 +63,7 @@
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
@@ -261,7 +262,7 @@
 
         boolean isSysUiPackage = info.packageName.equals(
                 mAtm.getSysUiServiceComponentLocked().getPackageName());
-        if (isSysUiPackage || mUid == Process.SYSTEM_UID) {
+        if (isSysUiPackage || UserHandle.getAppId(mUid) == Process.SYSTEM_UID) {
             // This is a system owned process and should not use an activity config.
             // TODO(b/151161907): Remove after support for display-independent (raw) SysUi configs.
             mIsActivityConfigOverrideAllowed = false;
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 8e0dc5d..075991e 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -183,7 +183,7 @@
         "android.frameworks.sensorservice@1.0",
         "android.frameworks.sensorservice-V1-ndk",
         "android.frameworks.stats@1.0",
-        "android.frameworks.stats-V1-ndk",
+        "android.frameworks.stats-V2-ndk",
         "android.system.suspend.control-V1-cpp",
         "android.system.suspend.control.internal-cpp",
         "android.system.suspend-V1-ndk",
diff --git a/services/proguard.flags b/services/proguard.flags
index eb5c714..4c2830e 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -72,6 +72,13 @@
 -keep,allowoptimization,allowaccessmodification class com.android.server.wm.** implements com.android.server.wm.DisplayAreaPolicy$Provider
 
 # JNI keep rules
+# The global keep rule for native methods allows stripping of such methods if they're unreferenced
+# in Java. However, because system_server explicitly registers these methods from native code,
+# stripping them in Java can cause runtime issues. As such, conservatively keep all such methods in
+# system_server subpackages as long as the containing class is also kept or referenced.
+-keepclassmembers class com.android.server.** {
+  native <methods>;
+}
 # TODO(b/210510433): Revisit and fix with @Keep, or consider auto-generating from
 # frameworks/base/services/core/jni/onload.cpp.
 -keep,allowoptimization,allowaccessmodification class com.android.server.broadcastradio.hal1.BroadcastRadioService { *; }
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index 9672085..68e5ebf 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -109,17 +109,16 @@
 
         @Override
         public boolean isFromTrustedProvider(String path, byte[] signature) {
-            return mHasFsverityPaths.contains(path);
+            if (!mHasFsverityPaths.contains(path)) {
+                return false;
+            }
+            String fakeSignature = new String(signature, StandardCharsets.UTF_8);
+            return GOOD_SIGNATURE.equals(fakeSignature);
         }
 
         @Override
-        public void setUpFsverity(String path, byte[] pkcs7Signature) throws IOException {
-            String fakeSignature = new String(pkcs7Signature, StandardCharsets.UTF_8);
-            if (GOOD_SIGNATURE.equals(fakeSignature)) {
-                mHasFsverityPaths.add(path);
-            } else {
-                throw new IOException("Failed to set up fake fs-verity");
-            }
+        public void setUpFsverity(String path) throws IOException {
+            mHasFsverityPaths.add(path);
         }
 
         @Override
@@ -813,8 +812,8 @@
             }
 
             @Override
-            public void setUpFsverity(String path, byte[] pkcs7Signature) throws IOException {
-                mFakeFsverityUtil.setUpFsverity(path, pkcs7Signature);
+            public void setUpFsverity(String path) throws IOException {
+                mFakeFsverityUtil.setUpFsverity(path);
             }
 
             @Override
diff --git a/telephony/java/android/telephony/AccessNetworkConstants.java b/telephony/java/android/telephony/AccessNetworkConstants.java
index 0fdf40d..27ba676 100644
--- a/telephony/java/android/telephony/AccessNetworkConstants.java
+++ b/telephony/java/android/telephony/AccessNetworkConstants.java
@@ -605,9 +605,9 @@
         EUTRAN_ARFCN_FREQUENCY_BAND_41(
                 EutranBand.BAND_41, 2496000, 39650, 41589, 2496000, 39650, 41589),
         EUTRAN_ARFCN_FREQUENCY_BAND_42(
-                EutranBand.BAND_42, 3400000, 41950, 43589, 3400000, 41950, 43589),
+                EutranBand.BAND_42, 3400000, 41590, 43589, 3400000, 41590, 43589),
         EUTRAN_ARFCN_FREQUENCY_BAND_43(
-                EutranBand.BAND_43, 3600000, 43950, 45589, 3600000, 43950, 45589),
+                EutranBand.BAND_43, 3600000, 43590, 45589, 3600000, 43590, 45589),
         EUTRAN_ARFCN_FREQUENCY_BAND_44(
                 EutranBand.BAND_44, 703000, 45590, 46589, 703000, 45590, 46589),
         EUTRAN_ARFCN_FREQUENCY_BAND_45(
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 87f45010..d8c1b57e3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -39,7 +39,6 @@
 import android.telecom.TelecomManager;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.data.ApnSetting;
-import android.telephony.data.DataCallResponse;
 import android.telephony.gba.TlsParams;
 import android.telephony.gba.UaSecurityProtocolIdentifier;
 import android.telephony.ims.ImsReasonInfo;
@@ -1137,27 +1136,6 @@
     public static final String KEY_DEFAULT_MTU_INT = "default_mtu_int";
 
     /**
-     * The data call retry configuration for different types of APN.
-     * @hide
-     */
-    public static final String KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS =
-            "carrier_data_call_retry_config_strings";
-
-    /**
-     * Delay in milliseconds between trying APN from the pool
-     * @hide
-     */
-    public static final String KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG =
-            "carrier_data_call_apn_delay_default_long";
-
-    /**
-     * Faster delay in milliseconds between trying APN from the pool
-     * @hide
-     */
-    public static final String KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG =
-            "carrier_data_call_apn_delay_faster_long";
-
-    /**
      * Delay in milliseconds for retrying APN after disconnect
      * @hide
      */
@@ -1165,25 +1143,7 @@
             "carrier_data_call_apn_retry_after_disconnect_long";
 
     /**
-     * The maximum times for telephony to retry data setup on the same APN requested by
-     * network through the data setup response retry timer
-     * {@link DataCallResponse#getRetryDurationMillis()}. This is to prevent that network keeps
-     * asking device to retry data setup forever and causes power consumption issue. For infinite
-     * retring same APN, configure this as 2147483647 (i.e. {@link Integer#MAX_VALUE}).
-     *
-     * Note if network does not suggest any retry timer, frameworks uses the retry configuration
-     * from {@link #KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS}, and the maximum retry times could
-     * be configured there.
-     * @hide
-     */
-    public static final String KEY_CARRIER_DATA_CALL_RETRY_NETWORK_REQUESTED_MAX_COUNT_INT =
-            "carrier_data_call_retry_network_requested_max_count_int";
-
-    /**
-     * Data call setup permanent failure causes by the carrier.
-     *
-     * @deprecated This API key was added in mistake and is not used anymore by the telephony data
-     * frameworks.
+     * Data call setup permanent failure causes by the carrier
      */
     @Deprecated
     public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS =
@@ -1203,19 +1163,6 @@
             "carrier_metered_roaming_apn_types_strings";
 
     /**
-     * APN types that are not allowed on cellular
-     * @hide
-     */
-    public static final String KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY =
-            "carrier_wwan_disallowed_apn_types_string_array";
-
-    /**
-     * APN types that are not allowed on IWLAN
-     * @hide
-     */
-    public static final String KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY =
-            "carrier_wlan_disallowed_apn_types_string_array";
-    /**
      * CDMA carrier ERI (Enhanced Roaming Indicator) file name
      * @hide
      */
@@ -4361,6 +4308,18 @@
             "data_switch_validation_timeout_long";
 
     /**
+     * The minimum timeout of UDP port 4500 NAT / firewall entries on the Internet PDN of this
+     * carrier network. This will be used by Android platform VPNs to tune IPsec NAT keepalive
+     * interval. If this value is too low to provide uninterrupted inbound connectivity, then
+     * Android system VPNs may indicate to applications that the VPN cannot support long-lived
+     * TCP connections.
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final String KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT =
+            "min_udp_port_4500_nat_timeout_sec_int";
+
+    /**
      * Specifies whether the system should prefix the EAP method to the anonymous identity.
      * The following prefix will be added if this key is set to TRUE:
      *   EAP-AKA: "0"
@@ -8061,6 +8020,14 @@
         public static final String KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL =
                 KEY_PREFIX + "supports_eap_aka_fast_reauth_bool";
 
+        /**
+         * Type of IP preference used to prioritize ePDG servers. Possible values are
+         * {@link #EPDG_ADDRESS_IPV4_PREFERRED}, {@link #EPDG_ADDRESS_IPV6_PREFERRED},
+         * {@link #EPDG_ADDRESS_IPV4_ONLY}
+         */
+        public static final String KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT =
+                KEY_PREFIX + "epdg_address_ip_type_preference_int";
+
         /** @hide */
         @IntDef({AUTHENTICATION_METHOD_EAP_ONLY, AUTHENTICATION_METHOD_CERT})
         public @interface AuthenticationMethodType {}
@@ -8125,6 +8092,39 @@
          */
         public static final int ID_TYPE_KEY_ID = 11;
 
+        /** @hide */
+        @IntDef({
+                EPDG_ADDRESS_IPV4_PREFERRED,
+                EPDG_ADDRESS_IPV6_PREFERRED,
+                EPDG_ADDRESS_IPV4_ONLY,
+                EPDG_ADDRESS_IPV6_ONLY,
+                EPDG_ADDRESS_SYSTEM_PREFERRED
+        })
+        public @interface EpdgAddressIpPreference {}
+
+        /** Prioritize IPv4 ePDG addresses. */
+        public static final int EPDG_ADDRESS_IPV4_PREFERRED = 0;
+
+        /** Prioritize IPv6 ePDG addresses */
+        public static final int EPDG_ADDRESS_IPV6_PREFERRED = 1;
+
+        /** Use IPv4 ePDG addresses only. */
+        public static final int EPDG_ADDRESS_IPV4_ONLY = 2;
+
+        /** Use IPv6 ePDG addresses only.
+         * @hide
+         */
+        public static final int EPDG_ADDRESS_IPV6_ONLY = 3;
+
+        /** Follow the priority from DNS resolution results, which are sorted by using RFC6724
+         * algorithm.
+         *
+         * @see <a href="https://tools.ietf.org/html/rfc6724#section-6">RFC 6724, Default Address
+         *     Selection for Internet Protocol Version 6 (IPv6)</a>
+         * @hide
+         */
+        public static final int EPDG_ADDRESS_SYSTEM_PREFERRED = 4;
+
         private Iwlan() {}
 
         private static PersistableBundle getDefaults() {
@@ -8208,7 +8208,7 @@
             defaults.putInt(KEY_EPDG_PCO_ID_IPV6_INT, 0);
             defaults.putInt(KEY_EPDG_PCO_ID_IPV4_INT, 0);
             defaults.putBoolean(KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL, false);
-
+            defaults.putInt(KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT, EPDG_ADDRESS_IPV4_PREFERRED);
             return defaults;
         }
     }
@@ -8349,7 +8349,6 @@
      * "1800000, maximum_retries=20" means for those capabilities, retry happens in 2.5s, 3s, 5s,
      * 10s, 15s, 20s, 40s, 1m, 2m, 4m, 10m, 20m, 30m, 30m, 30m, until reaching 20 retries.
      *
-     * // TODO: remove KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS
      * @hide
      */
     public static final String KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY =
@@ -8756,27 +8755,13 @@
         sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false);
         sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false);
         sDefaults.putInt(KEY_DEFAULT_MTU_INT, 1500);
-        sDefaults.putStringArray(KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS, new String[]{
-                "default:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
-                        + "320000:5000,640000:5000,1280000:5000,1800000:5000",
-                "mms:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
-                        + "320000:5000,640000:5000,1280000:5000,1800000:5000",
-                "ims:max_retries=10, 5000, 5000, 5000",
-                "others:max_retries=3, 5000, 5000, 5000"});
-        sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG, 20000);
-        sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG, 3000);
         sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_RETRY_AFTER_DISCONNECT_LONG, 3000);
-        sDefaults.putInt(KEY_CARRIER_DATA_CALL_RETRY_NETWORK_REQUESTED_MAX_COUNT_INT, 3);
         sDefaults.putString(KEY_CARRIER_ERI_FILE_NAME_STRING, "eri.xml");
         sDefaults.putInt(KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT, 7200);
         sDefaults.putStringArray(KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{"default", "mms", "dun", "supl"});
         sDefaults.putStringArray(KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
                 new String[]{"default", "mms", "dun", "supl"});
-        sDefaults.putStringArray(KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
-                new String[]{""});
-        sDefaults.putStringArray(KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
-                new String[]{""});
         sDefaults.putIntArray(KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY,
                 new int[] {TelephonyManager.NETWORK_TYPE_CDMA, TelephonyManager.NETWORK_TYPE_1xRTT,
                         TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyManager.NETWORK_TYPE_EVDO_A,
@@ -9198,6 +9183,7 @@
         sDefaults.putStringArray(KEY_MMI_TWO_DIGIT_NUMBER_PATTERN_STRING_ARRAY, new String[0]);
         sDefaults.putInt(KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT,
                 CellSignalStrengthLte.USE_RSRP);
+        sDefaults.putInt(KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT, 300);
         // Default wifi configurations.
         sDefaults.putAll(Wifi.getDefaults());
         sDefaults.putBoolean(ENABLE_EAP_METHOD_PREFIX_BOOL, false);
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index f794a79..3379ce5 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -1284,7 +1284,7 @@
                 && xorEqualsInt(this.mMmsProxyPort, other.mMmsProxyPort))
                 && xorEqualsString(this.mUser, other.mUser)
                 && xorEqualsString(this.mPassword, other.mPassword)
-                && xorEqualsInt(this.mAuthType, other.mAuthType)
+                && Objects.equals(this.mAuthType, other.mAuthType)
                 && !typeSameAny(this, other)
                 && Objects.equals(this.mOperatorNumeric, other.mOperatorNumeric)
                 && Objects.equals(this.mProtocol, other.mProtocol)
diff --git a/telephony/java/android/telephony/data/QosBearerFilter.java b/telephony/java/android/telephony/data/QosBearerFilter.java
index 0ab7b61..a0d9c1bd 100644
--- a/telephony/java/android/telephony/data/QosBearerFilter.java
+++ b/telephony/java/android/telephony/data/QosBearerFilter.java
@@ -130,6 +130,10 @@
         return precedence;
     }
 
+    public int getProtocol() {
+        return protocol;
+    }
+
     public static class PortRange implements Parcelable {
         int start;
         int end;
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0ce6b14..da1ffcd 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2510,9 +2510,6 @@
     CellIdentity getLastKnownCellIdentity(int subId, String callingPackage,
             String callingFeatureId);
 
-    /** Check if telephony new data stack is enabled. */
-    boolean isUsingNewDataStack();
-
     /**
      *  @return true if the modem service is set successfully, false otherwise.
      */
diff --git a/tests/SurfaceControlViewHostTest/OWNERS b/tests/SurfaceControlViewHostTest/OWNERS
new file mode 100644
index 0000000..0862c05
--- /dev/null
+++ b/tests/SurfaceControlViewHostTest/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/wm/OWNERS
diff --git a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java
index 2fbcf9d..1569613 100644
--- a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java
@@ -20,6 +20,7 @@
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.fail;
 
 import org.junit.Test;
@@ -31,8 +32,8 @@
     private static final Set<String> ALLOWED_PLMN_IDS = new HashSet<>();
     private static final Set<Integer> ALLOWED_CARRIER_IDS = new HashSet<>();
 
-    // Package private for use in VcnGatewayConnectionConfigTest
-    static VcnCellUnderlyingNetworkTemplate getTestNetworkTemplate() {
+    // Public for use in UnderlyingNetworkControllerTest
+    public static VcnCellUnderlyingNetworkTemplate.Builder getTestNetworkTemplateBuilder() {
         return new VcnCellUnderlyingNetworkTemplate.Builder()
                 .setMetered(MATCH_FORBIDDEN)
                 .setMinUpstreamBandwidthKbps(
@@ -44,13 +45,44 @@
                 .setOperatorPlmnIds(ALLOWED_PLMN_IDS)
                 .setSimSpecificCarrierIds(ALLOWED_CARRIER_IDS)
                 .setRoaming(MATCH_FORBIDDEN)
-                .setOpportunistic(MATCH_REQUIRED)
-                .build();
+                .setOpportunistic(MATCH_REQUIRED);
+    }
+
+    // Package private for use in VcnGatewayConnectionConfigTest
+    static VcnCellUnderlyingNetworkTemplate getTestNetworkTemplate() {
+        return getTestNetworkTemplateBuilder().build();
+    }
+
+    private void setAllCapabilities(
+            VcnCellUnderlyingNetworkTemplate.Builder builder, int matchCriteria) {
+        builder.setCbs(matchCriteria);
+        builder.setDun(matchCriteria);
+        builder.setIms(matchCriteria);
+        builder.setInternet(matchCriteria);
+        builder.setMms(matchCriteria);
+        builder.setRcs(matchCriteria);
+    }
+
+    private void verifyAllCapabilities(
+            VcnCellUnderlyingNetworkTemplate template,
+            int expectMatchCriteriaforNonInternet,
+            int expectMatchCriteriaforInternet) {
+        assertEquals(expectMatchCriteriaforNonInternet, template.getCbs());
+        assertEquals(expectMatchCriteriaforNonInternet, template.getDun());
+        assertEquals(expectMatchCriteriaforNonInternet, template.getIms());
+        assertEquals(expectMatchCriteriaforNonInternet, template.getMms());
+        assertEquals(expectMatchCriteriaforNonInternet, template.getRcs());
+
+        assertEquals(expectMatchCriteriaforInternet, template.getInternet());
     }
 
     @Test
     public void testBuilderAndGetters() {
-        final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate();
+        final VcnCellUnderlyingNetworkTemplate.Builder builder = getTestNetworkTemplateBuilder();
+        setAllCapabilities(builder, MATCH_REQUIRED);
+
+        final VcnCellUnderlyingNetworkTemplate networkPriority = builder.build();
+
         assertEquals(MATCH_FORBIDDEN, networkPriority.getMetered());
         assertEquals(
                 TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS,
@@ -68,6 +100,8 @@
         assertEquals(ALLOWED_CARRIER_IDS, networkPriority.getSimSpecificCarrierIds());
         assertEquals(MATCH_FORBIDDEN, networkPriority.getRoaming());
         assertEquals(MATCH_REQUIRED, networkPriority.getOpportunistic());
+
+        verifyAllCapabilities(networkPriority, MATCH_REQUIRED, MATCH_REQUIRED);
     }
 
     @Test
@@ -86,6 +120,8 @@
         assertEquals(new HashSet<Integer>(), networkPriority.getSimSpecificCarrierIds());
         assertEquals(MATCH_ANY, networkPriority.getRoaming());
         assertEquals(MATCH_ANY, networkPriority.getOpportunistic());
+
+        verifyAllCapabilities(networkPriority, MATCH_ANY, MATCH_REQUIRED);
     }
 
     @Test
@@ -112,6 +148,25 @@
     }
 
     @Test
+    public void testBuildFailWithoutRequiredCapabilities() {
+        try {
+            new VcnCellUnderlyingNetworkTemplate.Builder().setInternet(MATCH_ANY).build();
+
+            fail("Expected IAE for missing required capabilities");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testEqualsWithDifferentCapabilities() {
+        final VcnCellUnderlyingNetworkTemplate left =
+                new VcnCellUnderlyingNetworkTemplate.Builder().setDun(MATCH_REQUIRED).build();
+        final VcnCellUnderlyingNetworkTemplate right =
+                new VcnCellUnderlyingNetworkTemplate.Builder().setMms(MATCH_REQUIRED).build();
+        assertNotEquals(left, right);
+    }
+
+    @Test
     public void testPersistableBundle() {
         final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate();
         assertEquals(
@@ -119,4 +174,16 @@
                 VcnUnderlyingNetworkTemplate.fromPersistableBundle(
                         networkPriority.toPersistableBundle()));
     }
+
+    @Test
+    public void testPersistableBundleWithCapabilities() {
+        final VcnCellUnderlyingNetworkTemplate.Builder builder = getTestNetworkTemplateBuilder();
+        setAllCapabilities(builder, MATCH_REQUIRED);
+
+        final VcnCellUnderlyingNetworkTemplate networkPriority = builder.build();
+        assertEquals(
+                networkPriority,
+                VcnUnderlyingNetworkTemplate.fromPersistableBundle(
+                        networkPriority.toPersistableBundle()));
+    }
 }
diff --git a/tests/vcn/java/android/net/vcn/VcnConfigTest.java b/tests/vcn/java/android/net/vcn/VcnConfigTest.java
index 7ac51b7..b313c9f 100644
--- a/tests/vcn/java/android/net/vcn/VcnConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnConfigTest.java
@@ -16,7 +16,12 @@
 
 package android.net.vcn;
 
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -24,6 +29,7 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.os.Parcel;
+import android.util.ArraySet;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -42,19 +48,36 @@
     private static final Set<VcnGatewayConnectionConfig> GATEWAY_CONNECTION_CONFIGS =
             Collections.singleton(VcnGatewayConnectionConfigTest.buildTestConfig());
 
+    private static final Set<Integer> RESTRICTED_TRANSPORTS = new ArraySet<>();
+
+    static {
+        RESTRICTED_TRANSPORTS.add(TRANSPORT_WIFI);
+        RESTRICTED_TRANSPORTS.add(TRANSPORT_CELLULAR);
+    }
+
     private final Context mContext = mock(Context.class);
 
     // Public visibility for VcnManagementServiceTest
-    public static VcnConfig buildTestConfig(@NonNull Context context) {
+    public static VcnConfig buildTestConfig(
+            @NonNull Context context, Set<Integer> restrictedTransports) {
         VcnConfig.Builder builder = new VcnConfig.Builder(context);
 
         for (VcnGatewayConnectionConfig gatewayConnectionConfig : GATEWAY_CONNECTION_CONFIGS) {
             builder.addGatewayConnectionConfig(gatewayConnectionConfig);
         }
 
+        if (restrictedTransports != null) {
+            builder.setRestrictedUnderlyingNetworkTransports(restrictedTransports);
+        }
+
         return builder.build();
     }
 
+    // Public visibility for VcnManagementServiceTest
+    public static VcnConfig buildTestConfig(@NonNull Context context) {
+        return buildTestConfig(context, null);
+    }
+
     @Before
     public void setUp() throws Exception {
         doReturn(TEST_PACKAGE_NAME).when(mContext).getOpPackageName();
@@ -91,11 +114,25 @@
     }
 
     @Test
-    public void testBuilderAndGetters() {
+    public void testBuilderAndGettersDefaultValues() {
         final VcnConfig config = buildTestConfig(mContext);
 
         assertEquals(TEST_PACKAGE_NAME, config.getProvisioningPackageName());
         assertEquals(GATEWAY_CONNECTION_CONFIGS, config.getGatewayConnectionConfigs());
+        assertFalse(config.isTestModeProfile());
+        assertEquals(
+                Collections.singleton(TRANSPORT_WIFI),
+                config.getRestrictedUnderlyingNetworkTransports());
+    }
+
+    @Test
+    public void testBuilderAndGettersConfigRestrictedTransports() {
+        final VcnConfig config = buildTestConfig(mContext, RESTRICTED_TRANSPORTS);
+
+        assertEquals(TEST_PACKAGE_NAME, config.getProvisioningPackageName());
+        assertEquals(GATEWAY_CONNECTION_CONFIGS, config.getGatewayConnectionConfigs());
+        assertFalse(config.isTestModeProfile());
+        assertEquals(RESTRICTED_TRANSPORTS, config.getRestrictedUnderlyingNetworkTransports());
     }
 
     @Test
@@ -106,6 +143,24 @@
     }
 
     @Test
+    public void testPersistableBundleWithRestrictedTransports() {
+        final VcnConfig config = buildTestConfig(mContext, RESTRICTED_TRANSPORTS);
+
+        assertEquals(config, new VcnConfig(config.toPersistableBundle()));
+    }
+
+    @Test
+    public void testEqualityWithRestrictedTransports() {
+        final VcnConfig config = buildTestConfig(mContext, RESTRICTED_TRANSPORTS);
+        final VcnConfig configEqual = buildTestConfig(mContext, RESTRICTED_TRANSPORTS);
+        final VcnConfig configNotEqual =
+                buildTestConfig(mContext, Collections.singleton(TRANSPORT_WIFI));
+
+        assertEquals(config, configEqual);
+        assertNotEquals(config, configNotEqual);
+    }
+
+    @Test
     public void testParceling() {
         final VcnConfig config = buildTestConfig(mContext);
 
diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
index 2aef9ae..1883c85 100644
--- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
@@ -19,9 +19,11 @@
 import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
 import static android.net.vcn.VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES;
 import static android.net.vcn.VcnGatewayConnectionConfig.UNDERLYING_NETWORK_TEMPLATES_KEY;
+import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertTrue;
@@ -42,7 +44,9 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
@@ -79,6 +83,9 @@
             };
     public static final int MAX_MTU = 1360;
 
+    private static final Set<Integer> GATEWAY_OPTIONS =
+            Collections.singleton(VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY);
+
     public static final IkeTunnelConnectionParams TUNNEL_CONNECTION_PARAMS =
             TunnelConnectionParamsUtilsTest.buildTestParams();
 
@@ -93,14 +100,20 @@
                 EXPOSED_CAPS);
     }
 
-    // Public for use in VcnGatewayConnectionTest
-    public static VcnGatewayConnectionConfig buildTestConfig() {
+    // Public for use in UnderlyingNetworkControllerTest
+    public static VcnGatewayConnectionConfig buildTestConfig(
+            List<VcnUnderlyingNetworkTemplate> nwTemplates) {
         final VcnGatewayConnectionConfig.Builder builder =
-                newBuilder().setVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_TEMPLATES);
+                newBuilder().setVcnUnderlyingNetworkPriorities(nwTemplates);
 
         return buildTestConfigWithExposedCaps(builder, EXPOSED_CAPS);
     }
 
+    // Public for use in VcnGatewayConnectionTest
+    public static VcnGatewayConnectionConfig buildTestConfig() {
+        return buildTestConfig(UNDERLYING_NETWORK_TEMPLATES);
+    }
+
     private static VcnGatewayConnectionConfig.Builder newBuilder() {
         // Append a unique identifier to the name prefix to guarantee that all created
         // VcnGatewayConnectionConfigs have a unique name (required by VcnConfig).
@@ -109,10 +122,16 @@
                 TUNNEL_CONNECTION_PARAMS);
     }
 
-    private static VcnGatewayConnectionConfig buildTestConfigWithExposedCaps(
-            VcnGatewayConnectionConfig.Builder builder, int... exposedCaps) {
+    private static VcnGatewayConnectionConfig buildTestConfigWithExposedCapsAndOptions(
+            VcnGatewayConnectionConfig.Builder builder,
+            Set<Integer> gatewayOptions,
+            int... exposedCaps) {
         builder.setRetryIntervalsMillis(RETRY_INTERVALS_MS).setMaxMtu(MAX_MTU);
 
+        for (int option : gatewayOptions) {
+            builder.addGatewayOption(option);
+        }
+
         for (int caps : exposedCaps) {
             builder.addExposedCapability(caps);
         }
@@ -120,11 +139,28 @@
         return builder.build();
     }
 
+    private static VcnGatewayConnectionConfig buildTestConfigWithExposedCaps(
+            VcnGatewayConnectionConfig.Builder builder, int... exposedCaps) {
+        return buildTestConfigWithExposedCapsAndOptions(
+                builder, Collections.emptySet(), exposedCaps);
+    }
+
     // Public for use in VcnGatewayConnectionTest
     public static VcnGatewayConnectionConfig buildTestConfigWithExposedCaps(int... exposedCaps) {
         return buildTestConfigWithExposedCaps(newBuilder(), exposedCaps);
     }
 
+    private static VcnGatewayConnectionConfig buildTestConfigWithGatewayOptions(
+            VcnGatewayConnectionConfig.Builder builder, Set<Integer> gatewayOptions) {
+        return buildTestConfigWithExposedCapsAndOptions(builder, gatewayOptions, EXPOSED_CAPS);
+    }
+
+    // Public for use in VcnGatewayConnectionTest
+    public static VcnGatewayConnectionConfig buildTestConfigWithGatewayOptions(
+            Set<Integer> gatewayOptions) {
+        return buildTestConfigWithExposedCapsAndOptions(newBuilder(), gatewayOptions, EXPOSED_CAPS);
+    }
+
     @Test
     public void testBuilderRequiresNonNullGatewayConnectionName() {
         try {
@@ -211,6 +247,15 @@
     }
 
     @Test
+    public void testBuilderRequiresValidOption() {
+        try {
+            newBuilder().addGatewayOption(-1);
+            fail("Expected exception due to the invalid VCN gateway option");
+        } catch (IllegalArgumentException e) {
+        }
+    }
+
+    @Test
     public void testBuilderAndGetters() {
         final VcnGatewayConnectionConfig config = buildTestConfig();
 
@@ -225,6 +270,20 @@
 
         assertArrayEquals(RETRY_INTERVALS_MS, config.getRetryIntervalsMillis());
         assertEquals(MAX_MTU, config.getMaxMtu());
+
+        assertFalse(
+                config.hasGatewayOption(
+                        VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY));
+    }
+
+    @Test
+    public void testBuilderAndGettersWithOptions() {
+        final VcnGatewayConnectionConfig config =
+                buildTestConfigWithGatewayOptions(GATEWAY_OPTIONS);
+
+        for (int option : GATEWAY_OPTIONS) {
+            assertTrue(config.hasGatewayOption(option));
+        }
     }
 
     @Test
@@ -235,6 +294,14 @@
     }
 
     @Test
+    public void testPersistableBundleWithOptions() {
+        final VcnGatewayConnectionConfig config =
+                buildTestConfigWithGatewayOptions(GATEWAY_OPTIONS);
+
+        assertEquals(config, new VcnGatewayConnectionConfig(config.toPersistableBundle()));
+    }
+
+    @Test
     public void testParsePersistableBundleWithoutVcnUnderlyingNetworkTemplates() {
         PersistableBundle configBundle = buildTestConfig().toPersistableBundle();
         configBundle.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, null);
@@ -318,4 +385,27 @@
         assertNotEquals(UNDERLYING_NETWORK_TEMPLATES, networkTemplatesNotEqual);
         assertNotEquals(config, configNotEqual);
     }
+
+    private static VcnGatewayConnectionConfig buildConfigWithGatewayOptionsForEqualityTest(
+            Set<Integer> gatewayOptions) {
+        return buildTestConfigWithGatewayOptions(
+                new VcnGatewayConnectionConfig.Builder(
+                        "buildConfigWithGatewayOptionsForEqualityTest", TUNNEL_CONNECTION_PARAMS),
+                gatewayOptions);
+    }
+
+    @Test
+    public void testVcnGatewayOptionsEquality() throws Exception {
+        final VcnGatewayConnectionConfig config =
+                buildConfigWithGatewayOptionsForEqualityTest(GATEWAY_OPTIONS);
+
+        final VcnGatewayConnectionConfig configEqual =
+                buildConfigWithGatewayOptionsForEqualityTest(GATEWAY_OPTIONS);
+
+        final VcnGatewayConnectionConfig configNotEqual =
+                buildConfigWithGatewayOptionsForEqualityTest(Collections.emptySet());
+
+        assertEquals(config, configEqual);
+        assertNotEquals(config, configNotEqual);
+    }
 }
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 258642ac..075bc5e 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -258,7 +258,7 @@
 
         doReturn(Collections.singleton(TRANSPORT_WIFI))
                 .when(mMockDeps)
-                .getRestrictedTransports(any(), any());
+                .getRestrictedTransports(any(), any(), any());
     }
 
 
@@ -1038,18 +1038,18 @@
                 new LinkProperties());
     }
 
-    private void checkGetRestrictedTransports(
+    private void checkGetRestrictedTransportsFromCarrierConfig(
             ParcelUuid subGrp,
             TelephonySubscriptionSnapshot lastSnapshot,
             Set<Integer> expectedTransports) {
         Set<Integer> result =
                 new VcnManagementService.Dependencies()
-                        .getRestrictedTransports(subGrp, lastSnapshot);
+                        .getRestrictedTransportsFromCarrierConfig(subGrp, lastSnapshot);
         assertEquals(expectedTransports, result);
     }
 
     @Test
-    public void testGetRestrictedTransports() {
+    public void testGetRestrictedTransportsFromCarrierConfig() {
         final Set<Integer> restrictedTransports = new ArraySet<>();
         restrictedTransports.add(TRANSPORT_CELLULAR);
         restrictedTransports.add(TRANSPORT_WIFI);
@@ -1065,11 +1065,12 @@
                 mock(TelephonySubscriptionSnapshot.class);
         doReturn(carrierConfig).when(lastSnapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2));
 
-        checkGetRestrictedTransports(TEST_UUID_2, lastSnapshot, restrictedTransports);
+        checkGetRestrictedTransportsFromCarrierConfig(
+                TEST_UUID_2, lastSnapshot, restrictedTransports);
     }
 
     @Test
-    public void testGetRestrictedTransports_noRestrictPolicyConfigured() {
+    public void testGetRestrictedTransportsFromCarrierConfig_noRestrictPolicyConfigured() {
         final Set<Integer> restrictedTransports = Collections.singleton(TRANSPORT_WIFI);
 
         final PersistableBundleWrapper carrierConfig =
@@ -1078,17 +1079,54 @@
                 mock(TelephonySubscriptionSnapshot.class);
         doReturn(carrierConfig).when(lastSnapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2));
 
-        checkGetRestrictedTransports(TEST_UUID_2, lastSnapshot, restrictedTransports);
+        checkGetRestrictedTransportsFromCarrierConfig(
+                TEST_UUID_2, lastSnapshot, restrictedTransports);
     }
 
     @Test
-    public void testGetRestrictedTransports_noCarrierConfig() {
+    public void testGetRestrictedTransportsFromCarrierConfig_noCarrierConfig() {
         final Set<Integer> restrictedTransports = Collections.singleton(TRANSPORT_WIFI);
 
         final TelephonySubscriptionSnapshot lastSnapshot =
                 mock(TelephonySubscriptionSnapshot.class);
 
-        checkGetRestrictedTransports(TEST_UUID_2, lastSnapshot, restrictedTransports);
+        checkGetRestrictedTransportsFromCarrierConfig(
+                TEST_UUID_2, lastSnapshot, restrictedTransports);
+    }
+
+    @Test
+    public void testGetRestrictedTransportsFromCarrierConfigAndVcnConfig() {
+        // Configure restricted transport in CarrierConfig
+        final Set<Integer> restrictedTransportInCarrierConfig =
+                Collections.singleton(TRANSPORT_WIFI);
+
+        PersistableBundle carrierConfigBundle = new PersistableBundle();
+        carrierConfigBundle.putIntArray(
+                VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
+                restrictedTransportInCarrierConfig.stream().mapToInt(i -> i).toArray());
+        final PersistableBundleWrapper carrierConfig =
+                new PersistableBundleWrapper(carrierConfigBundle);
+
+        final TelephonySubscriptionSnapshot lastSnapshot =
+                mock(TelephonySubscriptionSnapshot.class);
+        doReturn(carrierConfig).when(lastSnapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2));
+
+        // Configure restricted transport in VcnConfig
+        final Context mockContext = mock(Context.class);
+        doReturn(TEST_PACKAGE_NAME).when(mockContext).getOpPackageName();
+        final VcnConfig vcnConfig =
+                VcnConfigTest.buildTestConfig(
+                        mockContext, Collections.singleton(TRANSPORT_CELLULAR));
+
+        // Verifications
+        final Set<Integer> expectedTransports = new ArraySet<>();
+        expectedTransports.add(TRANSPORT_CELLULAR);
+        expectedTransports.add(TRANSPORT_WIFI);
+
+        Set<Integer> result =
+                new VcnManagementService.Dependencies()
+                        .getRestrictedTransports(TEST_UUID_2, lastSnapshot, vcnConfig);
+        assertEquals(expectedTransports, result);
     }
 
     private void checkGetUnderlyingNetworkPolicy(
@@ -1103,7 +1141,7 @@
         if (isTransportRestricted) {
             restrictedTransports.add(transportType);
         }
-        doReturn(restrictedTransports).when(mMockDeps).getRestrictedTransports(any(), any());
+        doReturn(restrictedTransports).when(mMockDeps).getRestrictedTransports(any(), any(), any());
 
         final VcnUnderlyingNetworkPolicy policy =
                 startVcnAndGetPolicyForTransport(
@@ -1201,7 +1239,7 @@
     public void testGetUnderlyingNetworkPolicyCell_restrictWifi() throws Exception {
         doReturn(Collections.singleton(TRANSPORT_WIFI))
                 .when(mMockDeps)
-                .getRestrictedTransports(any(), any());
+                .getRestrictedTransports(any(), any(), any());
 
         setupSubscriptionAndStartVcn(TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isVcnActive */);
 
@@ -1344,6 +1382,23 @@
     }
 
     @Test
+    public void testVcnConfigChangeUpdatesPolicyListener() throws Exception {
+        setupActiveSubscription(TEST_UUID_2);
+
+        mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
+        mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
+
+        final Context mockContext = mock(Context.class);
+        doReturn(TEST_PACKAGE_NAME).when(mockContext).getOpPackageName();
+        final VcnConfig vcnConfig =
+                VcnConfigTest.buildTestConfig(
+                        mockContext, Collections.singleton(TRANSPORT_CELLULAR));
+        mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, vcnConfig, TEST_PACKAGE_NAME);
+
+        verify(mMockPolicyListener).onPolicyChanged();
+    }
+
+    @Test
     public void testRemoveVcnUpdatesPolicyListener() throws Exception {
         setupActiveSubscription(TEST_UUID_2);
 
@@ -1375,7 +1430,7 @@
         setupActiveSubscription(TEST_UUID_2);
 
         mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
-        mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListenerForTest(mMockPolicyListener);
+        mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
 
         final TelephonySubscriptionSnapshot snapshot =
                 buildSubscriptionSnapshot(
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 15d4f10..aad7a5e 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -50,23 +50,29 @@
 
 import static java.util.Collections.singletonList;
 
+import android.net.ConnectivityDiagnosticsManager.DataStallReport;
 import android.net.ConnectivityManager;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.Network;
 import android.net.NetworkAgent;
 import android.net.NetworkCapabilities;
 import android.net.ipsec.ike.ChildSaProposal;
 import android.net.ipsec.ike.IkeSessionConnectionInfo;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
 import android.net.ipsec.ike.exceptions.IkeException;
 import android.net.ipsec.ike.exceptions.IkeInternalException;
 import android.net.ipsec.ike.exceptions.IkeProtocolException;
 import android.net.vcn.VcnGatewayConnectionConfig;
 import android.net.vcn.VcnGatewayConnectionConfigTest;
 import android.net.vcn.VcnManager.VcnErrorCode;
+import android.os.PersistableBundle;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.vcn.VcnGatewayConnection.VcnChildSessionCallback;
+import com.android.server.vcn.routeselection.UnderlyingNetworkRecord;
 import com.android.server.vcn.util.MtuUtils;
 
 import org.junit.Before;
@@ -86,8 +92,11 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnectionTestBase {
+    private static final int PARALLEL_SA_COUNT = 4;
+
     private VcnIkeSession mIkeSession;
     private VcnNetworkAgent mNetworkAgent;
+    private Network mVcnNetwork;
 
     @Before
     public void setUp() throws Exception {
@@ -98,6 +107,9 @@
                 .when(mDeps)
                 .newNetworkAgent(any(), any(), any(), any(), any(), any(), any(), any(), any());
 
+        mVcnNetwork = mock(Network.class);
+        doReturn(mVcnNetwork).when(mNetworkAgent).getNetwork();
+
         mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1);
 
         mIkeSession = mGatewayConnection.buildIkeSession(TEST_UNDERLYING_NETWORK_RECORD_1.network);
@@ -166,19 +178,82 @@
         assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
     }
 
+    private void verifyDataStallTriggersMigration(
+            UnderlyingNetworkRecord networkRecord,
+            Network networkWithDataStall,
+            boolean expectMobilityUpdate)
+            throws Exception {
+        mGatewayConnection.setUnderlyingNetwork(networkRecord);
+        triggerChildOpened();
+        mTestLooper.dispatchAll();
+
+        final DataStallReport report =
+                new DataStallReport(
+                        networkWithDataStall,
+                        1234 /* reportTimestamp */,
+                        1 /* detectionMethod */,
+                        new LinkProperties(),
+                        new NetworkCapabilities(),
+                        new PersistableBundle());
+
+        mGatewayConnection.getConnectivityDiagnosticsCallback().onDataStallSuspected(report);
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
+
+        if (expectMobilityUpdate) {
+            verify(mIkeSession).setNetwork(networkRecord.network);
+        } else {
+            verify(mIkeSession, never()).setNetwork(any(Network.class));
+        }
+    }
+
+    @Test
+    public void testDataStallTriggersMigration() throws Exception {
+        verifyDataStallTriggersMigration(
+                TEST_UNDERLYING_NETWORK_RECORD_1, mVcnNetwork, true /* expectMobilityUpdate */);
+    }
+
+    @Test
+    public void testDataStallWontTriggerMigrationWhenOnOtherNetwork() throws Exception {
+        verifyDataStallTriggersMigration(
+                TEST_UNDERLYING_NETWORK_RECORD_1,
+                mock(Network.class),
+                false /* expectMobilityUpdate */);
+    }
+
+    @Test
+    public void testDataStallWontTriggerMigrationWhenUnderlyingNetworkLost() throws Exception {
+        verifyDataStallTriggersMigration(
+                null /* networkRecord */, mock(Network.class), false /* expectMobilityUpdate */);
+    }
+
     private void verifyVcnTransformsApplied(
             VcnGatewayConnection vcnGatewayConnection, boolean expectForwardTransform)
             throws Exception {
+        verifyVcnTransformsApplied(
+                vcnGatewayConnection,
+                expectForwardTransform,
+                Collections.singletonList(getChildSessionCallback()));
+    }
+
+    private void verifyVcnTransformsApplied(
+            VcnGatewayConnection vcnGatewayConnection,
+            boolean expectForwardTransform,
+            List<VcnChildSessionCallback> callbacks)
+            throws Exception {
         for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT}) {
-            getChildSessionCallback().onIpSecTransformCreated(makeDummyIpSecTransform(), direction);
+            for (VcnChildSessionCallback cb : callbacks) {
+                cb.onIpSecTransformCreated(makeDummyIpSecTransform(), direction);
+            }
             mTestLooper.dispatchAll();
 
-            verify(mIpSecSvc)
+            verify(mIpSecSvc, times(callbacks.size()))
                     .applyTunnelModeTransform(
                             eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any());
         }
 
-        verify(mIpSecSvc, expectForwardTransform ? times(1) : never())
+        verify(mIpSecSvc, expectForwardTransform ? times(callbacks.size()) : never())
                 .applyTunnelModeTransform(
                         eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(DIRECTION_FWD), anyInt(), any());
 
@@ -358,6 +433,89 @@
         verifySafeModeStateAndCallbackFired(1 /* invocationCount */, false /* isInSafeMode */);
     }
 
+    private List<VcnChildSessionCallback> openChildAndVerifyParallelSasRequested()
+            throws Exception {
+        doReturn(PARALLEL_SA_COUNT)
+                .when(mDeps)
+                .getParallelTunnelCount(eq(TEST_SUBSCRIPTION_SNAPSHOT), eq(TEST_SUB_GRP));
+
+        // Verify scheduled but not canceled when entering ConnectedState
+        verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+        triggerChildOpened();
+        mTestLooper.dispatchAll();
+
+        // Verify new child sessions requested
+        final ArgumentCaptor<VcnChildSessionCallback> captor =
+                ArgumentCaptor.forClass(VcnChildSessionCallback.class);
+        verify(mIkeSession, times(PARALLEL_SA_COUNT - 1))
+                .openChildSession(any(TunnelModeChildSessionParams.class), captor.capture());
+
+        return captor.getAllValues();
+    }
+
+    private List<VcnChildSessionCallback> verifyChildOpenedRequestsAndAppliesParallelSas()
+            throws Exception {
+        List<VcnChildSessionCallback> callbacks = openChildAndVerifyParallelSasRequested();
+
+        verifyVcnTransformsApplied(mGatewayConnection, false, callbacks);
+
+        // Mock IKE calling of onOpened()
+        for (VcnChildSessionCallback cb : callbacks) {
+            cb.onOpened(mock(VcnChildSessionConfiguration.class));
+        }
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
+        return callbacks;
+    }
+
+    @Test
+    public void testChildOpenedWithParallelSas() throws Exception {
+        verifyChildOpenedRequestsAndAppliesParallelSas();
+    }
+
+    @Test
+    public void testOpportunisticSa_ignoresPreOpenFailures() throws Exception {
+        List<VcnChildSessionCallback> callbacks = openChildAndVerifyParallelSasRequested();
+
+        for (VcnChildSessionCallback cb : callbacks) {
+            cb.onClosed();
+            cb.onClosedExceptionally(mock(IkeException.class));
+        }
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
+        assertEquals(mIkeConnectionInfo, mGatewayConnection.getIkeConnectionInfo());
+    }
+
+    private void verifyPostOpenFailuresCloseSession(boolean shouldCloseWithException)
+            throws Exception {
+        List<VcnChildSessionCallback> callbacks = verifyChildOpenedRequestsAndAppliesParallelSas();
+
+        for (VcnChildSessionCallback cb : callbacks) {
+            if (shouldCloseWithException) {
+                cb.onClosed();
+            } else {
+                cb.onClosedExceptionally(mock(IkeException.class));
+            }
+        }
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+        verify(mIkeSession).close();
+    }
+
+    @Test
+    public void testOpportunisticSa_handlesPostOpenFailures_onClosed() throws Exception {
+        verifyPostOpenFailuresCloseSession(false /* shouldCloseWithException */);
+    }
+
+    @Test
+    public void testOpportunisticSa_handlesPostOpenFailures_onClosedExceptionally()
+            throws Exception {
+        verifyPostOpenFailuresCloseSession(true /* shouldCloseWithException */);
+    }
+
     @Test
     public void testInternalAndDnsAddressesChanged() throws Exception {
         final List<LinkAddress> startingInternalAddrs =
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
index 6a9a1e2..692c8a8 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
@@ -24,6 +24,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY;
 
 import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR;
 import static com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration;
@@ -34,20 +35,25 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.CALLS_REAL_METHODS;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
 import android.net.IpSecManager;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
 import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.VcnGatewayConnectionConfig;
 import android.net.vcn.VcnGatewayConnectionConfigTest;
 import android.net.vcn.VcnTransportInfo;
 import android.net.wifi.WifiInfo;
@@ -64,6 +70,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 
 import java.net.InetAddress;
 import java.util.Arrays;
@@ -71,7 +78,9 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.Executor;
 
 /** Tests for TelephonySubscriptionTracker */
 @RunWith(AndroidJUnit4.class)
@@ -134,9 +143,9 @@
         capBuilder.setLinkDownstreamBandwidthKbps(TEST_DOWNSTREAM_BANDWIDTH);
         capBuilder.setAdministratorUids(new int[] {TEST_UID});
         final Network underlyingNetwork = mock(Network.class, CALLS_REAL_METHODS);
-        UnderlyingNetworkRecord record = new UnderlyingNetworkRecord(
-                underlyingNetwork,
-                capBuilder.build(), new LinkProperties(), false);
+        UnderlyingNetworkRecord record =
+                getTestNetworkRecord(
+                        underlyingNetwork, capBuilder.build(), new LinkProperties(), false);
         final NetworkCapabilities vcnCaps =
                 VcnGatewayConnection.buildNetworkCapabilities(
                         VcnGatewayConnectionConfigTest.buildTestConfig(),
@@ -202,7 +211,7 @@
         doReturn(TEST_DNS_ADDRESSES).when(childSessionConfig).getInternalDnsServers();
 
         UnderlyingNetworkRecord record =
-                new UnderlyingNetworkRecord(
+                getTestNetworkRecord(
                         mock(Network.class, CALLS_REAL_METHODS),
                         new NetworkCapabilities.Builder().build(),
                         underlyingLp,
@@ -287,5 +296,60 @@
         verify(vcnNetworkAgent).unregister();
 
         verifyWakeLockReleased();
+
+        verify(mConnDiagMgr)
+                .unregisterConnectivityDiagnosticsCallback(
+                        mGatewayConnection.getConnectivityDiagnosticsCallback());
+    }
+
+    private VcnGatewayConnection buildConnectionWithDataStallHandling(
+            boolean datatStallHandlingEnabled) throws Exception {
+        Set<Integer> options =
+                datatStallHandlingEnabled
+                        ? Collections.singleton(
+                                VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY)
+                        : Collections.emptySet();
+        final VcnGatewayConnectionConfig gatewayConfig =
+                VcnGatewayConnectionConfigTest.buildTestConfigWithGatewayOptions(options);
+        final VcnGatewayConnection gatewayConnection =
+                new VcnGatewayConnection(
+                        mVcnContext,
+                        TEST_SUB_GRP,
+                        TEST_SUBSCRIPTION_SNAPSHOT,
+                        gatewayConfig,
+                        mGatewayStatusCallback,
+                        true /* isMobileDataEnabled */,
+                        mDeps);
+        return gatewayConnection;
+    }
+
+    @Test
+    public void testDataStallHandlingEnabled() throws Exception {
+        final VcnGatewayConnection gatewayConnection =
+                buildConnectionWithDataStallHandling(true /* datatStallHandlingEnabled */);
+
+        final ArgumentCaptor<NetworkRequest> networkRequestCaptor =
+                ArgumentCaptor.forClass(NetworkRequest.class);
+        verify(mConnDiagMgr)
+                .registerConnectivityDiagnosticsCallback(
+                        networkRequestCaptor.capture(),
+                        any(Executor.class),
+                        eq(gatewayConnection.getConnectivityDiagnosticsCallback()));
+
+        final NetworkRequest nr = networkRequestCaptor.getValue();
+        final NetworkRequest expected =
+                new NetworkRequest.Builder().addTransportType(TRANSPORT_CELLULAR).build();
+        assertEquals(expected, nr);
+    }
+
+    @Test
+    public void testDataStallHandlingDisabled() throws Exception {
+        buildConnectionWithDataStallHandling(false /* datatStallHandlingEnabled */);
+
+        verify(mConnDiagMgr, never())
+                .registerConnectivityDiagnosticsCallback(
+                        any(NetworkRequest.class),
+                        any(Executor.class),
+                        any(ConnectivityDiagnosticsCallback.class));
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index 785bff1..bb123ff 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -35,6 +35,7 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.net.ConnectivityDiagnosticsManager;
 import android.net.ConnectivityManager;
 import android.net.InetAddresses;
 import android.net.IpSecConfig;
@@ -108,9 +109,23 @@
     protected static final long ELAPSED_REAL_TIME = 123456789L;
     protected static final String TEST_IPSEC_TUNNEL_IFACE = "IPSEC_IFACE";
 
+    protected static UnderlyingNetworkRecord getTestNetworkRecord(
+            Network network,
+            NetworkCapabilities networkCapabilities,
+            LinkProperties linkProperties,
+            boolean isBlocked) {
+        return new UnderlyingNetworkRecord(
+                network,
+                networkCapabilities,
+                linkProperties,
+                isBlocked,
+                false /* isSelected */,
+                0 /* priorityClass */);
+    }
+
     protected static final String TEST_TCP_BUFFER_SIZES_1 = "1,2,3,4";
     protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_1 =
-            new UnderlyingNetworkRecord(
+            getTestNetworkRecord(
                     mock(Network.class, CALLS_REAL_METHODS),
                     new NetworkCapabilities(),
                     new LinkProperties(),
@@ -123,7 +138,7 @@
 
     protected static final String TEST_TCP_BUFFER_SIZES_2 = "2,3,4,5";
     protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_2 =
-            new UnderlyingNetworkRecord(
+            getTestNetworkRecord(
                     mock(Network.class, CALLS_REAL_METHODS),
                     new NetworkCapabilities(),
                     new LinkProperties(),
@@ -157,6 +172,7 @@
 
     @NonNull protected final IpSecService mIpSecSvc;
     @NonNull protected final ConnectivityManager mConnMgr;
+    @NonNull protected final ConnectivityDiagnosticsManager mConnDiagMgr;
 
     @NonNull protected final IkeSessionConnectionInfo mIkeConnectionInfo;
     @NonNull protected final IkeSessionConfiguration mIkeSessionConfiguration;
@@ -186,6 +202,13 @@
         VcnTestUtils.setupSystemService(
                 mContext, mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
 
+        mConnDiagMgr = mock(ConnectivityDiagnosticsManager.class);
+        VcnTestUtils.setupSystemService(
+                mContext,
+                mConnDiagMgr,
+                Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
+                ConnectivityDiagnosticsManager.class);
+
         mIkeConnectionInfo =
                 new IkeSessionConnectionInfo(TEST_ADDR, TEST_ADDR_2, mock(Network.class));
         mIkeSessionConfiguration = new IkeSessionConfiguration.Builder(mIkeConnectionInfo).build();
@@ -200,6 +223,9 @@
         doReturn(mWakeLock)
                 .when(mDeps)
                 .newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any());
+        doReturn(1)
+                .when(mDeps)
+                .getParallelTunnelCount(eq(TEST_SUBSCRIPTION_SNAPSHOT), eq(TEST_SUB_GRP));
 
         setUpWakeupMessage(mTeardownTimeoutAlarm, VcnGatewayConnection.TEARDOWN_TIMEOUT_ALARM);
         setUpWakeupMessage(mDisconnectRequestAlarm, VcnGatewayConnection.DISCONNECT_REQUEST_ALARM);
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 b0d6895..629e988 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,7 @@
 
 package com.android.server.vcn.routeselection;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
 import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS;
@@ -24,8 +25,8 @@
 import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS;
 
 import static com.android.server.vcn.VcnTestUtils.setupSystemService;
-import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_ANY;
-import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.calculatePriorityClass;
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_FALLBACK;
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_INVALID;
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesCellPriorityRule;
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesPriorityRule;
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesWifiPriorityRule;
@@ -48,6 +49,7 @@
 import android.net.vcn.VcnCellUnderlyingNetworkTemplate;
 import android.net.vcn.VcnGatewayConnectionConfig;
 import android.net.vcn.VcnManager;
+import android.net.vcn.VcnUnderlyingNetworkTemplate;
 import android.net.vcn.VcnWifiUnderlyingNetworkTemplate;
 import android.os.ParcelUuid;
 import android.os.PersistableBundle;
@@ -64,6 +66,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.Set;
 import java.util.UUID;
 
@@ -102,6 +106,7 @@
     private static final NetworkCapabilities CELL_NETWORK_CAPABILITIES =
             new NetworkCapabilities.Builder()
                     .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
                     .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                     .setSubscriptionIds(Set.of(SUB_ID))
                     .setNetworkSpecifier(TEL_NETWORK_SPECIFIER)
@@ -135,25 +140,35 @@
                                 false /* isInTestMode */));
         doNothing().when(mVcnContext).ensureRunningOnLooperThread();
 
-        mWifiNetworkRecord =
-                new UnderlyingNetworkRecord(
-                        mNetwork,
-                        WIFI_NETWORK_CAPABILITIES,
-                        LINK_PROPERTIES,
-                        false /* isBlocked */);
-
-        mCellNetworkRecord =
-                new UnderlyingNetworkRecord(
-                        mNetwork,
-                        CELL_NETWORK_CAPABILITIES,
-                        LINK_PROPERTIES,
-                        false /* isBlocked */);
-
         setupSystemService(
                 mockContext, mTelephonyManager, Context.TELEPHONY_SERVICE, TelephonyManager.class);
         when(mTelephonyManager.createForSubscriptionId(SUB_ID)).thenReturn(mTelephonyManager);
         when(mTelephonyManager.getNetworkOperator()).thenReturn(PLMN_ID);
         when(mTelephonyManager.getSimSpecificCarrierId()).thenReturn(CARRIER_ID);
+
+        mWifiNetworkRecord =
+                getTestNetworkRecord(
+                        WIFI_NETWORK_CAPABILITIES,
+                        VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
+        mCellNetworkRecord =
+                getTestNetworkRecord(
+                        CELL_NETWORK_CAPABILITIES,
+                        VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
+    }
+
+    private UnderlyingNetworkRecord getTestNetworkRecord(
+            NetworkCapabilities nc, List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates) {
+        return new UnderlyingNetworkRecord(
+                mNetwork,
+                nc,
+                LINK_PROPERTIES,
+                false /* isBlocked */,
+                mVcnContext,
+                underlyingNetworkTemplates,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                null /* currentlySelected */,
+                null /* carrierConfig */);
     }
 
     @Test
@@ -490,37 +505,72 @@
                         mSubscriptionSnapshot));
     }
 
-    private void verifyCalculatePriorityClass(
-            UnderlyingNetworkRecord networkRecord, int expectedIndex) {
-        final int priorityIndex =
-                calculatePriorityClass(
+    private void verifyMatchCellWithRequiredCapabilities(
+            VcnCellUnderlyingNetworkTemplate template, boolean expectMatch) {
+        assertEquals(
+                expectMatch,
+                checkMatchesCellPriorityRule(
                         mVcnContext,
-                        networkRecord,
-                        VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                        template,
+                        mCellNetworkRecord,
                         SUB_GROUP,
-                        mSubscriptionSnapshot,
-                        null /* currentlySelected */,
-                        null /* carrierConfig */);
+                        mSubscriptionSnapshot));
+    }
 
-        assertEquals(expectedIndex, priorityIndex);
+    @Test
+    public void testMatchCell() {
+        final VcnCellUnderlyingNetworkTemplate template =
+                getCellNetworkPriorityBuilder().setInternet(MATCH_REQUIRED).build();
+        verifyMatchCellWithRequiredCapabilities(template, true /* expectMatch */);
+    }
+
+    @Test
+    public void testMatchCellFail_RequiredCapabilitiesMissing() {
+        final VcnCellUnderlyingNetworkTemplate template =
+                getCellNetworkPriorityBuilder().setCbs(MATCH_REQUIRED).build();
+        verifyMatchCellWithRequiredCapabilities(template, false /* expectMatch */);
+    }
+
+    @Test
+    public void testMatchCellFail_ForbiddenCapabilitiesFound() {
+        final VcnCellUnderlyingNetworkTemplate template =
+                getCellNetworkPriorityBuilder().setDun(MATCH_FORBIDDEN).build();
+        verifyMatchCellWithRequiredCapabilities(template, false /* expectMatch */);
     }
 
     @Test
     public void testCalculatePriorityClass() throws Exception {
-        verifyCalculatePriorityClass(mCellNetworkRecord, 2);
+        assertEquals(2, mCellNetworkRecord.priorityClass);
+    }
+
+    private void checkCalculatePriorityClassFailToMatchAny(
+            boolean hasInternet, int expectedPriorityClass) throws Exception {
+        final List<VcnUnderlyingNetworkTemplate> templatesRequireDun =
+                Collections.singletonList(
+                        new VcnCellUnderlyingNetworkTemplate.Builder()
+                                .setDun(MATCH_REQUIRED)
+                                .build());
+
+        final NetworkCapabilities.Builder ncBuilder =
+                new NetworkCapabilities.Builder()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+        if (hasInternet) {
+            ncBuilder.addCapability(NET_CAPABILITY_INTERNET);
+        }
+
+        final UnderlyingNetworkRecord nonDunNetworkRecord =
+                getTestNetworkRecord(ncBuilder.build(), templatesRequireDun);
+
+        assertEquals(expectedPriorityClass, nonDunNetworkRecord.priorityClass);
     }
 
     @Test
-    public void testCalculatePriorityClassFailToMatchAny() throws Exception {
-        final NetworkCapabilities nc =
-                new NetworkCapabilities.Builder()
-                        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
-                        .setSignalStrength(WIFI_RSSI_LOW)
-                        .setSsid(SSID)
-                        .build();
-        final UnderlyingNetworkRecord wifiNetworkRecord =
-                new UnderlyingNetworkRecord(mNetwork, nc, LINK_PROPERTIES, false /* isBlocked */);
+    public void testCalculatePriorityClassFailToMatchAny_InternetNetwork() throws Exception {
+        checkCalculatePriorityClassFailToMatchAny(true /* hasInternet */, PRIORITY_FALLBACK);
+    }
 
-        verifyCalculatePriorityClass(wifiNetworkRecord, PRIORITY_ANY);
+    @Test
+    public void testCalculatePriorityClassFailToMatchAny_NonInternetNetwork() throws Exception {
+        checkCalculatePriorityClassFailToMatchAny(false /* hasInternet */, PRIORITY_INVALID);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
index fad9669..2941fde 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
@@ -16,18 +16,29 @@
 
 package com.android.server.vcn.routeselection;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
+import static android.net.vcn.VcnCellUnderlyingNetworkTemplate.MATCH_ANY;
+import static android.net.vcn.VcnCellUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
+import static android.net.vcn.VcnCellUnderlyingNetworkTemplate.MATCH_REQUIRED;
+
 import static com.android.server.vcn.VcnTestUtils.setupSystemService;
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT;
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.WIFI_EXIT_RSSI_THRESHOLD_DEFAULT;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -42,7 +53,10 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.VcnCellUnderlyingNetworkTemplate;
+import android.net.vcn.VcnCellUnderlyingNetworkTemplateTest;
 import android.net.vcn.VcnGatewayConnectionConfigTest;
+import android.net.vcn.VcnUnderlyingNetworkTemplate;
 import android.os.ParcelUuid;
 import android.os.test.TestLooper;
 import android.telephony.CarrierConfigManager;
@@ -64,7 +78,10 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.Set;
 import java.util.UUID;
 
@@ -95,11 +112,39 @@
                     .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                     .build();
 
+    private static final NetworkCapabilities DUN_NETWORK_CAPABILITIES =
+            new NetworkCapabilities.Builder()
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                    .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                    .build();
+
+    private static final NetworkCapabilities CBS_NETWORK_CAPABILITIES =
+            new NetworkCapabilities.Builder()
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_CBS)
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                    .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                    .build();
+
     private static final LinkProperties INITIAL_LINK_PROPERTIES =
             getLinkPropertiesWithName("initial_iface");
     private static final LinkProperties UPDATED_LINK_PROPERTIES =
             getLinkPropertiesWithName("updated_iface");
 
+    private static final VcnCellUnderlyingNetworkTemplate CELL_TEMPLATE_DUN =
+            new VcnCellUnderlyingNetworkTemplate.Builder()
+                    .setInternet(MATCH_ANY)
+                    .setDun(MATCH_REQUIRED)
+                    .build();
+
+    private static final VcnCellUnderlyingNetworkTemplate CELL_TEMPLATE_CBS =
+            new VcnCellUnderlyingNetworkTemplate.Builder()
+                    .setInternet(MATCH_ANY)
+                    .setCbs(MATCH_REQUIRED)
+                    .build();
+
     @Mock private Context mContext;
     @Mock private VcnNetworkProvider mVcnNetworkProvider;
     @Mock private ConnectivityManager mConnectivityManager;
@@ -201,6 +246,107 @@
                         any());
     }
 
+    private void verifyRequestBackgroundNetwork(
+            ConnectivityManager cm,
+            int expectedSubId,
+            Set<Integer> expectedRequiredCaps,
+            Set<Integer> expectedForbiddenCaps) {
+        verify(cm)
+                .requestBackgroundNetwork(
+                        eq(
+                                getCellRequestForSubId(
+                                        expectedSubId,
+                                        expectedRequiredCaps,
+                                        expectedForbiddenCaps)),
+                        any(NetworkBringupCallback.class),
+                        any());
+    }
+
+    @Test
+    public void testNetworkCallbacksRegisteredOnStartupForNonInternetCapabilities() {
+        final ConnectivityManager cm = mock(ConnectivityManager.class);
+        setupSystemService(mContext, cm, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
+
+        // Build network templates
+        final List<VcnUnderlyingNetworkTemplate> networkTemplates = new ArrayList();
+
+        networkTemplates.add(
+                VcnCellUnderlyingNetworkTemplateTest.getTestNetworkTemplateBuilder()
+                        .setDun(MATCH_REQUIRED)
+                        .setInternet(MATCH_ANY)
+                        .build());
+
+        networkTemplates.add(
+                VcnCellUnderlyingNetworkTemplateTest.getTestNetworkTemplateBuilder()
+                        .setMms(MATCH_REQUIRED)
+                        .setCbs(MATCH_FORBIDDEN)
+                        .setInternet(MATCH_ANY)
+                        .build());
+
+        // Start UnderlyingNetworkController
+        new UnderlyingNetworkController(
+                mVcnContext,
+                VcnGatewayConnectionConfigTest.buildTestConfig(networkTemplates),
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mNetworkControllerCb);
+
+        // Verifications
+        for (final int subId : INITIAL_SUB_IDS) {
+            verifyRequestBackgroundNetwork(
+                    cm,
+                    subId,
+                    Collections.singleton(NET_CAPABILITY_INTERNET),
+                    Collections.emptySet());
+            verifyRequestBackgroundNetwork(
+                    cm, subId, Collections.singleton(NET_CAPABILITY_DUN), Collections.emptySet());
+            verifyRequestBackgroundNetwork(
+                    cm,
+                    subId,
+                    Collections.singleton(NET_CAPABILITY_MMS),
+                    Collections.singleton(NET_CAPABILITY_CBS));
+        }
+    }
+
+    @Test
+    public void testNetworkCallbacksRegisteredOnStartupWithDedupedtCapabilities() {
+        final ConnectivityManager cm = mock(ConnectivityManager.class);
+        setupSystemService(mContext, cm, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
+
+        // Build network templates
+        final List<VcnUnderlyingNetworkTemplate> networkTemplates = new ArrayList();
+        final VcnCellUnderlyingNetworkTemplate.Builder builder =
+                new VcnCellUnderlyingNetworkTemplate.Builder()
+                        .setMms(MATCH_REQUIRED)
+                        .setCbs(MATCH_FORBIDDEN)
+                        .setInternet(MATCH_ANY);
+
+        networkTemplates.add(builder.setMetered(MATCH_REQUIRED).build());
+        networkTemplates.add(builder.setMetered(MATCH_FORBIDDEN).build());
+
+        // Start UnderlyingNetworkController
+        new UnderlyingNetworkController(
+                mVcnContext,
+                VcnGatewayConnectionConfigTest.buildTestConfig(networkTemplates),
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mNetworkControllerCb);
+
+        // Verifications
+        for (final int subId : INITIAL_SUB_IDS) {
+            verifyRequestBackgroundNetwork(
+                    cm,
+                    subId,
+                    Collections.singleton(NET_CAPABILITY_INTERNET),
+                    Collections.emptySet());
+            verifyRequestBackgroundNetwork(
+                    cm,
+                    subId,
+                    Collections.singleton(NET_CAPABILITY_MMS),
+                    Collections.singleton(NET_CAPABILITY_CBS));
+        }
+    }
+
     private void verifyNetworkRequestsRegistered(Set<Integer> expectedSubIds) {
         verify(mConnectivityManager)
                 .requestBackgroundNetwork(
@@ -210,8 +356,13 @@
         for (final int subId : expectedSubIds) {
             verify(mConnectivityManager)
                     .requestBackgroundNetwork(
-                            eq(getCellRequestForSubId(subId)),
-                            any(NetworkBringupCallback.class), any());
+                            eq(
+                                    getCellRequestForSubId(
+                                            subId,
+                                            Collections.singleton(NET_CAPABILITY_INTERNET),
+                                            Collections.emptySet())),
+                            any(NetworkBringupCallback.class),
+                            any());
         }
 
         verify(mConnectivityManager)
@@ -253,6 +404,7 @@
     private NetworkRequest getWifiRequest(Set<Integer> netCapsSubIds) {
         return getExpectedRequestBase()
                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .setSubscriptionIds(netCapsSubIds)
                 .build();
     }
@@ -261,6 +413,7 @@
         // TODO (b/187991063): Add tests for carrier-config based thresholds
         return getExpectedRequestBase()
                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .setSubscriptionIds(netCapsSubIds)
                 .setSignalStrength(WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT)
                 .build();
@@ -270,16 +423,27 @@
         // TODO (b/187991063): Add tests for carrier-config based thresholds
         return getExpectedRequestBase()
                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .setSubscriptionIds(netCapsSubIds)
                 .setSignalStrength(WIFI_EXIT_RSSI_THRESHOLD_DEFAULT)
                 .build();
     }
 
-    private NetworkRequest getCellRequestForSubId(int subId) {
-        return getExpectedRequestBase()
-                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId))
-                .build();
+    private NetworkRequest getCellRequestForSubId(
+            int subId, Set<Integer> requiredCaps, Set<Integer> forbiddenCaps) {
+        final NetworkRequest.Builder nqBuilder =
+                getExpectedRequestBase()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                        .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId));
+
+        for (int cap : requiredCaps) {
+            nqBuilder.addCapability(cap);
+        }
+        for (int cap : forbiddenCaps) {
+            nqBuilder.addForbiddenCapability(cap);
+        }
+
+        return nqBuilder.build();
     }
 
     private NetworkRequest getRouteSelectionRequest(Set<Integer> netCapsSubIds) {
@@ -301,7 +465,6 @@
     private NetworkRequest.Builder getExpectedRequestBase() {
         final NetworkRequest.Builder builder =
                 new NetworkRequest.Builder()
-                        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                         .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
                         .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
                         .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
@@ -321,16 +484,30 @@
                 .unregisterNetworkCallback(any(UnderlyingNetworkListener.class));
     }
 
+    private static UnderlyingNetworkRecord getTestNetworkRecord(
+            Network network,
+            NetworkCapabilities networkCapabilities,
+            LinkProperties linkProperties,
+            boolean isBlocked) {
+        return new UnderlyingNetworkRecord(
+                network,
+                networkCapabilities,
+                linkProperties,
+                isBlocked,
+                false /* isSelected */,
+                0 /* priorityClass */);
+    }
+
     @Test
     public void testUnderlyingNetworkRecordEquals() {
         UnderlyingNetworkRecord recordA =
-                new UnderlyingNetworkRecord(
+                getTestNetworkRecord(
                         mNetwork,
                         INITIAL_NETWORK_CAPABILITIES,
                         INITIAL_LINK_PROPERTIES,
                         false /* isBlocked */);
         UnderlyingNetworkRecord recordB =
-                new UnderlyingNetworkRecord(
+                getTestNetworkRecord(
                         mNetwork,
                         INITIAL_NETWORK_CAPABILITIES,
                         INITIAL_LINK_PROPERTIES,
@@ -338,12 +515,24 @@
         UnderlyingNetworkRecord recordC =
                 new UnderlyingNetworkRecord(
                         mNetwork,
+                        INITIAL_NETWORK_CAPABILITIES,
+                        INITIAL_LINK_PROPERTIES,
+                        false /* isBlocked */,
+                        true /* isSelected */,
+                        -1 /* priorityClass */);
+        UnderlyingNetworkRecord recordD =
+                getTestNetworkRecord(
+                        mNetwork,
                         UPDATED_NETWORK_CAPABILITIES,
                         UPDATED_LINK_PROPERTIES,
                         false /* isBlocked */);
 
         assertEquals(recordA, recordB);
-        assertNotEquals(recordA, recordC);
+        assertEquals(recordA, recordC);
+        assertNotEquals(recordA, recordD);
+
+        assertTrue(UnderlyingNetworkRecord.isEqualIncludingPriorities(recordA, recordB));
+        assertFalse(UnderlyingNetworkRecord.isEqualIncludingPriorities(recordA, recordC));
     }
 
     @Test
@@ -366,6 +555,10 @@
                 .build();
     }
 
+    private void verifyOnSelectedUnderlyingNetworkChanged(UnderlyingNetworkRecord expectedRecord) {
+        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+    }
+
     private UnderlyingNetworkListener verifyRegistrationOnAvailableAndGetCallback(
             NetworkCapabilities networkCapabilities) {
         verify(mConnectivityManager)
@@ -384,12 +577,12 @@
         cb.onBlockedStatusChanged(mNetwork, false /* isFalse */);
 
         UnderlyingNetworkRecord expectedRecord =
-                new UnderlyingNetworkRecord(
+                getTestNetworkRecord(
                         mNetwork,
                         responseNetworkCaps,
                         INITIAL_LINK_PROPERTIES,
                         false /* isBlocked */);
-        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+        verifyOnSelectedUnderlyingNetworkChanged(expectedRecord);
         return cb;
     }
 
@@ -402,12 +595,12 @@
         cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
 
         UnderlyingNetworkRecord expectedRecord =
-                new UnderlyingNetworkRecord(
+                getTestNetworkRecord(
                         mNetwork,
                         responseNetworkCaps,
                         INITIAL_LINK_PROPERTIES,
                         false /* isBlocked */);
-        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+        verifyOnSelectedUnderlyingNetworkChanged(expectedRecord);
     }
 
     @Test
@@ -417,12 +610,12 @@
         cb.onLinkPropertiesChanged(mNetwork, UPDATED_LINK_PROPERTIES);
 
         UnderlyingNetworkRecord expectedRecord =
-                new UnderlyingNetworkRecord(
+                getTestNetworkRecord(
                         mNetwork,
                         buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS),
                         UPDATED_LINK_PROPERTIES,
                         false /* isBlocked */);
-        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+        verifyOnSelectedUnderlyingNetworkChanged(expectedRecord);
     }
 
     @Test
@@ -434,18 +627,16 @@
         cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
 
         UnderlyingNetworkRecord expectedRecord =
-                new UnderlyingNetworkRecord(
+                getTestNetworkRecord(
                         mNetwork,
                         responseNetworkCaps,
                         INITIAL_LINK_PROPERTIES,
                         false /* isBlocked */);
-        verify(mNetworkControllerCb, times(1))
-                .onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+        verifyOnSelectedUnderlyingNetworkChanged(expectedRecord);
         // onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't
         // change.
         cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
-        verify(mNetworkControllerCb, times(1))
-                .onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+        verifyOnSelectedUnderlyingNetworkChanged(expectedRecord);
     }
 
     @Test
@@ -458,18 +649,16 @@
         cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
 
         UnderlyingNetworkRecord expectedRecord =
-                new UnderlyingNetworkRecord(
+                getTestNetworkRecord(
                         mNetwork,
                         responseNetworkCaps,
                         INITIAL_LINK_PROPERTIES,
                         false /* isBlocked */);
-        verify(mNetworkControllerCb, times(1))
-                .onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+        verifyOnSelectedUnderlyingNetworkChanged(expectedRecord);
         // onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't
         // change.
         cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
-        verify(mNetworkControllerCb, times(1))
-                .onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+        verifyOnSelectedUnderlyingNetworkChanged(expectedRecord);
     }
 
     @Test
@@ -478,13 +667,7 @@
 
         cb.onBlockedStatusChanged(mNetwork, true /* isBlocked */);
 
-        UnderlyingNetworkRecord expectedRecord =
-                new UnderlyingNetworkRecord(
-                        mNetwork,
-                        buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS),
-                        INITIAL_LINK_PROPERTIES,
-                        true /* isBlocked */);
-        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+        verifyOnSelectedUnderlyingNetworkChanged(null);
     }
 
     @Test
@@ -520,5 +703,132 @@
         verify(mNetworkControllerCb, times(1)).onSelectedUnderlyingNetworkChanged(any());
     }
 
-    // TODO (b/187991063): Add tests for network prioritization
+    private UnderlyingNetworkListener setupControllerAndGetNetworkListener(
+            List<VcnUnderlyingNetworkTemplate> networkTemplates) {
+        final ConnectivityManager cm = mock(ConnectivityManager.class);
+        setupSystemService(mContext, cm, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
+
+        new UnderlyingNetworkController(
+                mVcnContext,
+                VcnGatewayConnectionConfigTest.buildTestConfig(networkTemplates),
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mNetworkControllerCb);
+
+        verify(cm)
+                .registerNetworkCallback(
+                        eq(getRouteSelectionRequest(INITIAL_SUB_IDS)),
+                        mUnderlyingNetworkListenerCaptor.capture(),
+                        any());
+
+        return mUnderlyingNetworkListenerCaptor.getValue();
+    }
+
+    private UnderlyingNetworkRecord bringupNetworkAndGetRecord(
+            UnderlyingNetworkListener cb,
+            NetworkCapabilities requestNetworkCaps,
+            List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            UnderlyingNetworkRecord currentlySelected) {
+        final Network network = mock(Network.class);
+        final NetworkCapabilities responseNetworkCaps =
+                buildResponseNwCaps(requestNetworkCaps, INITIAL_SUB_IDS);
+
+        cb.onAvailable(network);
+        cb.onCapabilitiesChanged(network, responseNetworkCaps);
+        cb.onLinkPropertiesChanged(network, INITIAL_LINK_PROPERTIES);
+        cb.onBlockedStatusChanged(network, false /* isFalse */);
+        return new UnderlyingNetworkRecord(
+                network,
+                responseNetworkCaps,
+                INITIAL_LINK_PROPERTIES,
+                false /* isBlocked */,
+                mVcnContext,
+                underlyingNetworkTemplates,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                currentlySelected,
+                null /* carrierConfig */);
+    }
+
+    @Test
+    public void testSelectMorePreferredNetwork() {
+        final List<VcnUnderlyingNetworkTemplate> networkTemplates = new ArrayList();
+        networkTemplates.add(CELL_TEMPLATE_DUN);
+        networkTemplates.add(CELL_TEMPLATE_CBS);
+
+        UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates);
+
+        // Bring up CBS network
+        final UnderlyingNetworkRecord cbsNetworkRecord =
+                bringupNetworkAndGetRecord(
+                        cb,
+                        CBS_NETWORK_CAPABILITIES,
+                        networkTemplates,
+                        null /* currentlySelected */);
+        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(cbsNetworkRecord));
+
+        // Bring up DUN network
+        final UnderlyingNetworkRecord dunNetworkRecord =
+                bringupNetworkAndGetRecord(
+                        cb, DUN_NETWORK_CAPABILITIES, networkTemplates, cbsNetworkRecord);
+        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(dunNetworkRecord));
+    }
+
+    @Test
+    public void testNeverSelectLessPreferredNetwork() {
+        final List<VcnUnderlyingNetworkTemplate> networkTemplates = new ArrayList();
+        networkTemplates.add(CELL_TEMPLATE_DUN);
+        networkTemplates.add(CELL_TEMPLATE_CBS);
+
+        UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates);
+
+        // Bring up DUN network
+        final UnderlyingNetworkRecord dunNetworkRecord =
+                bringupNetworkAndGetRecord(
+                        cb,
+                        DUN_NETWORK_CAPABILITIES,
+                        networkTemplates,
+                        null /* currentlySelected */);
+        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(dunNetworkRecord));
+
+        // Bring up CBS network
+        final UnderlyingNetworkRecord cbsNetworkRecord =
+                bringupNetworkAndGetRecord(
+                        cb, CBS_NETWORK_CAPABILITIES, networkTemplates, dunNetworkRecord);
+        verify(mNetworkControllerCb, never())
+                .onSelectedUnderlyingNetworkChanged(eq(cbsNetworkRecord));
+    }
+
+    @Test
+    public void testFailtoMatchTemplateAndFallBackToInternetNetwork() {
+        final List<VcnUnderlyingNetworkTemplate> networkTemplates = new ArrayList();
+
+        networkTemplates.add(
+                new VcnCellUnderlyingNetworkTemplate.Builder().setDun(MATCH_REQUIRED).build());
+        UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates);
+
+        // Bring up an Internet network without DUN capability
+        final UnderlyingNetworkRecord networkRecord =
+                bringupNetworkAndGetRecord(
+                        cb,
+                        INITIAL_NETWORK_CAPABILITIES,
+                        networkTemplates,
+                        null /* currentlySelected */);
+        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(networkRecord));
+    }
+
+    @Test
+    public void testFailtoMatchTemplateAndNeverFallBackToNonInternetNetwork() {
+        final List<VcnUnderlyingNetworkTemplate> networkTemplates = new ArrayList();
+
+        networkTemplates.add(
+                new VcnCellUnderlyingNetworkTemplate.Builder().setDun(MATCH_REQUIRED).build());
+        UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates);
+
+        bringupNetworkAndGetRecord(
+                cb, CBS_NETWORK_CAPABILITIES, networkTemplates, null /* currentlySelected */);
+
+        verify(mNetworkControllerCb, never())
+                .onSelectedUnderlyingNetworkChanged(any(UnderlyingNetworkRecord.class));
+    }
 }