Merge changes from topics "delegate_regi_new_state_losing_pdn", "ims_rcs_api_improvement_delegate_regi_state"

* changes:
  DelegateRegistrationState Improvement adding new states for DelegateRegistrationState:  - DEREGISTERING_REASON_LOSING_PDN  - DEREGISTERING_REASON_UNSPECIFIED
  IMS RCS API Improvements-DelegateRegistrationState
diff --git a/core/api/current.txt b/core/api/current.txt
index 0a1d590..8c313a2 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9024,6 +9024,72 @@
     field public static final int TELEPHONY = 4194304; // 0x400000
   }
 
+  public final class BluetoothCodecConfig implements android.os.Parcelable {
+    ctor public BluetoothCodecConfig(int);
+    method public int describeContents();
+    method public int getBitsPerSample();
+    method public int getChannelMode();
+    method public int getCodecPriority();
+    method public long getCodecSpecific1();
+    method public long getCodecSpecific2();
+    method public long getCodecSpecific3();
+    method public long getCodecSpecific4();
+    method public int getCodecType();
+    method public static int getMaxCodecType();
+    method public int getSampleRate();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int BITS_PER_SAMPLE_16 = 1; // 0x1
+    field public static final int BITS_PER_SAMPLE_24 = 2; // 0x2
+    field public static final int BITS_PER_SAMPLE_32 = 4; // 0x4
+    field public static final int BITS_PER_SAMPLE_NONE = 0; // 0x0
+    field public static final int CHANNEL_MODE_MONO = 1; // 0x1
+    field public static final int CHANNEL_MODE_NONE = 0; // 0x0
+    field public static final int CHANNEL_MODE_STEREO = 2; // 0x2
+    field public static final int CODEC_PRIORITY_DEFAULT = 0; // 0x0
+    field public static final int CODEC_PRIORITY_DISABLED = -1; // 0xffffffff
+    field public static final int CODEC_PRIORITY_HIGHEST = 1000000; // 0xf4240
+    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothCodecConfig> CREATOR;
+    field public static final int SAMPLE_RATE_176400 = 16; // 0x10
+    field public static final int SAMPLE_RATE_192000 = 32; // 0x20
+    field public static final int SAMPLE_RATE_44100 = 1; // 0x1
+    field public static final int SAMPLE_RATE_48000 = 2; // 0x2
+    field public static final int SAMPLE_RATE_88200 = 4; // 0x4
+    field public static final int SAMPLE_RATE_96000 = 8; // 0x8
+    field public static final int SAMPLE_RATE_NONE = 0; // 0x0
+    field public static final int SOURCE_CODEC_TYPE_AAC = 1; // 0x1
+    field public static final int SOURCE_CODEC_TYPE_APTX = 2; // 0x2
+    field public static final int SOURCE_CODEC_TYPE_APTX_HD = 3; // 0x3
+    field public static final int SOURCE_CODEC_TYPE_INVALID = 1000000; // 0xf4240
+    field public static final int SOURCE_CODEC_TYPE_LDAC = 4; // 0x4
+    field public static final int SOURCE_CODEC_TYPE_SBC = 0; // 0x0
+  }
+
+  public static final class BluetoothCodecConfig.Builder {
+    ctor public BluetoothCodecConfig.Builder();
+    method @NonNull public android.bluetooth.BluetoothCodecConfig build();
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setBitsPerSample(int);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setChannelMode(int);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecPriority(int);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecSpecific1(long);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecSpecific2(long);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecSpecific3(long);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecSpecific4(long);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecType(int);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setSampleRate(int);
+  }
+
+  public final class BluetoothCodecStatus implements android.os.Parcelable {
+    ctor public BluetoothCodecStatus(@Nullable android.bluetooth.BluetoothCodecConfig, @Nullable java.util.List<android.bluetooth.BluetoothCodecConfig>, @Nullable java.util.List<android.bluetooth.BluetoothCodecConfig>);
+    method public int describeContents();
+    method @Nullable public android.bluetooth.BluetoothCodecConfig getCodecConfig();
+    method @NonNull public java.util.List<android.bluetooth.BluetoothCodecConfig> getCodecsLocalCapabilities();
+    method @NonNull public java.util.List<android.bluetooth.BluetoothCodecConfig> getCodecsSelectableCapabilities();
+    method public boolean isCodecConfigSelectable(@Nullable android.bluetooth.BluetoothCodecConfig);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothCodecStatus> CREATOR;
+    field public static final String EXTRA_CODEC_STATUS = "android.bluetooth.extra.CODEC_STATUS";
+  }
+
   public final class BluetoothCsipSetCoordinator implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
     method public void close();
     method protected void finalize();
