Merge "Add flag for adjacent TF refactor" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 7977d73..3c2ae5a 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -56,7 +56,6 @@
         "android.media.tv.flags-aconfig-java",
         "android.multiuser.flags-aconfig-java",
         "android.net.platform.flags-aconfig-java",
-        "android.net.vcn.flags-aconfig-java-export",
         "android.net.wifi.flags-aconfig-java",
         "android.nfc.flags-aconfig-java",
         "android.os.flags-aconfig-java",
@@ -1204,25 +1203,6 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
-// VCN
-// TODO:376339506 Move the VCN code, the flag declaration and
-// java_aconfig_library to framework-connectivity-b
-aconfig_declarations {
-    name: "android.net.vcn.flags-aconfig",
-    package: "android.net.vcn",
-    container: "com.android.tethering",
-    exportable: true,
-    srcs: ["core/java/android/net/vcn/*.aconfig"],
-}
-
-java_aconfig_library {
-    name: "android.net.vcn.flags-aconfig-java-export",
-    aconfig_declarations: "android.net.vcn.flags-aconfig",
-    mode: "exported",
-    min_sdk_version: "35",
-    defaults: ["framework-minus-apex-aconfig-java-defaults"],
-}
-
 // DevicePolicy
 aconfig_declarations {
     name: "device_policy_aconfig_flags",
diff --git a/Android.bp b/Android.bp
index 42028e0..a525583b8 100644
--- a/Android.bp
+++ b/Android.bp
@@ -87,6 +87,7 @@
         ":framework-wifi-non-updatable-sources",
         ":PacProcessor-aidl-sources",
         ":ProxyHandler-aidl-sources",
+        ":vcn-utils-platform-sources",
         ":net-utils-framework-common-srcs",
 
         // AIDL from frameworks/base/native/
@@ -314,6 +315,7 @@
             ":framework-telephony-sources",
             ":framework-wifi-annotations",
             ":framework-wifi-non-updatable-sources",
+            ":vcn-utils-platform-sources",
             ":PacProcessor-aidl-sources",
             ":ProxyHandler-aidl-sources",
             ":net-utils-framework-common-srcs",
@@ -596,7 +598,7 @@
     srcs: [
         "core/java/com/android/internal/util/HexDump.java",
         "core/java/com/android/internal/util/WakeupMessage.java",
-        "core/java/android/net/vcn/util/PersistableBundleUtils.java",
+        "packages/Vcn/framework-b/src/android/net/vcn/util/PersistableBundleUtils.java",
         "telephony/java/android/telephony/Annotation.java",
     ],
 }
diff --git a/core/api/current.txt b/core/api/current.txt
index d9338a5..1b42977 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -20809,6 +20809,7 @@
     method public int describeContents();
     method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @FloatRange(from=0.0f, to=1.0f) public float getDefaultBrightness();
     method public int getDensityDpi();
+    method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @FloatRange(from=0.0f, to=1.0f) public float getDimBrightness();
     method @NonNull public java.util.Set<java.lang.String> getDisplayCategories();
     method public int getFlags();
     method public int getHeight();
@@ -20830,6 +20831,7 @@
     method @NonNull public android.hardware.display.VirtualDisplayConfig build();
     method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setBrightnessListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.display.VirtualDisplayConfig.BrightnessListener);
     method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDefaultBrightness(@FloatRange(from=0.0f, to=1.0f) float);
+    method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDimBrightness(@FloatRange(from=0.0f, to=1.0f) float);
     method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDisplayCategories(@NonNull java.util.Set<java.lang.String>);
     method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setFlags(int);
     method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setRequestedRefreshRate(@FloatRange(from=0.0f) float);
@@ -30132,128 +30134,6 @@
 
 }
 
-package android.net.vcn {
-
-  public final class VcnCellUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate {
-    method public int getCbs();
-    method public int getDun();
-    method public int getIms();
-    method public int getInternet();
-    method public int getMms();
-    method @NonNull public java.util.Set<java.lang.String> getOperatorPlmnIds();
-    method public int getOpportunistic();
-    method public int getRcs();
-    method public int getRoaming();
-    method @NonNull public java.util.Set<java.lang.Integer> getSimSpecificCarrierIds();
-  }
-
-  public static final class VcnCellUnderlyingNetworkTemplate.Builder {
-    ctor public VcnCellUnderlyingNetworkTemplate.Builder();
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate build();
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setCbs(int);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setDun(int);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setIms(int);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setInternet(int);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMetered(int);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMinDownstreamBandwidthKbps(int, int);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMinUpstreamBandwidthKbps(int, int);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMms(int);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOperatorPlmnIds(@NonNull java.util.Set<java.lang.String>);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOpportunistic(int);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setRcs(int);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setRoaming(int);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setSimSpecificCarrierIds(@NonNull java.util.Set<java.lang.Integer>);
-  }
-
-  public final class VcnConfig implements android.os.Parcelable {
-    method public int describeContents();
-    method @NonNull public java.util.Set<android.net.vcn.VcnGatewayConnectionConfig> getGatewayConnectionConfigs();
-    method @NonNull public java.util.Set<java.lang.Integer> getRestrictedUnderlyingNetworkTransports();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnConfig> CREATOR;
-  }
-
-  public static final class VcnConfig.Builder {
-    ctor public VcnConfig.Builder(@NonNull android.content.Context);
-    method @NonNull public android.net.vcn.VcnConfig.Builder addGatewayConnectionConfig(@NonNull android.net.vcn.VcnGatewayConnectionConfig);
-    method @NonNull public android.net.vcn.VcnConfig build();
-    method @NonNull public android.net.vcn.VcnConfig.Builder setRestrictedUnderlyingNetworkTransports(@NonNull java.util.Set<java.lang.Integer>);
-  }
-
-  public final class VcnGatewayConnectionConfig {
-    method @NonNull public int[] getExposedCapabilities();
-    method @NonNull public String getGatewayConnectionName();
-    method @IntRange(from=0x500) public int getMaxMtu();
-    method public int getMinUdpPort4500NatTimeoutSeconds();
-    method @NonNull public long[] getRetryIntervalsMillis();
-    method @NonNull public java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities();
-    method public boolean hasGatewayOption(int);
-    method @FlaggedApi("android.net.vcn.safe_mode_config") public boolean isSafeModeEnabled();
-    field @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static final int MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET = -1; // 0xffffffff
-    field public static final int VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY = 0; // 0x0
-  }
-
-  public static final class VcnGatewayConnectionConfig.Builder {
-    ctor public VcnGatewayConnectionConfig.Builder(@NonNull String, @NonNull android.net.ipsec.ike.IkeTunnelConnectionParams);
-    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addExposedCapability(int);
-    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addGatewayOption(int);
-    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig build();
-    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int);
-    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeGatewayOption(int);
-    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=0x500) int);
-    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMinUdpPort4500NatTimeoutSeconds(@IntRange(from=0x78) int);
-    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryIntervalsMillis(@NonNull long[]);
-    method @FlaggedApi("android.net.vcn.safe_mode_config") @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setSafeModeEnabled(boolean);
-    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setVcnUnderlyingNetworkPriorities(@NonNull java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate>);
-  }
-
-  public class VcnManager {
-    method @RequiresPermission("carrier privileges") public void clearVcnConfig(@NonNull android.os.ParcelUuid) throws java.io.IOException;
-    method @NonNull public java.util.List<android.os.ParcelUuid> getConfiguredSubscriptionGroups();
-    method public void registerVcnStatusCallback(@NonNull android.os.ParcelUuid, @NonNull java.util.concurrent.Executor, @NonNull android.net.vcn.VcnManager.VcnStatusCallback);
-    method @RequiresPermission("carrier privileges") public void setVcnConfig(@NonNull android.os.ParcelUuid, @NonNull android.net.vcn.VcnConfig) throws java.io.IOException;
-    method public void unregisterVcnStatusCallback(@NonNull android.net.vcn.VcnManager.VcnStatusCallback);
-    field public static final int VCN_ERROR_CODE_CONFIG_ERROR = 1; // 0x1
-    field public static final int VCN_ERROR_CODE_INTERNAL_ERROR = 0; // 0x0
-    field public static final int VCN_ERROR_CODE_NETWORK_ERROR = 2; // 0x2
-    field public static final int VCN_STATUS_CODE_ACTIVE = 2; // 0x2
-    field public static final int VCN_STATUS_CODE_INACTIVE = 1; // 0x1
-    field public static final int VCN_STATUS_CODE_NOT_CONFIGURED = 0; // 0x0
-    field public static final int VCN_STATUS_CODE_SAFE_MODE = 3; // 0x3
-  }
-
-  public abstract static class VcnManager.VcnStatusCallback {
-    ctor public VcnManager.VcnStatusCallback();
-    method public abstract void onGatewayConnectionError(@NonNull String, int, @Nullable Throwable);
-    method public abstract void onStatusChanged(int);
-  }
-
-  public abstract class VcnUnderlyingNetworkTemplate {
-    method public int getMetered();
-    method public int getMinEntryDownstreamBandwidthKbps();
-    method public int getMinEntryUpstreamBandwidthKbps();
-    method public int getMinExitDownstreamBandwidthKbps();
-    method public int getMinExitUpstreamBandwidthKbps();
-    field public static final int MATCH_ANY = 0; // 0x0
-    field public static final int MATCH_FORBIDDEN = 2; // 0x2
-    field public static final int MATCH_REQUIRED = 1; // 0x1
-  }
-
-  public final class VcnWifiUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate {
-    method @NonNull public java.util.Set<java.lang.String> getSsids();
-  }
-
-  public static final class VcnWifiUnderlyingNetworkTemplate.Builder {
-    ctor public VcnWifiUnderlyingNetworkTemplate.Builder();
-    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate build();
-    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMetered(int);
-    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMinDownstreamBandwidthKbps(int, int);
-    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMinUpstreamBandwidthKbps(int, int);
-    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setSsids(@NonNull java.util.Set<java.lang.String>);
-  }
-
-}
-
 package android.opengl {
 
   public class EGL14 {
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 804210f..fe6e995 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -265,10 +265,6 @@
 
 package android.net {
 
-  @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public final class ConnectivityFrameworkInitializerBaklava {
-    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static void registerServiceWrappers();
-  }
-
   public class LocalSocket implements java.io.Closeable {
     ctor public LocalSocket(@NonNull java.io.FileDescriptor);
   }
@@ -328,25 +324,6 @@
 
 }
 
-package android.net.vcn {
-
-  @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public final class VcnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
-    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public int describeContents();
-    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public long getApplicableRedactions();
-    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public int getMinUdpPort4500NatTimeoutSeconds();
-    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.TransportInfo makeCopy(long);
-    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnTransportInfo> CREATOR;
-  }
-
-  @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static final class VcnTransportInfo.Builder {
-    ctor @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public VcnTransportInfo.Builder();
-    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.vcn.VcnTransportInfo build();
-    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.vcn.VcnTransportInfo.Builder setMinUdpPort4500NatTimeoutSeconds(@IntRange(from=0x78) int);
-  }
-
-}
-
 package android.net.wifi {
 
   public final class WifiMigration {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 612a48a..04aca54 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -10614,28 +10614,6 @@
 
 }
 
-package android.net.vcn {
-
-  public class VcnManager {
-    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void addVcnNetworkPolicyChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener);
-    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.vcn.VcnNetworkPolicyResult applyVcnNetworkPolicy(@NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties);
-    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void removeVcnNetworkPolicyChangeListener(@NonNull android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener);
-  }
-
-  public static interface VcnManager.VcnNetworkPolicyChangeListener {
-    method public void onPolicyChanged();
-  }
-
-  public final class VcnNetworkPolicyResult implements android.os.Parcelable {
-    method public int describeContents();
-    method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities();
-    method public boolean isTeardownRequested();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnNetworkPolicyResult> CREATOR;
-  }
-
-}
-
 package android.net.wifi {
 
   public final class WifiKeystore {
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index eceaa8f..7257055 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -66,6 +66,7 @@
     private final DisplayCutout mDisplayCutout;
     private final boolean mIgnoreActivitySizeRestrictions;
     private final float mDefaultBrightness;
+    private final float mDimBrightness;
     private final IBrightnessListener mBrightnessListener;
 
     private VirtualDisplayConfig(
@@ -84,6 +85,7 @@
             @Nullable DisplayCutout displayCutout,
             boolean ignoreActivitySizeRestrictions,
             @FloatRange(from = 0.0f, to = 1.0f) float defaultBrightness,
+            @FloatRange(from = 0.0f, to = 1.0f) float dimBrightness,
             IBrightnessListener brightnessListener) {
         mName = name;
         mWidth = width;
@@ -100,6 +102,7 @@
         mDisplayCutout = displayCutout;
         mIgnoreActivitySizeRestrictions = ignoreActivitySizeRestrictions;
         mDefaultBrightness = defaultBrightness;
+        mDimBrightness = dimBrightness;
         mBrightnessListener = brightnessListener;
     }
 
@@ -180,6 +183,19 @@
     }
 
     /**
+     * Returns the dim brightness of the display.
+     *
+     * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of {@code 1.0}
+     * indicates the maximum supported brightness.</p>
+     *
+     * @see Builder#setDimBrightness(float)
+     */
+    @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+    public @FloatRange(from = 0.0f, to = 1.0f) float getDimBrightness() {
+        return mDimBrightness;
+    }
+
+    /**
      * Returns the listener to get notified about changes in the display brightness.
      * @hide
      */
@@ -278,6 +294,7 @@
         DisplayCutout.ParcelableWrapper.writeCutoutToParcel(mDisplayCutout, dest, flags);
         dest.writeBoolean(mIgnoreActivitySizeRestrictions);
         dest.writeFloat(mDefaultBrightness);
+        dest.writeFloat(mDimBrightness);
         dest.writeStrongBinder(mBrightnessListener != null ? mBrightnessListener.asBinder() : null);
     }
 
@@ -308,8 +325,8 @@
                 && mIgnoreActivitySizeRestrictions == that.mIgnoreActivitySizeRestrictions
                 && Objects.equals(mDisplayCutout, that.mDisplayCutout)
                 && mDefaultBrightness == that.mDefaultBrightness
+                && mDimBrightness == that.mDimBrightness
                 && Objects.equals(mBrightnessListener, that.mBrightnessListener);
-
     }
 
     @Override
@@ -318,7 +335,8 @@
                 mName, mWidth, mHeight, mDensityDpi, mFlags, mSurface, mUniqueId,
                 mDisplayIdToMirror, mWindowManagerMirroringEnabled, mDisplayCategories,
                 mRequestedRefreshRate, mIsHomeSupported, mDisplayCutout,
-                mIgnoreActivitySizeRestrictions, mDefaultBrightness, mBrightnessListener);
+                mIgnoreActivitySizeRestrictions, mDefaultBrightness, mDimBrightness,
+                mBrightnessListener);
         return hashCode;
     }
 
@@ -341,6 +359,7 @@
                 + " mDisplayCutout=" + mDisplayCutout
                 + " mIgnoreActivitySizeRestrictions=" + mIgnoreActivitySizeRestrictions
                 + " mDefaultBrightness=" + mDefaultBrightness
+                + " mDimBrightness=" + mDimBrightness
                 + ")";
     }
 
@@ -360,8 +379,8 @@
         mDisplayCutout = DisplayCutout.ParcelableWrapper.readCutoutFromParcel(in);
         mIgnoreActivitySizeRestrictions = in.readBoolean();
         mDefaultBrightness = in.readFloat();
+        mDimBrightness = in.readFloat();
         mBrightnessListener = IBrightnessListener.Stub.asInterface(in.readStrongBinder());
-
     }
 
     /**
@@ -432,6 +451,7 @@
         private DisplayCutout mDisplayCutout = null;
         private boolean mIgnoreActivitySizeRestrictions = false;
         private float mDefaultBrightness = 0.0f;
+        private float mDimBrightness = PowerManager.BRIGHTNESS_INVALID;
         private IBrightnessListener mBrightnessListener = null;
 
         /**
@@ -635,14 +655,14 @@
          *
          * <p>If unset, defaults to {@code 0.0}</p>
          *
+         * @throws IllegalArgumentException if the brightness is outside the valid range [0.0, 1.0]
          * @see android.view.View#setKeepScreenOn(boolean)
          * @see #setBrightnessListener(Executor, BrightnessListener)
          */
         @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
         @NonNull
         public Builder setDefaultBrightness(@FloatRange(from = 0.0f, to = 1.0f) float brightness) {
-            if (brightness < PowerManager.BRIGHTNESS_MIN
-                    || brightness > PowerManager.BRIGHTNESS_MAX) {
+            if (!isValidBrightness(brightness)) {
                 throw new IllegalArgumentException(
                         "Virtual display default brightness must be in range [0.0, 1.0]");
             }
@@ -651,6 +671,33 @@
         }
 
         /**
+         * Sets the dim brightness of the display.
+         *
+         * <p>The system will use this brightness value whenever the display should be dim, i.e.
+         * it is powered on and dimmed due to user activity or app activity.</p>
+         *
+         * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of
+         * {@code 1.0} indicates the maximum supported brightness.</p>
+         *
+         * <p>If set, the default brightness must also be set to a value greater or equal to the
+         * dim brightness. If unset, defaults to the system default.</p>
+         *
+         * @throws IllegalArgumentException if the brightness is outside the valid range [0.0, 1.0]
+         * @see Builder#setDefaultBrightness(float)
+         * @see #setBrightnessListener(Executor, BrightnessListener)
+         */
+        @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+        @NonNull
+        public Builder setDimBrightness(@FloatRange(from = 0.0f, to = 1.0f) float brightness) {
+            if (!isValidBrightness(brightness)) {
+                throw new IllegalArgumentException(
+                        "Virtual display dim brightness must be in range [0.0, 1.0]");
+            }
+            mDimBrightness = brightness;
+            return this;
+        }
+
+        /**
          * Sets the listener to get notified about changes in the display brightness.
          *
          * @param executor The executor where the callback is executed on.
@@ -666,11 +713,23 @@
             return this;
         }
 
+        private boolean isValidBrightness(float brightness) {
+            return !Float.isNaN(brightness) && PowerManager.BRIGHTNESS_MIN <= brightness
+                    && brightness <= PowerManager.BRIGHTNESS_MAX;
+        }
+
         /**
          * Builds the {@link VirtualDisplayConfig} instance.
+         *
+         * @throws IllegalArgumentException if the dim brightness is set to a value greater than
+         *   the default brightness.
          */
         @NonNull
         public VirtualDisplayConfig build() {
+            if (isValidBrightness(mDimBrightness) && mDimBrightness > mDefaultBrightness) {
+                throw new IllegalArgumentException(
+                        "The dim brightness must not be greater than the default brightness");
+            }
             return new VirtualDisplayConfig(
                     mName,
                     mWidth,
@@ -687,6 +746,7 @@
                     mDisplayCutout,
                     mIgnoreActivitySizeRestrictions,
                     mDefaultBrightness,
+                    mDimBrightness,
                     mBrightnessListener);
         }
     }
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index e85e580..4cac4dee 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -55,7 +55,7 @@
     void goToSleepWithDisplayId(int displayId, long time, int reason, int flags);
     @UnsupportedAppUsage(maxTargetSdk = 28)
     void nap(long time);