diff --git a/core/java/android/bluetooth/BluetoothCodecConfig.java b/core/java/android/bluetooth/BluetoothCodecConfig.java
index 1d0bf97..9a4151a 100644
--- a/core/java/android/bluetooth/BluetoothCodecConfig.java
+++ b/core/java/android/bluetooth/BluetoothCodecConfig.java
@@ -29,16 +29,14 @@
 
 /**
  * Represents the codec configuration for a Bluetooth A2DP source device.
+ * <p>Contains the source codec type, the codec priority, the codec sample
+ * rate, the codec bits per sample, and the codec channel mode.
+ * <p>The source codec type values are the same as those supported by the
+ * device hardware.
  *
  * {@see BluetoothA2dp}
- *
- * {@hide}
  */
 public final class BluetoothCodecConfig implements Parcelable {
-    // Add an entry for each source codec here.
-    // NOTE: The values should be same as those listed in the following file:
-    //   hardware/libhardware/include/hardware/bt_av.h
-
     /** @hide */
     @IntDef(prefix = "SOURCE_CODEC_TYPE_", value = {
             SOURCE_CODEC_TYPE_SBC,
@@ -46,33 +44,49 @@
             SOURCE_CODEC_TYPE_APTX,
             SOURCE_CODEC_TYPE_APTX_HD,
             SOURCE_CODEC_TYPE_LDAC,
-            SOURCE_CODEC_TYPE_MAX,
             SOURCE_CODEC_TYPE_INVALID
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SourceCodecType {}
 
-    @UnsupportedAppUsage
+    /**
+     * Source codec type SBC. This is the mandatory source codec
+     * type.
+     */
     public static final int SOURCE_CODEC_TYPE_SBC = 0;
 
-    @UnsupportedAppUsage
+    /**
+     * Source codec type AAC.
+     */
     public static final int SOURCE_CODEC_TYPE_AAC = 1;
 
-    @UnsupportedAppUsage
+    /**
+     * Source codec type APTX.
+     */
     public static final int SOURCE_CODEC_TYPE_APTX = 2;
 
-    @UnsupportedAppUsage
+    /**
+     * Source codec type APTX HD.
+     */
     public static final int SOURCE_CODEC_TYPE_APTX_HD = 3;
 
-    @UnsupportedAppUsage
+    /**
+     * Source codec type LDAC.
+     */
     public static final int SOURCE_CODEC_TYPE_LDAC = 4;
 
-    @UnsupportedAppUsage
-    public static final int SOURCE_CODEC_TYPE_MAX = 5;
-
-    @UnsupportedAppUsage
+    /**
+     * Source codec type invalid. This is the default value used for codec
+     * type.
+     */
     public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000;
 
+    /**
+     * Represents the count of valid source codec types. Can be accessed via
+     * {@link #getMaxCodecType}.
+     */
+    private static final int SOURCE_CODEC_TYPE_MAX = 5;
+
     /** @hide */
     @IntDef(prefix = "CODEC_PRIORITY_", value = {
             CODEC_PRIORITY_DISABLED,
@@ -82,16 +96,24 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface CodecPriority {}
 
-    @UnsupportedAppUsage
+    /**
+     * Codec priority disabled.
+     * Used to indicate that this codec is disabled and should not be used.
+     */
     public static final int CODEC_PRIORITY_DISABLED = -1;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec priority default.
+     * Default value used for codec priority.
+     */
     public static final int CODEC_PRIORITY_DEFAULT = 0;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec priority highest.
+     * Used to indicate the highest priority a codec can have.
+     */
     public static final int CODEC_PRIORITY_HIGHEST = 1000 * 1000;
 
-
     /** @hide */
     @IntDef(prefix = "SAMPLE_RATE_", value = {
             SAMPLE_RATE_NONE,
@@ -105,28 +127,42 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface SampleRate {}
 
-    @UnsupportedAppUsage
+    /**
+     * Codec sample rate 0 Hz. Default value used for
+     * codec sample rate.
+     */
     public static final int SAMPLE_RATE_NONE = 0;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec sample rate 44100 Hz.
+     */
     public static final int SAMPLE_RATE_44100 = 0x1 << 0;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec sample rate 48000 Hz.
+     */
     public static final int SAMPLE_RATE_48000 = 0x1 << 1;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec sample rate 88200 Hz.
+     */
     public static final int SAMPLE_RATE_88200 = 0x1 << 2;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec sample rate 96000 Hz.
+     */
     public static final int SAMPLE_RATE_96000 = 0x1 << 3;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec sample rate 176400 Hz.
+     */
     public static final int SAMPLE_RATE_176400 = 0x1 << 4;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec sample rate 192000 Hz.
+     */
     public static final int SAMPLE_RATE_192000 = 0x1 << 5;
 
-
     /** @hide */
     @IntDef(prefix = "BITS_PER_SAMPLE_", value = {
             BITS_PER_SAMPLE_NONE,
@@ -137,19 +173,27 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface BitsPerSample {}
 
-    @UnsupportedAppUsage
+    /**
+     * Codec bits per sample 0. Default value of the codec
+     * bits per sample.
+     */
     public static final int BITS_PER_SAMPLE_NONE = 0;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec bits per sample 16.
+     */
     public static final int BITS_PER_SAMPLE_16 = 0x1 << 0;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec bits per sample 24.
+     */
     public static final int BITS_PER_SAMPLE_24 = 0x1 << 1;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec bits per sample 32.
+     */
     public static final int BITS_PER_SAMPLE_32 = 0x1 << 2;
 
-
     /** @hide */
     @IntDef(prefix = "CHANNEL_MODE_", value = {
             CHANNEL_MODE_NONE,
@@ -159,13 +203,20 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface ChannelMode {}
 
-    @UnsupportedAppUsage
+    /**
+     * Codec channel mode NONE. Default value of the
+     * codec channel mode.
+     */
     public static final int CHANNEL_MODE_NONE = 0;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec channel mode MONO.
+     */
     public static final int CHANNEL_MODE_MONO = 0x1 << 0;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec channel mode STEREO.
+     */
     public static final int CHANNEL_MODE_STEREO = 0x1 << 1;
 
     private final @SourceCodecType int mCodecType;
@@ -178,6 +229,21 @@
     private final long mCodecSpecific3;
     private final long mCodecSpecific4;
 
+    /**
+     * Creates a new BluetoothCodecConfig.
+     *
+     * @param codecType the source codec type
+     * @param codecPriority the priority of this codec
+     * @param sampleRate the codec sample rate
+     * @param bitsPerSample the bits per sample of this codec
+     * @param channelMode the channel mode of this codec
+     * @param codecSpecific1 the specific value 1
+     * @param codecSpecific2 the specific value 2
+     * @param codecSpecific3 the specific value 3
+     * @param codecSpecific4 the specific value 4
+     * values to 0.
+     * @hide
+     */
     @UnsupportedAppUsage
     public BluetoothCodecConfig(@SourceCodecType int codecType, @CodecPriority int codecPriority,
             @SampleRate int sampleRate, @BitsPerSample int bitsPerSample,
@@ -195,17 +261,34 @@
         mCodecSpecific4 = codecSpecific4;
     }
 
-    @UnsupportedAppUsage
+    /**
+     * Creates a new BluetoothCodecConfig.
+     * <p> By default, the codec priority will be set
+     * to {@link BluetoothCodecConfig#CODEC_PRIORITY_DEFAULT}, the sample rate to
+     * {@link BluetoothCodecConfig#SAMPLE_RATE_NONE}, the bits per sample to
+     * {@link BluetoothCodecConfig#BITS_PER_SAMPLE_NONE}, the channel mode to
+     * {@link BluetoothCodecConfig#CHANNEL_MODE_NONE}, and all the codec specific
+     * values to 0.
+     *
+     * @param codecType the source codec type
+     */
     public BluetoothCodecConfig(@SourceCodecType int codecType) {
-        mCodecType = codecType;
-        mCodecPriority = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
-        mSampleRate = BluetoothCodecConfig.SAMPLE_RATE_NONE;
-        mBitsPerSample = BluetoothCodecConfig.BITS_PER_SAMPLE_NONE;
-        mChannelMode = BluetoothCodecConfig.CHANNEL_MODE_NONE;
-        mCodecSpecific1 = 0;
-        mCodecSpecific2 = 0;
-        mCodecSpecific3 = 0;
-        mCodecSpecific4 = 0;
+        this(codecType, BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                BluetoothCodecConfig.SAMPLE_RATE_NONE,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+                BluetoothCodecConfig.CHANNEL_MODE_NONE, 0, 0, 0, 0);
+    }
+
+    private BluetoothCodecConfig(Parcel in) {
+        mCodecType = in.readInt();
+        mCodecPriority = in.readInt();
+        mSampleRate = in.readInt();
+        mBitsPerSample = in.readInt();
+        mChannelMode = in.readInt();
+        mCodecSpecific1 = in.readLong();
+        mCodecSpecific2 = in.readLong();
+        mCodecSpecific3 = in.readLong();
+        mCodecSpecific4 = in.readLong();
     }
 
     @Override
@@ -226,10 +309,8 @@
     }
 
     /**
-     * Returns a hash based on the config values
-     *
-     * @return a hash based on the config values
-     * @hide
+     * Returns a hash representation of this BluetoothCodecConfig
+     * based on all the config values.
      */
     @Override
     public int hashCode() {
@@ -239,32 +320,24 @@
     }
 
     /**
-     * Checks whether the object contains valid codec configuration.
-     *
-     * @return true if the object contains valid codec configuration, otherwise false.
-     * @hide
-     */
-    public boolean isValid() {
-        return (mSampleRate != SAMPLE_RATE_NONE)
-                && (mBitsPerSample != BITS_PER_SAMPLE_NONE)
-                && (mChannelMode != CHANNEL_MODE_NONE);
-    }
-
-    /**
      * Adds capability string to an existing string.
      *
-     * @param prevStr the previous string with the capabilities. Can be a null pointer.
-     * @param capStr the capability string to append to prevStr argument.
-     * @return the result string in the form "prevStr|capStr".
+     * @param prevStr the previous string with the capabilities. Can be a {@code null} pointer
+     * @param capStr the capability string to append to prevStr argument
+     * @return the result string in the form "prevStr|capStr"
      */
-    private static String appendCapabilityToString(String prevStr,
-            String capStr) {
+    private static String appendCapabilityToString(@Nullable String prevStr,
+            @NonNull String capStr) {
         if (prevStr == null) {
             return capStr;
         }
         return prevStr + "|" + capStr;
     }
 
+    /**
+     * Returns a {@link String} that describes each BluetoothCodecConfig parameter
+     * current value.
+     */
     @Override
     public String toString() {
         String sampleRateStr = null;
@@ -331,8 +404,6 @@
     }
 
     /**
-     * Always returns 0
-     *
      * @return 0
      * @hide
      */
@@ -344,20 +415,7 @@
     public static final @android.annotation.NonNull Parcelable.Creator<BluetoothCodecConfig> CREATOR =
             new Parcelable.Creator<BluetoothCodecConfig>() {
                 public BluetoothCodecConfig createFromParcel(Parcel in) {
-                    final int codecType = in.readInt();
-                    final int codecPriority = in.readInt();
-                    final int sampleRate = in.readInt();
-                    final int bitsPerSample = in.readInt();
-                    final int channelMode = in.readInt();
-                    final long codecSpecific1 = in.readLong();
-                    final long codecSpecific2 = in.readLong();
-                    final long codecSpecific3 = in.readLong();
-                    final long codecSpecific4 = in.readLong();
-                    return new BluetoothCodecConfig(codecType, codecPriority,
-                            sampleRate, bitsPerSample,
-                            channelMode, codecSpecific1,
-                            codecSpecific2, codecSpecific3,
-                            codecSpecific4);
+                    return new BluetoothCodecConfig(in);
                 }
 
                 public BluetoothCodecConfig[] newArray(int size) {
@@ -368,8 +426,8 @@
     /**
      * Flattens the object to a parcel
      *
-     * @param out The Parcel in which the object should be written.
-     * @param flags Additional flags about how the object should be written.
+     * @param out The Parcel in which the object should be written
+     * @param flags Additional flags about how the object should be written
      *
      * @hide
      */
@@ -387,9 +445,8 @@
     }
 
     /**
-     * Gets the codec name.
-     *
-     * @return the codec name
+     * Returns the codec name converted to {@link String}.
+     * @hide
      */
     public @NonNull String getCodecName() {
         switch (mCodecType) {
@@ -412,137 +469,100 @@
     }
 
     /**
-     * Gets the codec type.
-     * See {@link android.bluetooth.BluetoothCodecConfig#SOURCE_CODEC_TYPE_SBC}.
-     *
-     * @return the codec type
+     * Returns the source codec type of this config.
      */
-    @UnsupportedAppUsage
     public @SourceCodecType int getCodecType() {
         return mCodecType;
     }
 
     /**
+     * Returns the valid codec types count.
+     */
+    public static int getMaxCodecType() {
+        return SOURCE_CODEC_TYPE_MAX;
+    }
+
+    /**
      * Checks whether the codec is mandatory.
+     * <p> The actual mandatory codec type for Android Bluetooth audio is SBC.
+     * See {@link #SOURCE_CODEC_TYPE_SBC}.
      *
-     * @return true if the codec is mandatory, otherwise false.
+     * @return {@code true} if the codec is mandatory, {@code false} otherwise
+     * @hide
      */
     public boolean isMandatoryCodec() {
         return mCodecType == SOURCE_CODEC_TYPE_SBC;
     }
 
     /**
-     * Gets the codec selection priority.
-     * The codec selection priority is relative to other codecs: larger value
-     * means higher priority. If 0, reset to default.
-     *
-     * @return the codec priority
+     * Returns the codec selection priority.
+     * <p>The codec selection priority is relative to other codecs: larger value
+     * means higher priority.
      */
-    @UnsupportedAppUsage
     public @CodecPriority int getCodecPriority() {
         return mCodecPriority;
     }
 
     /**
      * Sets the codec selection priority.
-     * The codec selection priority is relative to other codecs: larger value
-     * means higher priority. If 0, reset to default.
+     * <p>The codec selection priority is relative to other codecs: larger value
+     * means higher priority.
      *
-     * @param codecPriority the codec priority
+     * @param codecPriority the priority this codec should have
      * @hide
      */
-    @UnsupportedAppUsage
     public void setCodecPriority(@CodecPriority int codecPriority) {
         mCodecPriority = codecPriority;
     }
 
     /**
-     * Gets the codec sample rate. The value can be a bitmask with all
-     * supported sample rates:
-     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_NONE} or
-     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_44100} or
-     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_48000} or
-     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_88200} or
-     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_96000} or
-     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_176400} or
-     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_192000}
-     *
-     * @return the codec sample rate
+     * Returns the codec sample rate. The value can be a bitmask with all
+     * supported sample rates.
      */
-    @UnsupportedAppUsage
     public @SampleRate int getSampleRate() {
         return mSampleRate;
     }
 
     /**
-     * Gets the codec bits per sample. The value can be a bitmask with all
-     * bits per sample supported:
-     * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_NONE} or
-     * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_16} or
-     * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_24} or
-     * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_32}
-     *
-     * @return the codec bits per sample
+     * Returns the codec bits per sample. The value can be a bitmask with all
+     * bits per sample supported.
      */
-    @UnsupportedAppUsage
     public @BitsPerSample int getBitsPerSample() {
         return mBitsPerSample;
     }
 
     /**
-     * Gets the codec channel mode. The value can be a bitmask with all
-     * supported channel modes:
-     * {@link android.bluetooth.BluetoothCodecConfig#CHANNEL_MODE_NONE} or
-     * {@link android.bluetooth.BluetoothCodecConfig#CHANNEL_MODE_MONO} or
-     * {@link android.bluetooth.BluetoothCodecConfig#CHANNEL_MODE_STEREO}
-     *
-     * @return the codec channel mode
-     * @hide
+     * Returns the codec channel mode. The value can be a bitmask with all
+     * supported channel modes.
      */
-    @UnsupportedAppUsage
     public @ChannelMode int getChannelMode() {
         return mChannelMode;
     }
 
     /**
-     * Gets a codec specific value1.
-     *
-     * @return a codec specific value1.
+     * Returns the codec specific value1.
      */
-    @UnsupportedAppUsage
     public long getCodecSpecific1() {
         return mCodecSpecific1;
     }
 
     /**
-     * Gets a codec specific value2.
-     *
-     * @return a codec specific value2
-     * @hide
+     * Returns the codec specific value2.
      */
-    @UnsupportedAppUsage
     public long getCodecSpecific2() {
         return mCodecSpecific2;
     }
 
     /**
-     * Gets a codec specific value3.
-     *
-     * @return a codec specific value3
-     * @hide
+     * Returns the codec specific value3.
      */
-    @UnsupportedAppUsage
     public long getCodecSpecific3() {
         return mCodecSpecific3;
     }
 
     /**
-     * Gets a codec specific value4.
-     *
-     * @return a codec specific value4
-     * @hide
+     * Returns the codec specific value4.
      */
-    @UnsupportedAppUsage
     public long getCodecSpecific4() {
         return mCodecSpecific4;
     }
@@ -551,7 +571,7 @@
      * Checks whether a value set presented by a bitmask has zero or single bit
      *
      * @param valueSet the value set presented by a bitmask
-     * @return true if the valueSet contains zero or single bit, otherwise false.
+     * @return {@code true} if the valueSet contains zero or single bit, {@code false} otherwise
      * @hide
      */
     private static boolean hasSingleBit(int valueSet) {
@@ -559,9 +579,7 @@
     }
 
     /**
-     * Checks whether the object contains none or single sample rate.
-     *
-     * @return true if the object contains none or single sample rate, otherwise false.
+     * Returns whether the object contains none or single sample rate.
      * @hide
      */
     public boolean hasSingleSampleRate() {
@@ -569,9 +587,7 @@
     }
 
     /**
-     * Checks whether the object contains none or single bits per sample.
-     *
-     * @return true if the object contains none or single bits per sample, otherwise false.
+     * Returns whether the object contains none or single bits per sample.
      * @hide
      */
     public boolean hasSingleBitsPerSample() {
@@ -579,9 +595,7 @@
     }
 
     /**
-     * Checks whether the object contains none or single channel mode.
-     *
-     * @return true if the object contains none or single channel mode, otherwise false.
+     * Returns whether the object contains none or single channel mode.
      * @hide
      */
     public boolean hasSingleChannelMode() {
@@ -589,10 +603,10 @@
     }
 
     /**
-     * Checks whether the audio feeding parameters are same.
+     * Checks whether the audio feeding parameters are the same.
      *
      * @param other the codec config to compare against
-     * @return true if the audio feeding parameters are same, otherwise false
+     * @return {@code true} if the audio feeding parameters are same, {@code false} otherwise
      * @hide
      */
     public boolean sameAudioFeedingParameters(BluetoothCodecConfig other) {
@@ -606,7 +620,7 @@
      * Any parameters with NONE value will be considered to be a wildcard matching.
      *
      * @param other the codec config to compare against
-     * @return true if the audio feeding parameters are similar, otherwise false.
+     * @return {@code true} if the audio feeding parameters are similar, {@code false} otherwise
      * @hide
      */
     public boolean similarCodecFeedingParameters(BluetoothCodecConfig other) {
@@ -614,18 +628,18 @@
             return false;
         }
         int sampleRate = other.mSampleRate;
-        if (mSampleRate == BluetoothCodecConfig.SAMPLE_RATE_NONE
-                || sampleRate == BluetoothCodecConfig.SAMPLE_RATE_NONE) {
+        if (mSampleRate == SAMPLE_RATE_NONE
+                || sampleRate == SAMPLE_RATE_NONE) {
             sampleRate = mSampleRate;
         }
         int bitsPerSample = other.mBitsPerSample;
-        if (mBitsPerSample == BluetoothCodecConfig.BITS_PER_SAMPLE_NONE
-                || bitsPerSample == BluetoothCodecConfig.BITS_PER_SAMPLE_NONE) {
+        if (mBitsPerSample == BITS_PER_SAMPLE_NONE
+                || bitsPerSample == BITS_PER_SAMPLE_NONE) {
             bitsPerSample = mBitsPerSample;
         }
         int channelMode = other.mChannelMode;
-        if (mChannelMode == BluetoothCodecConfig.CHANNEL_MODE_NONE
-                || channelMode == BluetoothCodecConfig.CHANNEL_MODE_NONE) {
+        if (mChannelMode == CHANNEL_MODE_NONE
+                || channelMode == CHANNEL_MODE_NONE) {
             channelMode = mChannelMode;
         }
         return sameAudioFeedingParameters(new BluetoothCodecConfig(
@@ -636,25 +650,158 @@
 
     /**
      * Checks whether the codec specific parameters are the same.
+     * <p> Currently, only AAC VBR and LDAC Playback Quality on CodecSpecific1
+     * are compared.
      *
      * @param other the codec config to compare against
-     * @return true if the codec specific parameters are the same, otherwise false.
+     * @return {@code true} if the codec specific parameters are the same, {@code false} otherwise
      * @hide
      */
     public boolean sameCodecSpecificParameters(BluetoothCodecConfig other) {
         if (other == null && mCodecType != other.mCodecType) {
             return false;
         }
-        // Currently we only care about the AAC VBR and LDAC Playback Quality at CodecSpecific1
         switch (mCodecType) {
             case SOURCE_CODEC_TYPE_AAC:
             case SOURCE_CODEC_TYPE_LDAC:
                 if (mCodecSpecific1 != other.mCodecSpecific1) {
                     return false;
                 }
-                // fall through
             default:
                 return true;
         }
     }
+
+    /**
+     * Builder for {@link BluetoothCodecConfig}.
+     * <p> By default, the codec type will be set to
+     * {@link BluetoothCodecConfig#SOURCE_CODEC_TYPE_INVALID}, the codec priority
+     * to {@link BluetoothCodecConfig#CODEC_PRIORITY_DEFAULT}, the sample rate to
+     * {@link BluetoothCodecConfig#SAMPLE_RATE_NONE}, the bits per sample to
+     * {@link BluetoothCodecConfig#BITS_PER_SAMPLE_NONE}, the channel mode to
+     * {@link BluetoothCodecConfig#CHANNEL_MODE_NONE}, and all the codec specific
+     * values to 0.
+     */
+    public static final class Builder {
+        private int mCodecType = BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID;
+        private int mCodecPriority = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+        private int mSampleRate = BluetoothCodecConfig.SAMPLE_RATE_NONE;
+        private int mBitsPerSample = BluetoothCodecConfig.BITS_PER_SAMPLE_NONE;
+        private int mChannelMode = BluetoothCodecConfig.CHANNEL_MODE_NONE;
+        private long mCodecSpecific1 = 0;
+        private long mCodecSpecific2 = 0;
+        private long mCodecSpecific3 = 0;
+        private long mCodecSpecific4 = 0;
+
+        /**
+         * Set codec type for Bluetooth codec config.
+         *
+         * @param codecType of this codec
+         * @return the same Builder instance
+         */
+        public @NonNull Builder setCodecType(@SourceCodecType int codecType) {
+            mCodecType = codecType;
+            return this;
+        }
+
+        /**
+         * Set codec priority for Bluetooth codec config.
+         *
+         * @param codecPriority of this codec
+         * @return the same Builder instance
+         */
+        public @NonNull Builder setCodecPriority(@CodecPriority int codecPriority) {
+            mCodecPriority = codecPriority;
+            return this;
+        }
+
+        /**
+         * Set sample rate for Bluetooth codec config.
+         *
+         * @param sampleRate of this codec
+         * @return the same Builder instance
+         */
+        public @NonNull Builder setSampleRate(@SampleRate int sampleRate) {
+            mSampleRate = sampleRate;
+            return this;
+        }
+
+        /**
+         * Set the bits per sample for Bluetooth codec config.
+         *
+         * @param bitsPerSample of this codec
+         * @return the same Builder instance
+         */
+        public @NonNull Builder setBitsPerSample(@BitsPerSample int bitsPerSample) {
+            mBitsPerSample = bitsPerSample;
+            return this;
+        }
+
+        /**
+         * Set the channel mode for Bluetooth codec config.
+         *
+         * @param channelMode of this codec
+         * @return the same Builder instance
+         */
+        public @NonNull Builder setChannelMode(@ChannelMode int channelMode) {
+            mChannelMode = channelMode;
+            return this;
+        }
+
+        /**
+         * Set the first codec specific values for Bluetooth codec config.
+         *
+         * @param codecSpecific1 codec specific value or 0 if default
+         * @return the same Builder instance
+         */
+        public @NonNull Builder setCodecSpecific1(long codecSpecific1) {
+            mCodecSpecific1 = codecSpecific1;
+            return this;
+        }
+
+        /**
+         * Set the second codec specific values for Bluetooth codec config.
+         *
+         * @param codecSpecific2 codec specific value or 0 if default
+         * @return the same Builder instance
+         */
+        public @NonNull Builder setCodecSpecific2(long codecSpecific2) {
+            mCodecSpecific2 = codecSpecific2;
+            return this;
+        }
+
+        /**
+         * Set the third codec specific values for Bluetooth codec config.
+         *
+         * @param codecSpecific3 codec specific value or 0 if default
+         * @return the same Builder instance
+         */
+        public @NonNull Builder setCodecSpecific3(long codecSpecific3) {
+            mCodecSpecific3 = codecSpecific3;
+            return this;
+        }
+
+        /**
+         * Set the fourth codec specific values for Bluetooth codec config.
+         *
+         * @param codecSpecific4 codec specific value or 0 if default
+         * @return the same Builder instance
+         */
+        public @NonNull Builder setCodecSpecific4(long codecSpecific4) {
+            mCodecSpecific4 = codecSpecific4;
+            return this;
+        }
+
+        /**
+         * Build {@link BluetoothCodecConfig}.
+         * @return new BluetoothCodecConfig built
+         */
+        public @NonNull BluetoothCodecConfig build() {
+            return new BluetoothCodecConfig(mCodecType, mCodecPriority,
+                    mSampleRate, mBitsPerSample,
+                    mChannelMode, mCodecSpecific1,
+                    mCodecSpecific2, mCodecSpecific3,
+                    mCodecSpecific4);
+        }
+    }
 }
diff --git a/core/java/android/bluetooth/BluetoothCodecStatus.java b/core/java/android/bluetooth/BluetoothCodecStatus.java
index 7764ebe..02606fe 100644
--- a/core/java/android/bluetooth/BluetoothCodecStatus.java
+++ b/core/java/android/bluetooth/BluetoothCodecStatus.java
@@ -16,12 +16,13 @@
 
 package android.bluetooth;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -29,8 +30,6 @@
  * A2DP source device.
  *
  * {@see BluetoothA2dp}
- *
- * {@hide}
  */
 public final class BluetoothCodecStatus implements Parcelable {
     /**
@@ -39,22 +38,27 @@
      * This extra represents the current codec status of the A2DP
      * profile.
      */
-    @UnsupportedAppUsage
     public static final String EXTRA_CODEC_STATUS =
             "android.bluetooth.extra.CODEC_STATUS";
 
     private final @Nullable BluetoothCodecConfig mCodecConfig;
-    private final BluetoothCodecConfig[] mCodecsLocalCapabilities;
-    private final BluetoothCodecConfig[] mCodecsSelectableCapabilities;
+    private final @Nullable List<BluetoothCodecConfig> mCodecsLocalCapabilities;
+    private final @Nullable List<BluetoothCodecConfig> mCodecsSelectableCapabilities;
 
     public BluetoothCodecStatus(@Nullable BluetoothCodecConfig codecConfig,
-            @Nullable BluetoothCodecConfig[] codecsLocalCapabilities,
-            @Nullable BluetoothCodecConfig[] codecsSelectableCapabilities) {
+            @Nullable List<BluetoothCodecConfig> codecsLocalCapabilities,
+            @Nullable List<BluetoothCodecConfig> codecsSelectableCapabilities) {
         mCodecConfig = codecConfig;
         mCodecsLocalCapabilities = codecsLocalCapabilities;
         mCodecsSelectableCapabilities = codecsSelectableCapabilities;
     }
 
+    private BluetoothCodecStatus(Parcel in) {
+        mCodecConfig = in.readTypedObject(BluetoothCodecConfig.CREATOR);
+        mCodecsLocalCapabilities = in.createTypedArrayList(BluetoothCodecConfig.CREATOR);
+        mCodecsSelectableCapabilities = in.createTypedArrayList(BluetoothCodecConfig.CREATOR);
+    }
+
     @Override
     public boolean equals(@Nullable Object o) {
         if (o instanceof BluetoothCodecStatus) {
@@ -68,26 +72,25 @@
     }
 
     /**
-     * Checks whether two arrays of capabilities contain same capabilities.
-     * The order of the capabilities in each array is ignored.
+     * Checks whether two lists of capabilities contain same capabilities.
+     * The order of the capabilities in each list is ignored.
      *
-     * @param c1 the first array of capabilities to compare
-     * @param c2 the second array of capabilities to compare
-     * @return true if both arrays contain same capabilities
-     * @hide
+     * @param c1 the first list of capabilities to compare
+     * @param c2 the second list of capabilities to compare
+     * @return {@code true} if both lists contain same capabilities
      */
-    public static boolean sameCapabilities(BluetoothCodecConfig[] c1,
-                                           BluetoothCodecConfig[] c2) {
+    private static boolean sameCapabilities(@Nullable List<BluetoothCodecConfig> c1,
+                                           @Nullable List<BluetoothCodecConfig> c2) {
         if (c1 == null) {
             return (c2 == null);
         }
         if (c2 == null) {
             return false;
         }
-        if (c1.length != c2.length) {
+        if (c1.size() != c2.size()) {
             return false;
         }
-        return Arrays.asList(c1).containsAll(Arrays.asList(c2));
+        return c1.containsAll(c2);
     }
 
     /**
@@ -95,10 +98,9 @@
      * Any parameters of the codec config with NONE value will be considered a wildcard matching.
      *
      * @param codecConfig the codec config to compare against
-     * @return true if the codec config matches, otherwise false
-     * @hide
+     * @return {@code true} if the codec config matches, {@code false} otherwise
      */
-    public boolean isCodecConfigSelectable(BluetoothCodecConfig codecConfig) {
+    public boolean isCodecConfigSelectable(@Nullable BluetoothCodecConfig codecConfig) {
         if (codecConfig == null || !codecConfig.hasSingleSampleRate()
                 || !codecConfig.hasSingleBitsPerSample() || !codecConfig.hasSingleChannelMode()) {
             return false;
@@ -128,10 +130,7 @@
     }
 
     /**
-     * Returns a hash based on the codec config and local capabilities
-     *
-     * @return a hash based on the config values
-     * @hide
+     * Returns a hash based on the codec config and local capabilities.
      */
     @Override
     public int hashCode() {
@@ -139,17 +138,19 @@
                 mCodecsLocalCapabilities);
     }
 
+    /**
+     * Returns a {@link String} that describes each BluetoothCodecStatus parameter
+     * current value.
+     */
     @Override
     public String toString() {
         return "{mCodecConfig:" + mCodecConfig
-                + ",mCodecsLocalCapabilities:" + Arrays.toString(mCodecsLocalCapabilities)
-                + ",mCodecsSelectableCapabilities:" + Arrays.toString(mCodecsSelectableCapabilities)
+                + ",mCodecsLocalCapabilities:" + mCodecsLocalCapabilities
+                + ",mCodecsSelectableCapabilities:" + mCodecsSelectableCapabilities
                 + "}";
     }
 
     /**
-     * Always returns 0
-     *
      * @return 0
      * @hide
      */
@@ -161,16 +162,7 @@
     public static final @android.annotation.NonNull Parcelable.Creator<BluetoothCodecStatus> CREATOR =
             new Parcelable.Creator<BluetoothCodecStatus>() {
                 public BluetoothCodecStatus createFromParcel(Parcel in) {
-                    final BluetoothCodecConfig codecConfig = in.readTypedObject(
-                            BluetoothCodecConfig.CREATOR);
-                    final BluetoothCodecConfig[] codecsLocalCapabilities = in.createTypedArray(
-                            BluetoothCodecConfig.CREATOR);
-                    final BluetoothCodecConfig[] codecsSelectableCapabilities = in.createTypedArray(
-                            BluetoothCodecConfig.CREATOR);
-
-                    return new BluetoothCodecStatus(codecConfig,
-                            codecsLocalCapabilities,
-                            codecsSelectableCapabilities);
+                    return new BluetoothCodecStatus(in);
                 }
 
                 public BluetoothCodecStatus[] newArray(int size) {
@@ -179,47 +171,38 @@
             };
 
     /**
-     * Flattens the object to a parcel
+     * Flattens the object to a parcel.
      *
-     * @param out The Parcel in which the object should be written.
-     * @param flags Additional flags about how the object should be written.
-     *
-     * @hide
+     * @param out The Parcel in which the object should be written
+     * @param flags Additional flags about how the object should be written
      */
     @Override
-    public void writeToParcel(Parcel out, int flags) {
+    public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeTypedObject(mCodecConfig, 0);
-        out.writeTypedArray(mCodecsLocalCapabilities, 0);
-        out.writeTypedArray(mCodecsSelectableCapabilities, 0);
+        out.writeTypedList(mCodecsLocalCapabilities);
+        out.writeTypedList(mCodecsSelectableCapabilities);
     }
 
     /**
-     * Gets the current codec configuration.
-     *
-     * @return the current codec configuration
+     * Returns the current codec configuration.
      */
-    @UnsupportedAppUsage
     public @Nullable BluetoothCodecConfig getCodecConfig() {
         return mCodecConfig;
     }
 
     /**
-     * Gets the codecs local capabilities.
-     *
-     * @return an array with the codecs local capabilities
+     * Returns the codecs local capabilities.
      */
-    @UnsupportedAppUsage
-    public @Nullable BluetoothCodecConfig[] getCodecsLocalCapabilities() {
-        return mCodecsLocalCapabilities;
+    public @NonNull List<BluetoothCodecConfig> getCodecsLocalCapabilities() {
+        return (mCodecsLocalCapabilities == null)
+                ? Collections.emptyList() : mCodecsLocalCapabilities;
     }
 
     /**
-     * Gets the codecs selectable capabilities.
-     *
-     * @return an array with the codecs selectable capabilities
+     * Returns the codecs selectable capabilities.
      */
-    @UnsupportedAppUsage
-    public @Nullable BluetoothCodecConfig[] getCodecsSelectableCapabilities() {
-        return mCodecsSelectableCapabilities;
+    public @NonNull List<BluetoothCodecConfig> getCodecsSelectableCapabilities() {
+        return (mCodecsSelectableCapabilities == null)
+                ? Collections.emptyList() : mCodecsSelectableCapabilities;
     }
 }
diff --git a/core/java/android/bluetooth/BluetoothLeBroadcast.java b/core/java/android/bluetooth/BluetoothLeBroadcast.java
new file mode 100644
index 0000000..fed9f91
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothLeBroadcast.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * This class provides the public APIs to control the Bluetooth LE Broadcast Source profile.
+ *
+ * <p>BluetoothLeBroadcast is a proxy object for controlling the Bluetooth LE Broadcast
+ * Source Service via IPC. Use {@link BluetoothAdapter#getProfileProxy}
+ * to get the BluetoothLeBroadcast proxy object.
+ *
+ * @hide
+ */
+public final class BluetoothLeBroadcast implements BluetoothProfile {
+    private static final String TAG = "BluetoothLeBroadcast";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    /**
+     * Constants used by the LE Audio Broadcast profile for the Broadcast state
+     *
+     * @hide
+     */
+    @IntDef(prefix = {"LE_AUDIO_BROADCAST_STATE_"}, value = {
+      LE_AUDIO_BROADCAST_STATE_DISABLED,
+      LE_AUDIO_BROADCAST_STATE_ENABLING,
+      LE_AUDIO_BROADCAST_STATE_ENABLED,
+      LE_AUDIO_BROADCAST_STATE_DISABLING,
+      LE_AUDIO_BROADCAST_STATE_PLAYING,
+      LE_AUDIO_BROADCAST_STATE_NOT_PLAYING
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface LeAudioBroadcastState {}
+
+    /**
+     * Indicates that LE Audio Broadcast mode is currently disabled
+     *
+     * @hide
+     */
+    public static final int LE_AUDIO_BROADCAST_STATE_DISABLED = 10;
+
+    /**
+     * Indicates that LE Audio Broadcast mode is being enabled
+     *
+     * @hide
+     */
+    public static final int LE_AUDIO_BROADCAST_STATE_ENABLING = 11;
+
+    /**
+     * Indicates that LE Audio Broadcast mode is currently enabled
+     *
+     * @hide
+     */
+    public static final int LE_AUDIO_BROADCAST_STATE_ENABLED = 12;
+    /**
+     * Indicates that LE Audio Broadcast mode is being disabled
+     *
+     * @hide
+     */
+    public static final int LE_AUDIO_BROADCAST_STATE_DISABLING = 13;
+
+    /**
+     * Indicates that an LE Audio Broadcast mode is currently playing
+     *
+     * @hide
+     */
+    public static final int LE_AUDIO_BROADCAST_STATE_PLAYING = 14;
+
+    /**
+     * Indicates that LE Audio Broadcast is currently not playing
+     *
+     * @hide
+     */
+    public static final int LE_AUDIO_BROADCAST_STATE_NOT_PLAYING = 15;
+
+    /**
+     * Constants used by the LE Audio Broadcast profile for encryption key length
+     *
+     * @hide
+     */
+    @IntDef(prefix = {"LE_AUDIO_BROADCAST_ENCRYPTION_KEY_"}, value = {
+      LE_AUDIO_BROADCAST_ENCRYPTION_KEY_32BIT,
+      LE_AUDIO_BROADCAST_ENCRYPTION_KEY_128BIT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface LeAudioEncryptionKeyLength {}
+
+    /**
+     * Indicates that the LE Audio Broadcast encryption key size is 32 bits.
+     *
+     * @hide
+     */
+    public static final int LE_AUDIO_BROADCAST_ENCRYPTION_KEY_32BIT = 16;
+
+    /**
+     * Indicates that the LE Audio Broadcast encryption key size is 128 bits.
+     *
+     * @hide
+     */
+    public static final int LE_AUDIO_BROADCAST_ENCRYPTION_KEY_128BIT = 17;
+
+    /**
+     * Interface for receiving events related to broadcasts
+     */
+    public interface Callback {
+        /**
+         * Called when broadcast state has changed
+         *
+         * @param prevState broadcast state before the change
+         * @param newState broadcast state after the change
+         */
+        @LeAudioBroadcastState
+        void onBroadcastStateChange(int prevState, int newState);
+        /**
+         * Called when encryption key has been updated
+         *
+         * @param success true if the key was updated successfully, false otherwise
+         */
+        void onEncryptionKeySet(boolean success);
+    }
+
+    /**
+     * Create a BluetoothLeBroadcast proxy object for interacting with the local
+     * LE Audio Broadcast Source service.
+     *
+     * @hide
+     */
+    /*package*/ BluetoothLeBroadcast(Context context,
+                                     BluetoothProfile.ServiceListener listener) {
+    }
+
+    /**
+     * Not supported since LE Audio Broadcasts do not establish a connection
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @hide
+     */
+    @Override
+    public int getConnectionState(BluetoothDevice device) {
+        throw new UnsupportedOperationException(
+                   "LE Audio Broadcasts are not connection-oriented.");
+    }
+
+    /**
+     * Not supported since LE Audio Broadcasts do not establish a connection
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @hide
+     */
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        throw new UnsupportedOperationException(
+                   "LE Audio Broadcasts are not connection-oriented.");
+    }
+
+    /**
+     * Not supported since LE Audio Broadcasts do not establish a connection
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @hide
+     */
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        throw new UnsupportedOperationException(
+                   "LE Audio Broadcasts are not connection-oriented.");
+    }
+
+    /**
+     * Enable LE Audio Broadcast mode.
+     *
+     * Generates a new broadcast ID and enables sending of encrypted or unencrypted
+     * isochronous PDUs
+     *
+     * @hide
+     */
+    public int enableBroadcastMode() {
+        if (DBG) log("enableBroadcastMode");
+        return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_BROADCAST_MODE_FAILED;
+    }
+
+    /**
+     * Disable LE Audio Broadcast mode.
+     *
+     * @hide
+     */
+    public int disableBroadcastMode() {
+        if (DBG) log("disableBroadcastMode");
+        return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_BROADCAST_MODE_FAILED;
+    }
+
+    /**
+     * Get the current LE Audio broadcast state
+     *
+     * @hide
+     */
+    @LeAudioBroadcastState
+    public int getBroadcastState() {
+        if (DBG) log("getBroadcastState");
+        return LE_AUDIO_BROADCAST_STATE_DISABLED;
+    }
+
+    /**
+     * Enable LE Audio broadcast encryption
+     *
+     * @param keyLength if useExisting is true, this specifies the length of the key that should
+     *                  be generated
+     * @param useExisting true, if an existing key should be used
+     *                    false, if a new key should be generated
+     *
+     * @hide
+     */
+    @LeAudioEncryptionKeyLength
+    public int enableEncryption(boolean useExisting, int keyLength) {
+        if (DBG) log("enableEncryption useExisting=" + useExisting + " keyLength=" + keyLength);
+        return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_ENABLE_ENCRYPTION_FAILED;
+    }
+
+    /**
+     * Disable LE Audio broadcast encryption
+     *
+     * @param removeExisting true, if the existing key should be removed
+     *                       false, otherwise
+     *
+     * @hide
+     */
+    public int disableEncryption(boolean removeExisting) {
+        if (DBG) log("disableEncryption removeExisting=" + removeExisting);
+        return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_DISABLE_ENCRYPTION_FAILED;
+    }
+
+    /**
+     * Enable or disable LE Audio broadcast encryption
+     *
+     * @param key use the provided key if non-null, generate a new key if null
+     * @param keyLength 0 if encryption is disabled, 4 bytes (low security),
+     *                  16 bytes (high security)
+     *
+     * @hide
+     */
+    @LeAudioEncryptionKeyLength
+    public int setEncryptionKey(byte[] key, int keyLength) {
+        if (DBG) log("setEncryptionKey key=" + key + " keyLength=" + keyLength);
+        return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_ENCRYPTION_KEY_FAILED;
+    }
+
+
+    /**
+     * Get the encryption key that was set before
+     *
+     * @return encryption key as a byte array or null if no encryption key was set
+     *
+     * @hide
+     */
+    public byte[] getEncryptionKey() {
+        if (DBG) log("getEncryptionKey");
+        return null;
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 4f2dba7..e047e5d 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -233,12 +233,19 @@
     int CSIP_SET_COORDINATOR = 25;
 
     /**
+     * LE Audio Broadcast Source
+     *
+     * @hide
+     */
+    int LE_AUDIO_BROADCAST = 26;
+
+    /**
      * Max profile ID. This value should be updated whenever a new profile is added to match
      * the largest value assigned to a profile.
      *
      * @hide
      */
-    int MAX_PROFILE_ID = 25;
+    int MAX_PROFILE_ID = 26;
 
     /**
      * Default priority for devices that we try to auto-connect to and
diff --git a/core/java/android/bluetooth/BluetoothStatusCodes.java b/core/java/android/bluetooth/BluetoothStatusCodes.java
index ca01784..9dafa07 100644
--- a/core/java/android/bluetooth/BluetoothStatusCodes.java
+++ b/core/java/android/bluetooth/BluetoothStatusCodes.java
@@ -226,6 +226,66 @@
     public static final int ERROR_DISCONNECT_REASON_BAD_PARAMETERS = 1109;
 
     /**
+     * Indicates that setting the LE Audio Broadcast mode failed.
+     * <p>
+     * Example solution: Change parameters and try again. If error persists, the app can report
+     * telemetry and/or log the error in a bugreport.
+     *
+     * @hide
+     */
+    public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_BROADCAST_MODE_FAILED = 1110;
+
+    /**
+     * Indicates that setting a new encryption key for Bluetooth LE Audio Broadcast Source failed.
+     * <p>
+     * Example solution: Change parameters and try again. If error persists, the app can report
+     * telemetry and/or log the error in a bugreport.
+     *
+     * @hide
+     */
+    public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_ENCRYPTION_KEY_FAILED = 1111;
+
+    /**
+     * Indicates that connecting to a remote Broadcast Audio Scan Service failed.
+     * <p>
+     * Example solution: Change parameters and try again. If error persists, the app can report
+     * telemetry and/or log the error in a bugreport.
+     *
+     * @hide
+     */
+    public static final int ERROR_LE_AUDIO_BROADCAST_AUDIO_SCAN_SERVICE_CONNECT_FAILED = 1112;
+
+    /**
+     * Indicates that disconnecting from a remote Broadcast Audio Scan Service failed.
+     * <p>
+     * Example solution: Change parameters and try again. If error persists, the app can report
+     * telemetry and/or log the error in a bugreport.
+     *
+     * @hide
+     */
+    public static final int ERROR_LE_AUDIO_BROADCAST_AUDIO_SCAN_SERVICE_DISCONNECT_FAILED = 1113;
+
+    /**
+     * Indicates that enabling LE Audio Broadcast encryption failed
+     * <p>
+     * Example solution: Change parameters and try again. If error persists, the app can report
+     * telemetry and/or log the error in a bugreport.
+     *
+     * @hide
+     */
+    public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_ENABLE_ENCRYPTION_FAILED = 1114;
+
+    /**
+     * Indicates that disabling LE Audio Broadcast encryption failed
+     * <p>
+     * Example solution: Change parameters and try again. If error persists, the app can report
+     * telemetry and/or log the error in a bugreport.
+     *
+     * @hide
+     */
+    public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_DISABLE_ENCRYPTION_FAILED = 1115;
+
+    /**
      * Indicates that an unknown error has occurred has occurred.
      */
     public static final int ERROR_UNKNOWN = Integer.MAX_VALUE;
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 2c4ff58..84c9fa9 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -409,7 +409,7 @@
 
     /**
      * Value for {@link #flags}: {@code true} if the application may use cleartext network traffic
-     * (e.g., HTTP rather than HTTPS; WebSockets rather than WebSockets Secure; XMPP, IMAP, STMP
+     * (e.g., HTTP rather than HTTPS; WebSockets rather than WebSockets Secure; XMPP, IMAP, SMTP
      * without STARTTLS or TLS). If {@code false}, the app declares that it does not intend to use
      * cleartext network traffic, in which case platform components (e.g., HTTP stacks,
      * {@code DownloadManager}, {@code MediaPlayer}) will refuse app's requests to use cleartext
diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java
index 6b33e4f..50a6bfc 100644
--- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java
+++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java
@@ -29,6 +29,7 @@
 import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.vcn.util.PersistableBundleUtils;
 
 import java.util.ArrayList;
@@ -200,6 +201,15 @@
                 && mRequireOpportunistic == rhs.mRequireOpportunistic;
     }
 
+    /** @hide */
+    @Override
+    void dumpTransportSpecificFields(IndentingPrintWriter pw) {
+        pw.println("mAllowedNetworkPlmnIds: " + mAllowedNetworkPlmnIds.toString());
+        pw.println("mAllowedSpecificCarrierIds: " + mAllowedSpecificCarrierIds.toString());
+        pw.println("mAllowRoaming: " + mAllowRoaming);
+        pw.println("mRequireOpportunistic: " + mRequireOpportunistic);
+    }
+
     /** This class is used to incrementally build WifiNetworkPriority objects. */
     public static class Builder extends VcnUnderlyingNetworkPriority.Builder<Builder> {
         @NonNull private final Set<String> mAllowedNetworkPlmnIds = new ArraySet<>();
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index de4ada2..31e38c0 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -160,7 +160,9 @@
                 TimeUnit.MINUTES.toMillis(15)
             };
 
-    private static final LinkedHashSet<VcnUnderlyingNetworkPriority>
+    /** @hide */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static final LinkedHashSet<VcnUnderlyingNetworkPriority>
             DEFAULT_UNDERLYING_NETWORK_PRIORITIES = new LinkedHashSet<>();
 
     static {
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java
index 82f6ae7..551f757 100644
--- a/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java
@@ -21,8 +21,10 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.PersistableBundle;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
@@ -37,10 +39,17 @@
     /** @hide */
     protected static final int NETWORK_PRIORITY_TYPE_CELL = 2;
 
-    /** Denotes that network quality needs to be OK */
-    public static final int NETWORK_QUALITY_OK = 10000;
     /** Denotes that any network quality is acceptable */
-    public static final int NETWORK_QUALITY_ANY = Integer.MAX_VALUE;
+    public static final int NETWORK_QUALITY_ANY = 0;
+    /** Denotes that network quality needs to be OK */
+    public static final int NETWORK_QUALITY_OK = 100000;
+
+    private static final SparseArray<String> NETWORK_QUALITY_TO_STRING_MAP = new SparseArray<>();
+
+    static {
+        NETWORK_QUALITY_TO_STRING_MAP.put(NETWORK_QUALITY_ANY, "NETWORK_QUALITY_ANY");
+        NETWORK_QUALITY_TO_STRING_MAP.put(NETWORK_QUALITY_OK, "NETWORK_QUALITY_OK");
+    }
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -125,6 +134,28 @@
                 && mAllowMetered == rhs.mAllowMetered;
     }
 
+    /** @hide */
+    abstract void dumpTransportSpecificFields(IndentingPrintWriter pw);
+
+    /**
+     * Dumps the state of this record for logging and debugging purposes.
+     *
+     * @hide
+     */
+    public void dump(IndentingPrintWriter pw) {
+        pw.println(this.getClass().getSimpleName() + ":");
+        pw.increaseIndent();
+
+        pw.println(
+                "mNetworkQuality: "
+                        + NETWORK_QUALITY_TO_STRING_MAP.get(
+                                mNetworkQuality, "Invalid value " + mNetworkQuality));
+        pw.println("mAllowMetered: " + mAllowMetered);
+        dumpTransportSpecificFields(pw);
+
+        pw.decreaseIndent();
+    }
+
     /** Retrieve the required network quality. */
     @NetworkQuality
     public int getNetworkQuality() {
diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java
index fc7e7e2..2ba9169 100644
--- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java
+++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java
@@ -22,6 +22,7 @@
 import android.os.PersistableBundle;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
 
 import java.util.Objects;
 
@@ -81,6 +82,12 @@
         return mSsid == rhs.mSsid;
     }
 
+    /** @hide */
+    @Override
+    void dumpTransportSpecificFields(IndentingPrintWriter pw) {
+        pw.println("mSsid: " + mSsid);
+    }
+
     /** Retrieve the required SSID, or {@code null} if there is no requirement on SSID. */
     @Nullable
     public String getSsid() {
diff --git a/core/java/android/os/AppZygote.java b/core/java/android/os/AppZygote.java
index 74b814e..c8b4226e 100644
--- a/core/java/android/os/AppZygote.java
+++ b/core/java/android/os/AppZygote.java
@@ -45,6 +45,8 @@
     // Last UID/GID of the range the AppZygote can setuid()/setgid() to
     private final int mZygoteUidGidMax;
 
+    private final int mZygoteRuntimeFlags;
+
     private final Object mLock = new Object();
 
     /**
@@ -56,11 +58,13 @@
 
     private final ApplicationInfo mAppInfo;
 
-    public AppZygote(ApplicationInfo appInfo, int zygoteUid, int uidGidMin, int uidGidMax) {
+    public AppZygote(ApplicationInfo appInfo, int zygoteUid, int uidGidMin, int uidGidMax,
+            int runtimeFlags) {
         mAppInfo = appInfo;
         mZygoteUid = zygoteUid;
         mZygoteUidGidMin = uidGidMin;
         mZygoteUidGidMax = uidGidMax;
+        mZygoteRuntimeFlags = runtimeFlags;
     }
 
     /**
@@ -110,7 +114,7 @@
                     mZygoteUid,
                     mZygoteUid,
                     null,  // gids
-                    0,  // runtimeFlags
+                    mZygoteRuntimeFlags,  // runtimeFlags
                     "app_zygote",  // seInfo
                     abi,  // abi
                     abi, // acceptedAbiList
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 3661ee0..8894c85 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -4430,6 +4430,9 @@
      * @return the Serializable object, or null if the Serializable name
      * wasn't found in the parcel.
      *
+     * Unlike {@link #readSerializable(ClassLoader, Class)}, it uses the nearest valid class loader
+     * up the execution stack to instantiate the Serializable object.
+     *
      * @deprecated Use the type-safer version {@link #readSerializable(ClassLoader, Class)} starting
      *       from Android {@link Build.VERSION_CODES#TIRAMISU}.
      */
@@ -4440,9 +4443,11 @@
     }
 
     /**
-     * Same as {@link #readSerializable()} but accepts {@code loader} parameter
-     * as the primary classLoader for resolving the Serializable class; and {@code clazz} parameter
-     * as the required type.
+     * Same as {@link #readSerializable()} but accepts {@code loader} and {@code clazz} parameters.
+     *
+     * @param loader A ClassLoader from which to instantiate the Serializable object,
+     * or null for the default class loader.
+     * @param clazz The type of the object expected.
      *
      * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
      * is not an instance of that class or any of its children class or there there was an error
@@ -4452,7 +4457,8 @@
     public <T extends Serializable> T readSerializable(@Nullable ClassLoader loader,
             @NonNull Class<T> clazz) {
         Objects.requireNonNull(clazz);
-        return readSerializableInternal(loader, clazz);
+        return readSerializableInternal(
+                loader == null ? getClass().getClassLoader() : loader, clazz);
     }
 
     /**
diff --git a/core/java/android/util/LocalLog.java b/core/java/android/util/LocalLog.java
index 8c4dcb3..cd077e1 100644
--- a/core/java/android/util/LocalLog.java
+++ b/core/java/android/util/LocalLog.java
@@ -22,6 +22,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.time.Duration;
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.util.ArrayDeque;
@@ -63,7 +64,8 @@
         if (mUseLocalTimestamps) {
             logLine = LocalDateTime.now() + " - " + msg;
         } else {
-            logLine = SystemClock.elapsedRealtime() + " / " + Instant.now() + " - " + msg;
+            logLine = Duration.ofMillis(SystemClock.elapsedRealtime())
+                    + " / " + Instant.now() + " - " + msg;
         }
         append(logLine);
     }
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index b0cf5dc..ae9d716 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -56,6 +56,7 @@
 import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -95,6 +96,9 @@
     // property for runtime configuration differentiation in vendor
     private static final String VENDOR_SKU_PROPERTY = "ro.boot.product.vendor.sku";
 
+    private static final ArrayMap<String, ArraySet<String>> EMPTY_PERMISSIONS =
+            new ArrayMap<>();
+
     // Group-ids that are given to all packages as read from etc/permissions/*.xml.
     int[] mGlobalGids = EmptyArray.INT;
 
@@ -224,6 +228,11 @@
     final ArrayMap<String, ArraySet<String>> mSystemExtPrivAppPermissions = new ArrayMap<>();
     final ArrayMap<String, ArraySet<String>> mSystemExtPrivAppDenyPermissions = new ArrayMap<>();
 
+    final ArrayMap<String, ArrayMap<String, ArraySet<String>>> mApexPrivAppPermissions =
+            new ArrayMap<>();
+    final ArrayMap<String, ArrayMap<String, ArraySet<String>>> mApexPrivAppDenyPermissions =
+            new ArrayMap<>();
+
     final ArrayMap<String, ArrayMap<String, Boolean>> mOemPermissions = new ArrayMap<>();
 
     // Allowed associations between applications.  If there are any entries
@@ -360,6 +369,18 @@
         return mPrivAppDenyPermissions.get(packageName);
     }
 
+    /** Get privapp permission allowlist for an apk-in-apex. */
+    public ArraySet<String> getApexPrivAppPermissions(String module, String packageName) {
+        return mApexPrivAppPermissions.getOrDefault(module, EMPTY_PERMISSIONS)
+                .get(packageName);
+    }
+
+    /** Get privapp permissions denylist for an apk-in-apex. */
+    public ArraySet<String> getApexPrivAppDenyPermissions(String module, String packageName) {
+        return mApexPrivAppDenyPermissions.getOrDefault(module, EMPTY_PERMISSIONS)
+                .get(packageName);
+    }
+
     public ArraySet<String> getVendorPrivAppPermissions(String packageName) {
         return mVendorPrivAppPermissions.get(packageName);
     }
@@ -573,8 +594,8 @@
         if (!isSystemProcess()) {
             return;
         }
-        // Read configuration of features and libs from apex module.
-        int apexPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES;
+        // Read configuration of features, libs and priv-app permissions from apex module.
+        int apexPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS;
         // TODO: Use a solid way to filter apex module folders?
         for (File f: FileUtils.listFilesOrEmpty(Environment.getApexDirectory())) {
             if (f.isFile() || f.getPath().contains("@")) {
@@ -1040,10 +1061,10 @@
                     } break;
                     case "privapp-permissions": {
                         if (allowPrivappPermissions) {
-                            // privapp permissions from system, vendor, product and system_ext
-                            // partitions are stored separately. This is to prevent xml files in
-                            // the vendor partition from granting permissions to priv apps in the
-                            // system partition and vice versa.
+                            // privapp permissions from system, apex, vendor, product and
+                            // system_ext partitions are stored separately. This is to
+                            // prevent xml files in the vendor partition from granting
+                            // permissions to priv apps in the system partition and vice versa.
                             boolean vendor = permFile.toPath().startsWith(
                                     Environment.getVendorDirectory().toPath() + "/")
                                     || permFile.toPath().startsWith(
@@ -1052,6 +1073,8 @@
                                     Environment.getProductDirectory().toPath() + "/");
                             boolean systemExt = permFile.toPath().startsWith(
                                     Environment.getSystemExtDirectory().toPath() + "/");
+                            boolean apex = permFile.toPath().startsWith(
+                                    Environment.getApexDirectory().toPath() + "/");
                             if (vendor) {
                                 readPrivAppPermissions(parser, mVendorPrivAppPermissions,
                                         mVendorPrivAppDenyPermissions);
@@ -1061,6 +1084,8 @@
                             } else if (systemExt) {
                                 readPrivAppPermissions(parser, mSystemExtPrivAppPermissions,
                                         mSystemExtPrivAppDenyPermissions);
+                            } else if (apex) {
+                                readApexPrivAppPermissions(parser, permFile);
                             } else {
                                 readPrivAppPermissions(parser, mPrivAppPermissions,
                                         mPrivAppDenyPermissions);
@@ -1616,6 +1641,43 @@
         }
     }
 
+
+    /**
+     * Returns the module name for a file in the apex module's partition.
+     */
+    private String getApexModuleNameFromFilePath(Path path) {
+        final Path apexDirectoryPath = Environment.getApexDirectory().toPath();
+        if (!path.startsWith(apexDirectoryPath)) {
+            throw new IllegalArgumentException("File " + path + " is not part of an APEX.");
+        }
+        // File must be in <apex_directory>/<module_name>/[extra_paths/]<xml_file>
+        if (path.getNameCount() <= (apexDirectoryPath.getNameCount() + 1)) {
+            throw new IllegalArgumentException("File " + path + " is in the APEX partition,"
+                                                + " but not inside a module.");
+        }
+        return path.getName(apexDirectoryPath.getNameCount()).toString();
+    }
+
+    private void readApexPrivAppPermissions(XmlPullParser parser, File permFile)
+            throws IOException, XmlPullParserException {
+        final String moduleName = getApexModuleNameFromFilePath(permFile.toPath());
+        final ArrayMap<String, ArraySet<String>> privAppPermissions;
+        if (mApexPrivAppPermissions.containsKey(moduleName)) {
+            privAppPermissions = mApexPrivAppPermissions.get(moduleName);
+        } else {
+            privAppPermissions = new ArrayMap<>();
+            mApexPrivAppPermissions.put(moduleName, privAppPermissions);
+        }
+        final ArrayMap<String, ArraySet<String>> privAppDenyPermissions;
+        if (mApexPrivAppDenyPermissions.containsKey(moduleName)) {
+            privAppDenyPermissions = mApexPrivAppDenyPermissions.get(moduleName);
+        } else {
+            privAppDenyPermissions = new ArrayMap<>();
+            mApexPrivAppDenyPermissions.put(moduleName, privAppDenyPermissions);
+        }
+        readPrivAppPermissions(parser, privAppPermissions, privAppDenyPermissions);
+    }
+
     private static boolean isSystemProcess() {
         return Process.myUid() == Process.SYSTEM_UID;
     }
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index d052d70..f4acfaa 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -547,7 +547,7 @@
     <attr name="allowTaskReparenting" format="boolean" />
 
     <!-- Declare that this application may use cleartext traffic, such as HTTP rather than HTTPS;
-         WebSockets rather than WebSockets Secure; XMPP, IMAP, STMP without STARTTLS or TLS.
+         WebSockets rather than WebSockets Secure; XMPP, IMAP, SMTP without STARTTLS or TLS.
          Defaults to true. If set to false {@code false}, the application declares that it does not
          intend to use cleartext network traffic, in which case platform components (e.g. HTTP
          stacks, {@code DownloadManager}, {@code MediaPlayer}) will refuse applications's requests
@@ -1762,7 +1762,7 @@
         <!-- @deprecated replaced by setting appCategory attribute to "game" -->
         <attr name="isGame" />
         <!-- Declare that this application may use cleartext traffic, such as HTTP rather than
-             HTTPS; WebSockets rather than WebSockets Secure; XMPP, IMAP, STMP without STARTTLS or
+             HTTPS; WebSockets rather than WebSockets Secure; XMPP, IMAP, SMTP without STARTTLS or
              TLS). Defaults to true. If set to false {@code false}, the application declares that it
              does not intend to use cleartext network traffic, in which case platform components
              (e.g. HTTP stacks, {@code DownloadManager}, {@code MediaPlayer}) will refuse
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecConfigTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecConfigTest.java
index 59b4665..bd55426 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecConfigTest.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecConfigTest.java
@@ -17,7 +17,6 @@
 package android.bluetooth;
 
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
 
 import junit.framework.TestCase;
 
@@ -34,7 +33,6 @@
         BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
         BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
         BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
-        BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX,
         BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID,
     };
     private static final int[] kCodecPriorityArray = new int[] {
@@ -168,20 +166,11 @@
             long codec_specific3 = selectCodecSpecific3(config_id);
             long codec_specific4 = selectCodecSpecific4(config_id);
 
-            BluetoothCodecConfig bcc = new BluetoothCodecConfig(codec_type, codec_priority,
+            BluetoothCodecConfig bcc = buildBluetoothCodecConfig(codec_type, codec_priority,
                                                                 sample_rate, bits_per_sample,
                                                                 channel_mode, codec_specific1,
                                                                 codec_specific2, codec_specific3,
                                                                 codec_specific4);
-            if (sample_rate == BluetoothCodecConfig.SAMPLE_RATE_NONE) {
-                assertFalse(bcc.isValid());
-            } else if (bits_per_sample == BluetoothCodecConfig.BITS_PER_SAMPLE_NONE) {
-                assertFalse(bcc.isValid());
-            } else if (channel_mode == BluetoothCodecConfig.CHANNEL_MODE_NONE) {
-                assertFalse(bcc.isValid());
-            } else {
-                assertTrue(bcc.isValid());
-            }
 
             if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC) {
                 assertTrue(bcc.isMandatoryCodec());
@@ -204,10 +193,6 @@
             if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC) {
                 assertEquals("LDAC", bcc.getCodecName());
             }
-            if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX) {
-                assertEquals("UNKNOWN CODEC(" + BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX + ")",
-                             bcc.getCodecName());
-            }
             if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID) {
                 assertEquals("INVALID CODEC", bcc.getCodecName());
             }
@@ -227,7 +212,7 @@
     @SmallTest
     public void testBluetoothCodecConfig_equals() {
         BluetoothCodecConfig bcc1 =
-            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                      BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                      BluetoothCodecConfig.SAMPLE_RATE_44100,
                                      BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -235,7 +220,7 @@
                                      1000, 2000, 3000, 4000);
 
         BluetoothCodecConfig bcc2_same =
-            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                      BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                      BluetoothCodecConfig.SAMPLE_RATE_44100,
                                      BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -244,7 +229,7 @@
         assertTrue(bcc1.equals(bcc2_same));
 
         BluetoothCodecConfig bcc3_codec_type =
-            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
                                      BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                      BluetoothCodecConfig.SAMPLE_RATE_44100,
                                      BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -253,7 +238,7 @@
         assertFalse(bcc1.equals(bcc3_codec_type));
 
         BluetoothCodecConfig bcc4_codec_priority =
-            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                      BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST,
                                      BluetoothCodecConfig.SAMPLE_RATE_44100,
                                      BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -262,7 +247,7 @@
         assertFalse(bcc1.equals(bcc4_codec_priority));
 
         BluetoothCodecConfig bcc5_sample_rate =
-            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                      BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                      BluetoothCodecConfig.SAMPLE_RATE_48000,
                                      BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -271,7 +256,7 @@
         assertFalse(bcc1.equals(bcc5_sample_rate));
 
         BluetoothCodecConfig bcc6_bits_per_sample =
-            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                      BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                      BluetoothCodecConfig.SAMPLE_RATE_44100,
                                      BluetoothCodecConfig.BITS_PER_SAMPLE_24,
@@ -280,7 +265,7 @@
         assertFalse(bcc1.equals(bcc6_bits_per_sample));
 
         BluetoothCodecConfig bcc7_channel_mode =
-            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                      BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                      BluetoothCodecConfig.SAMPLE_RATE_44100,
                                      BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -289,7 +274,7 @@
         assertFalse(bcc1.equals(bcc7_channel_mode));
 
         BluetoothCodecConfig bcc8_codec_specific1 =
-            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                      BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                      BluetoothCodecConfig.SAMPLE_RATE_44100,
                                      BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -298,7 +283,7 @@
         assertFalse(bcc1.equals(bcc8_codec_specific1));
 
         BluetoothCodecConfig bcc9_codec_specific2 =
-            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                      BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                      BluetoothCodecConfig.SAMPLE_RATE_44100,
                                      BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -307,7 +292,7 @@
         assertFalse(bcc1.equals(bcc9_codec_specific2));
 
         BluetoothCodecConfig bcc10_codec_specific3 =
-            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                      BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                      BluetoothCodecConfig.SAMPLE_RATE_44100,
                                      BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -316,7 +301,7 @@
         assertFalse(bcc1.equals(bcc10_codec_specific3));
 
         BluetoothCodecConfig bcc11_codec_specific4 =
-            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                      BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                      BluetoothCodecConfig.SAMPLE_RATE_44100,
                                      BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -324,4 +309,21 @@
                                      1000, 2000, 3000, 4004);
         assertFalse(bcc1.equals(bcc11_codec_specific4));
     }
+
+    private BluetoothCodecConfig buildBluetoothCodecConfig(int sourceCodecType,
+            int codecPriority, int sampleRate, int bitsPerSample, int channelMode,
+            long codecSpecific1, long codecSpecific2, long codecSpecific3, long codecSpecific4) {
+        return new BluetoothCodecConfig.Builder()
+                    .setCodecType(sourceCodecType)
+                    .setCodecPriority(codecPriority)
+                    .setSampleRate(sampleRate)
+                    .setBitsPerSample(bitsPerSample)
+                    .setChannelMode(channelMode)
+                    .setCodecSpecific1(codecSpecific1)
+                    .setCodecSpecific2(codecSpecific2)
+                    .setCodecSpecific3(codecSpecific3)
+                    .setCodecSpecific4(codecSpecific4)
+                    .build();
+
+    }
 }
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecStatusTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecStatusTest.java
index 83bf2ed..1cb2dca 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecStatusTest.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecStatusTest.java
@@ -17,13 +17,13 @@
 package android.bluetooth;
 
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
-
-import java.util.Arrays;
-import java.util.Objects;
 
 import junit.framework.TestCase;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
 /**
  * Unit test cases for {@link BluetoothCodecStatus}.
  * <p>
@@ -34,7 +34,7 @@
 
     // Codec configs: A and B are same; C is different
     private static final BluetoothCodecConfig config_A =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -42,7 +42,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig config_B =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -50,7 +50,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig config_C =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -59,7 +59,7 @@
 
     // Local capabilities: A and B are same; C is different
     private static final BluetoothCodecConfig local_capability1_A =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -69,7 +69,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability1_B =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -79,7 +79,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability1_C =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -89,7 +89,7 @@
 
 
     private static final BluetoothCodecConfig local_capability2_A =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -99,7 +99,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability2_B =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -109,7 +109,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability2_C =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -118,7 +118,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability3_A =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -128,7 +128,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability3_B =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -138,7 +138,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability3_C =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -147,7 +147,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability4_A =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -157,7 +157,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability4_B =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -167,7 +167,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability4_C =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -176,7 +176,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability5_A =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000 |
@@ -190,7 +190,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability5_B =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000 |
@@ -204,7 +204,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability5_C =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000 |
@@ -219,7 +219,7 @@
 
     // Selectable capabilities: A and B are same; C is different
     private static final BluetoothCodecConfig selectable_capability1_A =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -228,7 +228,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability1_B =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -237,7 +237,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability1_C =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -245,7 +245,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability2_A =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -254,7 +254,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability2_B =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -263,7 +263,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability2_C =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -271,7 +271,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability3_A =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -280,7 +280,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability3_B =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -289,7 +289,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability3_C =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -297,7 +297,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability4_A =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_24,
@@ -306,7 +306,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability4_B =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_24,
@@ -315,7 +315,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability4_C =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_24,
@@ -323,7 +323,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability5_A =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000 |
@@ -337,7 +337,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability5_B =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000 |
@@ -351,7 +351,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability5_C =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000 |
@@ -363,79 +363,87 @@
                                  BluetoothCodecConfig.CHANNEL_MODE_STEREO,
                                  1000, 2000, 3000, 4000);
 
-    private static final BluetoothCodecConfig[] local_capability_A = {
-        local_capability1_A,
-        local_capability2_A,
-        local_capability3_A,
-        local_capability4_A,
-        local_capability5_A,
-    };
+    private static final List<BluetoothCodecConfig> LOCAL_CAPABILITY_A =
+            new ArrayList() {{
+                    add(local_capability1_A);
+                    add(local_capability2_A);
+                    add(local_capability3_A);
+                    add(local_capability4_A);
+                    add(local_capability5_A);
+            }};
 
-    private static final BluetoothCodecConfig[] local_capability_B = {
-        local_capability1_B,
-        local_capability2_B,
-        local_capability3_B,
-        local_capability4_B,
-        local_capability5_B,
-    };
+    private static final List<BluetoothCodecConfig> LOCAL_CAPABILITY_B =
+            new ArrayList() {{
+                    add(local_capability1_B);
+                    add(local_capability2_B);
+                    add(local_capability3_B);
+                    add(local_capability4_B);
+                    add(local_capability5_B);
+            }};
 
-    private static final BluetoothCodecConfig[] local_capability_B_reordered = {
-        local_capability5_B,
-        local_capability4_B,
-        local_capability2_B,
-        local_capability3_B,
-        local_capability1_B,
-    };
+    private static final List<BluetoothCodecConfig> LOCAL_CAPABILITY_B_REORDERED =
+            new ArrayList() {{
+                    add(local_capability5_B);
+                    add(local_capability4_B);
+                    add(local_capability2_B);
+                    add(local_capability3_B);
+                    add(local_capability1_B);
+            }};
 
-    private static final BluetoothCodecConfig[] local_capability_C = {
-        local_capability1_C,
-        local_capability2_C,
-        local_capability3_C,
-        local_capability4_C,
-        local_capability5_C,
-    };
+    private static final List<BluetoothCodecConfig> LOCAL_CAPABILITY_C =
+            new ArrayList() {{
+                    add(local_capability1_C);
+                    add(local_capability2_C);
+                    add(local_capability3_C);
+                    add(local_capability4_C);
+                    add(local_capability5_C);
+            }};
 
-    private static final BluetoothCodecConfig[] selectable_capability_A = {
-        selectable_capability1_A,
-        selectable_capability2_A,
-        selectable_capability3_A,
-        selectable_capability4_A,
-        selectable_capability5_A,
-    };
+    private static final List<BluetoothCodecConfig> SELECTABLE_CAPABILITY_A =
+            new ArrayList() {{
+                    add(selectable_capability1_A);
+                    add(selectable_capability2_A);
+                    add(selectable_capability3_A);
+                    add(selectable_capability4_A);
+                    add(selectable_capability5_A);
+            }};
 
-    private static final BluetoothCodecConfig[] selectable_capability_B = {
-        selectable_capability1_B,
-        selectable_capability2_B,
-        selectable_capability3_B,
-        selectable_capability4_B,
-        selectable_capability5_B,
-    };
+    private static final List<BluetoothCodecConfig> SELECTABLE_CAPABILITY_B =
+            new ArrayList() {{
+                    add(selectable_capability1_B);
+                    add(selectable_capability2_B);
+                    add(selectable_capability3_B);
+                    add(selectable_capability4_B);
+                    add(selectable_capability5_B);
+            }};
 
-    private static final BluetoothCodecConfig[] selectable_capability_B_reordered = {
-        selectable_capability5_B,
-        selectable_capability4_B,
-        selectable_capability2_B,
-        selectable_capability3_B,
-        selectable_capability1_B,
-    };
+    private static final List<BluetoothCodecConfig> SELECTABLE_CAPABILITY_B_REORDERED =
+            new ArrayList() {{
+                    add(selectable_capability5_B);
+                    add(selectable_capability4_B);
+                    add(selectable_capability2_B);
+                    add(selectable_capability3_B);
+                    add(selectable_capability1_B);
+            }};
 
-    private static final BluetoothCodecConfig[] selectable_capability_C = {
-        selectable_capability1_C,
-        selectable_capability2_C,
-        selectable_capability3_C,
-        selectable_capability4_C,
-        selectable_capability5_C,
-    };
+    private static final List<BluetoothCodecConfig> SELECTABLE_CAPABILITY_C =
+            new ArrayList() {{
+                    add(selectable_capability1_C);
+                    add(selectable_capability2_C);
+                    add(selectable_capability3_C);
+                    add(selectable_capability4_C);
+                    add(selectable_capability5_C);
+            }};
 
     private static final BluetoothCodecStatus bcs_A =
-        new BluetoothCodecStatus(config_A, local_capability_A, selectable_capability_A);
+            new BluetoothCodecStatus(config_A, LOCAL_CAPABILITY_A, SELECTABLE_CAPABILITY_A);
     private static final BluetoothCodecStatus bcs_B =
-        new BluetoothCodecStatus(config_B, local_capability_B, selectable_capability_B);
+            new BluetoothCodecStatus(config_B, LOCAL_CAPABILITY_B, SELECTABLE_CAPABILITY_B);
     private static final BluetoothCodecStatus bcs_B_reordered =
-        new BluetoothCodecStatus(config_B, local_capability_B_reordered,
-                                 selectable_capability_B_reordered);
+            new BluetoothCodecStatus(config_B, LOCAL_CAPABILITY_B_REORDERED,
+                                 SELECTABLE_CAPABILITY_B_REORDERED);
     private static final BluetoothCodecStatus bcs_C =
-        new BluetoothCodecStatus(config_C, local_capability_C, selectable_capability_C);
+            new BluetoothCodecStatus(config_C, LOCAL_CAPABILITY_C, SELECTABLE_CAPABILITY_C);
 
     @SmallTest
     public void testBluetoothCodecStatus_get_methods() {
@@ -444,16 +452,16 @@
         assertTrue(Objects.equals(bcs_A.getCodecConfig(), config_B));
         assertFalse(Objects.equals(bcs_A.getCodecConfig(), config_C));
 
-        assertTrue(Arrays.equals(bcs_A.getCodecsLocalCapabilities(), local_capability_A));
-        assertTrue(Arrays.equals(bcs_A.getCodecsLocalCapabilities(), local_capability_B));
-        assertFalse(Arrays.equals(bcs_A.getCodecsLocalCapabilities(), local_capability_C));
+        assertTrue(bcs_A.getCodecsLocalCapabilities().equals(LOCAL_CAPABILITY_A));
+        assertTrue(bcs_A.getCodecsLocalCapabilities().equals(LOCAL_CAPABILITY_B));
+        assertFalse(bcs_A.getCodecsLocalCapabilities().equals(LOCAL_CAPABILITY_C));
 
-        assertTrue(Arrays.equals(bcs_A.getCodecsSelectableCapabilities(),
-                                 selectable_capability_A));
-        assertTrue(Arrays.equals(bcs_A.getCodecsSelectableCapabilities(),
-                                  selectable_capability_B));
-        assertFalse(Arrays.equals(bcs_A.getCodecsSelectableCapabilities(),
-                                  selectable_capability_C));
+        assertTrue(bcs_A.getCodecsSelectableCapabilities()
+                                 .equals(SELECTABLE_CAPABILITY_A));
+        assertTrue(bcs_A.getCodecsSelectableCapabilities()
+                                  .equals(SELECTABLE_CAPABILITY_B));
+        assertFalse(bcs_A.getCodecsSelectableCapabilities()
+                                  .equals(SELECTABLE_CAPABILITY_C));
     }
 
     @SmallTest
@@ -465,4 +473,21 @@
         assertFalse(bcs_A.equals(bcs_C));
         assertFalse(bcs_C.equals(bcs_A));
     }
+
+    private static BluetoothCodecConfig buildBluetoothCodecConfig(int sourceCodecType,
+            int codecPriority, int sampleRate, int bitsPerSample, int channelMode,
+            long codecSpecific1, long codecSpecific2, long codecSpecific3, long codecSpecific4) {
+        return new BluetoothCodecConfig.Builder()
+                    .setCodecType(sourceCodecType)
+                    .setCodecPriority(codecPriority)
+                    .setSampleRate(sampleRate)
+                    .setBitsPerSample(bitsPerSample)
+                    .setChannelMode(channelMode)
+                    .setCodecSpecific1(codecSpecific1)
+                    .setCodecSpecific2(codecSpecific2)
+                    .setCodecSpecific3(codecSpecific3)
+                    .setCodecSpecific4(codecSpecific4)
+                    .build();
+
+    }
 }
diff --git a/packages/Nsd/service/src/com/android/server/NativeDaemonConnector.java b/packages/Nsd/service/src/com/android/server/NativeDaemonConnector.java
index 02475dd..ec8d779 100644
--- a/packages/Nsd/service/src/com/android/server/NativeDaemonConnector.java
+++ b/packages/Nsd/service/src/com/android/server/NativeDaemonConnector.java
@@ -20,16 +20,15 @@
 import android.net.LocalSocketAddress;
 import android.os.Build;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.util.LocalLog;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.power.ShutdownThread;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -83,13 +82,6 @@
 
     NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
             int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl) {
-        this(callbacks, socket, responseQueueSize, logTag, maxLogSize, wl,
-                FgThread.get().getLooper());
-    }
-
-    NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
-            int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl,
-            Looper looper) {
         mCallbacks = callbacks;
         mSocket = socket;
         mResponseQueue = new ResponseQueue(responseQueueSize);
@@ -97,10 +89,12 @@
         if (mWakeLock != null) {
             mWakeLock.setReferenceCounted(true);
         }
-        mLooper = looper;
         mSequenceNumber = new AtomicInteger(0);
         TAG = logTag != null ? logTag : "NativeDaemonConnector";
         mLocalLog = new LocalLog(maxLogSize);
+        final HandlerThread thread = new HandlerThread(TAG);
+        thread.start();
+        mLooper = thread.getLooper();
     }
 
     /**
@@ -135,23 +129,15 @@
         mCallbackHandler = new Handler(mLooper, this);
 
         while (true) {
-            if (isShuttingDown()) break;
             try {
                 listenToSocket();
             } catch (Exception e) {
                 loge("Error in NativeDaemonConnector: " + e);
-                if (isShuttingDown()) break;
                 SystemClock.sleep(5000);
             }
         }
     }
 
-    private static boolean isShuttingDown() {
-        String shutdownAct = SystemProperties.get(
-            ShutdownThread.SHUTDOWN_ACTION_PROPERTY, "");
-        return shutdownAct != null && shutdownAct.length() > 0;
-    }
-
     @Override
     public boolean handleMessage(Message msg) {
         final String event = (String) msg.obj;
diff --git a/packages/Nsd/service/src/com/android/server/NsdService.java b/packages/Nsd/service/src/com/android/server/NsdService.java
index 76ecea7..497107d 100644
--- a/packages/Nsd/service/src/com/android/server/NsdService.java
+++ b/packages/Nsd/service/src/com/android/server/NsdService.java
@@ -16,12 +16,9 @@
 
 package com.android.server;
 
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.database.ContentObserver;
-import android.net.Uri;
 import android.net.nsd.INsdManager;
 import android.net.nsd.INsdManagerCallback;
 import android.net.nsd.INsdServiceConnector;
@@ -33,7 +30,6 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.util.Base64;
 import android.util.Log;
 import android.util.Pair;
@@ -66,7 +62,6 @@
     private static final long CLEANUP_DELAY_MS = 10000;
 
     private final Context mContext;
-    private final NsdSettings mNsdSettings;
     private final NsdStateMachine mNsdStateMachine;
     private final DaemonConnection mDaemon;
     private final NativeCallbackReceiver mDaemonCallback;
@@ -121,30 +116,14 @@
             this.removeMessages(NsdManager.DAEMON_CLEANUP);
         }
 
-        /**
-         * Observes the NSD on/off setting, and takes action when changed.
-         */
-        private void registerForNsdSetting() {
-            final ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
-                @Override
-                public void onChange(boolean selfChange) {
-                    notifyEnabled(isNsdEnabled());
-                }
-            };
-
-            final Uri uri = Settings.Global.getUriFor(Settings.Global.NSD_ON);
-            mNsdSettings.registerContentObserver(uri, contentObserver);
-        }
-
         NsdStateMachine(String name, Handler handler) {
             super(name, handler);
             addState(mDefaultState);
                 addState(mDisabledState, mDefaultState);
                 addState(mEnabledState, mDefaultState);
-            State initialState = isNsdEnabled() ? mEnabledState : mDisabledState;
+            State initialState = mEnabledState;
             setInitialState(initialState);
             setLogRecSize(25);
-            registerForNsdSetting();
         }
 
         class DefaultState extends State {
@@ -580,11 +559,9 @@
     }
 
     @VisibleForTesting
-    NsdService(Context ctx, NsdSettings settings, Handler handler,
-            DaemonConnectionSupplier fn, long cleanupDelayMs) {
+    NsdService(Context ctx, Handler handler, DaemonConnectionSupplier fn, long cleanupDelayMs) {
         mCleanupDelayMs = cleanupDelayMs;
         mContext = ctx;
-        mNsdSettings = settings;
         mNsdStateMachine = new NsdStateMachine(TAG, handler);
         mNsdStateMachine.start();
         mDaemonCallback = new NativeCallbackReceiver();
@@ -592,12 +569,11 @@
     }
 
     public static NsdService create(Context context) throws InterruptedException {
-        NsdSettings settings = NsdSettings.makeDefault(context);
         HandlerThread thread = new HandlerThread(TAG);
         thread.start();
         Handler handler = new Handler(thread.getLooper());
-        NsdService service = new NsdService(context, settings, handler,
-                DaemonConnection::new, CLEANUP_DELAY_MS);
+        NsdService service =
+                new NsdService(context, handler, DaemonConnection::new, CLEANUP_DELAY_MS);
         service.mDaemonCallback.awaitConnection();
         return service;
     }
@@ -669,10 +645,6 @@
         }
     }
 
-    private void notifyEnabled(boolean isEnabled) {
-        mNsdStateMachine.sendMessage(isEnabled ? NsdManager.ENABLE : NsdManager.DISABLE);
-    }
-
     private void sendNsdStateChangeBroadcast(boolean isEnabled) {
         final Intent intent = new Intent(NsdManager.ACTION_NSD_STATE_CHANGED);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
@@ -681,14 +653,6 @@
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
-    private boolean isNsdEnabled() {
-        boolean ret = mNsdSettings.isEnabled();
-        if (DBG) {
-            Log.d(TAG, "Network service discovery is " + (ret ? "enabled" : "disabled"));
-        }
-        return ret;
-    }
-
     private int getUniqueId() {
         if (++mUniqueId == INVALID_ID) return ++mUniqueId;
         return mUniqueId;
@@ -1075,35 +1039,4 @@
             }
         }
     }
-
-    /**
-     * Interface which encapsulates dependencies of NsdService that are hard to mock, hard to
-     * override, or have side effects on global state in unit tests.
-     */
-    @VisibleForTesting
-    public interface NsdSettings {
-        boolean isEnabled();
-        void putEnabledStatus(boolean isEnabled);
-        void registerContentObserver(Uri uri, ContentObserver observer);
-
-        static NsdSettings makeDefault(Context context) {
-            final ContentResolver resolver = context.getContentResolver();
-            return new NsdSettings() {
-                @Override
-                public boolean isEnabled() {
-                    return Settings.Global.getInt(resolver, Settings.Global.NSD_ON, 1) == 1;
-                }
-
-                @Override
-                public void putEnabledStatus(boolean isEnabled) {
-                    Settings.Global.putInt(resolver, Settings.Global.NSD_ON, isEnabled ? 1 : 0);
-                }
-
-                @Override
-                public void registerContentObserver(Uri uri, ContentObserver observer) {
-                    resolver.registerContentObserver(uri, false, observer);
-                }
-            };
-        }
-    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 3c78560..99e3160 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -28,13 +28,16 @@
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.content.Context;
+import android.os.Build;
 import android.os.ParcelUuid;
 import android.util.Log;
 
+import androidx.annotation.RequiresApi;
+
 import com.android.settingslib.R;
 
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 public class A2dpProfile implements LocalBluetoothProfile {
@@ -226,6 +229,10 @@
         return support == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED;
     }
 
+    /**
+     * @return whether high quality audio is enabled or not
+     */
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     public boolean isHighQualityAudioEnabled(BluetoothDevice device) {
         BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice();
         if (bluetoothDevice == null) {
@@ -271,6 +278,13 @@
         }
     }
 
+    /**
+     * Gets the label associated with the codec of a Bluetooth device.
+     *
+     * @param device to get codec label from
+     * @return the label associated with the device codec
+     */
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     public String getHighQualityAudioOptionLabel(BluetoothDevice device) {
         BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice();
         int unknownCodecId = R.string.bluetooth_profile_a2dp_high_quality_unknown_codec;
@@ -280,18 +294,18 @@
         }
         // We want to get the highest priority codec, since that's the one that will be used with
         // this device, and see if it is high-quality (ie non-mandatory).
-        BluetoothCodecConfig[] selectable = null;
+        List<BluetoothCodecConfig> selectable = null;
         if (mService.getCodecStatus(device) != null) {
             selectable = mService.getCodecStatus(device).getCodecsSelectableCapabilities();
             // To get the highest priority, we sort in reverse.
-            Arrays.sort(selectable,
+            Collections.sort(selectable,
                     (a, b) -> {
                         return b.getCodecPriority() - a.getCodecPriority();
                     });
         }
 
-        final BluetoothCodecConfig codecConfig = (selectable == null || selectable.length < 1)
-                ? null : selectable[0];
+        final BluetoothCodecConfig codecConfig = (selectable == null || selectable.size() < 1)
+                ? null : selectable.get(0);
         final int codecType = (codecConfig == null || codecConfig.isMandatoryCodec())
                 ? BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID : codecConfig.getCodecType();
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
index 9afdd43c..f167721 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
@@ -43,6 +43,9 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.shadow.api.Shadow;
 
+import java.util.Arrays;
+import java.util.List;
+
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowBluetoothAdapter.class})
 public class A2dpProfileTest {
@@ -179,7 +182,7 @@
                 BluetoothProfile.STATE_CONNECTED);
         BluetoothCodecStatus status = mock(BluetoothCodecStatus.class);
         BluetoothCodecConfig config = mock(BluetoothCodecConfig.class);
-        BluetoothCodecConfig[] configs = {config};
+        List<BluetoothCodecConfig> configs = Arrays.asList(config);
         when(mBluetoothA2dp.getCodecStatus(mDevice)).thenReturn(status);
         when(status.getCodecsSelectableCapabilities()).thenReturn(configs);
 
@@ -194,7 +197,7 @@
                 BluetoothProfile.STATE_CONNECTED);
         BluetoothCodecStatus status = mock(BluetoothCodecStatus.class);
         BluetoothCodecConfig config = mock(BluetoothCodecConfig.class);
-        BluetoothCodecConfig[] configs = {config};
+        List<BluetoothCodecConfig> configs = Arrays.asList(config);
         when(mBluetoothA2dp.getCodecStatus(mDevice)).thenReturn(status);
         when(status.getCodecsSelectableCapabilities()).thenReturn(configs);
 
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index b77270f..f35afa5 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2238,7 +2238,8 @@
                 // not the calling one.
                 appInfo.packageName = app.getHostingRecord().getDefiningPackageName();
                 appInfo.uid = uid;
-                appZygote = new AppZygote(appInfo, uid, firstUid, lastUid);
+                int runtimeFlags = decideTaggingLevel(app);
+                appZygote = new AppZygote(appInfo, uid, firstUid, lastUid, runtimeFlags);
                 mAppZygotes.put(app.info.processName, uid, appZygote);
                 zygoteProcessList = new ArrayList<ProcessRecord>();
                 mAppZygoteProcesses.put(appZygote, zygoteProcessList);
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index 6ea84ce..ee5bda3 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -171,25 +171,28 @@
     }
 
     private NetworkMetrics getMetricsForNetwork(long timeMs, int netId) {
-        collectPendingMetricsSnapshot(timeMs);
         NetworkMetrics metrics = mNetworkMetrics.get(netId);
-        if (metrics == null) {
-            // TODO: allow to change transport for a given netid.
-            metrics = new NetworkMetrics(netId, getTransports(netId), mConnectTb);
+        final NetworkCapabilities nc = mCallback.getNetworkCapabilities(netId);
+        final long transports = (nc != null) ? BitUtils.packBits(nc.getTransportTypes()) : 0;
+        final boolean forceCollect =
+                (metrics != null && nc != null && metrics.transports != transports);
+        collectPendingMetricsSnapshot(timeMs, forceCollect);
+        if (metrics == null || forceCollect) {
+            metrics = new NetworkMetrics(netId, transports, mConnectTb);
             mNetworkMetrics.put(netId, metrics);
         }
         return metrics;
     }
 
     private NetworkMetricsSnapshot[] getNetworkMetricsSnapshots() {
-        collectPendingMetricsSnapshot(System.currentTimeMillis());
+        collectPendingMetricsSnapshot(System.currentTimeMillis(), false /* forceCollect */);
         return mNetworkMetricsSnapshots.toArray();
     }
 
-    private void collectPendingMetricsSnapshot(long timeMs) {
+    private void collectPendingMetricsSnapshot(long timeMs, boolean forceCollect) {
         // Detects time differences larger than the snapshot collection period.
         // This is robust against clock jumps and long inactivity periods.
-        if (Math.abs(timeMs - mLastSnapshot) <= METRICS_SNAPSHOT_SPAN_MS) {
+        if (!forceCollect && Math.abs(timeMs - mLastSnapshot) <= METRICS_SNAPSHOT_SPAN_MS) {
             return;
         }
         mLastSnapshot = projectSnapshotTime(timeMs);
@@ -394,14 +397,6 @@
         return list;
     }
 
-    private long getTransports(int netId) {
-        final NetworkCapabilities nc = mCallback.getNetworkCapabilities(netId);
-        if (nc == null) {
-            return 0;
-        }
-        return BitUtils.packBits(nc.getTransportTypes());
-    }
-
     /** Helper class for buffering summaries of NetworkMetrics at regular time intervals */
     static class NetworkMetricsSnapshot {
 
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index b45d87f..81106b1 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -566,7 +566,13 @@
         }
     }
 
-    private static final class Data {
+    /**
+     * Container class for all networkpolicy events data.
+     *
+     * Note: This class needs to be public for RingBuffer class to be able to create
+     * new instances of this.
+     */
+    public static final class Data {
         public int type;
         public long timeStamp;
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 1aa80a9..5b4084e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2438,6 +2438,15 @@
         }
     }
 
+    private String getApexPackageNameContainingPackage(String pkg) {
+        ApexManager apexManager = ApexManager.getInstance();
+        return apexManager.getActiveApexPackageNameContainingPackage(pkg);
+    }
+
+    private boolean isApexApp(String pkg) {
+        return getApexPackageNameContainingPackage(pkg) != null;
+    }
+
     private int runGetPrivappPermissions() {
         final String pkg = getNextArg();
         if (pkg == null) {
@@ -2453,6 +2462,9 @@
         } else if (isSystemExtApp(pkg)) {
             privAppPermissions = SystemConfig.getInstance()
                     .getSystemExtPrivAppPermissions(pkg);
+        } else if (isApexApp(pkg)) {
+            privAppPermissions = SystemConfig.getInstance()
+                    .getApexPrivAppPermissions(getApexPackageNameContainingPackage(pkg), pkg);
         } else {
             privAppPermissions = SystemConfig.getInstance().getPrivAppPermissions(pkg);
         }
@@ -2477,6 +2489,9 @@
         } else if (isSystemExtApp(pkg)) {
             privAppPermissions = SystemConfig.getInstance()
                     .getSystemExtPrivAppDenyPermissions(pkg);
+        } else if (isApexApp(pkg)) {
+            privAppPermissions = SystemConfig.getInstance()
+                    .getApexPrivAppDenyPermissions(getApexPackageNameContainingPackage(pkg), pkg);
         } else {
             privAppPermissions = SystemConfig.getInstance().getPrivAppDenyPermissions(pkg);
         }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 7b12709..f733a2e 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -3443,10 +3443,15 @@
             return true;
         }
         final String permissionName = permission.getName();
-        if (isInSystemConfigPrivAppPermissions(pkg, permissionName)) {
+        final ApexManager apexManager = ApexManager.getInstance();
+        final String containingApexPackageName =
+                apexManager.getActiveApexPackageNameContainingPackage(packageName);
+        if (isInSystemConfigPrivAppPermissions(pkg, permissionName,
+                containingApexPackageName)) {
             return true;
         }
-        if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName)) {
+        if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName,
+                containingApexPackageName)) {
             return false;
         }
         // Updated system apps do not need to be allowlisted
@@ -3463,9 +3468,6 @@
         }
         // Only enforce the allowlist on boot
         if (!mSystemReady) {
-            final ApexManager apexManager = ApexManager.getInstance();
-            final String containingApexPackageName =
-                    apexManager.getActiveApexPackageNameContainingPackage(packageName);
             final boolean isInUpdatedApex = containingApexPackageName != null
                     && !apexManager.isFactory(apexManager.getPackageInfo(containingApexPackageName,
                     MATCH_ACTIVE_PACKAGE));
@@ -3489,7 +3491,7 @@
     }
 
     private boolean isInSystemConfigPrivAppPermissions(@NonNull AndroidPackage pkg,
-            @NonNull String permission) {
+            @NonNull String permission, String containingApexPackageName) {
         final SystemConfig systemConfig = SystemConfig.getInstance();
         final Set<String> permissions;
         if (pkg.isVendor()) {
@@ -3498,6 +3500,26 @@
             permissions = systemConfig.getProductPrivAppPermissions(pkg.getPackageName());
         } else if (pkg.isSystemExt()) {
             permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName());
+        } else if (containingApexPackageName != null) {
+            final Set<String> privAppPermissions = systemConfig.getPrivAppPermissions(
+                    pkg.getPackageName());
+            final Set<String> apexPermissions = systemConfig.getApexPrivAppPermissions(
+                    containingApexPackageName, pkg.getPackageName());
+            if (privAppPermissions != null) {
+                // TODO(andreionea): Remove check as soon as all apk-in-apex
+                // permission allowlists are migrated.
+                Slog.w(TAG, "Package " + pkg.getPackageName() + " is an APK in APEX,"
+                        + " but has permission allowlist on the system image. Please bundle the"
+                        + " allowlist in the " + containingApexPackageName + " APEX instead.");
+                if (apexPermissions != null) {
+                    permissions = new ArraySet<>(privAppPermissions);
+                    permissions.addAll(apexPermissions);
+                } else {
+                    permissions = privAppPermissions;
+                }
+            } else {
+                permissions = apexPermissions;
+            }
         } else {
             permissions = systemConfig.getPrivAppPermissions(pkg.getPackageName());
         }
@@ -3505,7 +3527,7 @@
     }
 
     private boolean isInSystemConfigPrivAppDenyPermissions(@NonNull AndroidPackage pkg,
-            @NonNull String permission) {
+            @NonNull String permission, String containingApexPackageName) {
         final SystemConfig systemConfig = SystemConfig.getInstance();
         final Set<String> permissions;
         if (pkg.isVendor()) {
@@ -3514,6 +3536,9 @@
             permissions = systemConfig.getProductPrivAppDenyPermissions(pkg.getPackageName());
         } else if (pkg.isSystemExt()) {
             permissions = systemConfig.getSystemExtPrivAppDenyPermissions(pkg.getPackageName());
+        } else if (containingApexPackageName != null) {
+            permissions = systemConfig.getApexPrivAppDenyPermissions(containingApexPackageName,
+                    pkg.getPackageName());
         } else {
             permissions = systemConfig.getPrivAppDenyPermissions(pkg.getPackageName());
         }
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 584530c..8b80b4a 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -687,6 +687,7 @@
         mUnderlyingNetworkController =
                 mDeps.newUnderlyingNetworkController(
                         mVcnContext,
+                        mConnectionConfig,
                         subscriptionGroup,
                         mLastSnapshot,
                         mUnderlyingNetworkControllerCallback);
@@ -2376,11 +2377,12 @@
         /** Builds a new UnderlyingNetworkController. */
         public UnderlyingNetworkController newUnderlyingNetworkController(
                 VcnContext vcnContext,
+                VcnGatewayConnectionConfig connectionConfig,
                 ParcelUuid subscriptionGroup,
                 TelephonySubscriptionSnapshot snapshot,
                 UnderlyingNetworkControllerCallback callback) {
             return new UnderlyingNetworkController(
-                    vcnContext, subscriptionGroup, snapshot, callback);
+                    vcnContext, connectionConfig, subscriptionGroup, snapshot, callback);
         }
 
         /** Builds a new IkeSession. */
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 bea8ae9..7b26fe0 100644
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
@@ -15,25 +15,36 @@
  */
 package com.android.server.vcn.routeselection;
 
+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;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_ANY;
+import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_OK;
 
 import static com.android.server.VcnManagementService.LOCAL_LOG;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.NetworkCapabilities;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.VcnCellUnderlyingNetworkPriority;
 import android.net.vcn.VcnManager;
+import android.net.vcn.VcnUnderlyingNetworkPriority;
+import android.net.vcn.VcnWifiUnderlyingNetworkPriority;
 import android.os.ParcelUuid;
 import android.os.PersistableBundle;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.util.Slog;
-import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.VcnContext;
 
+import java.util.LinkedHashSet;
 import java.util.Set;
 
 /** @hide */
@@ -56,52 +67,20 @@
      */
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     static final int WIFI_EXIT_RSSI_THRESHOLD_DEFAULT = -74;
-    /** Priority for any cellular network for which the subscription is listed as opportunistic */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int PRIORITY_OPPORTUNISTIC_CELLULAR = 0;
-    /** Priority for any WiFi network which is in use, and satisfies the in-use RSSI threshold */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int PRIORITY_WIFI_IN_USE = 1;
-    /** Priority for any WiFi network which satisfies the prospective-network RSSI threshold */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int PRIORITY_WIFI_PROSPECTIVE = 2;
-    /** Priority for any standard macro cellular network */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int PRIORITY_MACRO_CELLULAR = 3;
+
     /** Priority for any other networks (including unvalidated, etc) */
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     static final int PRIORITY_ANY = Integer.MAX_VALUE;
 
-    private static final SparseArray<String> PRIORITY_TO_STRING_MAP = new SparseArray<>();
-
-    static {
-        PRIORITY_TO_STRING_MAP.put(
-                PRIORITY_OPPORTUNISTIC_CELLULAR, "PRIORITY_OPPORTUNISTIC_CELLULAR");
-        PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_IN_USE, "PRIORITY_WIFI_IN_USE");
-        PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_PROSPECTIVE, "PRIORITY_WIFI_PROSPECTIVE");
-        PRIORITY_TO_STRING_MAP.put(PRIORITY_MACRO_CELLULAR, "PRIORITY_MACRO_CELLULAR");
-        PRIORITY_TO_STRING_MAP.put(PRIORITY_ANY, "PRIORITY_ANY");
-    }
-
-    /**
-     * Gives networks a priority class, based on the following priorities:
-     *
-     * <ol>
-     *   <li>Opportunistic cellular
-     *   <li>Carrier WiFi, signal strength >= WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT
-     *   <li>Carrier WiFi, active network + signal strength >= WIFI_EXIT_RSSI_THRESHOLD_DEFAULT
-     *   <li>Macro cellular
-     *   <li>Any others
-     * </ol>
-     */
-    static int calculatePriorityClass(
+    /** Gives networks a priority class, based on configured VcnGatewayConnectionConfig */
+    public static int calculatePriorityClass(
+            VcnContext vcnContext,
             UnderlyingNetworkRecord networkRecord,
+            LinkedHashSet<VcnUnderlyingNetworkPriority> underlyingNetworkPriorities,
             ParcelUuid subscriptionGroup,
             TelephonySubscriptionSnapshot snapshot,
             UnderlyingNetworkRecord currentlySelected,
             PersistableBundle carrierConfig) {
-        final NetworkCapabilities caps = networkRecord.networkCapabilities;
-
         // mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED
 
         if (networkRecord.isBlocked) {
@@ -109,8 +88,167 @@
             return PRIORITY_ANY;
         }
 
-        if (caps.hasTransport(TRANSPORT_CELLULAR)
-                && isOpportunistic(snapshot, caps.getSubscriptionIds())) {
+        if (snapshot == null) {
+            logWtf("Got null snapshot");
+            return PRIORITY_ANY;
+        }
+
+        int priorityIndex = 0;
+        for (VcnUnderlyingNetworkPriority nwPriority : underlyingNetworkPriorities) {
+            if (checkMatchesPriorityRule(
+                    vcnContext,
+                    nwPriority,
+                    networkRecord,
+                    subscriptionGroup,
+                    snapshot,
+                    currentlySelected,
+                    carrierConfig)) {
+                return priorityIndex;
+            }
+            priorityIndex++;
+        }
+        return PRIORITY_ANY;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static boolean checkMatchesPriorityRule(
+            VcnContext vcnContext,
+            VcnUnderlyingNetworkPriority networkPriority,
+            UnderlyingNetworkRecord networkRecord,
+            ParcelUuid subscriptionGroup,
+            TelephonySubscriptionSnapshot snapshot,
+            UnderlyingNetworkRecord currentlySelected,
+            PersistableBundle carrierConfig) {
+        // TODO: Check Network Quality reported by metric monitors/probers.
+
+        final NetworkCapabilities caps = networkRecord.networkCapabilities;
+        if (!networkPriority.allowMetered() && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)) {
+            return false;
+        }
+
+        if (vcnContext.isInTestMode() && caps.hasTransport(TRANSPORT_TEST)) {
+            return true;
+        }
+
+        if (networkPriority instanceof VcnWifiUnderlyingNetworkPriority) {
+            return checkMatchesWifiPriorityRule(
+                    (VcnWifiUnderlyingNetworkPriority) networkPriority,
+                    networkRecord,
+                    currentlySelected,
+                    carrierConfig);
+        }
+
+        if (networkPriority instanceof VcnCellUnderlyingNetworkPriority) {
+            return checkMatchesCellPriorityRule(
+                    vcnContext,
+                    (VcnCellUnderlyingNetworkPriority) networkPriority,
+                    networkRecord,
+                    subscriptionGroup,
+                    snapshot);
+        }
+
+        logWtf(
+                "Got unknown VcnUnderlyingNetworkPriority class: "
+                        + networkPriority.getClass().getSimpleName());
+        return false;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static boolean checkMatchesWifiPriorityRule(
+            VcnWifiUnderlyingNetworkPriority networkPriority,
+            UnderlyingNetworkRecord networkRecord,
+            UnderlyingNetworkRecord currentlySelected,
+            PersistableBundle carrierConfig) {
+        final NetworkCapabilities caps = networkRecord.networkCapabilities;
+
+        if (!caps.hasTransport(TRANSPORT_WIFI)) {
+            return false;
+        }
+
+        // TODO: Move the Network Quality check to the network metric monitor framework.
+        if (networkPriority.getNetworkQuality()
+                > getWifiQuality(networkRecord, currentlySelected, carrierConfig)) {
+            return false;
+        }
+
+        if (networkPriority.getSsid() != null && networkPriority.getSsid() != caps.getSsid()) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private static int getWifiQuality(
+            UnderlyingNetworkRecord networkRecord,
+            UnderlyingNetworkRecord currentlySelected,
+            PersistableBundle carrierConfig) {
+        final NetworkCapabilities caps = networkRecord.networkCapabilities;
+        final boolean isSelectedNetwork =
+                currentlySelected != null
+                        && networkRecord.network.equals(currentlySelected.network);
+
+        if (isSelectedNetwork
+                && caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)) {
+            return NETWORK_QUALITY_OK;
+        }
+
+        if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) {
+            return NETWORK_QUALITY_OK;
+        }
+
+        return NETWORK_QUALITY_ANY;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static boolean checkMatchesCellPriorityRule(
+            VcnContext vcnContext,
+            VcnCellUnderlyingNetworkPriority networkPriority,
+            UnderlyingNetworkRecord networkRecord,
+            ParcelUuid subscriptionGroup,
+            TelephonySubscriptionSnapshot snapshot) {
+        final NetworkCapabilities caps = networkRecord.networkCapabilities;
+
+        if (!caps.hasTransport(TRANSPORT_CELLULAR)) {
+            return false;
+        }
+
+        final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+                ((TelephonyNetworkSpecifier) caps.getNetworkSpecifier());
+        if (telephonyNetworkSpecifier == null) {
+            logWtf("Got null NetworkSpecifier");
+            return false;
+        }
+
+        final int subId = telephonyNetworkSpecifier.getSubscriptionId();
+        final TelephonyManager subIdSpecificTelephonyMgr =
+                vcnContext
+                        .getContext()
+                        .getSystemService(TelephonyManager.class)
+                        .createForSubscriptionId(subId);
+
+        if (!networkPriority.getAllowedPlmnIds().isEmpty()) {
+            final String plmnId = subIdSpecificTelephonyMgr.getNetworkOperator();
+            if (!networkPriority.getAllowedPlmnIds().contains(plmnId)) {
+                return false;
+            }
+        }
+
+        if (!networkPriority.getAllowedSpecificCarrierIds().isEmpty()) {
+            final int carrierId = subIdSpecificTelephonyMgr.getSimSpecificCarrierId();
+            if (!networkPriority.getAllowedSpecificCarrierIds().contains(carrierId)) {
+                return false;
+            }
+        }
+
+        if (!networkPriority.allowRoaming() && !caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)) {
+            return false;
+        }
+
+        if (networkPriority.requireOpportunistic()) {
+            if (!isOpportunistic(snapshot, caps.getSubscriptionIds())) {
+                return false;
+            }
+
             // If this carrier is the active data provider, ensure that opportunistic is only
             // ever prioritized if it is also the active data subscription. This ensures that
             // if an opportunistic subscription is still in the process of being switched to,
@@ -121,36 +259,15 @@
             // Allow the following two cases:
             // 1. Active subId is NOT in the group that this VCN is supporting
             // 2. This opportunistic subscription is for the active subId
-            if (!snapshot.getAllSubIdsInGroup(subscriptionGroup)
+            if (snapshot.getAllSubIdsInGroup(subscriptionGroup)
                             .contains(SubscriptionManager.getActiveDataSubscriptionId())
-                    || caps.getSubscriptionIds()
+                    && !caps.getSubscriptionIds()
                             .contains(SubscriptionManager.getActiveDataSubscriptionId())) {
-                return PRIORITY_OPPORTUNISTIC_CELLULAR;
+                return false;
             }
         }
 
-        if (caps.hasTransport(TRANSPORT_WIFI)) {
-            if (caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)
-                    && currentlySelected != null
-                    && networkRecord.network.equals(currentlySelected.network)) {
-                return PRIORITY_WIFI_IN_USE;
-            }
-
-            if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) {
-                return PRIORITY_WIFI_PROSPECTIVE;
-            }
-        }
-
-        // Disallow opportunistic subscriptions from matching PRIORITY_MACRO_CELLULAR, as might
-        // be the case when Default Data SubId (CBRS) != Active Data SubId (MACRO), as might be
-        // the case if the Default Data SubId does not support certain services (eg voice
-        // calling)
-        if (caps.hasTransport(TRANSPORT_CELLULAR)
-                && !isOpportunistic(snapshot, caps.getSubscriptionIds())) {
-            return PRIORITY_MACRO_CELLULAR;
-        }
-
-        return PRIORITY_ANY;
+        return true;
     }
 
     static boolean isOpportunistic(
@@ -185,10 +302,6 @@
         return WIFI_EXIT_RSSI_THRESHOLD_DEFAULT;
     }
 
-    static String priorityClassToString(int priorityClass) {
-        return PRIORITY_TO_STRING_MAP.get(priorityClass, "unknown");
-    }
-
     private static void logWtf(String msg) {
         Slog.wtf(TAG, msg);
         LOCAL_LOG.log(TAG + " WTF: " + msg);
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 071c7a6..cd124ee 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
@@ -32,6 +32,8 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.VcnGatewayConnectionConfig;
+import android.net.vcn.VcnUnderlyingNetworkPriority;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.ParcelUuid;
@@ -68,6 +70,7 @@
     @NonNull private static final String TAG = UnderlyingNetworkController.class.getSimpleName();
 
     @NonNull private final VcnContext mVcnContext;
+    @NonNull private final VcnGatewayConnectionConfig mConnectionConfig;
     @NonNull private final ParcelUuid mSubscriptionGroup;
     @NonNull private final UnderlyingNetworkControllerCallback mCb;
     @NonNull private final Dependencies mDeps;
@@ -91,24 +94,22 @@
 
     public UnderlyingNetworkController(
             @NonNull VcnContext vcnContext,
+            @NonNull VcnGatewayConnectionConfig connectionConfig,
             @NonNull ParcelUuid subscriptionGroup,
             @NonNull TelephonySubscriptionSnapshot snapshot,
             @NonNull UnderlyingNetworkControllerCallback cb) {
-        this(
-                vcnContext,
-                subscriptionGroup,
-                snapshot,
-                cb,
-                new Dependencies());
+        this(vcnContext, connectionConfig, subscriptionGroup, snapshot, cb, new Dependencies());
     }
 
     private UnderlyingNetworkController(
             @NonNull VcnContext vcnContext,
+            @NonNull VcnGatewayConnectionConfig connectionConfig,
             @NonNull ParcelUuid subscriptionGroup,
             @NonNull TelephonySubscriptionSnapshot snapshot,
             @NonNull UnderlyingNetworkControllerCallback cb,
             @NonNull Dependencies deps) {
         mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
+        mConnectionConfig = Objects.requireNonNull(connectionConfig, "Missing connectionConfig");
         mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
         mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot");
         mCb = Objects.requireNonNull(cb, "Missing cb");
@@ -399,6 +400,8 @@
             TreeSet<UnderlyingNetworkRecord> sorted =
                     new TreeSet<>(
                             UnderlyingNetworkRecord.getComparator(
+                                    mVcnContext,
+                                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
                                     mSubscriptionGroup,
                                     mLastSnapshot,
                                     mCurrentRecord,
@@ -495,12 +498,31 @@
         pw.println(
                 "Currently selected: " + (mCurrentRecord == null ? null : mCurrentRecord.network));
 
+        pw.println("VcnUnderlyingNetworkPriority list:");
+        pw.increaseIndent();
+        int index = 0;
+        for (VcnUnderlyingNetworkPriority priority :
+                mConnectionConfig.getVcnUnderlyingNetworkPriorities()) {
+            pw.println("Priority index: " + index);
+            priority.dump(pw);
+            index++;
+        }
+        pw.decreaseIndent();
+        pw.println();
+
         pw.println("Underlying networks:");
         pw.increaseIndent();
         if (mRouteSelectionCallback != null) {
             for (UnderlyingNetworkRecord record :
                     mRouteSelectionCallback.getSortedUnderlyingNetworks()) {
-                record.dump(pw, mSubscriptionGroup, mLastSnapshot, mCurrentRecord, mCarrierConfig);
+                record.dump(
+                        mVcnContext,
+                        pw,
+                        mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+                        mSubscriptionGroup,
+                        mLastSnapshot,
+                        mCurrentRecord,
+                        mCarrierConfig);
             }
         }
         pw.decreaseIndent();
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 65c69de..27ba854 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
@@ -21,6 +21,7 @@
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.net.vcn.VcnUnderlyingNetworkPriority;
 import android.os.ParcelUuid;
 import android.os.PersistableBundle;
 
@@ -28,8 +29,10 @@
 import com.android.internal.annotations.VisibleForTesting.Visibility;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.VcnContext;
 
 import java.util.Comparator;
+import java.util.LinkedHashSet;
 import java.util.Objects;
 
 /**
@@ -73,22 +76,64 @@
     }
 
     static Comparator<UnderlyingNetworkRecord> getComparator(
+            VcnContext vcnContext,
+            LinkedHashSet<VcnUnderlyingNetworkPriority> underlyingNetworkPriorities,
             ParcelUuid subscriptionGroup,
             TelephonySubscriptionSnapshot snapshot,
             UnderlyingNetworkRecord currentlySelected,
             PersistableBundle carrierConfig) {
         return (left, right) -> {
-            return Integer.compare(
+            final int leftIndex =
                     NetworkPriorityClassifier.calculatePriorityClass(
-                            left, subscriptionGroup, snapshot, currentlySelected, carrierConfig),
+                            vcnContext,
+                            left,
+                            underlyingNetworkPriorities,
+                            subscriptionGroup,
+                            snapshot,
+                            currentlySelected,
+                            carrierConfig);
+            final int rightIndex =
                     NetworkPriorityClassifier.calculatePriorityClass(
-                            right, subscriptionGroup, snapshot, currentlySelected, carrierConfig));
+                            vcnContext,
+                            right,
+                            underlyingNetworkPriorities,
+                            subscriptionGroup,
+                            snapshot,
+                            currentlySelected,
+                            carrierConfig);
+
+            // 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)) {
+                    return -1;
+                }
+                if (isSelected(left, currentlySelected)) {
+                    return 1;
+                }
+            }
+            return Integer.compare(leftIndex, rightIndex);
         };
     }
 
+    private static boolean isSelected(
+            UnderlyingNetworkRecord recordToCheck, UnderlyingNetworkRecord currentlySelected) {
+        if (currentlySelected == null) {
+            return false;
+        }
+        if (currentlySelected.network == recordToCheck.network) {
+            return true;
+        }
+        return false;
+    }
+
     /** Dumps the state of this record for logging and debugging purposes. */
     void dump(
+            VcnContext vcnContext,
             IndentingPrintWriter pw,
+            LinkedHashSet<VcnUnderlyingNetworkPriority> underlyingNetworkPriorities,
             ParcelUuid subscriptionGroup,
             TelephonySubscriptionSnapshot snapshot,
             UnderlyingNetworkRecord currentlySelected,
@@ -96,15 +141,17 @@
         pw.println("UnderlyingNetworkRecord:");
         pw.increaseIndent();
 
-        final int priorityClass =
+        final int priorityIndex =
                 NetworkPriorityClassifier.calculatePriorityClass(
-                        this, subscriptionGroup, snapshot, currentlySelected, carrierConfig);
-        pw.println(
-                "Priority class: "
-                        + NetworkPriorityClassifier.priorityClassToString(priorityClass)
-                        + " ("
-                        + priorityClass
-                        + ")");
+                        vcnContext,
+                        this,
+                        underlyingNetworkPriorities,
+                        subscriptionGroup,
+                        snapshot,
+                        currentlySelected,
+                        carrierConfig);
+
+        pw.println("Priority index:" + priorityIndex);
         pw.println("mNetwork: " + network);
         pw.println("mNetworkCapabilities: " + networkCapabilities);
         pw.println("mLinkProperties: " + linkProperties);
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index 8a0af2d..5628321 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -179,7 +179,7 @@
 
         doReturn(mUnderlyingNetworkController)
                 .when(mDeps)
-                .newUnderlyingNetworkController(any(), any(), any(), any());
+                .newUnderlyingNetworkController(any(), any(), any(), any(), any());
         doReturn(mWakeLock)
                 .when(mDeps)
                 .newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any());
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
new file mode 100644
index 0000000..2e1aab6
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vcn.routeselection;
+
+import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_OK;
+
+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.checkMatchesCellPriorityRule;
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesPriorityRule;
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesWifiPriorityRule;
+import static com.android.server.vcn.routeselection.UnderlyingNetworkControllerTest.getLinkPropertiesWithName;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.VcnCellUnderlyingNetworkPriority;
+import android.net.vcn.VcnGatewayConnectionConfig;
+import android.net.vcn.VcnManager;
+import android.net.vcn.VcnWifiUnderlyingNetworkPriority;
+import android.os.ParcelUuid;
+import android.os.PersistableBundle;
+import android.os.test.TestLooper;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
+
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.VcnContext;
+import com.android.server.vcn.VcnNetworkProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Set;
+import java.util.UUID;
+
+public class NetworkPriorityClassifierTest {
+    private static final String SSID = "TestWifi";
+    private static final String SSID_OTHER = "TestWifiOther";
+    private static final String PLMN_ID = "123456";
+    private static final String PLMN_ID_OTHER = "234567";
+
+    private static final int SUB_ID = 1;
+    private static final int WIFI_RSSI = -60;
+    private static final int WIFI_RSSI_HIGH = -50;
+    private static final int WIFI_RSSI_LOW = -80;
+    private static final int CARRIER_ID = 1;
+    private static final int CARRIER_ID_OTHER = 2;
+
+    private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0));
+
+    private static final NetworkCapabilities WIFI_NETWORK_CAPABILITIES =
+            new NetworkCapabilities.Builder()
+                    .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                    .setSignalStrength(WIFI_RSSI)
+                    .setSsid(SSID)
+                    .build();
+
+    private static final TelephonyNetworkSpecifier TEL_NETWORK_SPECIFIER =
+            new TelephonyNetworkSpecifier.Builder().setSubscriptionId(SUB_ID).build();
+    private static final NetworkCapabilities CELL_NETWORK_CAPABILITIES =
+            new NetworkCapabilities.Builder()
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                    .setSubscriptionIds(Set.of(SUB_ID))
+                    .setNetworkSpecifier(TEL_NETWORK_SPECIFIER)
+                    .build();
+
+    private static final LinkProperties LINK_PROPERTIES = getLinkPropertiesWithName("test_iface");
+
+    @Mock private Network mNetwork;
+    @Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
+    @Mock private TelephonyManager mTelephonyManager;
+
+    private TestLooper mTestLooper;
+    private VcnContext mVcnContext;
+    private UnderlyingNetworkRecord mWifiNetworkRecord;
+    private UnderlyingNetworkRecord mCellNetworkRecord;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        final Context mockContext = mock(Context.class);
+        mTestLooper = new TestLooper();
+        mVcnContext =
+                spy(
+                        new VcnContext(
+                                mockContext,
+                                mTestLooper.getLooper(),
+                                mock(VcnNetworkProvider.class),
+                                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);
+    }
+
+    @Test
+    public void testMatchWithoutNotMeteredBit() {
+        final VcnWifiUnderlyingNetworkPriority wifiNetworkPriority =
+                new VcnWifiUnderlyingNetworkPriority.Builder()
+                        .setNetworkQuality(NETWORK_QUALITY_OK)
+                        .setAllowMetered(false /* allowMetered */)
+                        .build();
+
+        assertFalse(
+                checkMatchesPriorityRule(
+                        mVcnContext,
+                        wifiNetworkPriority,
+                        mWifiNetworkRecord,
+                        SUB_GROUP,
+                        mSubscriptionSnapshot,
+                        null /* currentlySelecetd */,
+                        null /* carrierConfig */));
+    }
+
+    private void verifyMatchWifi(
+            boolean isSelectedNetwork, PersistableBundle carrierConfig, boolean expectMatch) {
+        final VcnWifiUnderlyingNetworkPriority wifiNetworkPriority =
+                new VcnWifiUnderlyingNetworkPriority.Builder()
+                        .setNetworkQuality(NETWORK_QUALITY_OK)
+                        .setAllowMetered(true /* allowMetered */)
+                        .build();
+        final UnderlyingNetworkRecord selectedNetworkRecord =
+                isSelectedNetwork ? mWifiNetworkRecord : null;
+        assertEquals(
+                expectMatch,
+                checkMatchesWifiPriorityRule(
+                        wifiNetworkPriority,
+                        mWifiNetworkRecord,
+                        selectedNetworkRecord,
+                        carrierConfig));
+    }
+
+    @Test
+    public void testMatchSelectedWifi() {
+        verifyMatchWifi(
+                true /* isSelectedNetwork */, null /* carrierConfig */, true /* expectMatch */);
+    }
+
+    @Test
+    public void testMatchSelectedWifiBelowRssiThreshold() {
+        final PersistableBundle carrierConfig = new PersistableBundle();
+        carrierConfig.putInt(
+                VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY, WIFI_RSSI_HIGH);
+        carrierConfig.putInt(
+                VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, WIFI_RSSI_HIGH);
+
+        verifyMatchWifi(true /* isSelectedNetwork */, carrierConfig, false /* expectMatch */);
+    }
+
+    @Test
+    public void testMatchUnselectedWifi() {
+        verifyMatchWifi(
+                false /* isSelectedNetwork */, null /* carrierConfig */, true /* expectMatch */);
+    }
+
+    @Test
+    public void testMatchUnselectedWifiBelowRssiThreshold() {
+        final PersistableBundle carrierConfig = new PersistableBundle();
+        carrierConfig.putInt(
+                VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, WIFI_RSSI_HIGH);
+
+        verifyMatchWifi(false /* isSelectedNetwork */, carrierConfig, false /* expectMatch */);
+    }
+
+    private void verifyMatchWifiWithSsid(boolean useMatchedSsid, boolean expectMatch) {
+        final String nwPrioritySsid = useMatchedSsid ? SSID : SSID_OTHER;
+        final VcnWifiUnderlyingNetworkPriority wifiNetworkPriority =
+                new VcnWifiUnderlyingNetworkPriority.Builder()
+                        .setNetworkQuality(NETWORK_QUALITY_OK)
+                        .setAllowMetered(true /* allowMetered */)
+                        .setSsid(nwPrioritySsid)
+                        .build();
+
+        assertEquals(
+                expectMatch,
+                checkMatchesWifiPriorityRule(
+                        wifiNetworkPriority,
+                        mWifiNetworkRecord,
+                        null /* currentlySelecetd */,
+                        null /* carrierConfig */));
+    }
+
+    @Test
+    public void testMatchWifiWithSsid() {
+        verifyMatchWifiWithSsid(true /* useMatchedSsid */, true /* expectMatch */);
+    }
+
+    @Test
+    public void testMatchWifiFailWithWrongSsid() {
+        verifyMatchWifiWithSsid(false /* useMatchedSsid */, false /* expectMatch */);
+    }
+
+    private static VcnCellUnderlyingNetworkPriority.Builder getCellNetworkPriorityBuilder() {
+        return new VcnCellUnderlyingNetworkPriority.Builder()
+                .setNetworkQuality(NETWORK_QUALITY_OK)
+                .setAllowMetered(true /* allowMetered */)
+                .setAllowRoaming(true /* allowRoaming */);
+    }
+
+    @Test
+    public void testMatchMacroCell() {
+        assertTrue(
+                checkMatchesCellPriorityRule(
+                        mVcnContext,
+                        getCellNetworkPriorityBuilder().build(),
+                        mCellNetworkRecord,
+                        SUB_GROUP,
+                        mSubscriptionSnapshot));
+    }
+
+    @Test
+    public void testMatchOpportunisticCell() {
+        final VcnCellUnderlyingNetworkPriority opportunisticCellNetworkPriority =
+                getCellNetworkPriorityBuilder()
+                        .setRequireOpportunistic(true /* requireOpportunistic */)
+                        .build();
+
+        when(mSubscriptionSnapshot.isOpportunistic(SUB_ID)).thenReturn(true);
+        when(mSubscriptionSnapshot.getAllSubIdsInGroup(SUB_GROUP)).thenReturn(new ArraySet<>());
+
+        assertTrue(
+                checkMatchesCellPriorityRule(
+                        mVcnContext,
+                        opportunisticCellNetworkPriority,
+                        mCellNetworkRecord,
+                        SUB_GROUP,
+                        mSubscriptionSnapshot));
+    }
+
+    private void verifyMatchMacroCellWithAllowedPlmnIds(
+            boolean useMatchedPlmnId, boolean expectMatch) {
+        final String networkPriorityPlmnId = useMatchedPlmnId ? PLMN_ID : PLMN_ID_OTHER;
+        final VcnCellUnderlyingNetworkPriority networkPriority =
+                getCellNetworkPriorityBuilder()
+                        .setAllowedPlmnIds(Set.of(networkPriorityPlmnId))
+                        .build();
+
+        assertEquals(
+                expectMatch,
+                checkMatchesCellPriorityRule(
+                        mVcnContext,
+                        networkPriority,
+                        mCellNetworkRecord,
+                        SUB_GROUP,
+                        mSubscriptionSnapshot));
+    }
+
+    @Test
+    public void testMatchMacroCellWithAllowedPlmnIds() {
+        verifyMatchMacroCellWithAllowedPlmnIds(true /* useMatchedPlmnId */, true /* expectMatch */);
+    }
+
+    @Test
+    public void testMatchMacroCellFailWithDisallowedPlmnIds() {
+        verifyMatchMacroCellWithAllowedPlmnIds(
+                false /* useMatchedPlmnId */, false /* expectMatch */);
+    }
+
+    private void verifyMatchMacroCellWithAllowedSpecificCarrierIds(
+            boolean useMatchedCarrierId, boolean expectMatch) {
+        final int networkPriorityCarrierId = useMatchedCarrierId ? CARRIER_ID : CARRIER_ID_OTHER;
+        final VcnCellUnderlyingNetworkPriority networkPriority =
+                getCellNetworkPriorityBuilder()
+                        .setAllowedSpecificCarrierIds(Set.of(networkPriorityCarrierId))
+                        .build();
+
+        assertEquals(
+                expectMatch,
+                checkMatchesCellPriorityRule(
+                        mVcnContext,
+                        networkPriority,
+                        mCellNetworkRecord,
+                        SUB_GROUP,
+                        mSubscriptionSnapshot));
+    }
+
+    @Test
+    public void testMatchMacroCellWithAllowedSpecificCarrierIds() {
+        verifyMatchMacroCellWithAllowedSpecificCarrierIds(
+                true /* useMatchedCarrierId */, true /* expectMatch */);
+    }
+
+    @Test
+    public void testMatchMacroCellFailWithDisallowedSpecificCarrierIds() {
+        verifyMatchMacroCellWithAllowedSpecificCarrierIds(
+                false /* useMatchedCarrierId */, false /* expectMatch */);
+    }
+
+    @Test
+    public void testMatchWifiFailWithoutNotRoamingBit() {
+        final VcnCellUnderlyingNetworkPriority networkPriority =
+                getCellNetworkPriorityBuilder().setAllowRoaming(false /* allowRoaming */).build();
+
+        assertFalse(
+                checkMatchesCellPriorityRule(
+                        mVcnContext,
+                        networkPriority,
+                        mCellNetworkRecord,
+                        SUB_GROUP,
+                        mSubscriptionSnapshot));
+    }
+
+    private void verifyCalculatePriorityClass(
+            UnderlyingNetworkRecord networkRecord, int expectedIndex) {
+        final int priorityIndex =
+                calculatePriorityClass(
+                        mVcnContext,
+                        networkRecord,
+                        VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_PRIORITIES,
+                        SUB_GROUP,
+                        mSubscriptionSnapshot,
+                        null /* currentlySelected */,
+                        null /* carrierConfig */);
+
+        assertEquals(expectedIndex, priorityIndex);
+    }
+
+    @Test
+    public void testCalculatePriorityClass() throws Exception {
+        verifyCalculatePriorityClass(mCellNetworkRecord, 2);
+    }
+
+    @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 */);
+
+        verifyCalculatePriorityClass(wifiNetworkRecord, PRIORITY_ANY);
+    }
+}
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 c954cb8..fad9669 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
@@ -42,6 +42,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.VcnGatewayConnectionConfigTest;
 import android.os.ParcelUuid;
 import android.os.test.TestLooper;
 import android.telephony.CarrierConfigManager;
@@ -145,7 +146,11 @@
 
         mUnderlyingNetworkController =
                 new UnderlyingNetworkController(
-                        mVcnContext, SUB_GROUP, mSubscriptionSnapshot, mNetworkControllerCb);
+                        mVcnContext,
+                        VcnGatewayConnectionConfigTest.buildTestConfig(),
+                        SUB_GROUP,
+                        mSubscriptionSnapshot,
+                        mNetworkControllerCb);
     }
 
     private void resetVcnContext() {
@@ -153,7 +158,8 @@
         doNothing().when(mVcnContext).ensureRunningOnLooperThread();
     }
 
-    private static LinkProperties getLinkPropertiesWithName(String iface) {
+    // Package private for use in NetworkPriorityClassifierTest
+    static LinkProperties getLinkPropertiesWithName(String iface) {
         LinkProperties linkProperties = new LinkProperties();
         linkProperties.setInterfaceName(iface);
         return linkProperties;
@@ -182,7 +188,11 @@
                         true /* isInTestMode */);
 
         new UnderlyingNetworkController(
-                vcnContext, SUB_GROUP, mSubscriptionSnapshot, mNetworkControllerCb);
+                vcnContext,
+                VcnGatewayConnectionConfigTest.buildTestConfig(),
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mNetworkControllerCb);
 
         verify(cm)
                 .registerNetworkCallback(
@@ -345,6 +355,17 @@
         return verifyRegistrationOnAvailableAndGetCallback(INITIAL_NETWORK_CAPABILITIES);
     }
 
+    private static NetworkCapabilities buildResponseNwCaps(
+            NetworkCapabilities requestNetworkCaps, Set<Integer> netCapsSubIds) {
+        final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+                new TelephonyNetworkSpecifier.Builder()
+                        .setSubscriptionId(netCapsSubIds.iterator().next())
+                        .build();
+        return new NetworkCapabilities.Builder(requestNetworkCaps)
+                .setNetworkSpecifier(telephonyNetworkSpecifier)
+                .build();
+    }
+
     private UnderlyingNetworkListener verifyRegistrationOnAvailableAndGetCallback(
             NetworkCapabilities networkCapabilities) {
         verify(mConnectivityManager)
@@ -355,14 +376,17 @@
 
         UnderlyingNetworkListener cb = mUnderlyingNetworkListenerCaptor.getValue();
         cb.onAvailable(mNetwork);
-        cb.onCapabilitiesChanged(mNetwork, networkCapabilities);
+
+        final NetworkCapabilities responseNetworkCaps =
+                buildResponseNwCaps(networkCapabilities, INITIAL_SUB_IDS);
+        cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
         cb.onLinkPropertiesChanged(mNetwork, INITIAL_LINK_PROPERTIES);
         cb.onBlockedStatusChanged(mNetwork, false /* isFalse */);
 
         UnderlyingNetworkRecord expectedRecord =
                 new UnderlyingNetworkRecord(
                         mNetwork,
-                        networkCapabilities,
+                        responseNetworkCaps,
                         INITIAL_LINK_PROPERTIES,
                         false /* isBlocked */);
         verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
@@ -373,12 +397,14 @@
     public void testRecordTrackerCallbackNotifiedForNetworkCapabilitiesChange() {
         UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
 
-        cb.onCapabilitiesChanged(mNetwork, UPDATED_NETWORK_CAPABILITIES);
+        final NetworkCapabilities responseNetworkCaps =
+                buildResponseNwCaps(UPDATED_NETWORK_CAPABILITIES, UPDATED_SUB_IDS);
+        cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
 
         UnderlyingNetworkRecord expectedRecord =
                 new UnderlyingNetworkRecord(
                         mNetwork,
-                        UPDATED_NETWORK_CAPABILITIES,
+                        responseNetworkCaps,
                         INITIAL_LINK_PROPERTIES,
                         false /* isBlocked */);
         verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
@@ -393,7 +419,7 @@
         UnderlyingNetworkRecord expectedRecord =
                 new UnderlyingNetworkRecord(
                         mNetwork,
-                        INITIAL_NETWORK_CAPABILITIES,
+                        buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS),
                         UPDATED_LINK_PROPERTIES,
                         false /* isBlocked */);
         verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
@@ -403,19 +429,21 @@
     public void testRecordTrackerCallbackNotifiedForNetworkSuspended() {
         UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
 
-        cb.onCapabilitiesChanged(mNetwork, SUSPENDED_NETWORK_CAPABILITIES);
+        final NetworkCapabilities responseNetworkCaps =
+                buildResponseNwCaps(SUSPENDED_NETWORK_CAPABILITIES, UPDATED_SUB_IDS);
+        cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
 
         UnderlyingNetworkRecord expectedRecord =
                 new UnderlyingNetworkRecord(
                         mNetwork,
-                        SUSPENDED_NETWORK_CAPABILITIES,
+                        responseNetworkCaps,
                         INITIAL_LINK_PROPERTIES,
                         false /* isBlocked */);
         verify(mNetworkControllerCb, times(1))
                 .onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
         // onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't
         // change.
-        cb.onCapabilitiesChanged(mNetwork, SUSPENDED_NETWORK_CAPABILITIES);
+        cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
         verify(mNetworkControllerCb, times(1))
                 .onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
     }
@@ -425,19 +453,21 @@
         UnderlyingNetworkListener cb =
                 verifyRegistrationOnAvailableAndGetCallback(SUSPENDED_NETWORK_CAPABILITIES);
 
-        cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES);
+        final NetworkCapabilities responseNetworkCaps =
+                buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS);
+        cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
 
         UnderlyingNetworkRecord expectedRecord =
                 new UnderlyingNetworkRecord(
                         mNetwork,
-                        INITIAL_NETWORK_CAPABILITIES,
+                        responseNetworkCaps,
                         INITIAL_LINK_PROPERTIES,
                         false /* isBlocked */);
         verify(mNetworkControllerCb, times(1))
                 .onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
         // onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't
         // change.
-        cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES);
+        cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
         verify(mNetworkControllerCb, times(1))
                 .onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
     }
@@ -451,7 +481,7 @@
         UnderlyingNetworkRecord expectedRecord =
                 new UnderlyingNetworkRecord(
                         mNetwork,
-                        INITIAL_NETWORK_CAPABILITIES,
+                        buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS),
                         INITIAL_LINK_PROPERTIES,
                         true /* isBlocked */);
         verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
@@ -470,7 +500,8 @@
     public void testRecordTrackerCallbackIgnoresDuplicateRecord() {
         UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
 
-        cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES);
+        cb.onCapabilitiesChanged(
+                mNetwork, buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS));
 
         // Verify no more calls to the UnderlyingNetworkControllerCallback when the
         // UnderlyingNetworkRecord does not actually change
@@ -482,7 +513,8 @@
         UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
         mUnderlyingNetworkController.teardown();
 
-        cb.onCapabilitiesChanged(mNetwork, UPDATED_NETWORK_CAPABILITIES);
+        cb.onCapabilitiesChanged(
+                mNetwork, buildResponseNwCaps(UPDATED_NETWORK_CAPABILITIES, INITIAL_SUB_IDS));
 
         // Verify that the only call was during onAvailable()
         verify(mNetworkControllerCb, times(1)).onSelectedUnderlyingNetworkChanged(any());