-    float getBrightnessConstraint(int constraint);
+    float getBrightnessConstraint(int displayId, int constraint);
     @UnsupportedAppUsage
     boolean isInteractive();
     boolean isDisplayInteractive(int displayId);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 9e7bf47..cd48f08 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1266,9 +1266,17 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public float getBrightnessConstraint(int constraint) {
+    public float getBrightnessConstraint(@BrightnessConstraint int constraint) {
+        return getBrightnessConstraint(Display.DEFAULT_DISPLAY, constraint);
+    }
+
+    /**
+     * Gets a float screen brightness setting for a specific display.
+     * @hide
+     */
+    public float getBrightnessConstraint(int displayId, @BrightnessConstraint int constraint) {
         try {
-            return mService.getBrightnessConstraint(constraint);
+            return mService.getBrightnessConstraint(displayId, constraint);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 8b6458a..4307884 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -361,6 +361,12 @@
     public float brightnessDefault;
 
     /**
+     * The current dim brightness of the display. Value between 0.0 and 1.0,
+     * derived from the configuration of the display device of this logical display.
+     */
+    public float brightnessDim;
+
+    /**
      * The {@link RoundedCorners} if present, otherwise {@code null}.
      */
     @Nullable
@@ -479,6 +485,7 @@
                 && brightnessMinimum == other.brightnessMinimum
                 && brightnessMaximum == other.brightnessMaximum
                 && brightnessDefault == other.brightnessDefault
+                && brightnessDim == other.brightnessDim
                 && Objects.equals(roundedCorners, other.roundedCorners)
                 && installOrientation == other.installOrientation
                 && Objects.equals(displayShape, other.displayShape)
@@ -546,6 +553,7 @@
         brightnessMinimum = other.brightnessMinimum;
         brightnessMaximum = other.brightnessMaximum;
         brightnessDefault = other.brightnessDefault;
+        brightnessDim = other.brightnessDim;
         roundedCorners = other.roundedCorners;
         installOrientation = other.installOrientation;
         displayShape = other.displayShape;
@@ -620,6 +628,7 @@
         brightnessMinimum = source.readFloat();
         brightnessMaximum = source.readFloat();
         brightnessDefault = source.readFloat();
+        brightnessDim = source.readFloat();
         roundedCorners = source.readTypedObject(RoundedCorners.CREATOR);
         int numUserDisabledFormats = source.readInt();
         userDisabledHdrTypes = new int[numUserDisabledFormats];
@@ -696,6 +705,7 @@
         dest.writeFloat(brightnessMinimum);
         dest.writeFloat(brightnessMaximum);
         dest.writeFloat(brightnessDefault);
+        dest.writeFloat(brightnessDim);
         dest.writeTypedObject(roundedCorners, flags);
         dest.writeInt(userDisabledHdrTypes.length);
         for (int i = 0; i < userDisabledHdrTypes.length; i++) {
@@ -994,6 +1004,8 @@
         sb.append(brightnessMaximum);
         sb.append(", brightnessDefault ");
         sb.append(brightnessDefault);
+        sb.append(", brightnessDim ");
+        sb.append(brightnessDim);
         sb.append(", installOrientation ");
         sb.append(Surface.rotationToString(installOrientation));
         sb.append(", layoutLimitedRefreshRate ");
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 02c7901..049189f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -28277,7 +28277,7 @@
         mPrivateFlags |= PFLAG_FORCE_LAYOUT;
         mPrivateFlags |= PFLAG_INVALIDATED;
 
-        if (mParent != null) {
+        if (mParent != null && !mParent.isLayoutRequested()) {
             mParent.requestLayout();
         }
         if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedBasicEnvelopeEffect.java b/core/java/com/android/internal/vibrator/persistence/SerializedBasicEnvelopeEffect.java
new file mode 100644
index 0000000..a090c7a
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedBasicEnvelopeEffect.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2024 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.internal.vibrator.persistence;
+
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_DURATION_MS;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_INITIAL_SHARPNESS;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_INTENSITY;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_SHARPNESS;
+import static com.android.internal.vibrator.persistence.XmlConstants.NAMESPACE;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_BASIC_ENVELOPE_EFFECT;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_CONTROL_POINT;
+
+import android.annotation.NonNull;
+import android.os.VibrationEffect;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Serialized representation of a basic envelope effect created via
+ * {@link VibrationEffect.BasicEnvelopeBuilder}.
+ *
+ * @hide
+ */
+final class SerializedBasicEnvelopeEffect implements SerializedComposedEffect.SerializedSegment {
+    private final BasicControlPoint[] mControlPoints;
+    private final float mInitialSharpness;
+
+    SerializedBasicEnvelopeEffect(BasicControlPoint[] controlPoints, float initialSharpness) {
+        mControlPoints = controlPoints;
+        mInitialSharpness = initialSharpness;
+    }
+
+    @Override
+    public void write(@NonNull TypedXmlSerializer serializer) throws IOException {
+        serializer.startTag(NAMESPACE, TAG_BASIC_ENVELOPE_EFFECT);
+
+        if (!Float.isNaN(mInitialSharpness)) {
+            serializer.attributeFloat(NAMESPACE, ATTRIBUTE_INITIAL_SHARPNESS, mInitialSharpness);
+        }
+
+        for (BasicControlPoint point : mControlPoints) {
+            serializer.startTag(NAMESPACE, TAG_CONTROL_POINT);
+            serializer.attributeFloat(NAMESPACE, ATTRIBUTE_INTENSITY, point.mIntensity);
+            serializer.attributeFloat(NAMESPACE, ATTRIBUTE_SHARPNESS, point.mSharpness);
+            serializer.attributeLong(NAMESPACE, ATTRIBUTE_DURATION_MS, point.mDurationMs);
+            serializer.endTag(NAMESPACE, TAG_CONTROL_POINT);
+        }
+
+        serializer.endTag(NAMESPACE, TAG_BASIC_ENVELOPE_EFFECT);
+    }
+
+    @Override
+    public void deserializeIntoComposition(@NonNull VibrationEffect.Composition composition) {
+        VibrationEffect.BasicEnvelopeBuilder builder = new VibrationEffect.BasicEnvelopeBuilder();
+
+        if (!Float.isNaN(mInitialSharpness)) {
+            builder.setInitialSharpness(mInitialSharpness);
+        }
+
+        for (BasicControlPoint point : mControlPoints) {
+            builder.addControlPoint(point.mIntensity, point.mSharpness, point.mDurationMs);
+        }
+        composition.addEffect(builder.build());
+    }
+
+    @Override
+    public String toString() {
+        return "SerializedBasicEnvelopeEffect{"
+                + "initialSharpness=" + (Float.isNaN(mInitialSharpness) ? "" : mInitialSharpness)
+                + ", controlPoints=" + Arrays.toString(mControlPoints)
+                + '}';
+    }
+
+    static final class Builder {
+        private final List<BasicControlPoint> mControlPoints;
+        private float mInitialSharpness = Float.NaN;
+
+        Builder() {
+            mControlPoints = new ArrayList<>();
+        }
+
+        void setInitialSharpness(float sharpness) {
+            mInitialSharpness = sharpness;
+        }
+
+        void addControlPoint(float intensity, float sharpness, long durationMs) {
+            mControlPoints.add(new BasicControlPoint(intensity, sharpness, durationMs));
+        }
+
+        SerializedBasicEnvelopeEffect build() {
+            return new SerializedBasicEnvelopeEffect(
+                    mControlPoints.toArray(new BasicControlPoint[0]), mInitialSharpness);
+        }
+    }
+
+    /** Parser implementation for {@link SerializedBasicEnvelopeEffect}. */
+    static final class Parser {
+
+        @NonNull
+        static SerializedBasicEnvelopeEffect parseNext(@NonNull TypedXmlPullParser parser,
+                @XmlConstants.Flags int flags) throws XmlParserException, IOException {
+            XmlValidator.checkStartTag(parser, TAG_BASIC_ENVELOPE_EFFECT);
+            XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_INITIAL_SHARPNESS);
+
+            Builder builder = new Builder();
+            builder.setInitialSharpness(
+                    XmlReader.readAttributeFloatInRange(parser, ATTRIBUTE_INITIAL_SHARPNESS, 0f, 1f,
+                            Float.NaN));
+
+            int outerDepth = parser.getDepth();
+
+            // Read all nested tags
+            while (XmlReader.readNextTagWithin(parser, outerDepth)) {
+                parseControlPoint(parser, builder);
+                // Consume tag
+                XmlReader.readEndTag(parser);
+            }
+
+            // Check schema assertions about <basic-envelope-effect>
+            XmlValidator.checkParserCondition(!builder.mControlPoints.isEmpty(),
+                    "Expected tag %s to have at least one control point",
+                    TAG_BASIC_ENVELOPE_EFFECT);
+            XmlValidator.checkParserCondition(builder.mControlPoints.getLast().mIntensity == 0,
+                    "Basic envelope effects must end at a zero intensity control point");
+
+            return builder.build();
+        }
+
+        private static void parseControlPoint(TypedXmlPullParser parser, Builder builder)
+                throws XmlParserException {
+            XmlValidator.checkStartTag(parser, TAG_CONTROL_POINT);
+            XmlValidator.checkTagHasNoUnexpectedAttributes(
+                    parser, ATTRIBUTE_DURATION_MS, ATTRIBUTE_INTENSITY,
+                    ATTRIBUTE_SHARPNESS);
+            float intensity = XmlReader.readAttributeFloatInRange(parser, ATTRIBUTE_INTENSITY, 0,
+                    1);
+            float sharpness = XmlReader.readAttributeFloatInRange(parser, ATTRIBUTE_SHARPNESS, 0,
+                    1);
+            long durationMs = XmlReader.readAttributePositiveLong(parser, ATTRIBUTE_DURATION_MS);
+
+            builder.addControlPoint(intensity, sharpness, durationMs);
+        }
+    }
+
+    private static final class BasicControlPoint {
+        private final float mIntensity;
+        private final float mSharpness;
+        private final long mDurationMs;
+
+        BasicControlPoint(float intensity, float sharpness, long durationMs) {
+            mIntensity = intensity;
+            mSharpness = sharpness;
+            mDurationMs = durationMs;
+        }
+
+        @Override
+        public String toString() {
+            return String.format(Locale.ROOT, "(%.2f, %.2f, %dms)", mIntensity, mSharpness,
+                    mDurationMs);
+        }
+    }
+}
+
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedWaveformEnvelopeEffect.java b/core/java/com/android/internal/vibrator/persistence/SerializedWaveformEnvelopeEffect.java
new file mode 100644
index 0000000..6a89343
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedWaveformEnvelopeEffect.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2024 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.internal.vibrator.persistence;
+
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_AMPLITUDE;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_DURATION_MS;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_FREQUENCY_HZ;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_INITIAL_FREQUENCY_HZ;
+import static com.android.internal.vibrator.persistence.XmlConstants.NAMESPACE;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_CONTROL_POINT;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_ENVELOPE_EFFECT;
+
+import android.annotation.NonNull;
+import android.os.VibrationEffect;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Serialized representation of a waveform envelope effect created via
+ * {@link VibrationEffect.WaveformEnvelopeBuilder}.
+ *
+ * @hide
+ */
+final class SerializedWaveformEnvelopeEffect implements SerializedComposedEffect.SerializedSegment {
+
+    private final WaveformControlPoint[] mControlPoints;
+    private final float mInitialFrequency;
+
+    SerializedWaveformEnvelopeEffect(WaveformControlPoint[] controlPoints, float initialFrequency) {
+        mControlPoints = controlPoints;
+        mInitialFrequency = initialFrequency;
+    }
+
+    @Override
+    public void write(@NonNull TypedXmlSerializer serializer) throws IOException {
+        serializer.startTag(NAMESPACE, TAG_WAVEFORM_ENVELOPE_EFFECT);
+
+        if (!Float.isNaN(mInitialFrequency)) {
+            serializer.attributeFloat(NAMESPACE, ATTRIBUTE_INITIAL_FREQUENCY_HZ, mInitialFrequency);
+        }
+
+        for (WaveformControlPoint point : mControlPoints) {
+            serializer.startTag(NAMESPACE, TAG_CONTROL_POINT);
+            serializer.attributeFloat(NAMESPACE, ATTRIBUTE_AMPLITUDE, point.mAmplitude);
+            serializer.attributeFloat(NAMESPACE, ATTRIBUTE_FREQUENCY_HZ, point.mFrequency);
+            serializer.attributeLong(NAMESPACE, ATTRIBUTE_DURATION_MS, point.mDurationMs);
+            serializer.endTag(NAMESPACE, TAG_CONTROL_POINT);
+        }
+
+        serializer.endTag(NAMESPACE, TAG_WAVEFORM_ENVELOPE_EFFECT);
+    }
+
+    @Override
+    public void deserializeIntoComposition(@NonNull VibrationEffect.Composition composition) {
+        VibrationEffect.WaveformEnvelopeBuilder builder =
+                new VibrationEffect.WaveformEnvelopeBuilder();
+
+        if (!Float.isNaN(mInitialFrequency)) {
+            builder.setInitialFrequencyHz(mInitialFrequency);
+        }
+
+        for (WaveformControlPoint point : mControlPoints) {
+            builder.addControlPoint(point.mAmplitude, point.mFrequency, point.mDurationMs);
+        }
+        composition.addEffect(builder.build());
+    }
+
+    @Override
+    public String toString() {
+        return "SerializedWaveformEnvelopeEffect{"
+                + "InitialFrequency=" + (Float.isNaN(mInitialFrequency) ? "" : mInitialFrequency)
+                + ", controlPoints=" + Arrays.toString(mControlPoints)
+                + '}';
+    }
+
+    static final class Builder {
+        private final List<WaveformControlPoint> mControlPoints;
+        private float mInitialFrequencyHz = Float.NaN;
+
+        Builder() {
+            mControlPoints = new ArrayList<>();
+        }
+
+        void setInitialFrequencyHz(float frequencyHz) {
+            mInitialFrequencyHz = frequencyHz;
+        }
+
+        void addControlPoint(float amplitude, float frequencyHz, long durationMs) {
+            mControlPoints.add(new WaveformControlPoint(amplitude, frequencyHz, durationMs));
+        }
+
+        SerializedWaveformEnvelopeEffect build() {
+            return new SerializedWaveformEnvelopeEffect(
+                    mControlPoints.toArray(new WaveformControlPoint[0]), mInitialFrequencyHz);
+        }
+    }
+
+    /** Parser implementation for {@link SerializedWaveformEnvelopeEffect}. */
+    static final class Parser {
+
+        @NonNull
+        static SerializedWaveformEnvelopeEffect parseNext(@NonNull TypedXmlPullParser parser,
+                @XmlConstants.Flags int flags) throws XmlParserException, IOException {
+            XmlValidator.checkStartTag(parser, TAG_WAVEFORM_ENVELOPE_EFFECT);
+            XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_INITIAL_FREQUENCY_HZ);
+
+            Builder builder = new Builder();
+            builder.setInitialFrequencyHz(
+                    XmlReader.readAttributePositiveFloat(parser, ATTRIBUTE_INITIAL_FREQUENCY_HZ,
+                            Float.NaN));
+
+            int outerDepth = parser.getDepth();
+
+            while (XmlReader.readNextTagWithin(parser, outerDepth)) {
+                parseControlPoint(parser, builder);
+                // Consume tag
+                XmlReader.readEndTag(parser);
+            }
+
+            // Check schema assertions about <waveform-envelope-effect>
+            XmlValidator.checkParserCondition(!builder.mControlPoints.isEmpty(),
+                    "Expected tag %s to have at least one control point",
+                    TAG_WAVEFORM_ENVELOPE_EFFECT);
+
+            return builder.build();
+        }
+
+        private static void parseControlPoint(TypedXmlPullParser parser, Builder builder)
+                throws XmlParserException {
+            XmlValidator.checkStartTag(parser, TAG_CONTROL_POINT);
+            XmlValidator.checkTagHasNoUnexpectedAttributes(
+                    parser, ATTRIBUTE_DURATION_MS, ATTRIBUTE_AMPLITUDE,
+                    ATTRIBUTE_FREQUENCY_HZ);
+            float amplitude = XmlReader.readAttributeFloatInRange(parser, ATTRIBUTE_AMPLITUDE, 0,
+                    1);
+            float frequencyHz = XmlReader.readAttributePositiveFloat(parser,
+                    ATTRIBUTE_FREQUENCY_HZ);
+            long durationMs = XmlReader.readAttributePositiveLong(parser, ATTRIBUTE_DURATION_MS);
+
+            builder.addControlPoint(amplitude, frequencyHz, durationMs);
+        }
+    }
+
+    private static final class WaveformControlPoint {
+        private final float mAmplitude;
+        private final float mFrequency;
+        private final long mDurationMs;
+
+        WaveformControlPoint(float amplitude, float frequency, long durationMs) {
+            mAmplitude = amplitude;
+            mFrequency = frequency;
+            mDurationMs = durationMs;
+        }
+
+        @Override
+        public String toString() {
+            return String.format(Locale.ROOT, "(%.2f, %.2f, %dms)", mAmplitude, mFrequency,
+                    mDurationMs);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java
index a9fbcaf..314bfe4 100644
--- a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java
+++ b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java
@@ -16,11 +16,13 @@
 
 package com.android.internal.vibrator.persistence;
 
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_BASIC_ENVELOPE_EFFECT;
 import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PREDEFINED_EFFECT;
 import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PRIMITIVE_EFFECT;
 import static com.android.internal.vibrator.persistence.XmlConstants.TAG_VENDOR_EFFECT;
 import static com.android.internal.vibrator.persistence.XmlConstants.TAG_VIBRATION_EFFECT;
 import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_EFFECT;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_ENVELOPE_EFFECT;
 
 import android.annotation.NonNull;
 import android.os.VibrationEffect;
@@ -92,6 +94,32 @@
  *   }
  * </pre>
  *
+ * * Waveform Envelope effects
+ *
+ * <pre>
+ *     {@code
+ *       <vibration-effect>
+ *         <waveform-envelope-effect initialFrequencyHz="20.0">
+ *           <control-point amplitude="0.2" frequencyHz="80.0" durationMs="50" />
+ *           <control-point amplitude="0.5" frequencyHz="150.0" durationMs="50" />
+ *         </envelope-effect>
+ *       </vibration-effect>
+ *     }
+ * </pre>
+ *
+ * * Basic Envelope effects
+ *
+ * <pre>
+ *     {@code
+ *       <vibration-effect>
+ *         <basic-envelope-effect initialSharpness="0.3">
+ *            <control-point intensity="0.2" sharpness="0.5" durationMs="50" />
+ *            <control-point intensity="0.0" sharpness="1.0" durationMs="50" />
+ *          </envelope-effect>
+ *       </vibration-effect>
+ *     }
+ * </pre>
+ *
  * @hide
  */
 public class VibrationEffectXmlParser {
@@ -151,6 +179,18 @@
                 serializedVibration = new SerializedComposedEffect(
                         SerializedAmplitudeStepWaveform.Parser.parseNext(parser));
                 break;
+            case TAG_WAVEFORM_ENVELOPE_EFFECT:
+                if (Flags.normalizedPwleEffects()) {
+                    serializedVibration = new SerializedComposedEffect(
+                            SerializedWaveformEnvelopeEffect.Parser.parseNext(parser, flags));
+                    break;
+                } // else fall through
+            case TAG_BASIC_ENVELOPE_EFFECT:
+                if (Flags.normalizedPwleEffects()) {
+                    serializedVibration = new SerializedComposedEffect(
+                            SerializedBasicEnvelopeEffect.Parser.parseNext(parser, flags));
+                    break;
+                } // else fall through
             default:
                 throw new XmlParserException("Unexpected tag " + parser.getName()
                         + " in vibration tag " + vibrationTagName);
diff --git a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java
index cb834a5..ebe3434 100644
--- a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java
+++ b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java
@@ -19,9 +19,11 @@
 import android.annotation.NonNull;
 import android.os.PersistableBundle;
 import android.os.VibrationEffect;
+import android.os.vibrator.BasicPwleSegment;
 import android.os.vibrator.Flags;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.PwleSegment;
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationEffectSegment;
 
@@ -45,6 +47,8 @@
  *     <li>A composition created exclusively via
  *         {@link VibrationEffect.Composition#addPrimitive(int, float, int)}
  *     <li>{@link VibrationEffect#createVendorEffect(PersistableBundle)}
+ *     <li>{@link VibrationEffect.WaveformEnvelopeBuilder}
+ *     <li>{@link VibrationEffect.BasicEnvelopeBuilder}
  * </ul>
  *
  * @hide
@@ -77,6 +81,12 @@
         if (firstSegment instanceof PrimitiveSegment) {
             return serializePrimitiveEffect(composed);
         }
+        if (Flags.normalizedPwleEffects() && firstSegment instanceof PwleSegment) {
+            return serializeWaveformEnvelopeEffect(composed);
+        }
+        if (Flags.normalizedPwleEffects() && firstSegment instanceof BasicPwleSegment) {
+            return serializeBasicEnvelopeEffect(composed);
+        }
         return serializeWaveformEffect(composed);
     }
 
@@ -110,6 +120,53 @@
         return new SerializedComposedEffect(primitives);
     }
 
+    private static SerializedComposedEffect serializeWaveformEnvelopeEffect(
+            VibrationEffect.Composed effect) throws XmlSerializerException {
+        SerializedWaveformEnvelopeEffect.Builder builder =
+                new SerializedWaveformEnvelopeEffect.Builder();
+        List<VibrationEffectSegment> segments = effect.getSegments();
+        XmlValidator.checkSerializerCondition(effect.getRepeatIndex() == -1,
+                "Unsupported repeating waveform envelope effect %s", effect);
+        for (int i = 0; i < segments.size(); i++) {
+            XmlValidator.checkSerializerCondition(segments.get(i) instanceof PwleSegment,
+                    "Unsupported segment for waveform envelope effect %s", segments.get(i));
+            PwleSegment segment = (PwleSegment) segments.get(i);
+
+            if (i == 0 && segment.getStartFrequencyHz() != segment.getEndFrequencyHz()) {
+                // Initial frequency explicitly defined.
+                builder.setInitialFrequencyHz(segment.getStartFrequencyHz());
+            }
+
+            builder.addControlPoint(segment.getEndAmplitude(), segment.getEndFrequencyHz(),
+                    segment.getDuration());
+        }
+
+        return new SerializedComposedEffect(builder.build());
+    }
+
+    private static SerializedComposedEffect serializeBasicEnvelopeEffect(
+            VibrationEffect.Composed effect) throws XmlSerializerException {
+        SerializedBasicEnvelopeEffect.Builder builder = new SerializedBasicEnvelopeEffect.Builder();
+        List<VibrationEffectSegment> segments = effect.getSegments();
+        XmlValidator.checkSerializerCondition(effect.getRepeatIndex() == -1,
+                "Unsupported repeating basic envelope effect %s", effect);
+        for (int i = 0; i < segments.size(); i++) {
+            XmlValidator.checkSerializerCondition(segments.get(i) instanceof BasicPwleSegment,
+                    "Unsupported segment for basic envelope effect %s", segments.get(i));
+            BasicPwleSegment segment = (BasicPwleSegment) segments.get(i);
+
+            if (i == 0 && segment.getStartSharpness() != segment.getEndSharpness()) {
+                // Initial sharpness explicitly defined.
+                builder.setInitialSharpness(segment.getStartSharpness());
+            }
+
+            builder.addControlPoint(segment.getEndIntensity(), segment.getEndSharpness(),
+                    segment.getDuration());
+        }
+
+        return new SerializedComposedEffect(builder.build());
+    }
+
     private static SerializedComposedEffect serializeWaveformEffect(
             VibrationEffect.Composed effect) throws XmlSerializerException {
         SerializedAmplitudeStepWaveform.Builder serializedWaveformBuilder =
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlConstants.java b/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
index 4122215..df262cf 100644
--- a/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
+++ b/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
@@ -42,14 +42,22 @@
     public static final String TAG_PREDEFINED_EFFECT = "predefined-effect";
     public static final String TAG_PRIMITIVE_EFFECT = "primitive-effect";
     public static final String TAG_VENDOR_EFFECT = "vendor-effect";
+    public static final String TAG_WAVEFORM_ENVELOPE_EFFECT = "waveform-envelope-effect";
+    public static final String TAG_BASIC_ENVELOPE_EFFECT = "basic-envelope-effect";
     public static final String TAG_WAVEFORM_EFFECT = "waveform-effect";
     public static final String TAG_WAVEFORM_ENTRY = "waveform-entry";
     public static final String TAG_REPEATING = "repeating";
+    public static final String TAG_CONTROL_POINT = "control-point";
 
     public static final String ATTRIBUTE_NAME = "name";
     public static final String ATTRIBUTE_FALLBACK = "fallback";
     public static final String ATTRIBUTE_DURATION_MS = "durationMs";
     public static final String ATTRIBUTE_AMPLITUDE = "amplitude";
+    public static final String ATTRIBUTE_FREQUENCY_HZ = "frequencyHz";
+    public static final String ATTRIBUTE_INITIAL_FREQUENCY_HZ = "initialFrequencyHz";
+    public static final String ATTRIBUTE_INTENSITY = "intensity";
+    public static final String ATTRIBUTE_SHARPNESS = "sharpness";
+    public static final String ATTRIBUTE_INITIAL_SHARPNESS = "initialSharpness";
     public static final String ATTRIBUTE_SCALE = "scale";
     public static final String ATTRIBUTE_DELAY_MS = "delayMs";
     public static final String ATTRIBUTE_DELAY_TYPE = "delayType";
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlReader.java b/core/java/com/android/internal/vibrator/persistence/XmlReader.java
index 0ac6fef..1c4a783 100644
--- a/core/java/com/android/internal/vibrator/persistence/XmlReader.java
+++ b/core/java/com/android/internal/vibrator/persistence/XmlReader.java
@@ -221,12 +221,63 @@
         if (parser.getAttributeIndex(NAMESPACE, attrName) < 0) {
             return defaultValue;
         }
+
+        return readAttributeFloatInRange(parser, attrName, lowerInclusive, upperInclusive);
+    }
+
+    /**
+     * Read attribute from current tag as a float within given inclusive range.
+     */
+    public static float readAttributeFloatInRange(
+            TypedXmlPullParser parser, String attrName, float lowerInclusive,
+            float upperInclusive) throws XmlParserException {
         String tagName = parser.getName();
         float value = readAttributeFloat(parser, attrName);
 
         XmlValidator.checkParserCondition(value >= lowerInclusive && value <= upperInclusive,
-                "Unexpected %s = %f in tag %s, expected %s in [%f, %f]",
-                attrName, value, tagName, attrName, lowerInclusive, upperInclusive);
+                "Unexpected %s = %f in tag %s, expected %s in [%f, %f]", attrName, value, tagName,
+                attrName, lowerInclusive, upperInclusive);
+        return value;
+    }
+
+    /**
+     * Read attribute from current tag as a positive float, returning default value if attribute
+     * is missing.
+     */
+    public static float readAttributePositiveFloat(TypedXmlPullParser parser, String attrName,
+            float defaultValue) throws XmlParserException {
+        if (parser.getAttributeIndex(NAMESPACE, attrName) < 0) {
+            return defaultValue;
+        }
+
+        return readAttributePositiveFloat(parser, attrName);
+    }
+
+    /**
+     * Read attribute from current tag as a positive float.
+     */
+    public static float readAttributePositiveFloat(TypedXmlPullParser parser, String attrName)
+            throws XmlParserException {
+        String tagName = parser.getName();
+        float value = readAttributeFloat(parser, attrName);
+
+        XmlValidator.checkParserCondition(value > 0,
+                "Unexpected %s = %d in tag %s, expected %s > 0", attrName, value, tagName,
+                attrName);
+        return value;
+    }
+
+    /**
+     * Read attribute from current tag as a positive long.
+     */
+    public static long readAttributePositiveLong(TypedXmlPullParser parser, String attrName)
+            throws XmlParserException {
+        String tagName = parser.getName();
+        long value = readAttributeLong(parser, attrName);
+
+        XmlValidator.checkParserCondition(value > 0,
+                "Unexpected %s = %d in tag %s, expected %s > 0", attrName, value, tagName,
+                attrName);
         return value;
     }
 
@@ -251,4 +302,15 @@
             throw XmlParserException.createFromPullParserException(tagName, attrName, rawValue, e);
         }
     }
+
+    private static long readAttributeLong(TypedXmlPullParser parser, String attrName)
+            throws XmlParserException {
+        String tagName = parser.getName();
+        try {
+            return parser.getAttributeLong(NAMESPACE, attrName);
+        } catch (XmlPullParserException e) {
+            String rawValue = parser.getAttributeValue(NAMESPACE, attrName);
+            throw XmlParserException.createFromPullParserException(tagName, attrName, rawValue, e);
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/view/ViewGroupTest.java b/core/tests/coretests/src/android/view/ViewGroupTest.java
index 43c404e..ae3ad36 100644
--- a/core/tests/coretests/src/android/view/ViewGroupTest.java
+++ b/core/tests/coretests/src/android/view/ViewGroupTest.java
@@ -213,35 +213,6 @@
         assertTrue(autofillableViews.containsAll(Arrays.asList(viewA, viewC)));
     }
 
-    @Test
-    public void testMeasureCache() {
-        final int spec1 = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.AT_MOST);
-        final int spec2 = View.MeasureSpec.makeMeasureSpec(50, View.MeasureSpec.AT_MOST);
-        final Context context = getInstrumentation().getContext();
-        final View child = new View(context);
-        final TestView parent = new TestView(context, 0);
-        parent.addView(child);
-
-        child.setPadding(1, 2, 3, 4);
-        parent.measure(spec1, spec1);
-        assertEquals(4, parent.getMeasuredWidth());
-        assertEquals(6, parent.getMeasuredHeight());
-
-        child.setPadding(5, 6, 7, 8);
-        parent.measure(spec2, spec2);
-        assertEquals(12, parent.getMeasuredWidth());
-        assertEquals(14, parent.getMeasuredHeight());
-
-        // This ends the state of forceLayout.
-        parent.layout(0, 0, 50, 50);
-
-        // The cached values should be cleared after the new setPadding is called. And the measured
-        // width and height should be up-to-date.
-        parent.measure(spec1, spec1);
-        assertEquals(12, parent.getMeasuredWidth());
-        assertEquals(14, parent.getMeasuredHeight());
-    }
-
     private static void getUnobscuredTouchableRegion(Region outRegion, View view) {
         outRegion.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
         final ViewParent parent = view.getParent();
@@ -269,19 +240,6 @@
         protected void onLayout(boolean changed, int l, int t, int r, int b) {
             // We don't layout this view.
         }
-
-        @Override
-        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-            int measuredWidth = 0;
-            int measuredHeight = 0;
-            final int count = getChildCount();
-            for (int i = 0; i < count; i++) {
-                final View child = getChildAt(i);
-                measuredWidth += child.getPaddingLeft() + child.getPaddingRight();
-                measuredHeight += child.getPaddingTop() + child.getPaddingBottom();
-            }
-            setMeasuredDimension(measuredWidth, measuredHeight);
-        }
     }
 
     public static class AutofillableTestView extends TestView {
diff --git a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
index 4071057..5f25e93 100644
--- a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
@@ -439,6 +439,498 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testWaveformEnvelopeEffect_allSucceed() throws Exception {
+        VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder()
+                .addControlPoint(0.2f, 80f, 10)
+                .addControlPoint(0.5f, 150f, 10)
+                .build();
+
+        String xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect>
+                        <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
+                        <control-point amplitude="0.5" frequencyHz="150.0" durationMs="10" />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+
+        assertPublicApisParserSucceeds(xml, effect);
+        assertPublicApisSerializerSucceeds(effect, xml);
+        assertPublicApisRoundTrip(effect);
+        assertHiddenApisParserSucceeds(xml, effect);
+        assertHiddenApisSerializerSucceeds(effect, xml);
+        assertHiddenApisRoundTrip(effect);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testWaveformEnvelopeEffectWithInitialFrequency_allSucceed() throws Exception {
+        VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder()
+                .setInitialFrequencyHz(20)
+                .addControlPoint(0.2f, 80f, 10)
+                .addControlPoint(0.5f, 150f, 10)
+                .build();
+
+        String xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect initialFrequencyHz="20.0">
+                        <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
+                        <control-point amplitude="0.5" frequencyHz="150.0" durationMs="10" />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+
+        assertPublicApisParserSucceeds(xml, effect);
+        assertPublicApisSerializerSucceeds(effect, xml);
+        assertPublicApisRoundTrip(effect);
+        assertHiddenApisParserSucceeds(xml, effect);
+        assertHiddenApisSerializerSucceeds(effect, xml);
+        assertHiddenApisRoundTrip(effect);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testWaveformEnvelopeEffect_badXml_throwsException() throws IOException {
+        // Incomplete XML
+        assertParseElementFails("""
+                <vibration-effect>
+                    <waveform-envelope-effect>
+                        <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
+                </vibration-effect>
+                """);
+        assertParseElementFails("""
+                <vibration-effect>
+                    <waveform-envelope-effect>
+                        <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10">
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """);
+        assertParseElementFails("""
+                <vibration-effect>
+                    <waveform-envelope-effect>
+                        <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
+                    </waveform-envelope-effect>
+                """);
+
+        // Bad vibration XML
+        assertParseElementFails("""
+                <vibration-effect>
+                        <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """);
+
+        // "waveform-envelope-effect" tag with invalid attributes
+        assertParseElementFails("""
+                <vibration-effect>
+                    <waveform-envelope-effect init_freq="20.0">
+                        <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testWaveformEnvelopeEffect_noControlPoints_allFail() throws IOException {
+        String xml = "<vibration-effect><waveform-envelope-effect/></vibration-effect>";
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = "<vibration-effect><waveform-envelope-effect> \n "
+                + "</waveform-envelope-effect></vibration-effect>";
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = "<vibration-effect><waveform-envelope-effect>invalid</waveform-envelope-effect"
+                + "></vibration-effect>";
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect>
+                    <control-point />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect initialFrequencyHz="20.0" />
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                <waveform-envelope-effect initialFrequencyHz="20.0"> \n </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect initialFrequencyHz="20.0">
+                    invalid
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect initialFrequencyHz="20.0">
+                    <control-point />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testWaveformEnvelopeEffect_badControlPointData_allFail() throws IOException {
+        String xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect>
+                    <control-point amplitude="-1" frequencyHz="80.0" durationMs="100" />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect>
+                    <control-point amplitude="0.2" frequencyHz="0" durationMs="100" />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect initialFrequencyHz="0">
+                    <control-point amplitude="0.2" frequencyHz="30" durationMs="100" />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect>
+                    <control-point amplitude="0.2" frequencyHz="80.0" durationMs="0" />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect>
+                    <control-point amplitude="0.2" />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testWaveformEnvelopeEffect_featureFlagDisabled_allFail() throws Exception {
+        VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder()
+                .setInitialFrequencyHz(20)
+                .addControlPoint(0.2f, 80f, 10)
+                .addControlPoint(0.5f, 150f, 10)
+                .build();
+
+        String xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect initialFrequencyHz="20.0">
+                        <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
+                        <control-point amplitude="0.5" frequencyHz="150.0" durationMs="10" />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+
+        assertPublicApisParserFails(xml);
+        assertPublicApisSerializerFails(effect);
+        assertHiddenApisParserFails(xml);
+        assertHiddenApisSerializerFails(effect);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testBasicEnvelopeEffect_allSucceed() throws Exception {
+        VibrationEffect effect = new VibrationEffect.BasicEnvelopeBuilder()
+                .addControlPoint(0.2f, 0.5f, 10)
+                .addControlPoint(0.0f, 1f, 10)
+                .build();
+
+        String xml = """
+                <vibration-effect>
+                    <basic-envelope-effect>
+                        <control-point intensity="0.2" sharpness="0.5" durationMs="10" />
+                        <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+
+        assertPublicApisParserSucceeds(xml, effect);
+        assertPublicApisSerializerSucceeds(effect, xml);
+        assertPublicApisRoundTrip(effect);
+        assertHiddenApisParserSucceeds(xml, effect);
+        assertHiddenApisSerializerSucceeds(effect, xml);
+        assertHiddenApisRoundTrip(effect);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testBasicEnvelopeEffectWithInitialSharpness_allSucceed() throws Exception {
+        VibrationEffect effect = new VibrationEffect.BasicEnvelopeBuilder()
+                .setInitialSharpness(0.3f)
+                .addControlPoint(0.2f, 0.5f, 10)
+                .addControlPoint(0.0f, 1f, 10)
+                .build();
+
+        String xml = """
+                <vibration-effect>
+                    <basic-envelope-effect initialSharpness="0.3">
+                        <control-point intensity="0.2" sharpness="0.5" durationMs="10" />
+                        <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+
+        assertPublicApisParserSucceeds(xml, effect);
+        assertPublicApisSerializerSucceeds(effect, xml);
+        assertPublicApisRoundTrip(effect);
+        assertHiddenApisParserSucceeds(xml, effect);
+        assertHiddenApisSerializerSucceeds(effect, xml);
+        assertHiddenApisRoundTrip(effect);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testBasicEnvelopeEffect_badXml_throwsException() throws IOException {
+        // Incomplete XML
+        assertParseElementFails("""
+                <vibration-effect>
+                    <basic-envelope-effect>
+                        <control-point intensity="0.2" sharpness="0.8" durationMs="10" />
+                        <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                </vibration-effect>
+                """);
+        assertParseElementFails("""
+                <vibration-effect>
+                    <basic-envelope-effect>
+                        <control-point intensity="0.2" sharpness="0.8" durationMs="10">
+                        <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """);
+        assertParseElementFails("""
+                <vibration-effect>
+                    <basic-envelope-effect>
+                        <control-point intensity="0.2" sharpness="0.8" durationMs="10" />
+                        <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                """);
+
+        // Bad vibration XML
+        assertParseElementFails("""
+                <vibration-effect>
+                        <control-point intensity="0.2" sharpness="0.8" durationMs="10" />
+                        <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """);
+
+        // "basic-envelope-effect" tag with invalid attributes
+        assertParseElementFails("""
+                <vibration-effect>
+                    <basic-envelope-effect init_sharp="20.0">
+                        <control-point intensity="0.2" sharpness="0.8" durationMs="10" />
+                        <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testBasicEnvelopeEffect_noControlPoints_allFail() throws IOException {
+        String xml = "<vibration-effect><basic-envelope-effect/></vibration-effect>";
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = "<vibration-effect><basic-envelope-effect> \n "
+                + "</basic-envelope-effect></vibration-effect>";
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = "<vibration-effect><basic-envelope-effect>invalid</basic-envelope-effect"
+                + "></vibration-effect>";
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <basic-envelope-effect>
+                    <control-point />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <basic-envelope-effect initialSharpness="0.2" />
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                <basic-envelope-effect initialSharpness="0.2"> \n </basic-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <basic-envelope-effect initialSharpness="0.2">
+                    invalid
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <basic-envelope-effect initialSharpness="0.2">
+                    <control-point />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testBasicEnvelopeEffect_badControlPointData_allFail() throws IOException {
+        String xml = """
+                <vibration-effect>
+                    <basic-envelope-effect>
+                    <control-point intensity="-1" sharpness="0.8" durationMs="100" />
+                    <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <basic-envelope-effect>
+                    <control-point intensity="0.2" sharpness="-1" durationMs="100" />
+                    <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <basic-envelope-effect initialSharpness="-1.0">
+                    <control-point intensity="0.2" sharpness="0.8" durationMs="0" />
+                    <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <basic-envelope-effect initialSharpness="2.0">
+                    <control-point intensity="0.2" sharpness="0.8" durationMs="0" />
+                    <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <basic-envelope-effect>
+                    <control-point intensity="0.2" sharpness="0.8" durationMs="10" />
+                    <control-point intensity="0.5" sharpness="0.8" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <basic-envelope-effect>
+                    <control-point intensity="0.2" />
+                    <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testBasicEnvelopeEffect_featureFlagDisabled_allFail() throws Exception {
+        VibrationEffect effect = new VibrationEffect.BasicEnvelopeBuilder()
+                .setInitialSharpness(0.3f)
+                .addControlPoint(0.2f, 0.5f, 10)
+                .addControlPoint(0.0f, 1f, 10)
+                .build();
+
+        String xml = """
+                <vibration-effect>
+                    <basic-envelope-effect initialSharpness="0.3">
+                        <control-point intensity="0.2" sharpness="0.5" durationMs="10" />
+                        <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+
+        assertPublicApisParserFails(xml);
+        assertPublicApisSerializerFails(effect);
+
+        assertHiddenApisParserFails(xml);
+        assertHiddenApisSerializerFails(effect);
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
     public void testVendorEffect_allSucceed() throws Exception {
         PersistableBundle vendorData = new PersistableBundle();
diff --git a/core/xsd/vibrator/vibration/schema/current.txt b/core/xsd/vibrator/vibration/schema/current.txt
index b4148d6..29f8d19 100644
--- a/core/xsd/vibrator/vibration/schema/current.txt
+++ b/core/xsd/vibrator/vibration/schema/current.txt
@@ -1,6 +1,23 @@
 // Signature format: 2.0
 package com.android.internal.vibrator.persistence {
 
+  public class BasicControlPoint {
+    ctor public BasicControlPoint();
+    method public long getDurationMs();
+    method public float getIntensity();
+    method public float getSharpness();
+    method public void setDurationMs(long);
+    method public void setIntensity(float);
+    method public void setSharpness(float);
+  }
+
+  public class BasicEnvelopeEffect {
+    ctor public BasicEnvelopeEffect();
+    method public java.util.List<com.android.internal.vibrator.persistence.BasicControlPoint> getControlPoint();
+    method public float getInitialSharpness();
+    method public void setInitialSharpness(float);
+  }
+
   public class PredefinedEffect {
     ctor public PredefinedEffect();
     method public com.android.internal.vibrator.persistence.PredefinedEffectName getName();
@@ -47,14 +64,18 @@
 
   public class VibrationEffect {
     ctor public VibrationEffect();
+    method public com.android.internal.vibrator.persistence.BasicEnvelopeEffect getBasicEnvelopeEffect_optional();
     method public com.android.internal.vibrator.persistence.PredefinedEffect getPredefinedEffect_optional();
     method public com.android.internal.vibrator.persistence.PrimitiveEffect getPrimitiveEffect_optional();
     method public byte[] getVendorEffect_optional();
     method public com.android.internal.vibrator.persistence.WaveformEffect getWaveformEffect_optional();
+    method public com.android.internal.vibrator.persistence.WaveformEnvelopeEffect getWaveformEnvelopeEffect_optional();
+    method public void setBasicEnvelopeEffect_optional(com.android.internal.vibrator.persistence.BasicEnvelopeEffect);
     method public void setPredefinedEffect_optional(com.android.internal.vibrator.persistence.PredefinedEffect);
     method public void setPrimitiveEffect_optional(com.android.internal.vibrator.persistence.PrimitiveEffect);
     method public void setVendorEffect_optional(byte[]);
     method public void setWaveformEffect_optional(com.android.internal.vibrator.persistence.WaveformEffect);
+    method public void setWaveformEnvelopeEffect_optional(com.android.internal.vibrator.persistence.WaveformEnvelopeEffect);
   }
 
   public class VibrationSelect {
@@ -67,6 +88,16 @@
     enum_constant public static final com.android.internal.vibrator.persistence.WaveformAmplitudeDefault _default;
   }
 
+  public class WaveformControlPoint {
+    ctor public WaveformControlPoint();
+    method public float getAmplitude();
+    method public long getDurationMs();
+    method public float getFrequencyHz();
+    method public void setAmplitude(float);
+    method public void setDurationMs(long);
+    method public void setFrequencyHz(float);
+  }
+
   public class WaveformEffect {
     ctor public WaveformEffect();
     method public com.android.internal.vibrator.persistence.WaveformEffect.Repeating getRepeating();
@@ -87,6 +118,13 @@
     method public void setDurationMs(java.math.BigInteger);
   }
 
+  public class WaveformEnvelopeEffect {
+    ctor public WaveformEnvelopeEffect();
+    method public java.util.List<com.android.internal.vibrator.persistence.WaveformControlPoint> getControlPoint();
+    method public float getInitialFrequencyHz();
+    method public void setInitialFrequencyHz(float);
+  }
+
   public class XmlParser {
     ctor public XmlParser();
     method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
diff --git a/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd b/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
index 910a9b7..b4df2d1 100644
--- a/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
+++ b/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
@@ -54,6 +54,12 @@
                 <xs:element name="primitive-effect" type="PrimitiveEffect"/>
             </xs:sequence>
 
+            <!-- Waveform envelope effect -->
+            <xs:element name="waveform-envelope-effect" type="WaveformEnvelopeEffect"/>
+
+            <!-- Basic envelope effect -->
+            <xs:element name="basic-envelope-effect" type="BasicEnvelopeEffect"/>
+
         </xs:choice>
     </xs:complexType>
 
@@ -180,4 +186,54 @@
         </xs:restriction>
     </xs:simpleType>
 
+    <!-- Definition of a waveform envelope effect -->
+    <xs:complexType name="WaveformEnvelopeEffect">
+        <xs:sequence>
+            <xs:element name="control-point" maxOccurs="unbounded" minOccurs="1"
+                type="WaveformControlPoint" />
+        </xs:sequence>
+        <xs:attribute name="initialFrequencyHz" type="ControlPointFrequency" />
+    </xs:complexType>
+
+    <!-- Definition of a basic envelope effect -->
+    <xs:complexType name="BasicEnvelopeEffect">
+        <xs:sequence>
+            <xs:element name="control-point" maxOccurs="unbounded" minOccurs="1"
+                type="BasicControlPoint" />
+        </xs:sequence>
+        <xs:attribute name="initialSharpness" type="NormalizedControlPointUnit" />
+    </xs:complexType>
+
+    <xs:complexType name="WaveformControlPoint">
+        <xs:attribute name="amplitude" type="NormalizedControlPointUnit" use="required"/>
+        <xs:attribute name="frequencyHz" type="ControlPointFrequency" use="required"/>
+        <xs:attribute name="durationMs" type="PositiveLong" use="required"/>
+    </xs:complexType>
+
+    <xs:complexType name="BasicControlPoint">
+        <xs:attribute name="intensity" type="NormalizedControlPointUnit" use="required"/>
+        <xs:attribute name="sharpness" type="NormalizedControlPointUnit" use="required"/>
+        <xs:attribute name="durationMs" type="PositiveLong" use="required"/>
+    </xs:complexType>
+
+    <xs:simpleType name="ControlPointFrequency">
+        <xs:restriction base="xs:float">
+            <xs:minExclusive value="0"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <xs:simpleType name="PositiveLong">
+        <xs:restriction base="xs:long">
+            <xs:minExclusive value="0"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <!-- Normalized control point unit float in [0,1] -->
+    <xs:simpleType name="NormalizedControlPointUnit">
+        <xs:restriction base="xs:float">
+            <xs:minInclusive value="0"/>
+            <xs:maxInclusive value="1"/>
+        </xs:restriction>
+    </xs:simpleType>
+
 </xs:schema>
diff --git a/core/xsd/vibrator/vibration/vibration.xsd b/core/xsd/vibrator/vibration/vibration.xsd
index 3c8e016..fba966f 100644
--- a/core/xsd/vibrator/vibration/vibration.xsd
+++ b/core/xsd/vibrator/vibration/vibration.xsd
@@ -52,6 +52,12 @@
                 <xs:element name="primitive-effect" type="PrimitiveEffect"/>
             </xs:sequence>
 
+            <!-- Waveform envelope effect -->
+            <xs:element name="waveform-envelope-effect" type="WaveformEnvelopeEffect"/>
+
+            <!-- Basic envelope effect -->
+            <xs:element name="basic-envelope-effect" type="BasicEnvelopeEffect"/>
+
         </xs:choice>
     </xs:complexType>
 
@@ -157,4 +163,54 @@
         </xs:restriction>
     </xs:simpleType>
 
+    <!-- Definition of a waveform envelope effect -->
+    <xs:complexType name="WaveformEnvelopeEffect">
+        <xs:sequence>
+            <xs:element name="control-point" maxOccurs="unbounded" minOccurs="1"
+                type="WaveformControlPoint" />
+        </xs:sequence>
+        <xs:attribute name="initialFrequencyHz" type="ControlPointFrequency" />
+    </xs:complexType>
+
+    <!-- Definition of a basic envelope effect -->
+    <xs:complexType name="BasicEnvelopeEffect">
+        <xs:sequence>
+            <xs:element name="control-point" maxOccurs="unbounded" minOccurs="1"
+                type="BasicControlPoint" />
+        </xs:sequence>
+        <xs:attribute name="initialSharpness" type="NormalizedControlPointUnit" />
+    </xs:complexType>
+
+    <xs:complexType name="WaveformControlPoint">
+        <xs:attribute name="amplitude" type="NormalizedControlPointUnit" use="required"/>
+        <xs:attribute name="frequencyHz" type="ControlPointFrequency" use="required"/>
+        <xs:attribute name="durationMs" type="PositiveLong" use="required"/>
+    </xs:complexType>
+
+    <xs:complexType name="BasicControlPoint">
+        <xs:attribute name="intensity" type="NormalizedControlPointUnit" use="required"/>
+        <xs:attribute name="sharpness" type="NormalizedControlPointUnit" use="required"/>
+        <xs:attribute name="durationMs" type="PositiveLong" use="required"/>
+    </xs:complexType>
+
+    <xs:simpleType name="ControlPointFrequency">
+        <xs:restriction base="xs:float">
+            <xs:minExclusive value="0"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <xs:simpleType name="PositiveLong">
+        <xs:restriction base="xs:long">
+            <xs:minExclusive value="0"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <!-- Normalized control point unit float in [0,1] -->
+    <xs:simpleType name="NormalizedControlPointUnit">
+        <xs:restriction base="xs:float">
+            <xs:minInclusive value="0"/>
+            <xs:maxInclusive value="1"/>
+        </xs:restriction>
+    </xs:simpleType>
+
 </xs:schema>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt
new file mode 100644
index 0000000..9e3edf6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2024 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.wm.shell.compatui.letterbox
+
+import com.android.wm.shell.dagger.WMSingleton
+import javax.inject.Inject
+
+/**
+ * Encapsulate the logic related to the use of a single or multiple surfaces when
+ * implementing letterbox in shell.
+ */
+@WMSingleton
+class LetterboxControllerStrategy @Inject constructor() {
+
+    // Different letterbox implementation modes.
+    enum class LetterboxMode { SINGLE_SURFACE, MULTIPLE_SURFACES }
+
+    @Volatile
+    private var currentMode: LetterboxMode = LetterboxMode.SINGLE_SURFACE
+
+    fun configureLetterboxMode() {
+        // TODO(b/377875146): Define criteria for switching between [LetterboxMode]s.
+        currentMode = if (android.os.SystemProperties.getInt(
+                "multi_interface",
+                0
+            ) == 0
+        ) {
+            LetterboxMode.SINGLE_SURFACE
+        } else {
+            LetterboxMode.MULTIPLE_SURFACES
+        }
+    }
+
+    /**
+     * @return The specific mode to use for implementing letterboxing for the given [request].
+     */
+    fun getLetterboxImplementationMode(): LetterboxMode = currentMode
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt
index a48dc8b..4718071 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt
@@ -35,7 +35,8 @@
     shellInit: ShellInit,
     private val transitions: Transitions,
     private val letterboxController: LetterboxController,
-    private val transitionStateHolder: TransitionStateHolder
+    private val transitionStateHolder: TransitionStateHolder,
+    private val letterboxModeStrategy: LetterboxControllerStrategy
 ) : Transitions.TransitionObserver {
 
     companion object {
@@ -63,7 +64,6 @@
         // We recognise the operation to execute and delegate to the LetterboxController
         // the related operation.
         // TODO(b/377875151): Identify Desktop Windowing Transactions.
-        // TODO(b/377857898): Handling multiple surfaces
         // TODO(b/371500295): Handle input events detection.
         for (change in info.changes) {
             change.taskInfo?.let { ti ->
@@ -83,6 +83,7 @@
                     } else {
                         val isTopActivityLetterboxed = ti.appCompatTaskInfo.isTopActivityLetterboxed
                         if (isTopActivityLetterboxed) {
+                            letterboxModeStrategy.configureLetterboxMode()
                             createLetterboxSurface(
                                 key,
                                 startTransaction,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxUtils.kt
new file mode 100644
index 0000000..ef964f4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxUtils.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2024 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.wm.shell.compatui.letterbox
+
+import android.graphics.Rect
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+
+/**
+ * Creates a [LetterboxController] which is the composition of other two [LetterboxController].
+ * It basically invokes the method on both of them.
+ */
+infix fun LetterboxController.append(other: LetterboxController) = object : LetterboxController {
+    override fun createLetterboxSurface(
+        key: LetterboxKey,
+        transaction: Transaction,
+        parentLeash: SurfaceControl
+    ) {
+        this@append.createLetterboxSurface(key, transaction, parentLeash)
+        other.createLetterboxSurface(key, transaction, parentLeash)
+    }
+
+    override fun destroyLetterboxSurface(
+        key: LetterboxKey,
+        transaction: Transaction
+    ) {
+        this@append.destroyLetterboxSurface(key, transaction)
+        other.destroyLetterboxSurface(key, transaction)
+    }
+
+    override fun updateLetterboxSurfaceVisibility(
+        key: LetterboxKey,
+        transaction: Transaction,
+        visible: Boolean
+    ) {
+        this@append.updateLetterboxSurfaceVisibility(key, transaction, visible)
+        other.updateLetterboxSurfaceVisibility(key, transaction, visible)
+    }
+
+    override fun updateLetterboxSurfaceBounds(
+        key: LetterboxKey,
+        transaction: Transaction,
+        taskBounds: Rect,
+        activityBounds: Rect
+    ) {
+        this@append.updateLetterboxSurfaceBounds(key, transaction, taskBounds, activityBounds)
+        other.updateLetterboxSurfaceBounds(key, transaction, taskBounds, activityBounds)
+    }
+
+    override fun dump() {
+        this@append.dump()
+        other.dump()
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxController.kt
new file mode 100644
index 0000000..8d06570
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxController.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2024 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.wm.shell.compatui.letterbox
+
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.MULTIPLE_SURFACES
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.SINGLE_SURFACE
+import com.android.wm.shell.dagger.WMSingleton
+import javax.inject.Inject
+
+/**
+ * [LetterboxController] implementation working as coordinator of other [LetterboxController]
+ * implementations.
+ */
+@WMSingleton
+class MixedLetterboxController @Inject constructor(
+    private val singleSurfaceController: SingleSurfaceLetterboxController,
+    private val multipleSurfaceController: MultiSurfaceLetterboxController,
+    private val controllerStrategy: LetterboxControllerStrategy
+) : LetterboxController by singleSurfaceController append multipleSurfaceController {
+
+    override fun createLetterboxSurface(
+        key: LetterboxKey,
+        transaction: Transaction,
+        parentLeash: SurfaceControl
+    ) {
+        when (controllerStrategy.getLetterboxImplementationMode()) {
+            SINGLE_SURFACE -> {
+                multipleSurfaceController.destroyLetterboxSurface(key, transaction)
+                singleSurfaceController.createLetterboxSurface(key, transaction, parentLeash)
+            }
+
+            MULTIPLE_SURFACES -> {
+                singleSurfaceController.destroyLetterboxSurface(key, transaction)
+                multipleSurfaceController.createLetterboxSurface(key, transaction, parentLeash)
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index df2b849..806be8c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -74,8 +74,9 @@
 import com.android.wm.shell.common.transition.TransitionStateHolder;
 import com.android.wm.shell.compatui.letterbox.LetterboxCommandHandler;
 import com.android.wm.shell.compatui.letterbox.LetterboxController;
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy;
 import com.android.wm.shell.compatui.letterbox.LetterboxTransitionObserver;
-import com.android.wm.shell.compatui.letterbox.SingleSurfaceLetterboxController;
+import com.android.wm.shell.compatui.letterbox.MixedLetterboxController;
 import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
 import com.android.wm.shell.dagger.pip.PipModule;
 import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler;
@@ -1338,14 +1339,15 @@
             @NonNull ShellInit shellInit,
             @NonNull Transitions transitions,
             @NonNull LetterboxController letterboxController,
-            @NonNull TransitionStateHolder transitionStateHolder
+            @NonNull TransitionStateHolder transitionStateHolder,
+            @NonNull LetterboxControllerStrategy letterboxControllerStrategy
     ) {
         return new LetterboxTransitionObserver(shellInit, transitions, letterboxController,
-                transitionStateHolder);
+                transitionStateHolder, letterboxControllerStrategy);
     }
 
     @WMSingleton
     @Binds
     abstract LetterboxController bindsLetterboxController(
-            SingleSurfaceLetterboxController letterboxController);
+            MixedLetterboxController letterboxController);
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt
index f9f01bc..95a0c82 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt
@@ -98,6 +98,10 @@
         activityBounds = activityBounds
     )
 
+    fun invokeDump() {
+        letterboxController.dump()
+    }
+
     fun checkSurfaceBuilderInvoked(times: Int = 1, name: String = "", callSite: String = "") {
         verify(surfaceBuilder, times(times)).createSurface(
             eq(transaction),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt
index 8825bb4..78bb721 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt
@@ -96,6 +96,7 @@
 
                 validateOutput {
                     r.creationEventDetected(expected = false)
+                    r.configureStrategyInvoked(expected = false)
                     r.visibilityEventDetected(expected = false)
                     r.destroyEventDetected(expected = false)
                     r.updateSurfaceBoundsEventDetected(expected = false)
@@ -123,6 +124,7 @@
 
                 validateOutput {
                     r.creationEventDetected(expected = true)
+                    r.configureStrategyInvoked(expected = true)
                     r.visibilityEventDetected(expected = true, visible = true)
                     r.destroyEventDetected(expected = false)
                     r.updateSurfaceBoundsEventDetected(
@@ -217,6 +219,7 @@
         private val letterboxController: LetterboxController
         private val letterboxObserver: LetterboxTransitionObserver
         private val transitionStateHolder: TransitionStateHolder
+        private val letterboxStrategy: LetterboxControllerStrategy
 
         val observerFactory: () -> LetterboxTransitionObserver
 
@@ -225,6 +228,7 @@
             shellInit = ShellInit(executor)
             transitions = mock<Transitions>()
             letterboxController = mock<LetterboxController>()
+            letterboxStrategy = mock<LetterboxControllerStrategy>()
             transitionStateHolder =
                 TransitionStateHolder(shellInit, mock<RecentsTransitionHandler>())
             spyOn(transitionStateHolder)
@@ -233,7 +237,8 @@
                     shellInit,
                     transitions,
                     letterboxController,
-                    transitionStateHolder
+                    transitionStateHolder,
+                    letterboxStrategy
                 )
             observerFactory = { letterboxObserver }
         }
@@ -302,6 +307,9 @@
             eq(activityBounds)
         )
 
+        fun configureStrategyInvoked(expected: Boolean) =
+            verify(letterboxStrategy, expected.asMode()).configureLetterboxMode()
+
         fun createTopActivityChange(
             inputBuilder: TransitionObserverInputBuilder,
             isLetterboxed: Boolean = true,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt
new file mode 100644
index 0000000..06b8052
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2024 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.wm.shell.compatui.letterbox
+
+import android.content.Context
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import java.util.function.Consumer
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+/**
+ * Tests for [LetterboxUtils].
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:LetterboxUtilsTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LetterboxUtilsTest : ShellTestCase() {
+
+    val firstLetterboxController = mock<LetterboxController>()
+    val secondLetterboxController = mock<LetterboxController>()
+    val thirdLetterboxController = mock<LetterboxController>()
+
+    private val letterboxControllerBuilder: (LetterboxSurfaceBuilder) -> LetterboxController =
+        { _ ->
+            firstLetterboxController.append(secondLetterboxController)
+                .append(thirdLetterboxController)
+        }
+
+    @Test
+    fun `Appended LetterboxController invoked creation on all the controllers`() {
+        runTestScenario { r ->
+            r.sendCreateSurfaceRequest()
+
+            r.verifyCreateSurfaceInvokedWithRequest(target = firstLetterboxController)
+            r.verifyCreateSurfaceInvokedWithRequest(target = secondLetterboxController)
+            r.verifyCreateSurfaceInvokedWithRequest(target = thirdLetterboxController)
+        }
+    }
+
+    @Test
+    fun `Appended LetterboxController invoked destroy on all the controllers`() {
+        runTestScenario { r ->
+            r.sendDestroySurfaceRequest()
+            r.verifyDestroySurfaceInvokedWithRequest(target = firstLetterboxController)
+            r.verifyDestroySurfaceInvokedWithRequest(target = secondLetterboxController)
+            r.verifyDestroySurfaceInvokedWithRequest(target = thirdLetterboxController)
+        }
+    }
+
+    @Test
+    fun `Appended LetterboxController invoked update visibility on all the controllers`() {
+        runTestScenario { r ->
+            r.sendUpdateSurfaceVisibilityRequest(visible = true)
+            r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = firstLetterboxController)
+            r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = secondLetterboxController)
+            r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = thirdLetterboxController)
+        }
+    }
+
+    @Test
+    fun `Appended LetterboxController invoked update bounds on all the controllers`() {
+        runTestScenario { r ->
+            r.sendUpdateSurfaceBoundsRequest(taskBounds = Rect(), activityBounds = Rect())
+            r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = firstLetterboxController)
+            r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = secondLetterboxController)
+            r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = thirdLetterboxController)
+        }
+    }
+
+    @Test
+    fun `Appended LetterboxController invoked update dump on all the controllers`() {
+        runTestScenario { r ->
+            r.invokeDump()
+            r.verifyDumpInvoked(target = firstLetterboxController)
+            r.verifyDumpInvoked(target = secondLetterboxController)
+            r.verifyDumpInvoked(target = thirdLetterboxController)
+        }
+    }
+
+    /**
+     * Runs a test scenario providing a Robot.
+     */
+    fun runTestScenario(consumer: Consumer<AppendLetterboxControllerRobotTest>) {
+        val robot = AppendLetterboxControllerRobotTest(mContext, letterboxControllerBuilder)
+        consumer.accept(robot)
+    }
+
+    class AppendLetterboxControllerRobotTest(
+        ctx: Context,
+        builder: (LetterboxSurfaceBuilder) -> LetterboxController
+    ) : LetterboxControllerRobotTest(ctx, builder) {
+
+        fun verifyCreateSurfaceInvokedWithRequest(
+            target: LetterboxController,
+            times: Int = 1
+        ) {
+            verify(target, times(times)).createLetterboxSurface(any(), any(), any())
+        }
+
+        fun verifyDestroySurfaceInvokedWithRequest(
+            target: LetterboxController,
+            times: Int = 1
+        ) {
+            verify(target, times(times)).destroyLetterboxSurface(any(), any())
+        }
+
+        fun verifyUpdateVisibilitySurfaceInvokedWithRequest(
+            target: LetterboxController,
+            times: Int = 1
+        ) {
+            verify(target, times(times)).updateLetterboxSurfaceVisibility(any(), any(), any())
+        }
+
+        fun verifyUpdateSurfaceBoundsInvokedWithRequest(
+            target: LetterboxController,
+            times: Int = 1
+        ) {
+            verify(target, times(times)).updateLetterboxSurfaceBounds(any(), any(), any(), any())
+        }
+
+        fun verifyDumpInvoked(
+            target: LetterboxController,
+            times: Int = 1
+        ) {
+            verify(target, times(times)).dump()
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxControllerTest.kt
new file mode 100644
index 0000000..e6bff4c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxControllerTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2024 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.wm.shell.compatui.letterbox
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode
+import java.util.function.Consumer
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+/**
+ * Tests for [MixedLetterboxController].
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:MixedLetterboxControllerTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MixedLetterboxControllerTest : ShellTestCase() {
+
+    @Test
+    fun `When strategy is SINGLE_SURFACE and a create request is sent multi are destroyed`() {
+        runTestScenario { r ->
+            r.configureStrategyFor(LetterboxMode.SINGLE_SURFACE)
+            r.sendCreateSurfaceRequest()
+            r.checkCreateInvokedOnSingleController()
+            r.checkDestroyInvokedOnMultiController()
+        }
+    }
+
+    @Test
+    fun `When strategy is MULTIPLE_SURFACES and a create request is sent single is destroyed`() {
+        runTestScenario { r ->
+            r.configureStrategyFor(LetterboxMode.MULTIPLE_SURFACES)
+            r.sendCreateSurfaceRequest()
+            r.checkDestroyInvokedOnSingleController()
+            r.checkCreateInvokedOnMultiController()
+        }
+    }
+
+    /**
+     * Runs a test scenario providing a Robot.
+     */
+    fun runTestScenario(consumer: Consumer<MixedLetterboxControllerRobotTest>) {
+        val robot = MixedLetterboxControllerRobotTest(mContext, ObjectToTestHolder())
+        consumer.accept(robot)
+    }
+
+    class MixedLetterboxControllerRobotTest(
+        ctx: Context,
+        private val objectToTestHolder: ObjectToTestHolder
+    ) : LetterboxControllerRobotTest(ctx, objectToTestHolder.controllerBuilder) {
+
+        fun configureStrategyFor(letterboxMode: LetterboxMode) {
+            doReturn(letterboxMode).`when`(objectToTestHolder.controllerStrategy)
+                .getLetterboxImplementationMode()
+        }
+
+        fun checkCreateInvokedOnSingleController(times: Int = 1) {
+            verify(
+                objectToTestHolder.singleLetterboxController,
+                times(times)
+            ).createLetterboxSurface(any(), any(), any())
+        }
+
+        fun checkCreateInvokedOnMultiController(times: Int = 1) {
+            verify(
+                objectToTestHolder.multipleLetterboxController,
+                times(times)
+            ).createLetterboxSurface(any(), any(), any())
+        }
+
+        fun checkDestroyInvokedOnSingleController(times: Int = 1) {
+            verify(
+                objectToTestHolder.singleLetterboxController,
+                times(times)
+            ).destroyLetterboxSurface(any(), any())
+        }
+
+        fun checkDestroyInvokedOnMultiController(times: Int = 1) {
+            verify(
+                objectToTestHolder.multipleLetterboxController,
+                times(times)
+            ).destroyLetterboxSurface(any(), any())
+        }
+    }
+
+    data class ObjectToTestHolder(
+        val singleLetterboxController: SingleSurfaceLetterboxController =
+        mock<SingleSurfaceLetterboxController>(),
+        val multipleLetterboxController: MultiSurfaceLetterboxController =
+        mock<MultiSurfaceLetterboxController>(),
+        val controllerStrategy: LetterboxControllerStrategy = mock<LetterboxControllerStrategy>()
+    ) {
+
+        private val mixedController =
+            MixedLetterboxController(
+                singleLetterboxController,
+                multipleLetterboxController,
+                controllerStrategy
+            )
+
+        val controllerBuilder: (LetterboxSurfaceBuilder) -> LetterboxController =
+            { _ -> mixedController }
+    }
+}
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index d993b87..28d96e3 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -28,12 +28,14 @@
 #define INDENT "  "
 #define INDENT2 "    "
 
+namespace android {
+
 namespace {
+
 // Time to spend fading out the pointer completely.
 const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
-} // namespace
 
-namespace android {
+} // namespace
 
 // --- MouseCursorController ---
 
@@ -64,17 +66,23 @@
     mLocked.pointerSprite.clear();
 }
 
-void MouseCursorController::move(float deltaX, float deltaY) {
+FloatPoint MouseCursorController::move(float deltaX, float deltaY) {
 #if DEBUG_MOUSE_CURSOR_UPDATES
     ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY);
 #endif
     if (deltaX == 0.0f && deltaY == 0.0f) {
-        return;
+        return {0, 0};
     }
 
+    // When transition occurs, the MouseCursorController object may or may not be deleted, depending
+    // if there's another display on the other side of the transition. At this point we still need
+    // to move the cursor to the boundary.
     std::scoped_lock lock(mLock);
-
-    setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY);
+    const FloatPoint position{mLocked.pointerX + deltaX, mLocked.pointerY + deltaY};
+    setPositionLocked(position.x, position.y);
+    // The amount of the delta that was not consumed as a result of the cursor
+    // hitting the edge of the display.
+    return {position.x - mLocked.pointerX, position.y - mLocked.pointerY};
 }
 
 void MouseCursorController::setPosition(float x, float y) {
@@ -85,19 +93,23 @@
     setPositionLocked(x, y);
 }
 
-void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) {
-    const auto& v = mLocked.viewport;
-    if (!v.isValid()) return;
-
+FloatRect MouseCursorController::getBoundsLocked() REQUIRES(mLock) {
     // The valid bounds for a mouse cursor. Since the right and bottom edges are considered outside
     // the display, clip the bounds by one pixel instead of letting the cursor get arbitrarily
     // close to the outside edge.
-    const FloatRect bounds{
+    return FloatRect{
             static_cast<float>(mLocked.viewport.logicalLeft),
             static_cast<float>(mLocked.viewport.logicalTop),
             static_cast<float>(mLocked.viewport.logicalRight - 1),
             static_cast<float>(mLocked.viewport.logicalBottom - 1),
     };
+}
+
+void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) {
+    const auto& v = mLocked.viewport;
+    if (!v.isValid()) return;
+
+    const FloatRect bounds = getBoundsLocked();
     mLocked.pointerX = std::max(bounds.left, std::min(bounds.right, x));
     mLocked.pointerY = std::max(bounds.top, std::min(bounds.bottom, y));
 
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index 12b31a8..e14a55d 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -20,9 +20,6 @@
 #include <gui/DisplayEventReceiver.h>
 #include <input/DisplayViewport.h>
 #include <input/Input.h>
-#include <utils/BitSet.h>
-#include <utils/Looper.h>
-#include <utils/RefBase.h>
 
 #include <functional>
 #include <map>
@@ -43,7 +40,8 @@
     MouseCursorController(PointerControllerContext& context);
     ~MouseCursorController();
 
-    void move(float deltaX, float deltaY);
+    // Move the pointer and return unconsumed delta
+    FloatPoint move(float deltaX, float deltaY);
     void setPosition(float x, float y);
     FloatPoint getPosition() const;
     ui::LogicalDisplayId getDisplayId() const;
@@ -113,6 +111,7 @@
     bool doFadingAnimationLocked(nsecs_t timestamp);
 
     void startAnimationLocked();
+    FloatRect getBoundsLocked();
 };
 
 } // namespace android
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 78d7d3a..a713f1d 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -138,15 +138,19 @@
     return mDisplayInfoListener->mLock;
 }
 
-void PointerController::move(float deltaX, float deltaY) {
+FloatPoint PointerController::move(float deltaX, float deltaY) {
     const ui::LogicalDisplayId displayId = mCursorController.getDisplayId();
-    vec2 transformed;
+    ui::Transform transform;
     {
         std::scoped_lock lock(getLock());
-        const auto& transform = getTransformForDisplayLocked(displayId);
-        transformed = transformWithoutTranslation(transform, {deltaX, deltaY});
+        transform = getTransformForDisplayLocked(displayId);
     }
-    mCursorController.move(transformed.x, transformed.y);
+
+    const vec2 transformed = transformWithoutTranslation(transform, {deltaX, deltaY});
+
+    const FloatPoint unconsumedDelta = mCursorController.move(transformed.x, transformed.y);
+    return FloatPoint(transformWithoutTranslation(transform.inverse(),
+                                                  {unconsumedDelta.x, unconsumedDelta.y}));
 }
 
 void PointerController::setPosition(float x, float y) {
@@ -295,6 +299,11 @@
     mCursorController.setSkipScreenshot(false);
 }
 
+ui::Transform PointerController::getDisplayTransform() const {
+    std::scoped_lock lock(getLock());
+    return getTransformForDisplayLocked(mLocked.pointerDisplayId);
+}
+
 void PointerController::doInactivityTimeout() {
     fade(Transition::GRADUAL);
 }
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index ee8d121..8b33190 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -51,7 +51,7 @@
 
     ~PointerController() override;
 
-    void move(float deltaX, float deltaY) override;
+    FloatPoint move(float deltaX, float deltaY) override;
     void setPosition(float x, float y) override;
     FloatPoint getPosition() const override;
     ui::LogicalDisplayId getDisplayId() const override;
@@ -67,6 +67,7 @@
     void setCustomPointerIcon(const SpriteIcon& icon) override;
     void setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) override;
     void clearSkipScreenshotFlags() override;
+    ui::Transform getDisplayTransform() const override;
 
     virtual void setInactivityTimeout(InactivityTimeout inactivityTimeout);
     void doInactivityTimeout();
@@ -165,7 +166,7 @@
 
     ~TouchPointerController() override;
 
-    void move(float, float) override {
+    FloatPoint move(float, float) override {
         LOG_ALWAYS_FATAL("Should not be called");
     }
     void setPosition(float, float) override {
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index 5b00fca..80c934a 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -40,6 +40,8 @@
     CURSOR_TYPE_CUSTOM = -1,
 };
 
+static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
+
 using ::testing::AllOf;
 using ::testing::Field;
 using ::testing::NiceMock;
@@ -399,6 +401,135 @@
                          testing::Values(PointerControllerInterface::ControllerType::MOUSE,
                                          PointerControllerInterface::ControllerType::STYLUS));
 
+class MousePointerControllerTest : public PointerControllerTest {
+protected:
+    MousePointerControllerTest() {
+        sp<MockSprite> testPointerSprite(new NiceMock<MockSprite>);
+        EXPECT_CALL(*mSpriteController, createSprite).WillOnce(Return(testPointerSprite));
+
+        // create a mouse pointer controller
+        mPointerController =
+                PointerController::create(mPolicy, mLooper, *mSpriteController,
+                                          PointerControllerInterface::ControllerType::MOUSE);
+
+        // set display viewport
+        DisplayViewport viewport;
+        viewport.displayId = ui::LogicalDisplayId::DEFAULT;
+        viewport.logicalRight = 5;
+        viewport.logicalBottom = 5;
+        viewport.physicalRight = 5;
+        viewport.physicalBottom = 5;
+        viewport.deviceWidth = 5;
+        viewport.deviceHeight = 5;
+        mPointerController->setDisplayViewport(viewport);
+    }
+};
+
+struct MousePointerControllerTestParam {
+    vec2 startPosition;
+    vec2 delta;
+    vec2 endPosition;
+    vec2 unconsumedDelta;
+};
+
+class PointerControllerViewportTransitionTest
+      : public MousePointerControllerTest,
+        public testing::WithParamInterface<MousePointerControllerTestParam> {};
+
+TEST_P(PointerControllerViewportTransitionTest, testPointerViewportTransition) {
+    const auto& params = GetParam();
+
+    mPointerController->setPosition(params.startPosition.x, params.startPosition.y);
+    auto unconsumedDelta = mPointerController->move(params.delta.x, params.delta.y);
+
+    auto position = mPointerController->getPosition();
+    EXPECT_NEAR(position.x, params.endPosition.x, EPSILON);
+    EXPECT_NEAR(position.y, params.endPosition.y, EPSILON);
+    EXPECT_NEAR(unconsumedDelta.x, params.unconsumedDelta.x, EPSILON);
+    EXPECT_NEAR(unconsumedDelta.y, params.unconsumedDelta.y, EPSILON);
+}
+
+INSTANTIATE_TEST_SUITE_P(PointerControllerViewportTransitionTest,
+                         PointerControllerViewportTransitionTest,
+                         testing::Values(
+                                 // no transition
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {2.0f, 2.0f},
+                                                                 {4.0f, 4.0f},
+                                                                 {0.0f, 0.0f}},
+                                 // right boundary
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {3.0f, 0.0f},
+                                                                 {4.0f, 2.0f},
+                                                                 {1.0f, 0.0f}},
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {3.0f, -1.0f},
+                                                                 {4.0f, 1.0f},
+                                                                 {1.0f, 0.0f}},
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {3.0f, 1.0f},
+                                                                 {4.0f, 3.0f},
+                                                                 {1.0f, 0.0f}},
+                                 // left boundary
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {-3.0f, 0.0f},
+                                                                 {0.0f, 2.0f},
+                                                                 {-1.0f, 0.0f}},
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {-3.0f, -1.0f},
+                                                                 {0.0f, 1.0f},
+                                                                 {-1.0f, 0.0f}},
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {-3.0f, 1.0f},
+                                                                 {0.0f, 3.0f},
+                                                                 {-1.0f, 0.0f}},
+                                 // bottom boundary
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {0.0f, 3.0f},
+                                                                 {2.0f, 4.0f},
+                                                                 {0.0f, 1.0f}},
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {-1.0f, 3.0f},
+                                                                 {1.0f, 4.0f},
+                                                                 {0.0f, 1.0f}},
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {1.0f, 3.0f},
+                                                                 {3.0f, 4.0f},
+                                                                 {0.0f, 1.0f}},
+                                 // top boundary
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {0.0f, -3.0f},
+                                                                 {2.0f, 0.0f},
+                                                                 {0.0f, -1.0f}},
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {-1.0f, -3.0f},
+                                                                 {1.0f, 0.0f},
+                                                                 {0.0f, -1.0f}},
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {1.0f, -3.0f},
+                                                                 {3.0f, 0.0f},
+                                                                 {0.0f, -1.0f}},
+                                 // top-left corner
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {-3.0f, -3.0f},
+                                                                 {0.0f, 0.0f},
+                                                                 {-1.0f, -1.0f}},
+                                 // top-right corner
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {3.0f, -3.0f},
+                                                                 {4.0f, 0.0f},
+                                                                 {1.0f, -1.0f}},
+                                 // bottom-right corner
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {3.0f, 3.0f},
+                                                                 {4.0f, 4.0f},
+                                                                 {1.0f, 1.0f}},
+                                 // bottom-left corner
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {-3.0f, 3.0f},
+                                                                 {0.0f, 4.0f},
+                                                                 {-1.0f, 1.0f}}));
+
 class PointerControllerWindowInfoListenerTest : public Test {};
 
 TEST_F(PointerControllerWindowInfoListenerTest,
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index 4f601f0..db1f6a2 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -107,7 +107,7 @@
  *     &lt;intent-filter&gt;
  *         &lt;action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/&gt;
  *     &lt;/intent-filter&gt;
- *     &lt;meta-data android:name="android.nfc.cardemulation.host_apdu_ervice" android:resource="@xml/apduservice"/&gt;
+ *     &lt;meta-data android:name="android.nfc.cardemulation.host_apdu_service" android:resource="@xml/apduservice"/&gt;
  * &lt;/service&gt;</pre>
  *
  * This meta-data tag points to an apduservice.xml file.
diff --git a/nfc/java/android/nfc/cardemulation/OffHostApduService.java b/nfc/java/android/nfc/cardemulation/OffHostApduService.java
index 2286e84..8d8a172 100644
--- a/nfc/java/android/nfc/cardemulation/OffHostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/OffHostApduService.java
@@ -96,7 +96,7 @@
  *     &lt;intent-filter&gt;
  *         &lt;action android:name="android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE"/&gt;
  *     &lt;/intent-filter&gt;
- *     &lt;meta-data android:name="android.nfc.cardemulation.off_host_apdu_ervice" android:resource="@xml/apduservice"/&gt;
+ *     &lt;meta-data android:name="android.nfc.cardemulation.off_host_apdu_service" android:resource="@xml/apduservice"/&gt;
  * &lt;/service&gt;</pre>
  *
  * This meta-data tag points to an apduservice.xml file.
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceStateProviders.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceStateProviders.kt
index 366d54b..7436ac1 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceStateProviders.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceStateProviders.kt
@@ -123,7 +123,12 @@
      *
      * @return true if the result is handled
      */
-    fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean = false
+    fun onActivityResult(
+        context: PreferenceLifecycleContext,
+        requestCode: Int,
+        resultCode: Int,
+        data: Intent?,
+    ): Boolean = false
 }
 
 /**
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
index 153089e..03b225e 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
@@ -194,8 +194,8 @@
     }
 
     fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
-        for (preference in lifecycleAwarePreferences) {
-            if (preference.onActivityResult(requestCode, resultCode, data)) break
+        lifecycleAwarePreferences.firstOrNull {
+            it.onActivityResult(preferenceLifecycleContext, requestCode, resultCode, data)
         }
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorTest.kt
new file mode 100644
index 0000000..8acf538
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorTest.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2024 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.systemui.volume.dialog.domain.interactor
+
+import android.app.ActivityManager
+import android.media.AudioManager
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.plugins.fakeVolumeDialogController
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.data.repository.volumeDialogVisibilityRepository
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class VolumeDialogSafetyWarningInteractorTest : SysuiTestCase() {
+
+    private val kosmos: Kosmos = testKosmos()
+
+    private lateinit var underTest: VolumeDialogSafetyWarningInteractor
+
+    @Before
+    fun setup() {
+        kosmos.useUnconfinedTestDispatcher()
+        underTest = kosmos.volumeDialogSafetyWarningInteractor
+    }
+
+    @Test
+    fun dismiss_isShowingSafetyWarning_isFalse() =
+        with(kosmos) {
+            runTest {
+                val isShowingSafetyWarning by collectLastValue(underTest.isShowingSafetyWarning)
+
+                underTest.onSafetyWarningDismissed()
+
+                assertThat(isShowingSafetyWarning).isFalse()
+            }
+        }
+
+    @Test
+    fun flagShowUi_isShowingSafetyWarning_isTrue() =
+        with(kosmos) {
+            runTest {
+                val isShowingSafetyWarning by collectLastValue(underTest.isShowingSafetyWarning)
+
+                fakeVolumeDialogController.onShowSafetyWarning(AudioManager.FLAG_SHOW_UI)
+
+                assertThat(isShowingSafetyWarning).isTrue()
+            }
+        }
+
+    @Test
+    fun flagShowUiWarnings_isShowingSafetyWarning_isTrue() =
+        with(kosmos) {
+            runTest {
+                val isShowingSafetyWarning by collectLastValue(underTest.isShowingSafetyWarning)
+
+                fakeVolumeDialogController.onShowSafetyWarning(AudioManager.FLAG_SHOW_UI_WARNINGS)
+
+                assertThat(isShowingSafetyWarning).isTrue()
+            }
+        }
+
+    @Test
+    fun invisibleAndNoFlags_isShowingSafetyWarning_isFalse() =
+        with(kosmos) {
+            runTest {
+                val isShowingSafetyWarning by collectLastValue(underTest.isShowingSafetyWarning)
+                volumeDialogVisibilityRepository.updateVisibility {
+                    VolumeDialogVisibilityModel.Invisible
+                }
+
+                fakeVolumeDialogController.onShowSafetyWarning(0)
+
+                assertThat(isShowingSafetyWarning).isFalse()
+            }
+        }
+
+    @Test
+    fun visibleAndNoFlags_isShowingSafetyWarning_isTrue() =
+        with(kosmos) {
+            runTest {
+                val isShowingSafetyWarning by collectLastValue(underTest.isShowingSafetyWarning)
+                volumeDialogVisibilityRepository.updateVisibility {
+                    VolumeDialogVisibilityModel.Visible(
+                        Events.SHOW_REASON_VOLUME_CHANGED,
+                        false,
+                        ActivityManager.LOCK_TASK_MODE_LOCKED,
+                    )
+                }
+
+                fakeVolumeDialogController.onShowSafetyWarning(0)
+
+                assertThat(isShowingSafetyWarning).isTrue()
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt
index bba0050..a42ae03 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt
@@ -18,6 +18,7 @@
 
 import android.content.res.Resources
 import com.android.internal.R
+import com.android.systemui.common.ui.GlobalConfig
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -36,7 +37,7 @@
 constructor(
     @Application private val applicationScope: CoroutineScope,
     @Main private val resources: Resources,
-    configurationRepository: ConfigurationRepository,
+    @GlobalConfig configurationRepository: ConfigurationRepository,
 ) {
     /**
      * Whether to enable emergency services calls while the SIM card is locked. This is disabled in
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 61cd7c7..641400a 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -42,6 +42,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shared.system.SysUiStatsLog
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -71,7 +72,7 @@
     private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
     private val falsingCollector: FalsingCollector,
     private val dismissCallbackRegistry: DismissCallbackRegistry,
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val trustRepository: TrustRepository,
     @Application private val applicationScope: CoroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
index 1aaf4fb..ec9ee91 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
@@ -37,6 +37,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
 import com.android.systemui.util.icuMessageFormat
 import javax.inject.Inject
@@ -62,7 +63,7 @@
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val repository: SimBouncerRepository,
     private val telephonyManager: TelephonyManager,
-    @Main private val resources: Resources,
+    @ShadeDisplayAware private val resources: Resources,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val euiccManager: EuiccManager?,
     // TODO(b/307977401): Replace this with `MobileConnectionsInteractor` when available.
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt
index 38fc2a8..84a423e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt
@@ -21,11 +21,12 @@
 import android.view.WindowManager
 import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.settingslib.Utils
+import com.android.systemui.common.ui.GlobalConfig
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyboard.docking.domain.interactor.KeyboardDockingIndicationInteractor
-import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.surfaceeffects.glowboxeffect.GlowBoxConfig
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -37,9 +38,9 @@
 @Inject
 constructor(
     private val windowManager: WindowManager,
-    private val context: Context,
+    @Application private val context: Context,
     keyboardDockingIndicationInteractor: KeyboardDockingIndicationInteractor,
-    @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
+    @GlobalConfig configurationInteractor: ConfigurationInteractor,
     @Background private val backgroundScope: CoroutineScope,
 ) {
 
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index eff5fc0..d8fc52b 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -216,20 +216,13 @@
             "handleKeyGestureEvent: Received OPEN_NOTES gesture event from keycodes: " +
                 event.keycodes.contentToString()
         }
-        if (
-            event.keycodes.contains(KEYCODE_N) &&
-                event.hasModifiers(KeyEvent.META_CTRL_ON or KeyEvent.META_META_ON)
-        ) {
-            debugLog { "Note task triggered by keyboard shortcut" }
-            backgroundExecutor.execute { controller.showNoteTask(KEYBOARD_SHORTCUT) }
-            return true
-        }
         if (event.keycodes.size == 1 && event.keycodes[0] == KEYCODE_STYLUS_BUTTON_TAIL) {
             debugLog { "Note task triggered by stylus tail button" }
             backgroundExecutor.execute { controller.showNoteTask(TAIL_BUTTON) }
             return true
         }
-        return false
+        backgroundExecutor.execute { controller.showNoteTask(KEYBOARD_SHORTCUT) }
+        return true
     }
 
     private fun isKeyGestureSupported(gestureType: Int): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt
index 094ec39..203a157 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt
@@ -16,19 +16,31 @@
 
 package com.android.systemui.volume.dialog
 
+import android.content.Context
+import android.media.AudioManager
 import com.android.app.tracing.coroutines.coroutineScopeTraced
 import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.plugins.VolumeDialog
+import com.android.systemui.volume.SafetyWarningDialog
 import com.android.systemui.volume.dialog.dagger.VolumeDialogPluginComponent
+import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogPluginViewModel
 import javax.inject.Inject
+import kotlin.coroutines.resume
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.suspendCancellableCoroutine
 
+@OptIn(ExperimentalCoroutinesApi::class)
 class VolumeDialogPlugin
 @Inject
 constructor(
     @Application private val applicationCoroutineScope: CoroutineScope,
+    private val context: Context,
+    private val audioManager: AudioManager,
     private val volumeDialogPluginComponentFactory: VolumeDialogPluginComponent.Factory,
 ) : VolumeDialog {
 
@@ -41,14 +53,39 @@
                 coroutineScopeTraced("[Volume]plugin") {
                     pluginComponent =
                         volumeDialogPluginComponentFactory.create(this).also {
-                            it.viewModel().launchVolumeDialog()
+                            bindPlugin(it.viewModel())
                         }
                 }
             }
     }
 
+    private fun CoroutineScope.bindPlugin(viewModel: VolumeDialogPluginViewModel) {
+        viewModel.launchVolumeDialog()
+
+        viewModel.isShowingSafetyWarning
+            .mapLatest { isShowingSafetyWarning ->
+                if (isShowingSafetyWarning) {
+                    showSafetyWarningVisibility { viewModel.onSafetyWarningDismissed() }
+                }
+            }
+            .launchIn(this)
+    }
+
     override fun destroy() {
         job?.cancel()
         pluginComponent = null
     }
+
+    private suspend fun showSafetyWarningVisibility(onDismissed: () -> Unit) =
+        suspendCancellableCoroutine { continuation ->
+            val dialog =
+                object : SafetyWarningDialog(context, audioManager) {
+                    override fun cleanUp() {
+                        onDismissed()
+                        continuation.resume(Unit)
+                    }
+                }
+            dialog.show()
+            continuation.invokeOnCancellation { dialog.dismiss() }
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractor.kt
new file mode 100644
index 0000000..f707d67
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractor.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 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.systemui.volume.dialog.domain.interactor
+
+import android.media.AudioManager
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogSafetyWarningModel
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+
+private const val VISIBLE_FLAGS = AudioManager.FLAG_SHOW_UI or AudioManager.FLAG_SHOW_UI_WARNINGS
+
+@VolumeDialogPluginScope
+class VolumeDialogSafetyWarningInteractor
+@Inject
+constructor(
+    private val stateInteractor: VolumeDialogStateInteractor,
+    visibilityInteractor: VolumeDialogVisibilityInteractor,
+) {
+
+    val isShowingSafetyWarning: Flow<Boolean> =
+        stateInteractor.volumeDialogState.map {
+            when (it.isShowingSafetyWarning) {
+                is VolumeDialogSafetyWarningModel.Visible ->
+                    if (it.isShowingSafetyWarning.flags and VISIBLE_FLAGS == 0) {
+                        visibilityInteractor.dialogVisibility.first() is
+                            VolumeDialogVisibilityModel.Visible
+                    } else {
+                        true
+                    }
+                is VolumeDialogSafetyWarningModel.Invisible -> false
+            }
+        }
+
+    fun onSafetyWarningDismissed() {
+        stateInteractor.setSafetyWarning(VolumeDialogSafetyWarningModel.Invisible)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt
index 5c7289b..51e7924 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
 import com.android.systemui.volume.dialog.data.repository.VolumeDialogStateRepository
 import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogSafetyWarningModel
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
 import javax.inject.Inject
@@ -61,6 +62,9 @@
                             oldState.copy(shouldShowA11ySlider = event.showA11yStream)
                         }
                     }
+                    is VolumeDialogEventModel.ShowSafetyWarning -> {
+                        setSafetyWarning(VolumeDialogSafetyWarningModel.Visible(event.flags))
+                    }
                     else -> {
                         // do nothing
                     }
@@ -72,6 +76,10 @@
 
     val volumeDialogState: Flow<VolumeDialogStateModel> = volumeDialogStateRepository.state
 
+    fun setSafetyWarning(model: VolumeDialogSafetyWarningModel) {
+        volumeDialogStateRepository.updateState { it.copy(isShowingSafetyWarning = model) }
+    }
+
     /** Returns a copy of [model] filled with the values from [VolumeDialogController.State]. */
     private fun VolumeDialogController.State.copyIntoModel(
         model: VolumeDialogStateModel
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogSafetyWarningModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogSafetyWarningModel.kt
new file mode 100644
index 0000000..39fc222
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogSafetyWarningModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 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.systemui.volume.dialog.shared.model
+
+/** Models current Volume Safety Dialog state. */
+sealed interface VolumeDialogSafetyWarningModel {
+
+    /** Volume Safety Dialog is visible and has been shown with the [flags]. */
+    data class Visible(val flags: Int) : VolumeDialogSafetyWarningModel
+
+    /** Volume Safety Dialog is invisible. */
+    data object Invisible : VolumeDialogSafetyWarningModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogStateModel.kt
index 1792b99..838006d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogStateModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogStateModel.kt
@@ -21,6 +21,8 @@
 /** Models a state of the Volume Dialog. */
 data class VolumeDialogStateModel(
     val shouldShowA11ySlider: Boolean = false,
+    val isShowingSafetyWarning: VolumeDialogSafetyWarningModel =
+        VolumeDialogSafetyWarningModel.Invisible,
     val streamModels: Map<Int, VolumeDialogStreamModel> = mapOf(),
     val ringerModeInternal: Int = 0,
     val ringerModeExternal: Int = 0,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt
index ff525f46..9bab1b0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt
@@ -18,14 +18,17 @@
 
 import com.android.systemui.volume.Events
 import com.android.systemui.volume.dialog.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogSafetyWarningInteractor
 import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
 import com.android.systemui.volume.dialog.shared.VolumeDialogLogger
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
 import javax.inject.Inject
 import javax.inject.Provider
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.mapLatest
 
@@ -34,29 +37,35 @@
 class VolumeDialogPluginViewModel
 @Inject
 constructor(
+    @VolumeDialogPlugin private val coroutineScope: CoroutineScope,
     private val dialogVisibilityInteractor: VolumeDialogVisibilityInteractor,
+    private val dialogSafetyWarningInteractor: VolumeDialogSafetyWarningInteractor,
     private val volumeDialogProvider: Provider<VolumeDialog>,
     private val logger: VolumeDialogLogger,
 ) {
 
-    suspend fun launchVolumeDialog() {
-        coroutineScope {
-            dialogVisibilityInteractor.dialogVisibility
-                .mapLatest { visibilityModel ->
-                    with(visibilityModel) {
-                        if (this is VolumeDialogVisibilityModel.Visible) {
-                            showDialog()
-                            Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, keyguardLocked)
-                            logger.onShow(reason)
-                        }
-                        if (this is VolumeDialogVisibilityModel.Dismissed) {
-                            Events.writeEvent(Events.EVENT_DISMISS_DIALOG, reason)
-                            logger.onDismiss(reason)
-                        }
+    fun launchVolumeDialog() {
+        dialogVisibilityInteractor.dialogVisibility
+            .mapLatest { visibilityModel ->
+                with(visibilityModel) {
+                    if (this is VolumeDialogVisibilityModel.Visible) {
+                        showDialog()
+                        Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, keyguardLocked)
+                        logger.onShow(reason)
+                    }
+                    if (this is VolumeDialogVisibilityModel.Dismissed) {
+                        Events.writeEvent(Events.EVENT_DISMISS_DIALOG, reason)
+                        logger.onDismiss(reason)
                     }
                 }
-                .launchIn(this)
-        }
+            }
+            .launchIn(coroutineScope)
+    }
+
+    val isShowingSafetyWarning: Flow<Boolean> = dialogSafetyWarningInteractor.isShowingSafetyWarning
+
+    fun onSafetyWarningDismissed() {
+        dialogSafetyWarningInteractor.onSafetyWarningDismissed()
     }
 
     private fun showDialog() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
index 7a6ede4..b20dffb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
@@ -44,9 +44,9 @@
 @Inject
 constructor(
     private val context: Context,
-    dialogVisibilityInteractor: VolumeDialogVisibilityInteractor,
+    private val dialogVisibilityInteractor: VolumeDialogVisibilityInteractor,
     volumeDialogSlidersInteractor: VolumeDialogSlidersInteractor,
-    volumeDialogStateInteractor: VolumeDialogStateInteractor,
+    private val volumeDialogStateInteractor: VolumeDialogStateInteractor,
     devicePostureController: DevicePostureController,
     configurationController: ConfigurationController,
 ) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index d88b758..266cb51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -181,11 +181,9 @@
 
     @Test
     @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
-    fun handlesShortcut_metaCtrlN() {
+    fun handlesShortcut_keyGestureTypeOpenNotes() {
         val gestureEvent =
             KeyGestureEvent.Builder()
-                .setKeycodes(intArrayOf(KeyEvent.KEYCODE_N))
-                .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON)
                 .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES)
                 .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
                 .build()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorKosmos.kt
new file mode 100644
index 0000000..ddd0014
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 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.systemui.volume.dialog.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.volumeDialogSafetyWarningInteractor: VolumeDialogSafetyWarningInteractor by
+    Kosmos.Fixture {
+        VolumeDialogSafetyWarningInteractor(
+            volumeDialogStateInteractor,
+            volumeDialogVisibilityInteractor,
+        )
+    }
diff --git a/packages/Vcn/TEST_MAPPING b/packages/Vcn/TEST_MAPPING
index bde88fe..9722a83 100644
--- a/packages/Vcn/TEST_MAPPING
+++ b/packages/Vcn/TEST_MAPPING
@@ -1,4 +1,12 @@
 {
+  "presubmit": [
+    {
+      "name": "FrameworksVcnTests"
+    },
+    {
+      "name": "CtsVcnTestCases"
+    }
+  ],
   "postsubmit": [
     {
       "name": "FrameworksVcnTests"
diff --git a/packages/Vcn/flags/Android.bp b/packages/Vcn/flags/Android.bp
new file mode 100644
index 0000000..3943c6f
--- /dev/null
+++ b/packages/Vcn/flags/Android.bp
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2024 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 {
+    default_team: "trendy_team_enigma",
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aconfig_declarations {
+    name: "android.net.vcn.flags-aconfig",
+    package: "android.net.vcn",
+    container: "com.android.tethering",
+    exportable: true,
+    srcs: [
+        "flags.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "android.net.vcn.flags-aconfig-java-export",
+    aconfig_declarations: "android.net.vcn.flags-aconfig",
+    mode: "exported",
+    min_sdk_version: "35",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/core/java/android/net/vcn/flags.aconfig b/packages/Vcn/flags/flags.aconfig
similarity index 100%
rename from core/java/android/net/vcn/flags.aconfig
rename to packages/Vcn/flags/flags.aconfig
diff --git a/packages/Vcn/framework-b/Android.bp b/packages/Vcn/framework-b/Android.bp
index 8b010c7..c312116 100644
--- a/packages/Vcn/framework-b/Android.bp
+++ b/packages/Vcn/framework-b/Android.bp
@@ -19,6 +19,18 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+filegroup {
+    name: "vcn-utils-platform-sources",
+    srcs: [
+        "src/android/net/vcn/persistablebundleutils/**/*.java",
+        "src/android/net/vcn/util/**/*.java",
+    ],
+    path: "src",
+    visibility: [
+        "//frameworks/base", // For VpnProfile.java and Vpn.java
+    ],
+}
+
 java_defaults {
     name: "framework-connectivity-b-defaults",
     sdk_version: "module_current",
@@ -27,7 +39,25 @@
 
     srcs: [
         "src/**/*.java",
+        "src/**/*.aidl",
     ],
+
+    libs: [
+        "android.net.ipsec.ike.stubs.module_lib",
+        "app-compat-annotations",
+        "framework-wifi.stubs.module_lib",
+        "unsupportedappusage",
+    ],
+    static_libs: [
+        //TODO:375213246 Use a non-exported flag lib when VCN is in mainline
+        "android.net.vcn.flags-aconfig-java-export",
+    ],
+    aidl: {
+        include_dirs: [
+            // For connectivity-framework classes such as Network.aidl, NetworkCapabilities.aidl
+            "packages/modules/Connectivity/framework/aidl-export",
+        ],
+    },
 }
 
 java_sdk_library {
@@ -36,8 +66,35 @@
         "framework-connectivity-b-defaults",
     ],
 
+    //TODO: b/375213246 Use "framework-connectivity-jarjar-rules" when VCN is
+    // in mainline
+    jarjar_rules: "framework-vcn-jarjar-rules.txt",
+
     permitted_packages: [
+        "android.net",
         "android.net.vcn",
+        "com.android.server.vcn.util",
+
+    ],
+    api_packages: [
+        "android.net",
+        "android.net.vcn",
+    ],
+
+    // Allow VCN APIs to reference APIs in IKE and Connectivity
+    stub_only_libs: [
+        "android.net.ipsec.ike.stubs.module_lib",
+        "framework-connectivity.stubs.module_lib",
+    ],
+
+    // To use non-jarjard names of utilities such as android.util.IndentingPrintWriter
+    impl_only_libs: [
+        "framework-connectivity-pre-jarjar",
+    ],
+
+    aconfig_declarations: [
+        //TODO:375213246 Use a non-exported flag lib when VCN is in mainline
+        "android.net.vcn.flags-aconfig-java-export",
     ],
 
     impl_library_visibility: [
@@ -63,5 +120,18 @@
         "//packages/modules/Wifi/service/tests/wifitests",
     ],
 
-    // TODO: b/375213246 Expose this library to Tethering module
+    apex_available: [
+        // TODO: b/374174952 Remove it when VCN modularization is released
+        "//apex_available:platform",
+
+        "com.android.tethering",
+    ],
+}
+
+java_library {
+    name: "framework-connectivity-b-pre-jarjar",
+    defaults: ["framework-connectivity-b-defaults"],
+    libs: [
+        "framework-connectivity-pre-jarjar",
+    ],
 }
diff --git a/packages/Vcn/framework-b/api/current.txt b/packages/Vcn/framework-b/api/current.txt
index d802177..831b741 100644
--- a/packages/Vcn/framework-b/api/current.txt
+++ b/packages/Vcn/framework-b/api/current.txt
@@ -1 +1,123 @@
 // Signature format: 2.0
+package android.net.vcn {
+
+  public final class VcnCellUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate {
+    method public int getCbs();
+    method public int getDun();
+    method public int getIms();
+    method public int getInternet();
+    method public int getMms();
+    method @NonNull public java.util.Set<java.lang.String> getOperatorPlmnIds();
+    method public int getOpportunistic();
+    method public int getRcs();
+    method public int getRoaming();
+    method @NonNull public java.util.Set<java.lang.Integer> getSimSpecificCarrierIds();
+  }
+
+  public static final class VcnCellUnderlyingNetworkTemplate.Builder {
+    ctor public VcnCellUnderlyingNetworkTemplate.Builder();
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate build();
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setCbs(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setDun(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setIms(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setInternet(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMetered(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMinDownstreamBandwidthKbps(int, int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMinUpstreamBandwidthKbps(int, int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMms(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOperatorPlmnIds(@NonNull java.util.Set<java.lang.String>);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOpportunistic(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setRcs(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setRoaming(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setSimSpecificCarrierIds(@NonNull java.util.Set<java.lang.Integer>);
+  }
+
+  public final class VcnConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.Set<android.net.vcn.VcnGatewayConnectionConfig> getGatewayConnectionConfigs();
+    method @NonNull public java.util.Set<java.lang.Integer> getRestrictedUnderlyingNetworkTransports();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnConfig> CREATOR;
+  }
+
+  public static final class VcnConfig.Builder {
+    ctor public VcnConfig.Builder(@NonNull android.content.Context);
+    method @NonNull public android.net.vcn.VcnConfig.Builder addGatewayConnectionConfig(@NonNull android.net.vcn.VcnGatewayConnectionConfig);
+    method @NonNull public android.net.vcn.VcnConfig build();
+    method @NonNull public android.net.vcn.VcnConfig.Builder setRestrictedUnderlyingNetworkTransports(@NonNull java.util.Set<java.lang.Integer>);
+  }
+
+  public final class VcnGatewayConnectionConfig {
+    method @NonNull public int[] getExposedCapabilities();
+    method @NonNull public String getGatewayConnectionName();
+    method @IntRange(from=0x500) public int getMaxMtu();
+    method public int getMinUdpPort4500NatTimeoutSeconds();
+    method @NonNull public long[] getRetryIntervalsMillis();
+    method @NonNull public java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities();
+    method public boolean hasGatewayOption(int);
+    method @FlaggedApi("android.net.vcn.safe_mode_config") public boolean isSafeModeEnabled();
+    field @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static final int MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET = -1; // 0xffffffff
+    field public static final int VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY = 0; // 0x0
+  }
+
+  public static final class VcnGatewayConnectionConfig.Builder {
+    ctor public VcnGatewayConnectionConfig.Builder(@NonNull String, @NonNull android.net.ipsec.ike.IkeTunnelConnectionParams);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addExposedCapability(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addGatewayOption(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig build();
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeGatewayOption(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=0x500) int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMinUdpPort4500NatTimeoutSeconds(@IntRange(from=0x78) int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryIntervalsMillis(@NonNull long[]);
+    method @FlaggedApi("android.net.vcn.safe_mode_config") @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setSafeModeEnabled(boolean);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setVcnUnderlyingNetworkPriorities(@NonNull java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate>);
+  }
+
+  public class VcnManager {
+    method @RequiresPermission("carrier privileges") public void clearVcnConfig(@NonNull android.os.ParcelUuid) throws java.io.IOException;
+    method @NonNull public java.util.List<android.os.ParcelUuid> getConfiguredSubscriptionGroups();
+    method public void registerVcnStatusCallback(@NonNull android.os.ParcelUuid, @NonNull java.util.concurrent.Executor, @NonNull android.net.vcn.VcnManager.VcnStatusCallback);
+    method @RequiresPermission("carrier privileges") public void setVcnConfig(@NonNull android.os.ParcelUuid, @NonNull android.net.vcn.VcnConfig) throws java.io.IOException;
+    method public void unregisterVcnStatusCallback(@NonNull android.net.vcn.VcnManager.VcnStatusCallback);
+    field public static final int VCN_ERROR_CODE_CONFIG_ERROR = 1; // 0x1
+    field public static final int VCN_ERROR_CODE_INTERNAL_ERROR = 0; // 0x0
+    field public static final int VCN_ERROR_CODE_NETWORK_ERROR = 2; // 0x2
+    field public static final int VCN_STATUS_CODE_ACTIVE = 2; // 0x2
+    field public static final int VCN_STATUS_CODE_INACTIVE = 1; // 0x1
+    field public static final int VCN_STATUS_CODE_NOT_CONFIGURED = 0; // 0x0
+    field public static final int VCN_STATUS_CODE_SAFE_MODE = 3; // 0x3
+  }
+
+  public abstract static class VcnManager.VcnStatusCallback {
+    ctor public VcnManager.VcnStatusCallback();
+    method public abstract void onGatewayConnectionError(@NonNull String, int, @Nullable Throwable);
+    method public abstract void onStatusChanged(int);
+  }
+
+  public abstract class VcnUnderlyingNetworkTemplate {
+    method public int getMetered();
+    method public int getMinEntryDownstreamBandwidthKbps();
+    method public int getMinEntryUpstreamBandwidthKbps();
+    method public int getMinExitDownstreamBandwidthKbps();
+    method public int getMinExitUpstreamBandwidthKbps();
+    field public static final int MATCH_ANY = 0; // 0x0
+    field public static final int MATCH_FORBIDDEN = 2; // 0x2
+    field public static final int MATCH_REQUIRED = 1; // 0x1
+  }
+
+  public final class VcnWifiUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate {
+    method @NonNull public java.util.Set<java.lang.String> getSsids();
+  }
+
+  public static final class VcnWifiUnderlyingNetworkTemplate.Builder {
+    ctor public VcnWifiUnderlyingNetworkTemplate.Builder();
+    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate build();
+    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMetered(int);
+    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMinDownstreamBandwidthKbps(int, int);
+    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMinUpstreamBandwidthKbps(int, int);
+    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setSsids(@NonNull java.util.Set<java.lang.String>);
+  }
+
+}
+
diff --git a/packages/Vcn/framework-b/api/module-lib-current.txt b/packages/Vcn/framework-b/api/module-lib-current.txt
index d802177..8961b28 100644
--- a/packages/Vcn/framework-b/api/module-lib-current.txt
+++ b/packages/Vcn/framework-b/api/module-lib-current.txt
@@ -1 +1,28 @@
 // Signature format: 2.0
+package android.net {
+
+  @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public final class ConnectivityFrameworkInitializerBaklava {
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static void registerServiceWrappers();
+  }
+
+}
+
+package android.net.vcn {
+
+  @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public final class VcnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public int describeContents();
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public long getApplicableRedactions();
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public int getMinUdpPort4500NatTimeoutSeconds();
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.TransportInfo makeCopy(long);
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnTransportInfo> CREATOR;
+  }
+
+  @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static final class VcnTransportInfo.Builder {
+    ctor @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public VcnTransportInfo.Builder();
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.vcn.VcnTransportInfo build();
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.vcn.VcnTransportInfo.Builder setMinUdpPort4500NatTimeoutSeconds(@IntRange(from=0x78) int);
+  }
+
+}
+
diff --git a/packages/Vcn/framework-b/api/system-current.txt b/packages/Vcn/framework-b/api/system-current.txt
index d802177..9c5a677 100644
--- a/packages/Vcn/framework-b/api/system-current.txt
+++ b/packages/Vcn/framework-b/api/system-current.txt
@@ -1 +1,23 @@
 // Signature format: 2.0
+package android.net.vcn {
+
+  public class VcnManager {
+    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void addVcnNetworkPolicyChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.vcn.VcnNetworkPolicyResult applyVcnNetworkPolicy(@NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void removeVcnNetworkPolicyChangeListener(@NonNull android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener);
+  }
+
+  public static interface VcnManager.VcnNetworkPolicyChangeListener {
+    method public void onPolicyChanged();
+  }
+
+  public final class VcnNetworkPolicyResult implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities();
+    method public boolean isTeardownRequested();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnNetworkPolicyResult> CREATOR;
+  }
+
+}
+
diff --git a/packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt b/packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt
new file mode 100644
index 0000000..757043b
--- /dev/null
+++ b/packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt
@@ -0,0 +1,2 @@
+rule android.net.vcn.persistablebundleutils.** android.net.vcn.module.repackaged.persistablebundleutils.@1
+rule android.net.vcn.util.** android.net.vcn.module.repackaged.util.@1
\ No newline at end of file
diff --git a/core/java/android/net/ConnectivityFrameworkInitializerBaklava.java b/packages/Vcn/framework-b/src/android/net/ConnectivityFrameworkInitializerBaklava.java
similarity index 100%
rename from core/java/android/net/ConnectivityFrameworkInitializerBaklava.java
rename to packages/Vcn/framework-b/src/android/net/ConnectivityFrameworkInitializerBaklava.java
diff --git a/core/java/android/net/vcn/IVcnManagementService.aidl b/packages/Vcn/framework-b/src/android/net/vcn/IVcnManagementService.aidl
similarity index 100%
rename from core/java/android/net/vcn/IVcnManagementService.aidl
rename to packages/Vcn/framework-b/src/android/net/vcn/IVcnManagementService.aidl
diff --git a/core/java/android/net/vcn/IVcnStatusCallback.aidl b/packages/Vcn/framework-b/src/android/net/vcn/IVcnStatusCallback.aidl
similarity index 100%
rename from core/java/android/net/vcn/IVcnStatusCallback.aidl
rename to packages/Vcn/framework-b/src/android/net/vcn/IVcnStatusCallback.aidl
diff --git a/core/java/android/net/vcn/IVcnUnderlyingNetworkPolicyListener.aidl b/packages/Vcn/framework-b/src/android/net/vcn/IVcnUnderlyingNetworkPolicyListener.aidl
similarity index 100%
rename from core/java/android/net/vcn/IVcnUnderlyingNetworkPolicyListener.aidl
rename to packages/Vcn/framework-b/src/android/net/vcn/IVcnUnderlyingNetworkPolicyListener.aidl
diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
similarity index 100%
rename from core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
rename to packages/Vcn/framework-b/src/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
diff --git a/core/java/android/net/vcn/VcnConfig.aidl b/packages/Vcn/framework-b/src/android/net/vcn/VcnConfig.aidl
similarity index 100%
rename from core/java/android/net/vcn/VcnConfig.aidl
rename to packages/Vcn/framework-b/src/android/net/vcn/VcnConfig.aidl
diff --git a/core/java/android/net/vcn/VcnConfig.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnConfig.java
similarity index 100%
rename from core/java/android/net/vcn/VcnConfig.java
rename to packages/Vcn/framework-b/src/android/net/vcn/VcnConfig.java
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnGatewayConnectionConfig.java
similarity index 100%
rename from core/java/android/net/vcn/VcnGatewayConnectionConfig.java
rename to packages/Vcn/framework-b/src/android/net/vcn/VcnGatewayConnectionConfig.java
diff --git a/core/java/android/net/vcn/VcnManager.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnManager.java
similarity index 98%
rename from core/java/android/net/vcn/VcnManager.java
rename to packages/Vcn/framework-b/src/android/net/vcn/VcnManager.java
index f275714..594bbb8 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/VcnManager.java
@@ -334,7 +334,7 @@
      * @param executor the Executor that will be used for invoking all calls to the specified
      *     Listener
      * @param listener the VcnUnderlyingNetworkPolicyListener to be added
-     * @throws SecurityException if the caller does not have permission NETWORK_FACTORY
+     * @throws SecurityException if the caller does not have the required permission
      * @throws IllegalStateException if the specified VcnUnderlyingNetworkPolicyListener is already
      *     registered
      * @hide
@@ -423,7 +423,7 @@
      * @param executor the Executor that will be used for invoking all calls to the specified
      *     Listener
      * @param listener the VcnNetworkPolicyChangeListener to be added
-     * @throws SecurityException if the caller does not have permission NETWORK_FACTORY
+     * @throws SecurityException if the caller does not have the required permission
      * @throws IllegalStateException if the specified VcnNetworkPolicyChangeListener is already
      *     registered
      * @hide
@@ -455,7 +455,7 @@
      * <p>If the specified listener is not currently registered, this is a no-op.
      *
      * @param listener the VcnNetworkPolicyChangeListener that will be removed
-     * @throws SecurityException if the caller does not have permission NETWORK_FACTORY
+     * @throws SecurityException if the caller does not have the required permission
      * @hide
      */
     @SystemApi
@@ -489,7 +489,7 @@
      *     policy result for this Network.
      * @param linkProperties the LinkProperties to be used in determining the Network policy result
      *     for this Network.
-     * @throws SecurityException if the caller does not have permission NETWORK_FACTORY
+     * @throws SecurityException if the caller does not have the required permission
      * @return the {@link VcnNetworkPolicyResult} to be used for this Network.
      * @hide
      */
diff --git a/core/java/android/net/vcn/VcnNetworkPolicyResult.aidl b/packages/Vcn/framework-b/src/android/net/vcn/VcnNetworkPolicyResult.aidl
similarity index 100%
rename from core/java/android/net/vcn/VcnNetworkPolicyResult.aidl
rename to packages/Vcn/framework-b/src/android/net/vcn/VcnNetworkPolicyResult.aidl
diff --git a/core/java/android/net/vcn/VcnNetworkPolicyResult.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnNetworkPolicyResult.java
similarity index 100%
rename from core/java/android/net/vcn/VcnNetworkPolicyResult.java
rename to packages/Vcn/framework-b/src/android/net/vcn/VcnNetworkPolicyResult.java
diff --git a/core/java/android/net/vcn/VcnTransportInfo.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnTransportInfo.java
similarity index 100%
rename from core/java/android/net/vcn/VcnTransportInfo.java
rename to packages/Vcn/framework-b/src/android/net/vcn/VcnTransportInfo.java
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.aidl b/packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkPolicy.aidl
similarity index 100%
rename from core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.aidl
rename to packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkPolicy.aidl
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkPolicy.java
similarity index 100%
rename from core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java
rename to packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkPolicy.java
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkSpecifier.java
similarity index 100%
rename from core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java
rename to packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkSpecifier.java
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkTemplate.java
similarity index 100%
rename from core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
rename to packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkTemplate.java
diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
similarity index 100%
rename from core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
rename to packages/Vcn/framework-b/src/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
diff --git a/core/java/android/net/vcn/persistablebundleutils/CertUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/CertUtils.java
similarity index 100%
rename from core/java/android/net/vcn/persistablebundleutils/CertUtils.java
rename to packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/CertUtils.java
diff --git a/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java
similarity index 100%
rename from core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java
rename to packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java
diff --git a/core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java
similarity index 100%
rename from core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java
rename to packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java
similarity index 100%
rename from core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java
rename to packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java
similarity index 100%
rename from core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java
rename to packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
similarity index 100%
rename from core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
rename to packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtils.java
similarity index 100%
rename from core/java/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtils.java
rename to packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtils.java
diff --git a/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java
similarity index 100%
rename from core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java
rename to packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java
diff --git a/core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java
similarity index 100%
rename from core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java
rename to packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java
diff --git a/core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java
similarity index 100%
rename from core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java
rename to packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java
diff --git a/core/java/android/net/vcn/util/LogUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/util/LogUtils.java
similarity index 95%
rename from core/java/android/net/vcn/util/LogUtils.java
rename to packages/Vcn/framework-b/src/android/net/vcn/util/LogUtils.java
index 7f7f852..742aa76 100644
--- a/core/java/android/net/vcn/util/LogUtils.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/util/LogUtils.java
@@ -19,7 +19,7 @@
 import android.annotation.Nullable;
 import android.os.ParcelUuid;
 
-import com.android.internal.util.HexDump;
+import com.android.net.module.util.HexDump;
 
 /** @hide */
 public class LogUtils {
diff --git a/core/java/android/net/vcn/util/MtuUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/util/MtuUtils.java
similarity index 100%
rename from core/java/android/net/vcn/util/MtuUtils.java
rename to packages/Vcn/framework-b/src/android/net/vcn/util/MtuUtils.java
diff --git a/core/java/android/net/vcn/util/OneWayBoolean.java b/packages/Vcn/framework-b/src/android/net/vcn/util/OneWayBoolean.java
similarity index 100%
rename from core/java/android/net/vcn/util/OneWayBoolean.java
rename to packages/Vcn/framework-b/src/android/net/vcn/util/OneWayBoolean.java
diff --git a/core/java/android/net/vcn/util/PersistableBundleUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/util/PersistableBundleUtils.java
similarity index 99%
rename from core/java/android/net/vcn/util/PersistableBundleUtils.java
rename to packages/Vcn/framework-b/src/android/net/vcn/util/PersistableBundleUtils.java
index 4dc42c7..8a687d8 100644
--- a/core/java/android/net/vcn/util/PersistableBundleUtils.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/util/PersistableBundleUtils.java
@@ -21,7 +21,7 @@
 import android.os.ParcelUuid;
 import android.os.PersistableBundle;
 
-import com.android.internal.util.HexDump;
+import com.android.net.module.util.HexDump;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
diff --git a/packages/Vcn/service-b/Android.bp b/packages/Vcn/service-b/Android.bp
index 03ef4e6..26d8397 100644
--- a/packages/Vcn/service-b/Android.bp
+++ b/packages/Vcn/service-b/Android.bp
@@ -32,6 +32,33 @@
     visibility: ["//frameworks/base/services/core"],
 }
 
+// Do not static include this lib in VCN because these files exist in
+// both service-connectivity.jar and framework.jar
+// TODO: b/374174952 After VCN moves to Connectivity/ and the modularization is done
+// this lib can be removed and "service-connectivity-b-pre-jarjar" can include
+// "service-connectivity-pre-jarjar"
+java_library {
+    name: "connectivity-utils-service-vcn-internal",
+    sdk_version: "module_current",
+    min_sdk_version: "30",
+    srcs: [
+        ":framework-connectivity-shared-srcs",
+    ],
+    libs: [
+        "framework-annotations-lib",
+        "unsupportedappusage",
+    ],
+    visibility: [
+        "//visibility:private",
+    ],
+    apex_available: [
+        // TODO: b/374174952 Remove it when VCN modularization is released
+        "//apex_available:platform",
+
+        "com.android.tethering",
+    ],
+}
+
 java_library {
     name: "service-connectivity-b-pre-jarjar",
     sdk_version: "system_server_current",
@@ -42,8 +69,32 @@
         "src/**/*.java",
     ],
 
-    // TODO: b/375213246 Expose this library to Tethering module
+    libs: [
+        "android.net.ipsec.ike.stubs.module_lib",
+        "connectivity-utils-service-vcn-internal",
+        "framework-annotations-lib",
+        "framework-connectivity-pre-jarjar",
+        "framework-connectivity-t-pre-jarjar",
+        "framework-connectivity-b-pre-jarjar",
+        "framework-wifi.stubs.module_lib",
+        "modules-utils-statemachine",
+        "unsupportedappusage",
+    ],
+
+    // TODO: b/374174952 Dynamically include these libs when VCN
+    // modularization is released
+    static_libs: [
+        "net-utils-service-vcn",
+        "modules-utils-handlerexecutor",
+    ],
+
     visibility: [
         "//frameworks/base/services",
     ],
+    apex_available: [
+        // TODO: b/374174952 Remove it when VCN modularization is released
+        "//apex_available:platform",
+
+        "com.android.tethering",
+    ],
 }
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/packages/Vcn/service-b/src/com/android/server/VcnManagementService.java
similarity index 97%
rename from services/core/java/com/android/server/VcnManagementService.java
rename to packages/Vcn/service-b/src/com/android/server/VcnManagementService.java
index a45b715..26db6a9 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/packages/Vcn/service-b/src/com/android/server/VcnManagementService.java
@@ -86,6 +86,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
 import com.android.net.module.util.BinderUtils;
+import com.android.net.module.util.HandlerUtils;
 import com.android.net.module.util.LocationPermissionChecker;
 import com.android.net.module.util.PermissionUtils;
 import com.android.server.vcn.TelephonySubscriptionTracker;
@@ -1332,41 +1333,46 @@
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "| ");
 
         // Post to handler thread to prevent ConcurrentModificationExceptions, and avoid lock-hell.
-        mHandler.runWithScissors(() -> {
-            mNetworkProvider.dump(pw);
-            pw.println();
+        HandlerUtils.runWithScissorsForDump(
+                mHandler,
+                () -> {
+                    mNetworkProvider.dump(pw);
+                    pw.println();
 
-            mTrackingNetworkCallback.dump(pw);
-            pw.println();
+                    mTrackingNetworkCallback.dump(pw);
+                    pw.println();
 
-            synchronized (mLock) {
-                mLastSnapshot.dump(pw);
-                pw.println();
+                    synchronized (mLock) {
+                        mLastSnapshot.dump(pw);
+                        pw.println();
 
-                pw.println("mConfigs:");
-                pw.increaseIndent();
-                for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) {
-                    pw.println(entry.getKey() + ": "
-                            + entry.getValue().getProvisioningPackageName());
-                }
-                pw.decreaseIndent();
-                pw.println();
+                        pw.println("mConfigs:");
+                        pw.increaseIndent();
+                        for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) {
+                            pw.println(
+                                    entry.getKey()
+                                            + ": "
+                                            + entry.getValue().getProvisioningPackageName());
+                        }
+                        pw.decreaseIndent();
+                        pw.println();
 
-                pw.println("mVcns:");
-                pw.increaseIndent();
-                for (Vcn vcn : mVcns.values()) {
-                    vcn.dump(pw);
-                }
-                pw.decreaseIndent();
-                pw.println();
-            }
+                        pw.println("mVcns:");
+                        pw.increaseIndent();
+                        for (Vcn vcn : mVcns.values()) {
+                            vcn.dump(pw);
+                        }
+                        pw.decreaseIndent();
+                        pw.println();
+                    }
 
-            pw.println("Local log:");
-            pw.increaseIndent();
-            LOCAL_LOG.dump(pw);
-            pw.decreaseIndent();
-            pw.println();
-        }, DUMP_TIMEOUT_MILLIS);
+                    pw.println("Local log:");
+                    pw.increaseIndent();
+                    LOCAL_LOG.dump(pw);
+                    pw.decreaseIndent();
+                    pw.println();
+                },
+                DUMP_TIMEOUT_MILLIS);
     }
 
     // TODO(b/180452282): Make name more generic and implement directly with VcnManagementService
diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/packages/Vcn/service-b/src/com/android/server/vcn/TelephonySubscriptionTracker.java
similarity index 99%
rename from services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
rename to packages/Vcn/service-b/src/com/android/server/vcn/TelephonySubscriptionTracker.java
index 154897e..b448f75 100644
--- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/TelephonySubscriptionTracker.java
@@ -29,7 +29,6 @@
 import android.net.vcn.VcnManager;
 import android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.ParcelUuid;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
@@ -46,6 +45,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.modules.utils.HandlerExecutor;
 
 import java.util.ArrayList;
 import java.util.Collections;
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java
similarity index 99%
rename from services/core/java/com/android/server/vcn/Vcn.java
rename to packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java
index 95acb10..2524d0e 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java
@@ -40,7 +40,6 @@
 import android.net.vcn.VcnManager.VcnErrorCode;
 import android.net.vcn.util.LogUtils;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.Message;
 import android.os.ParcelUuid;
 import android.provider.Settings;
@@ -53,6 +52,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.modules.utils.HandlerExecutor;
 import com.android.server.VcnManagementService.VcnCallback;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/packages/Vcn/service-b/src/com/android/server/vcn/VcnContext.java
similarity index 100%
rename from services/core/java/com/android/server/vcn/VcnContext.java
rename to packages/Vcn/service-b/src/com/android/server/vcn/VcnContext.java
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/packages/Vcn/service-b/src/com/android/server/vcn/VcnGatewayConnection.java
similarity index 99%
rename from services/core/java/com/android/server/vcn/VcnGatewayConnection.java
rename to packages/Vcn/service-b/src/com/android/server/vcn/VcnGatewayConnection.java
index 9ccf040..f024b5f 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/VcnGatewayConnection.java
@@ -45,6 +45,7 @@
 import android.net.IpPrefix;
 import android.net.IpSecManager;
 import android.net.IpSecManager.IpSecTunnelInterface;
+import android.net.IpSecManager.PolicyDirection;
 import android.net.IpSecManager.ResourceUnavailableException;
 import android.net.IpSecTransform;
 import android.net.LinkAddress;
@@ -59,7 +60,6 @@
 import android.net.RouteInfo;
 import android.net.TelephonyNetworkSpecifier;
 import android.net.Uri;
-import android.net.annotations.PolicyDirection;
 import android.net.ipsec.ike.ChildSaProposal;
 import android.net.ipsec.ike.ChildSessionCallback;
 import android.net.ipsec.ike.ChildSessionConfiguration;
@@ -83,7 +83,6 @@
 import android.net.vcn.util.OneWayBoolean;
 import android.net.wifi.WifiInfo;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.Message;
 import android.os.ParcelUuid;
 import android.os.PowerManager;
@@ -101,6 +100,7 @@
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.WakeupMessage;
+import com.android.modules.utils.HandlerExecutor;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
 import com.android.server.vcn.routeselection.UnderlyingNetworkController;
diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java
similarity index 99%
rename from services/core/java/com/android/server/vcn/VcnNetworkProvider.java
rename to packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java
index 78ff432..4552f50 100644
--- a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java
@@ -33,7 +33,6 @@
 import android.net.NetworkScore;
 import android.net.vcn.VcnGatewayConnectionConfig;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
@@ -41,6 +40,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.modules.utils.HandlerExecutor;
 
 import java.util.Objects;
 import java.util.Set;
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
similarity index 99%
rename from services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
rename to packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
index e6a1ff9..72de613 100644
--- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -32,12 +32,12 @@
 import android.net.Network;
 import android.net.vcn.VcnManager;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.OutcomeReceiver;
 import android.os.PowerManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.modules.utils.HandlerExecutor;
 import com.android.server.vcn.VcnContext;
 
 import java.lang.annotation.ElementType;
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
similarity index 100%
rename from services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
rename to packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
similarity index 100%
rename from services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
rename to packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
similarity index 99%
rename from services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
rename to packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
index f7a564a..29a0762 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
@@ -41,7 +41,6 @@
 import android.net.vcn.VcnUnderlyingNetworkTemplate;
 import android.net.vcn.util.LogUtils;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.ParcelUuid;
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
@@ -52,6 +51,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.modules.utils.HandlerExecutor;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.VcnContext;
 import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
similarity index 100%
rename from services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
rename to packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
similarity index 100%
rename from services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
rename to packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
diff --git a/services/Android.bp b/services/Android.bp
index fc0bb33..225304f 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -294,6 +294,10 @@
         "service-permission.stubs.system_server",
         "service-rkp.stubs.system_server",
         "service-sdksandbox.stubs.system_server",
+
+        // TODO: b/30242953 This is for accessing IVcnManagementService and
+        // can be removed VCN is in mainline
+        "framework-connectivity-b-pre-jarjar",
     ],
 
     soong_config_variables: {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index c4e1036..e10bdaa 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -765,6 +765,7 @@
     private float mBacklightMinimum = Float.NaN;
     private float mBacklightMaximum = Float.NaN;
     private float mBrightnessDefault = Float.NaN;
+    private float mBrightnessDim = Float.NaN;
     private float mBrightnessRampFastDecrease = Float.NaN;
     private float mBrightnessRampFastIncrease = Float.NaN;
     private float mBrightnessRampSlowDecrease = Float.NaN;
@@ -1282,6 +1283,24 @@
     }
 
     /**
+     * Return the minimum brightness on a scale of 0.0f - 1.0f
+     *
+     * @return minimum brightness
+     */
+    public float getBrightnessMinimum() {
+        return getBrightnessFromBacklight(mBacklightMinimum);
+    }
+
+    /**
+     * Return the maximum brightness on a scale of 0.0f - 1.0f
+     *
+     * @return maximum brightness
+     */
+    public float getBrightnessMaximum() {
+        return getBrightnessFromBacklight(mBacklightMaximum);
+    }
+
+    /**
      * Return the default brightness on a scale of 0.0f - 1.0f
      *
      * @return default brightness
@@ -1290,6 +1309,15 @@
         return mBrightnessDefault;
     }
 
+    /**
+     * Return the dim brightness on a scale of 0.0f - 1.0f
+     *
+     * @return dim brightness
+     */
+    public float getBrightnessDim() {
+        return mBrightnessDim;
+    }
+
     public float getBrightnessRampFastDecrease() {
         return mBrightnessRampFastDecrease;
     }
@@ -1689,6 +1717,7 @@
                 + ", mBacklightMinimum=" + mBacklightMinimum
                 + ", mBacklightMaximum=" + mBacklightMaximum
                 + ", mBrightnessDefault=" + mBrightnessDefault
+                + ", mBrightnessDim=" + mBrightnessDim
                 + ", mQuirks=" + mQuirks
                 + "\n"
                 + "mLuxThrottlingData=" + mLuxThrottlingData
@@ -1906,6 +1935,7 @@
         mBacklightMinimum = PowerManager.BRIGHTNESS_MIN;
         mBacklightMaximum = PowerManager.BRIGHTNESS_MAX;
         mBrightnessDefault = BRIGHTNESS_DEFAULT;
+        mBrightnessDim = PowerManager.BRIGHTNESS_INVALID;
         mBrightnessRampFastDecrease = PowerManager.BRIGHTNESS_MAX;
         mBrightnessRampFastIncrease = PowerManager.BRIGHTNESS_MAX;
         mBrightnessRampSlowDecrease = PowerManager.BRIGHTNESS_MAX;
@@ -2003,6 +2033,15 @@
             mBacklightMinimum = min;
             mBacklightMaximum = max;
         }
+        final float dim = mContext.getResources().getFloat(com.android.internal.R.dimen
+                .config_screenBrightnessDimFloat);
+        if (dim == INVALID_BRIGHTNESS_IN_CONFIG) {
+            mBrightnessDim = BrightnessSynchronizer.brightnessIntToFloat(
+                    mContext.getResources().getInteger(com.android.internal.R.integer
+                            .config_screenBrightnessDim));
+        } else {
+            mBrightnessDim = dim;
+        }
     }
 
     private void loadBrightnessMap(DisplayConfiguration config) {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 1c1bdad..3aaf4f6 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -477,6 +477,7 @@
     public float brightnessMinimum;
     public float brightnessMaximum;
     public float brightnessDefault;
+    public float brightnessDim;
 
     // NaN means unsupported
     public float hdrSdrRatio = Float.NaN;
@@ -561,8 +562,8 @@
                 || !Objects.equals(ownerPackageName, other.ownerPackageName)
                 || !BrightnessSynchronizer.floatEquals(brightnessMinimum, other.brightnessMinimum)
                 || !BrightnessSynchronizer.floatEquals(brightnessMaximum, other.brightnessMaximum)
-                || !BrightnessSynchronizer.floatEquals(brightnessDefault,
-                other.brightnessDefault)
+                || !BrightnessSynchronizer.floatEquals(brightnessDefault, other.brightnessDefault)
+                || !BrightnessSynchronizer.floatEquals(brightnessDim, other.brightnessDim)
                 || !Objects.equals(roundedCorners, other.roundedCorners)
                 || installOrientation != other.installOrientation
                 || !Objects.equals(displayShape, other.displayShape)
@@ -618,6 +619,7 @@
         brightnessMinimum = other.brightnessMinimum;
         brightnessMaximum = other.brightnessMaximum;
         brightnessDefault = other.brightnessDefault;
+        brightnessDim = other.brightnessDim;
         hdrSdrRatio = other.hdrSdrRatio;
         roundedCorners = other.roundedCorners;
         installOrientation = other.installOrientation;
@@ -672,6 +674,7 @@
         sb.append(", brightnessMinimum ").append(brightnessMinimum);
         sb.append(", brightnessMaximum ").append(brightnessMaximum);
         sb.append(", brightnessDefault ").append(brightnessDefault);
+        sb.append(", brightnessDim ").append(brightnessDim);
         sb.append(", hdrSdrRatio ").append(hdrSdrRatio);
         if (roundedCorners != null) {
             sb.append(", roundedCorners ").append(roundedCorners);
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 0b8f7d5..d37dd30 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -812,9 +812,10 @@
 
                 // The display is trusted since it is created by system.
                 mInfo.flags |= DisplayDeviceInfo.FLAG_TRUSTED;
-                mInfo.brightnessMinimum = PowerManager.BRIGHTNESS_MIN;
-                mInfo.brightnessMaximum = PowerManager.BRIGHTNESS_MAX;
+                mInfo.brightnessMinimum = getDisplayDeviceConfig().getBrightnessMinimum();
+                mInfo.brightnessMaximum = getDisplayDeviceConfig().getBrightnessMaximum();
                 mInfo.brightnessDefault = getDisplayDeviceConfig().getBrightnessDefault();
+                mInfo.brightnessDim = getDisplayDeviceConfig().getBrightnessDim();
                 mInfo.hdrSdrRatio = mCurrentHdrSdrRatio;
             }
             return mInfo;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 8546598..1de9c95 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -548,6 +548,7 @@
             mBaseDisplayInfo.brightnessMinimum = deviceInfo.brightnessMinimum;
             mBaseDisplayInfo.brightnessMaximum = deviceInfo.brightnessMaximum;
             mBaseDisplayInfo.brightnessDefault = deviceInfo.brightnessDefault;
+            mBaseDisplayInfo.brightnessDim = deviceInfo.brightnessDim;
             mBaseDisplayInfo.hdrSdrRatio = deviceInfo.hdrSdrRatio;
             mBaseDisplayInfo.roundedCorners = deviceInfo.roundedCorners;
             mBaseDisplayInfo.installOrientation = deviceInfo.installOrientation;
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 6ae58c4..836f4ed 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -340,6 +340,7 @@
         private boolean mIsWindowManagerMirroring;
         private final DisplayCutout mDisplayCutout;
         private final float mDefaultBrightness;
+        private final float mDimBrightness;
         private float mCurrentBrightness;
         private final IBrightnessListener mBrightnessListener;
 
@@ -359,6 +360,7 @@
             mRequestedRefreshRate = virtualDisplayConfig.getRequestedRefreshRate();
             mDisplayCutout = virtualDisplayConfig.getDisplayCutout();
             mDefaultBrightness = virtualDisplayConfig.getDefaultBrightness();
+            mDimBrightness = virtualDisplayConfig.getDimBrightness();
             mCurrentBrightness = PowerManager.BRIGHTNESS_INVALID;
             mBrightnessListener = virtualDisplayConfig.getBrightnessListener();
             mMode = createMode(mWidth, mHeight, getRefreshRate());
@@ -645,6 +647,7 @@
                 mInfo.brightnessMinimum = PowerManager.BRIGHTNESS_MIN;
                 mInfo.brightnessMaximum = PowerManager.BRIGHTNESS_MAX;
                 mInfo.brightnessDefault = mDefaultBrightness;
+                mInfo.brightnessDim = mDimBrightness;
 
                 mInfo.ownerUid = mOwnerUid;
                 mInfo.ownerPackageName = mOwnerPackageName;
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 7ccab05..ceade37 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -267,7 +267,7 @@
                 }
             }
 
-            modifiers.add(new DisplayDimModifier(context));
+            modifiers.add(new DisplayDimModifier(data.mDisplayId, context));
             modifiers.add(new BrightnessLowPowerModeModifier());
             if (flags.isEvenDimmerEnabled() && data.mDisplayDeviceConfig.isEvenDimmerAvailable()) {
                 modifiers.add(new BrightnessLowLuxModifier(handler, listener, context,
diff --git a/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java b/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java
index ab880bf..0237af3 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java
@@ -38,12 +38,12 @@
     // mScreenBrightnessDimConfig.
     private final float mScreenBrightnessMinimumDimAmount;
 
-    DisplayDimModifier(Context context) {
+    DisplayDimModifier(int displayId, Context context) {
         PowerManager pm = Objects.requireNonNull(context.getSystemService(PowerManager.class));
         Resources resources = context.getResources();
 
         mScreenBrightnessDimConfig = BrightnessUtils.clampAbsoluteBrightness(
-                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM));
+                pm.getBrightnessConstraint(displayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM));
         mScreenBrightnessMinimumDimAmount = resources.getFloat(
                 R.dimen.config_screenBrightnessMinimumDimAmountFloat);
     }
diff --git a/services/core/java/com/android/server/pm/InstallDependencyHelper.java b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
index 13aab11..673a102 100644
--- a/services/core/java/com/android/server/pm/InstallDependencyHelper.java
+++ b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
@@ -32,11 +32,13 @@
 import android.content.pm.dependencyinstaller.IDependencyInstallerCallback;
 import android.content.pm.dependencyinstaller.IDependencyInstallerService;
 import android.content.pm.parsing.PackageLite;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.OutcomeReceiver;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
 
@@ -63,12 +65,11 @@
     private final Context mContext;
     private final SharedLibrariesImpl mSharedLibraries;
     private final PackageInstallerService mPackageInstallerService;
-    private final Object mRemoteServiceLock = new Object();
     @GuardedBy("mTrackers")
     private final List<DependencyInstallTracker> mTrackers = new ArrayList<>();
-
-    @GuardedBy("mRemoteServiceLock")
-    private ServiceConnector<IDependencyInstallerService> mRemoteService = null;
+    @GuardedBy("mRemoteServices")
+    private final ArrayMap<Integer, ServiceConnector<IDependencyInstallerService>> mRemoteServices =
+            new ArrayMap<>();
 
     InstallDependencyHelper(Context context, SharedLibrariesImpl sharedLibraries,
             PackageInstallerService packageInstallerService) {
@@ -97,13 +98,18 @@
 
         if (missing.isEmpty()) {
             if (DEBUG) {
-                Slog.d(TAG, "No missing dependency for " + pkg);
+                Slog.d(TAG, "No missing dependency for " + pkg.getPackageName());
             }
             // No need for dependency resolution. Move to installation directly.
             callback.onResult(null);
             return;
         }
 
+        if (DEBUG) {
+            Slog.d(TAG, "Missing dependencies found for pkg: " + pkg.getPackageName()
+                    + " user: " + userId);
+        }
+
         if (!bindToDependencyInstallerIfNeeded(userId, handler, snapshot)) {
             onError(callback, "Dependency Installer Service not found");
             return;
@@ -112,8 +118,8 @@
         IDependencyInstallerCallback serviceCallback =
                 new DependencyInstallerCallbackCallOnce(handler, callback, userId);
         boolean scheduleSuccess;
-        synchronized (mRemoteServiceLock) {
-            scheduleSuccess = mRemoteService.run(service -> {
+        synchronized (mRemoteServices) {
+            scheduleSuccess = mRemoteServices.get(userId).run(service -> {
                 service.onDependenciesRequired(missing,
                         new DependencyInstallerCallback(serviceCallback.asBinder()));
             });
@@ -149,15 +155,17 @@
 
     private boolean bindToDependencyInstallerIfNeeded(int userId, Handler handler,
             Computer snapshot) {
-        synchronized (mRemoteServiceLock) {
-            if (mRemoteService != null) {
+        synchronized (mRemoteServices) {
+            if (mRemoteServices.containsKey(userId)) {
                 if (DEBUG) {
-                    Slog.i(TAG, "DependencyInstallerService already bound");
+                    Slog.i(TAG, "DependencyInstallerService for user " + userId + " already bound");
                 }
                 return true;
             }
         }
 
+        Slog.i(TAG, "Attempting to bind to Dependency Installer Service for user " + userId);
+
         RoleManager roleManager = mContext.getSystemService(RoleManager.class);
         if (roleManager == null) {
             Slog.w(TAG, "Cannot find RoleManager system service");
@@ -166,7 +174,7 @@
         List<String> holders = roleManager.getRoleHoldersAsUser(
                 ROLE_SYSTEM_DEPENDENCY_INSTALLER, UserHandle.of(userId));
         if (holders.isEmpty()) {
-            Slog.w(TAG, "No holders of ROLE_SYSTEM_DEPENDENCY_INSTALLER found");
+            Slog.w(TAG, "No holders of ROLE_SYSTEM_DEPENDENCY_INSTALLER found for user " + userId);
             return false;
         }
 
@@ -178,6 +186,8 @@
                 /*includeInstantApps*/ false, /*resolveForStart*/ false);
 
         if (resolvedIntents.isEmpty()) {
+            Slog.w(TAG, "No package holding ROLE_SYSTEM_DEPENDENCY_INSTALLER found for user "
+                    + userId);
             return false;
         }
 
@@ -206,13 +216,14 @@
                 };
 
 
-        synchronized (mRemoteServiceLock) {
+        synchronized (mRemoteServices) {
             // Some other thread managed to connect to the service first
-            if (mRemoteService != null) {
+            if (mRemoteServices.containsKey(userId)) {
                 return true;
             }
-            mRemoteService = serviceConnector;
-            mRemoteService.setServiceLifecycleCallbacks(
+            mRemoteServices.put(userId, serviceConnector);
+            // Block the lock until we connect to the service
+            serviceConnector.setServiceLifecycleCallbacks(
                 new ServiceConnector.ServiceLifecycleCallbacks<>() {
                     @Override
                     public void onDisconnected(@NonNull IDependencyInstallerService service) {
@@ -228,17 +239,18 @@
                     }
 
                     private void destroy() {
-                        synchronized (mRemoteServiceLock) {
-                            if (mRemoteService != null) {
-                                mRemoteService.unbind();
-                                mRemoteService = null;
+                        synchronized (mRemoteServices) {
+                            if (mRemoteServices.containsKey(userId)) {
+                                mRemoteServices.get(userId).unbind();
+                                mRemoteServices.remove(userId);
                             }
                         }
                     }
 
                 });
-            AndroidFuture<IDependencyInstallerService> unusedFuture = mRemoteService.connect();
+            AndroidFuture<IDependencyInstallerService> unusedFuture = serviceConnector.connect();
         }
+        Slog.i(TAG, "Successfully bound to Dependency Installer Service for user " + userId);
         return true;
     }
 
@@ -318,30 +330,40 @@
                 Slog.d(TAG, "onAllDependenciesResolved started");
             }
 
-            // Before creating any tracker, validate the arguments
-            ArraySet<Integer> validSessionIds = validateSessionIds(sessionIds);
+            try {
+                // Before creating any tracker, validate the arguments
+                ArraySet<Integer> validSessionIds = validateSessionIds(sessionIds);
 
-            if (validSessionIds.isEmpty()) {
-                mCallback.onResult(null);
-                return;
-            }
-
-            // Create a tracker now if there are any pending sessions remaining.
-            DependencyInstallTracker tracker = new DependencyInstallTracker(
-                    mCallback, validSessionIds);
-            synchronized (mTrackers) {
-                mTrackers.add(tracker);
-            }
-
-            // By the time the tracker was created, some of the sessions in validSessionIds
-            // could have finished. Avoid waiting for them indefinitely.
-            for (int sessionId : validSessionIds) {
-                SessionInfo sessionInfo = mPackageInstallerService.getSessionInfo(sessionId);
-
-                // Don't wait for sessions that finished already
-                if (sessionInfo == null) {
-                    notifySessionComplete(sessionId, /*success=*/ true);
+                if (validSessionIds.isEmpty()) {
+                    mCallback.onResult(null);
+                    return;
                 }
+
+                // Create a tracker now if there are any pending sessions remaining.
+                DependencyInstallTracker tracker = new DependencyInstallTracker(
+                        mCallback, validSessionIds);
+                synchronized (mTrackers) {
+                    mTrackers.add(tracker);
+                }
+
+                // By the time the tracker was created, some of the sessions in validSessionIds
+                // could have finished. Avoid waiting for them indefinitely.
+                for (int sessionId : validSessionIds) {
+                    SessionInfo sessionInfo = mPackageInstallerService.getSessionInfo(sessionId);
+
+                    // Don't wait for sessions that finished already
+                    if (sessionInfo == null) {
+                        Binder.withCleanCallingIdentity(() -> {
+                            notifySessionComplete(sessionId, /*success=*/ true);
+                        });
+                    }
+                }
+            } catch (Exception e) {
+                // Allow calling the callback again
+                synchronized (this) {
+                    mDependencyInstallerCallbackInvoked = false;
+                }
+                throw e;
             }
         }
 
@@ -354,7 +376,10 @@
                 }
                 mDependencyInstallerCallbackInvoked = true;
             }
-            onError(mCallback, "Failed to resolve all dependencies automatically");
+
+            Binder.withCleanCallingIdentity(() -> {
+                onError(mCallback, "Failed to resolve all dependencies automatically");
+            });
         }
 
         private ArraySet<Integer> validateSessionIds(int[] sessionIds) {
@@ -369,7 +394,8 @@
                 // Continue waiting if session exists and hasn't passed or failed yet.
                 if (sessionInfo != null) {
                     if (sessionInfo.isSessionFailed) {
-                        throwValidationError("Session already finished: " + sessionId);
+                        throw new IllegalArgumentException("Session already finished: "
+                                + sessionId);
                     }
 
                     // Wait for session to finish install if it's not already successful.
@@ -398,25 +424,17 @@
                         s -> s.sessionId == sessionId).findFirst().orElse(null);
 
                 if (sessionInfo == null) {
-                    throwValidationError("Failed to find session: " + sessionId);
+                    throw new IllegalArgumentException("Failed to find session: " + sessionId);
                 }
 
                 // Historical session must have been successful, otherwise throw IAE.
                 if (!sessionInfo.isSessionApplied) {
-                    throwValidationError("Session already finished: " + sessionId);
+                    throw new IllegalArgumentException("Session already finished: " + sessionId);
                 }
             }
 
             return validSessionIds;
         }
-
-        private void throwValidationError(String msg) {
-            // Allow client to invoke callback again.
-            synchronized (this) {
-                mDependencyInstallerCallbackInvoked = false;
-            }
-            throw new IllegalArgumentException(msg);
-        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 8e73023..7c4d425 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4426,9 +4426,9 @@
         int screenDisplayId = displayId < 0 ? DEFAULT_DISPLAY : displayId;
 
         float minLinearBrightness = mPowerManager.getBrightnessConstraint(
-                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
+                screenDisplayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
         float maxLinearBrightness = mPowerManager.getBrightnessConstraint(
-                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
+                screenDisplayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
         float linearBrightness = mDisplayManager.getBrightness(screenDisplayId);
 
         float gammaBrightness = BrightnessUtils.convertLinearToGamma(linearBrightness);
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index c573293..36bc0b9 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -6099,16 +6099,28 @@
             }
         }
 
-        public float getBrightnessConstraint(int constraint) {
+        @Override
+        public float getBrightnessConstraint(
+                int displayId, @PowerManager.BrightnessConstraint int constraint) {
+            DisplayInfo info = null;
+            if (android.companion.virtualdevice.flags.Flags.displayPowerManagerApis()
+                    && mDisplayManagerInternal != null) {
+                info = mDisplayManagerInternal.getDisplayInfo(displayId);
+            }
             switch (constraint) {
                 case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM:
-                    return mScreenBrightnessMinimum;
+                    return info != null && isValidBrightnessValue(info.brightnessMinimum)
+                            ? info.brightnessMinimum : mScreenBrightnessMinimum;
                 case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM:
-                    return mScreenBrightnessMaximum;
+                    return info != null && isValidBrightnessValue(info.brightnessMaximum)
+                            ? info.brightnessMaximum : mScreenBrightnessMaximum;
                 case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT:
-                    return mScreenBrightnessDefault;
+                    return info != null && isValidBrightnessValue(info.brightnessDefault)
+                            ? info.brightnessDefault : mScreenBrightnessDefault;
                 case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM:
-                    return mScreenBrightnessDim;
+                    return android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()
+                            && info != null && isValidBrightnessValue(info.brightnessDim)
+                            ? info.brightnessDim : mScreenBrightnessDim;
                 default:
                     return PowerManager.BRIGHTNESS_INVALID_FLOAT;
             }
diff --git a/services/core/java/com/android/server/vcn/TEST_MAPPING b/services/core/java/com/android/server/vcn/TEST_MAPPING
deleted file mode 100644
index 5b04d88..0000000
--- a/services/core/java/com/android/server/vcn/TEST_MAPPING
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "FrameworksVcnTests"
-    },
-    {
-      "name": "CtsVcnTestCases"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 4e79e37..37e8f62 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -424,6 +424,7 @@
                 || first.brightnessMinimum != second.brightnessMinimum
                 || first.brightnessMaximum != second.brightnessMaximum
                 || first.brightnessDefault != second.brightnessDefault
+                || first.brightnessDim != second.brightnessDim
                 || first.installOrientation != second.installOrientation
                 || first.isForceSdr != second.isForceSdr
                 || !Objects.equals(first.layoutLimitedRefreshRate, second.layoutLimitedRefreshRate)
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
index b002a1f..241dc10 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -327,6 +329,23 @@
     }
 
     @Test
+    public void testBrightnessConfigurationFromDisplayDevice() {
+        mDisplayDeviceInfo.brightnessMinimum = 0.12f;
+        mDisplayDeviceInfo.brightnessDim = 0.34f;
+        mDisplayDeviceInfo.brightnessDefault = 0.56f;
+        mDisplayDeviceInfo.brightnessMaximum = 0.78f;
+
+        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice);
+        mLogicalDisplay.updateLocked(mDeviceRepo, mSyntheticModeManager);
+
+        DisplayInfo info = mLogicalDisplay.getDisplayInfoLocked();
+        assertThat(info.brightnessMinimum).isEqualTo(0.12f);
+        assertThat(info.brightnessDim).isEqualTo(0.34f);
+        assertThat(info.brightnessDefault).isEqualTo(0.56f);
+        assertThat(info.brightnessMaximum).isEqualTo(0.78f);
+    }
+
+    @Test
     public void testGetDisplayPosition() {
         Point expectedPosition = new Point(0, 0);
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
index 3ac7fb0..dbd5c65 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -25,10 +27,12 @@
 import android.hardware.display.VirtualDisplayConfig;
 import android.media.projection.IMediaProjection;
 import android.os.IBinder;
+import android.os.PowerManager;
 import android.os.Process;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.testing.TestableContext;
+import android.view.Display;
 import android.view.Surface;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -56,6 +60,9 @@
     private static final int MAX_DEVICES = 3;
     private static final int MAX_DEVICES_PER_PACKAGE = 2;
 
+    private static final float DEFAULT_BRIGHTNESS = 0.34f;
+    private static final float DIM_BRIGHTNESS = 0.12f;
+
     @Rule
     public final TestableContext mContext = new TestableContext(
             InstrumentationRegistry.getInstrumentation().getContext());
@@ -123,6 +130,64 @@
     }
 
     @Test
+    public void testCreateVirtualDisplay_createDisplayDeviceInfoFromDefaults() {
+        VirtualDisplayConfig config = new VirtualDisplayConfig.Builder(
+                "testDisplayName", /* width= */ 640, /* height= */ 480, /* densityDpi= */ 240)
+                .build();
+
+        final String packageName = "testpackage";
+        final String displayUniqueId = VirtualDisplayAdapter.generateDisplayUniqueId(
+                packageName, Process.myUid(), config);
+
+        DisplayDevice displayDevice = mAdapter.createVirtualDisplayLocked(
+                mMockCallback, /* projection= */ null, /* ownerUid= */ 10,
+                packageName, displayUniqueId, /* surface= */ null, /* flags= */ 0, config);
+
+        assertNotNull(displayDevice);
+        DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+        assertNotNull(info);
+
+        assertThat(info.width).isEqualTo(640);
+        assertThat(info.height).isEqualTo(480);
+        assertThat(info.densityDpi).isEqualTo(240);
+        assertThat(info.xDpi).isEqualTo(240);
+        assertThat(info.yDpi).isEqualTo(240);
+        assertThat(info.name).isEqualTo("testDisplayName");
+        assertThat(info.uniqueId).isEqualTo(displayUniqueId);
+        assertThat(info.ownerPackageName).isEqualTo(packageName);
+        assertThat(info.ownerUid).isEqualTo(10);
+        assertThat(info.type).isEqualTo(Display.TYPE_VIRTUAL);
+        assertThat(info.brightnessMinimum).isEqualTo(PowerManager.BRIGHTNESS_MIN);
+        assertThat(info.brightnessMaximum).isEqualTo(PowerManager.BRIGHTNESS_MAX);
+        assertThat(info.brightnessDefault).isEqualTo(PowerManager.BRIGHTNESS_MIN);
+        assertThat(info.brightnessDim).isEqualTo(PowerManager.BRIGHTNESS_INVALID);
+    }
+
+    @Test
+    public void testCreateVirtualDisplay_createDisplayDeviceInfoFromVirtualDisplayConfig() {
+        VirtualDisplayConfig config = new VirtualDisplayConfig.Builder(
+                "testDisplayName", /* width= */ 640, /* height= */ 480, /* densityDpi= */ 240)
+                .setDefaultBrightness(DEFAULT_BRIGHTNESS)
+                .setDimBrightness(DIM_BRIGHTNESS)
+                .build();
+
+        final String packageName = "testpackage";
+        final String displayUniqueId = VirtualDisplayAdapter.generateDisplayUniqueId(
+                packageName, Process.myUid(), config);
+
+        DisplayDevice displayDevice = mAdapter.createVirtualDisplayLocked(
+                mMockCallback, /* projection= */ null, /* ownerUid= */ 10,
+                packageName, displayUniqueId, /* surface= */ null, /* flags= */ 0, config);
+
+        assertNotNull(displayDevice);
+        DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+        assertNotNull(info);
+
+        assertThat(info.brightnessDefault).isEqualTo(DEFAULT_BRIGHTNESS);
+        assertThat(info.brightnessDim).isEqualTo(DIM_BRIGHTNESS);
+    }
+
+    @Test
     public void testCreatesVirtualDisplay_checkGeneratedDisplayUniqueIdPrefix() {
         VirtualDisplayConfig config = new VirtualDisplayConfig.Builder("test", /* width= */ 1,
                 /* height= */ 1, /* densityDpi= */ 1).build();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java
index be4e7c7..7e4042e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java
@@ -44,6 +44,8 @@
     private static final float MIN_DIM_AMOUNT = 0.05f;
     private static final float DIM_CONFIG = 0.4f;
 
+    private static final int DISPLAY_ID = 3;
+
     @Mock
     private Context mMockContext;
 
@@ -66,9 +68,9 @@
                 R.dimen.config_screenBrightnessMinimumDimAmountFloat)).thenReturn(MIN_DIM_AMOUNT);
         when(mMockContext.getSystemService(PowerManager.class)).thenReturn(mMockPowerManager);
         when(mMockPowerManager.getBrightnessConstraint(
-                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM)).thenReturn(DIM_CONFIG);
+                DISPLAY_ID, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM)).thenReturn(DIM_CONFIG);
 
-        mModifier = new DisplayDimModifier(mMockContext);
+        mModifier = new DisplayDimModifier(DISPLAY_ID, mMockContext);
         mRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
     }
 
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index b48c2d7..376091e 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -1257,6 +1257,36 @@
                 .isEqualTo(WAKEFULNESS_DOZING);
     }
 
+    @EnableFlags({
+            android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER,
+            android.companion.virtualdevice.flags.Flags.FLAG_DISPLAY_POWER_MANAGER_APIS})
+    @Test
+    public void getBrightnessConstraint_valuesMatchDisplayInfo() {
+        final int displayId = 7;
+        final DisplayInfo info = new DisplayInfo();
+        info.brightnessMinimum = 0.12f;
+        info.brightnessDim = 0.34f;
+        info.brightnessDefault = 0.56f;
+        info.brightnessMaximum = 0.78f;
+        when(mDisplayManagerInternalMock.getDisplayInfo(displayId)).thenReturn(info);
+
+        createService();
+        startSystem();
+
+        assertThat(mService.getBinderServiceInstance().getBrightnessConstraint(
+                displayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM))
+                .isEqualTo(info.brightnessMinimum);
+        assertThat(mService.getBinderServiceInstance().getBrightnessConstraint(
+                displayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM))
+                .isEqualTo(info.brightnessMaximum);
+        assertThat(mService.getBinderServiceInstance().getBrightnessConstraint(
+                displayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT))
+                .isEqualTo(info.brightnessDefault);
+        assertThat(mService.getBinderServiceInstance().getBrightnessConstraint(
+                displayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM))
+                .isEqualTo(info.brightnessDim);
+    }
+
     @SuppressWarnings("GuardedBy")
     @Test
     public void testAmbientSuppression_disablesDreamingAndWakesDevice() {
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index bc03c23..9db76d4 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -523,12 +523,12 @@
     }
 
     void prepareBrightnessDecrease(float currentBrightness) {
-        doReturn(0.0f).when(mPowerManager)
-                .getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
-        doReturn(1.0f).when(mPowerManager)
-                .getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
+        doReturn(0.0f).when(mPowerManager).getBrightnessConstraint(
+                DEFAULT_DISPLAY, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
+        doReturn(1.0f).when(mPowerManager).getBrightnessConstraint(
+                DEFAULT_DISPLAY, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
         doReturn(currentBrightness).when(mDisplayManager)
-                .getBrightness(0);
+                .getBrightness(DEFAULT_DISPLAY);
     }
 
     void verifyNewBrightness(float newBrightness) {
diff --git a/tests/vcn/Android.bp b/tests/vcn/Android.bp
index b16ba15..51a300b 100644
--- a/tests/vcn/Android.bp
+++ b/tests/vcn/Android.bp
@@ -14,21 +14,24 @@
 
 android_test {
     name: "FrameworksVcnTests",
+    // For access hidden connectivity methods in tests
+    defaults: ["framework-connectivity-test-defaults"],
     srcs: [
         "java/**/*.java",
         "java/**/*.kt",
     ],
     platform_apis: true,
-    defaults: ["framework-connectivity-test-defaults"],
     test_suites: ["device-tests"],
     certificate: "platform",
     static_libs: [
+        "android.net.vcn.flags-aconfig-java-export",
         "androidx.test.rules",
         "frameworks-base-testutils",
         "framework-protos",
         "mockito-target-minus-junit4",
         "net-tests-utils",
         "platform-test-annotations",
+        "service-connectivity-b-pre-jarjar",
         "services.core",
         "service-connectivity-tiramisu-pre-jarjar",
         "flag-junit",
diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
index b999475..77f82f0 100644
--- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
@@ -55,7 +55,6 @@
 import android.content.IntentFilter;
 import android.net.vcn.VcnManager;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.ParcelUuid;
 import android.os.PersistableBundle;
 import android.os.test.TestLooper;
@@ -72,6 +71,8 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.modules.utils.HandlerExecutor;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;