Merge "Remove erronous failure log" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 17e7d7a..2192965 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8893,8 +8893,8 @@
   }
 
   @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager {
-    method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.appfunctions.ExecuteAppFunctionResponse,android.app.appfunctions.AppFunctionException>);
-    method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
+    method @RequiresPermission(value=android.Manifest.permission.EXECUTE_APP_FUNCTIONS, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.appfunctions.ExecuteAppFunctionResponse,android.app.appfunctions.AppFunctionException>);
+    method @RequiresPermission(value=android.Manifest.permission.EXECUTE_APP_FUNCTIONS, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
     method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
     method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
     field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0
@@ -20785,6 +20785,7 @@
     method public void registerDisplayListener(android.hardware.display.DisplayManager.DisplayListener, android.os.Handler);
     method @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public void registerDisplayListener(@NonNull java.util.concurrent.Executor, long, @NonNull android.hardware.display.DisplayManager.DisplayListener);
     method public void unregisterDisplayListener(android.hardware.display.DisplayManager.DisplayListener);
+    field @FlaggedApi("com.android.server.display.feature.flags.display_category_built_in") public static final String DISPLAY_CATEGORY_BUILT_IN_DISPLAYS = "android.hardware.display.category.BUILT_IN_DISPLAYS";
     field public static final String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION";
     field @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public static final long EVENT_TYPE_DISPLAY_ADDED = 1L; // 0x1L
     field @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public static final long EVENT_TYPE_DISPLAY_CHANGED = 4L; // 0x4L
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 41f2862..ab82411 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -150,7 +150,6 @@
     field @FlaggedApi("com.android.window.flags.untrusted_embedding_any_app_permission") public static final String EMBED_ANY_APP_IN_UNTRUSTED_MODE = "android.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE";
     field @FlaggedApi("android.content.pm.emergency_install_permission") public static final String EMERGENCY_INSTALL_PACKAGES = "android.permission.EMERGENCY_INSTALL_PACKAGES";
     field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED";
-    field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final String EXECUTE_APP_FUNCTIONS_TRUSTED = "android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED";
     field public static final String EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS = "android.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS";
     field public static final String FORCE_BACK = "android.permission.FORCE_BACK";
     field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 7c1c868..0b0738e 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -909,6 +909,14 @@
     method @NonNull public android.companion.AssociationInfo.Builder setTimeApproved(long);
   }
 
+  public final class AssociationRequest implements android.os.Parcelable {
+    method public boolean isSkipRoleGrant();
+  }
+
+  public static final class AssociationRequest.Builder {
+    method @NonNull @RequiresPermission(android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES) public android.companion.AssociationRequest.Builder setSkipRoleGrant(boolean);
+  }
+
   public final class CompanionDeviceManager {
     method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void enableSecureTransport(boolean);
   }
@@ -1727,6 +1735,7 @@
     method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public void setShouldAlwaysRespectAppRequestedMode(boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setUserDisabledHdrTypes(@NonNull int[]);
     method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public boolean shouldAlwaysRespectAppRequestedMode();
+    field public static final String DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED = "android.hardware.display.category.ALL_INCLUDING_DISABLED";
     field public static final String DISPLAY_CATEGORY_REAR = "android.hardware.display.category.REAR";
     field public static final String HDR_OUTPUT_CONTROL_FLAG = "enable_hdr_output_control";
     field public static final int SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS = 2; // 0x2
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index e0a9371..9d1d9c7 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -162,7 +162,7 @@
      * both to fields in the rule itself (such as its name) and items with sub-fields.
      * @hide
      */
-    public static final int MAX_STRING_LENGTH = 1000;
+    public static final int MAX_STRING_LENGTH = 500;
 
     /**
      * The maximum string length for the trigger description rule, given UI constraints.
diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java
index 6fd8db9..0a3891f 100644
--- a/core/java/android/app/appfunctions/AppFunctionManager.java
+++ b/core/java/android/app/appfunctions/AppFunctionManager.java
@@ -72,10 +72,10 @@
  * <p>To execute an app function, the caller app can retrieve the {@code functionIdentifier} from
  * the {@code AppFunctionStaticMetadata} document and use it to build an {@link
  * ExecuteAppFunctionRequest}. Then, invoke {@link #executeAppFunction} with the request to execute
- * the app function. Callers need the {@code android.permission.EXECUTE_APP_FUNCTIONS} or {@code
- * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} permission to execute app functions from other
- * apps. An app can always execute its own app functions and doesn't need these permissions.
- * AppFunction SDK provides a convenient way to achieve this and is the preferred method.
+ * the app function. Callers need the {@code android.permission.EXECUTE_APP_FUNCTIONS} permission to
+ * execute app functions from other apps. An app can always execute its own app functions and
+ * doesn't need these permissions. AppFunction SDK provides a convenient way to achieve this and
+ * is the preferred method.
  *
  * <h3>Example</h3>
  *
@@ -141,32 +141,24 @@
      * Executes the app function.
      *
      * <p>Note: Applications can execute functions they define. To execute functions defined in
-     * another component, apps would need to have {@code
-     * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code
-     * android.permission.EXECUTE_APP_FUNCTIONS}.
+     * another component, apps would need to have the permission
+     * {@code android.permission.EXECUTE_APP_FUNCTIONS}.
      *
      * @param request the request to execute the app function
      * @param executor the executor to run the callback
      * @param cancellationSignal the cancellation signal to cancel the execution.
      * @param callback the callback to receive the function execution result or error.
      *     <p>If the calling app does not own the app function or does not have {@code
-     *     android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code
      *     android.permission.EXECUTE_APP_FUNCTIONS}, the execution result will contain {@code
      *     AppFunctionException.ERROR_DENIED}.
-     *     <p>If the caller only has {@code android.permission.EXECUTE_APP_FUNCTIONS} but the
-     *     function requires {@code android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED}, the execution
+     *     <p>If the caller only has {@code android.permission.EXECUTE_APP_FUNCTIONS}, the execution
      *     result will contain {@code AppFunctionException.ERROR_DENIED}
      *     <p>If the function requested for execution is disabled, then the execution result will
      *     contain {@code AppFunctionException.ERROR_DISABLED}
      *     <p>If the cancellation signal is issued, the operation is cancelled and no response is
      *     returned to the caller.
      */
-    @RequiresPermission(
-            anyOf = {
-                Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
-                Manifest.permission.EXECUTE_APP_FUNCTIONS
-            },
-            conditional = true)
+    @RequiresPermission(value = Manifest.permission.EXECUTE_APP_FUNCTIONS, conditional = true)
     @UserHandleAware
     public void executeAppFunction(
             @NonNull ExecuteAppFunctionRequest request,
@@ -222,9 +214,8 @@
      * Returns a boolean through a callback, indicating whether the app function is enabled.
      *
      * <p>This method can only check app functions owned by the caller, or those where the caller
-     * has visibility to the owner package and holds either the {@link
-     * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
-     * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission.
+     * has visibility to the owner package and holds the
+     * {@link Manifest.permission#EXECUTE_APP_FUNCTIONS} permission.
      *
      * <p>If the operation fails, the callback's {@link OutcomeReceiver#onError} is called with
      * errors:
@@ -241,12 +232,7 @@
      * @param executor the executor to run the request
      * @param callback the callback to receive the function enabled check result
      */
-    @RequiresPermission(
-            anyOf = {
-                Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
-                Manifest.permission.EXECUTE_APP_FUNCTIONS
-            },
-            conditional = true)
+    @RequiresPermission(value = Manifest.permission.EXECUTE_APP_FUNCTIONS, conditional = true)
     public void isAppFunctionEnabled(
             @NonNull String functionIdentifier,
             @NonNull String targetPackage,
diff --git a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
index 64dece9..cc3ca03 100644
--- a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
+++ b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
@@ -54,9 +54,8 @@
      * Returns (through a callback) a boolean indicating whether the app function is enabled.
      *
      * This method can only check app functions owned by the caller, or those where the caller
-     * has visibility to the owner package and holds either the {@link
-     * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
-     * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission.
+     * has visibility to the owner package and holds the {@link
+     * Manifest.permission#EXECUTE_APP_FUNCTIONS} permission.
      *
      * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
      *
diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
index 3ddda22..7743d48 100644
--- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
+++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
@@ -90,8 +90,7 @@
      * we need to have per-package app function schemas.
      *
      * <p>This schema should be set visible to callers from the package owner itself and for callers
-     * with {@link android.Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
-     * android.Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permissions.
+     * with the permission {@link android.Manifest.permission#EXECUTE_APP_FUNCTIONS}.
      *
      * @param packageName The package name to create a schema for.
      */
@@ -105,9 +104,8 @@
     /**
      * Creates a parent schema for all app function runtime schemas.
      *
-     * <p>This schema should be set visible to the owner itself and for callers with {@link
-     * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@link
-     * android.permission.EXECUTE_APP_FUNCTIONS} permissions.
+     * <p>This schema should be set visible to the owner itself and for callers with
+     * the permission {@link android.permission.EXECUTE_APP_FUNCTIONS}.
      */
     public static AppSearchSchema createParentAppFunctionRuntimeSchema() {
         return getAppFunctionRuntimeSchemaBuilder(RUNTIME_SCHEMA_TYPE).build();
diff --git a/core/java/android/app/appfunctions/IAppFunctionManager.aidl b/core/java/android/app/appfunctions/IAppFunctionManager.aidl
index 72335e4..098e1fe 100644
--- a/core/java/android/app/appfunctions/IAppFunctionManager.aidl
+++ b/core/java/android/app/appfunctions/IAppFunctionManager.aidl
@@ -34,7 +34,7 @@
     * @param request the request to execute an app function.
     * @param callback the callback to report the result.
     */
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional = true)")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = android.Manifest.permission.EXECUTE_APP_FUNCTIONS, conditional = true)")
     ICancellationSignal executeAppFunction(
         in ExecuteAppFunctionAidlRequest request,
         in IExecuteAppFunctionCallback callback
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index a098a60..67dea32 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -16,6 +16,7 @@
 
 package android.companion;
 
+import static android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES;
 import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED;
 
 import static com.android.internal.util.CollectionUtils.emptyIfNull;
@@ -28,7 +29,10 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.StringDef;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
 import android.annotation.UserIdInt;
+import android.app.KeyguardManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.drawable.Icon;
 import android.os.Build;
@@ -214,6 +218,11 @@
     private final boolean mForceConfirmation;
 
     /**
+     * Whether to skip the role grant, permission checks and consent dialog.
+     */
+    private final boolean mSkipRoleGrant;
+
+    /**
      * The app package name of the application the association will belong to.
      * Populated by the system.
      * @hide
@@ -283,6 +292,7 @@
             @Nullable CharSequence displayName,
             boolean selfManaged,
             boolean forceConfirmation,
+            boolean skipRoleGrant,
             @Nullable Icon deviceIcon) {
         mSingleDevice = singleDevice;
         mDeviceFilters = requireNonNull(deviceFilters);
@@ -290,6 +300,7 @@
         mDisplayName = displayName;
         mSelfManaged = selfManaged;
         mForceConfirmation = forceConfirmation;
+        mSkipRoleGrant = skipRoleGrant;
         mCreationTime = System.currentTimeMillis();
         mDeviceIcon = deviceIcon;
     }
@@ -333,6 +344,18 @@
     }
 
     /**
+     * Whether to skip the role grant, permission checks and consent dialog.
+     *
+     * @see Builder#setSkipRoleGrant(boolean)
+     * @hide
+     */
+    @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+    @TestApi
+    public boolean isSkipRoleGrant() {
+        return mSkipRoleGrant;
+    }
+
+    /**
      * Whether only a single device should match the provided filter.
      *
      * When scanning for a single device with a specific {@link BluetoothDeviceFilter} mac
@@ -407,6 +430,7 @@
         private CharSequence mDisplayName;
         private boolean mSelfManaged = false;
         private boolean mForceConfirmation = false;
+        private boolean mSkipRoleGrant = false;
         private Icon mDeviceIcon = null;
 
         public Builder() {}
@@ -494,6 +518,27 @@
         }
 
         /**
+         * Do not attempt to grant the role corresponding to the device profile.
+         *
+         * <p>This will skip the permission checks and consent dialog but will not fail if the
+         * role cannot be granted.</p>
+         *
+         * <p>Requires that the device not to have secure lock screen and that there no locked SIM
+         * card. See {@link KeyguardManager#isKeyguardSecure()}</p>
+         *
+         * @hide
+         */
+        @RequiresPermission(ASSOCIATE_COMPANION_DEVICES)
+        @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+        @TestApi
+        @NonNull
+        public Builder setSkipRoleGrant(boolean skipRoleGrant) {
+            checkNotUsed();
+            mSkipRoleGrant = skipRoleGrant;
+            return this;
+        }
+
+        /**
          * Set the device icon for the self-managed device and to display the icon in the
          * self-managed association dialog.
          * <p>The given device icon will be resized to 24dp x 24dp.
@@ -521,7 +566,8 @@
                         + "provide the display name of the device");
             }
             return new AssociationRequest(mSingleDevice, emptyIfNull(mDeviceFilters),
-                    mDeviceProfile, mDisplayName, mSelfManaged, mForceConfirmation, mDeviceIcon);
+                    mDeviceProfile, mDisplayName, mSelfManaged, mForceConfirmation, mSkipRoleGrant,
+                    mDeviceIcon);
         }
     }
 
@@ -597,6 +643,7 @@
                 + ", associatedDevice = " + mAssociatedDevice
                 + ", selfManaged = " + mSelfManaged
                 + ", forceConfirmation = " + mForceConfirmation
+                + ", skipRoleGrant = " + mSkipRoleGrant
                 + ", packageName = " + mPackageName
                 + ", userId = " + mUserId
                 + ", deviceProfilePrivilegesDescription = " + mDeviceProfilePrivilegesDescription
@@ -617,6 +664,7 @@
                 && Objects.equals(mAssociatedDevice, that.mAssociatedDevice)
                 && mSelfManaged == that.mSelfManaged
                 && mForceConfirmation == that.mForceConfirmation
+                && mSkipRoleGrant == that.mSkipRoleGrant
                 && Objects.equals(mPackageName, that.mPackageName)
                 && mUserId == that.mUserId
                 && Objects.equals(mDeviceProfilePrivilegesDescription,
@@ -637,6 +685,7 @@
         _hash = 31 * _hash + Objects.hashCode(mAssociatedDevice);
         _hash = 31 * _hash + Boolean.hashCode(mSelfManaged);
         _hash = 31 * _hash + Boolean.hashCode(mForceConfirmation);
+        _hash = 31 * _hash + Boolean.hashCode(mSkipRoleGrant);
         _hash = 31 * _hash + Objects.hashCode(mPackageName);
         _hash = 31 * _hash + mUserId;
         _hash = 31 * _hash + Objects.hashCode(mDeviceProfilePrivilegesDescription);
@@ -659,6 +708,7 @@
         if (mAssociatedDevice != null) flg |= 0x40;
         if (mPackageName != null) flg |= 0x80;
         if (mDeviceProfilePrivilegesDescription != null) flg |= 0x100;
+        if (mSkipRoleGrant) flg |= 0x200;
 
         dest.writeInt(flg);
         dest.writeParcelableList(mDeviceFilters, flags);
@@ -692,6 +742,7 @@
         boolean selfManaged = (flg & 0x2) != 0;
         boolean forceConfirmation = (flg & 0x4) != 0;
         boolean skipPrompt = (flg & 0x8) != 0;
+        boolean skipRoleGrant = (flg & 0x200) != 0;
         List<DeviceFilter<?>> deviceFilters = new ArrayList<>();
         in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader(),
                 (Class<android.companion.DeviceFilter<?>>) (Class<?>)
@@ -714,6 +765,7 @@
         this.mAssociatedDevice = associatedDevice;
         this.mSelfManaged = selfManaged;
         this.mForceConfirmation = forceConfirmation;
+        this.mSkipRoleGrant = skipRoleGrant;
         this.mPackageName = packageName;
         this.mUserId = userId;
         com.android.internal.util.AnnotationValidations.validate(
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 4696882..0312ad7 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -12426,8 +12426,8 @@
     }
 
     private void collectNestedIntentKeysRecur(Set<Intent> visited, boolean forceUnparcel) {
-        addExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED);
         if (mExtras != null && (forceUnparcel || !mExtras.isParcelled()) && !mExtras.isEmpty()) {
+            addExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED);
             for (String key : mExtras.keySet()) {
                 Object value;
                 try {
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 34e86a4..a96de4b 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -146,6 +146,22 @@
             "android.hardware.display.category.PRESENTATION";
 
     /**
+     * Display category: Built in displays.
+     *
+     * <p>
+     *     This category can be used to identify displays that are built into the device. The
+     *     displays that are returned may be inactive or disabled at the current moment. The
+     *     returned displays are useful in identifying the various sizes of built-in displays. The
+     *     id from {@link Display#getDisplayId()} is not guaranteed to be stable and may change
+     *     when the display becomes active.
+     * </p>
+     * @see #getDisplays(String)
+     */
+    @FlaggedApi(com.android.server.display.feature.flags.Flags.FLAG_DISPLAY_CATEGORY_BUILT_IN)
+    public static final String DISPLAY_CATEGORY_BUILT_IN_DISPLAYS =
+            "android.hardware.display.category.BUILT_IN_DISPLAYS";
+
+    /**
      * Display category: Rear displays.
      * <p>
      * This category can be used to identify complementary internal displays that are facing away
@@ -171,6 +187,8 @@
      * @see #getDisplays(String)
      * @hide
      */
+    @TestApi
+    @SuppressLint("UnflaggedApi")
     public static final String DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED =
             "android.hardware.display.category.ALL_INCLUDING_DISABLED";
 
@@ -623,9 +641,6 @@
      * is triggered whenever the properties of a {@link android.view.Display}, such as size,
      * state, density are modified.
      *
-     * This event is not triggered for refresh rate changes as they can change very often.
-     * To monitor refresh rate changes, subscribe to {@link EVENT_TYPE_DISPLAY_REFRESH_RATE}.
-     *
      * @see #registerDisplayListener(DisplayListener, Handler, long)
      *
      */
@@ -732,10 +747,13 @@
      * @see #DISPLAY_CATEGORY_PRESENTATION
      */
     public Display[] getDisplays(String category) {
-        boolean includeDisabled = (category != null
-                && category.equals(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED));
+        boolean includeDisabled = shouldIncludeDisabledDisplays(category);
         final int[] displayIds = mGlobal.getDisplayIds(includeDisabled);
-        if (DISPLAY_CATEGORY_PRESENTATION.equals(category)) {
+        if (Flags.displayCategoryBuiltIn()
+                && DISPLAY_CATEGORY_BUILT_IN_DISPLAYS.equals(category)) {
+            Display[] value = getDisplays(displayIds, DisplayManager::isBuiltInDisplay);
+            return value;
+        } else if (DISPLAY_CATEGORY_PRESENTATION.equals(category)) {
             return getDisplays(displayIds, DisplayManager::isPresentationDisplay);
         } else if (DISPLAY_CATEGORY_REAR.equals(category)) {
             return getDisplays(displayIds, DisplayManager::isRearDisplay);
@@ -745,6 +763,16 @@
         return new Display[0];
     }
 
+    private boolean shouldIncludeDisabledDisplays(@Nullable String category) {
+        if (DISPLAY_CATEGORY_BUILT_IN_DISPLAYS.equals(category)) {
+            return true;
+        }
+        if (DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED.equals(category)) {
+            return true;
+        }
+        return false;
+    }
+
     private Display[] getDisplays(int[] displayIds, Predicate<Display> predicate) {
         ArrayList<Display> tmpDisplays = new ArrayList<>();
         for (int displayId : displayIds) {
@@ -756,6 +784,13 @@
         return tmpDisplays.toArray(new Display[tmpDisplays.size()]);
     }
 
+    private static boolean isBuiltInDisplay(@Nullable Display display) {
+        if (display == null) {
+            return false;
+        }
+        return display.getType() == Display.TYPE_INTERNAL;
+    }
+
     private static boolean isPresentationDisplay(@Nullable Display display) {
         if (display == null || (display.getDisplayId() == DEFAULT_DISPLAY)
                 || (display.getFlags() & Display.FLAG_PRESENTATION) == 0) {
@@ -804,9 +839,6 @@
      * Registers a display listener to receive notifications about when
      * displays are added, removed or changed.
      *
-     * We encourage to use {@link #registerDisplayListener(Executor, long, DisplayListener)}
-     * instead to subscribe for explicit events of interest
-     *
      * @param listener The listener to register.
      * @param handler The handler on which the listener should be invoked, or null
      * if the listener should be invoked on the calling thread's looper.
@@ -815,9 +847,7 @@
      */
     public void registerDisplayListener(DisplayListener listener, Handler handler) {
         registerDisplayListener(listener, handler, EVENT_TYPE_DISPLAY_ADDED
-                | EVENT_TYPE_DISPLAY_CHANGED
-                | EVENT_TYPE_DISPLAY_REFRESH_RATE
-                | EVENT_TYPE_DISPLAY_REMOVED);
+                | EVENT_TYPE_DISPLAY_CHANGED | EVENT_TYPE_DISPLAY_REMOVED);
     }
 
     /**
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 339dbf2..b5715ed 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -1766,23 +1766,29 @@
         }
 
         if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_CHANGED) != 0) {
-            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED;
+            // For backward compatibility, a client subscribing to
+            // DisplayManager.EVENT_FLAG_DISPLAY_CHANGED will be enrolled to both Basic and
+            // RR changes
+            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
+                    | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
         }
 
-        if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REMOVED) != 0) {
+        if ((eventFlags
+                & DisplayManager.EVENT_TYPE_DISPLAY_REMOVED) != 0) {
             baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
         }
 
-        if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REFRESH_RATE) != 0) {
-            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
-        }
-
         if (Flags.displayListenerPerformanceImprovements()) {
+            if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REFRESH_RATE) != 0) {
+                baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
+            }
+
             if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_STATE) != 0) {
                 baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_STATE;
             }
         }
 
+
         return baseEventMask;
     }
 }
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 1cf293d..e79b2e7 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -142,6 +142,7 @@
     /** {@hide} */
     @VisibleForTesting
     public int mFlags;
+    private boolean mHasIntent = false;
 
     /**
      * Constructs a new, empty Bundle that uses a specific ClassLoader for
@@ -258,9 +259,20 @@
 
             // Keep as last statement to ensure visibility of other fields
             mParcelledData = parcelledData;
+            mHasIntent = from.mHasIntent;
         }
     }
 
+    /** @hide */
+    public boolean hasIntent() {
+        return mHasIntent;
+    }
+
+    /** @hide */
+    public void setHasIntent(boolean hasIntent) {
+        mHasIntent = hasIntent;
+    }
+
     /**
      * TODO: optimize this later (getting just the value part of a Bundle
      * with a single pair) once Bundle.forPair() above is implemented
@@ -1837,6 +1849,7 @@
                     parcel.writeInt(length);
                     parcel.writeInt(mParcelledByNative ? BUNDLE_MAGIC_NATIVE : BUNDLE_MAGIC);
                     parcel.appendFrom(mParcelledData, 0, length);
+                    parcel.writeBoolean(mHasIntent);
                 }
                 return;
             }
@@ -1851,7 +1864,6 @@
         int lengthPos = parcel.dataPosition();
         parcel.writeInt(-1); // placeholder, will hold length
         parcel.writeInt(BUNDLE_MAGIC);
-
         int startPos = parcel.dataPosition();
         parcel.writeArrayMapInternal(map);
         int endPos = parcel.dataPosition();
@@ -1861,6 +1873,7 @@
         int length = endPos - startPos;
         parcel.writeInt(length);
         parcel.setDataPosition(endPos);
+        parcel.writeBoolean(mHasIntent);
     }
 
     /**
@@ -1904,6 +1917,7 @@
                 mOwnsLazyValues = false;
                 initializeFromParcelLocked(parcel, /*ownsParcel*/ false, isNativeBundle);
             }
+            mHasIntent = parcel.readBoolean();
             return;
         }
 
@@ -1922,6 +1936,7 @@
         mOwnsLazyValues = true;
         mParcelledByNative = isNativeBundle;
         mParcelledData = p;
+        mHasIntent = parcel.readBoolean();
     }
 
     /** {@hide} */
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 6b1e918..ee62dea 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -149,11 +149,6 @@
     private static volatile boolean sStackTrackingEnabled = false;
 
     /**
-     * The extension binder object
-     */
-    private IBinder mExtension = null;
-
-    /**
      * Enable Binder IPC stack tracking. If enabled, every binder transaction will be logged to
      * {@link TransactionTracker}.
      *
@@ -1242,9 +1237,7 @@
 
     /** @hide */
     @Override
-    public final @Nullable IBinder getExtension() {
-        return mExtension;
-    }
+    public final native @Nullable IBinder getExtension();
 
     /**
      * Set the binder extension.
@@ -1252,12 +1245,7 @@
      *
      * @hide
      */
-    public final void setExtension(@Nullable IBinder extension) {
-        mExtension = extension;
-        setExtensionNative(extension);
-    }
-
-    private final native void setExtensionNative(@Nullable IBinder extension);
+    public final native void setExtension(@Nullable IBinder extension);
 
     /**
      * Default implementation rewinds the parcels and calls onTransact. On
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index 819d58d..a24dc57 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -398,7 +398,7 @@
         if ((bundle.mFlags & FLAG_HAS_BINDERS_KNOWN) == 0) {
             mFlags &= ~FLAG_HAS_BINDERS_KNOWN;
         }
-        mFlags |= bundle.mFlags & FLAG_HAS_INTENT;
+        setHasIntent(hasIntent() || bundle.hasIntent());
     }
 
     /**
@@ -465,7 +465,7 @@
      * @hide
      */
     public boolean hasIntent() {
-        return (mFlags & FLAG_HAS_INTENT) != 0;
+        return super.hasIntent();
     }
 
     /** {@hide} */
@@ -591,7 +591,7 @@
         mFlags &= ~FLAG_HAS_FDS_KNOWN;
         mFlags &= ~FLAG_HAS_BINDERS_KNOWN;
         if (intentClass != null && intentClass.isInstance(value)) {
-            mFlags |= FLAG_HAS_INTENT;
+            setHasIntent(true);
         }
     }
 
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index df680c0..73cd5ec 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -32,6 +32,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.util.AttributeSet;
+import android.util.TypedValue;
 import android.widget.FrameLayout;
 import android.widget.RelativeLayout;
 import android.widget.RemoteViews;
@@ -265,14 +266,20 @@
                 ? R.style.TextAppearance_DeviceDefault_Notification_Title
                 : R.style.TextAppearance_DeviceDefault_Notification_Info;
         // Most of the time, we're showing text in the minimized state
-        View headerText = findViewById(R.id.header_text);
-        if (headerText instanceof TextView) {
-            ((TextView) headerText).setTextAppearance(styleResId);
+        if (findViewById(R.id.header_text) instanceof TextView headerText) {
+            headerText.setTextAppearance(styleResId);
+            if (notificationsRedesignTemplates()) {
+                // TODO: b/378660052 - When inlining the redesign flag, this should be updated
+                //  directly in TextAppearance_DeviceDefault_Notification_Title so we won't need to
+                //  override it here.
+                float textSize = getContext().getResources().getDimension(
+                        R.dimen.notification_2025_title_text_size);
+                headerText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+            }
         }
         // If there's no summary or text, we show the app name instead of nothing
-        View appNameText = findViewById(R.id.app_name_text);
-        if (appNameText instanceof TextView) {
-            ((TextView) appNameText).setTextAppearance(styleResId);
+        if (findViewById(R.id.app_name_text) instanceof TextView appNameText) {
+            appNameText.setTextAppearance(styleResId);
         }
     }
 
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 2918610..9468301 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -95,7 +95,9 @@
         Flags::enableTopVisibleRootTaskPerUserTracking, true),
     ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX(
             Flags::enableDesktopRecentsTransitionsCornersBugfix, false),
-    ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS(Flags::enableDesktopSystemDialogsTransitions, true);
+    ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS(Flags::enableDesktopSystemDialogsTransitions, true),
+    ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES(
+        Flags::enableDesktopWindowingMultiInstanceFeatures, true);
 
     /**
      * Flag class, to be used in case the enum cannot be used because the flag is not accessible.
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 72cb9d1..98d1ef6 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -217,9 +217,9 @@
     void setUdfpsRefreshRateCallback(in IUdfpsRefreshRateRequestCallback callback);
 
     /**
-     * Notifies System UI that the display is ready to show system decorations.
+     * Notifies System UI that the system decorations should be added on the display.
      */
-    void onDisplayReady(int displayId);
+    void onDisplayAddSystemDecorations(int displayId);
 
     /**
      * Notifies System UI that the system decorations should be removed from the display.
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 3afe27e..a2f4ca2 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -280,6 +280,7 @@
             ],
 
             static_libs: [
+                "android.os.flags-aconfig-cc",
                 "libasync_safe",
                 "libbinderthreadstateutils",
                 "libdmabufinfo",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index aea1734..5c0b720 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -22,6 +22,7 @@
 #include <android-base/parsebool.h>
 #include <android-base/properties.h>
 #include <android/graphics/jni_runtime.h>
+#include <android_os.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <assert.h>
 #include <binder/IBinder.h>
@@ -893,9 +894,13 @@
                        madviseWillNeedFileSizeOdex,
                        "-XMadviseWillNeedOdexFileSize:");
 
-    parseRuntimeOption("dalvik.vm.madvise.artfile.size",
-                       madviseWillNeedFileSizeArt,
-                       "-XMadviseWillNeedArtFileSize:");
+    // Historically, dalvik.vm.madvise.artfile.size was set to UINT_MAX by default. With the
+    // disable_madvise_art_default flag rollout, we use this default only when the flag is disabled.
+    // TODO(b/382110550): Remove this property/flag entirely after validating and ramping.
+    const char* madvise_artfile_size_default =
+            android::os::disable_madvise_artfile_default() ? "" : "4294967295";
+    parseRuntimeOption("dalvik.vm.madvise.artfile.size", madviseWillNeedFileSizeArt,
+                       "-XMadviseWillNeedArtFileSize:", madvise_artfile_size_default);
 
     /*
      * Profile related options.
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 91b25c2..639f5bf 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -74,7 +74,6 @@
     jmethodID mExecTransact;
     jmethodID mGetInterfaceDescriptor;
     jmethodID mTransactionCallback;
-    jmethodID mGetExtension;
 
     // Object state.
     jfieldID mObject;
@@ -490,12 +489,8 @@
             if (mVintf) {
                 ::android::internal::Stability::markVintf(b.get());
             }
-            if (mSetExtensionCalled) {
-                jobject javaIBinderObject = env->CallObjectMethod(obj, gBinderOffsets.mGetExtension);
-                sp<IBinder> extensionFromJava = ibinderForJavaObject(env, javaIBinderObject);
-                if (extensionFromJava != nullptr) {
-                    b.get()->setExtension(extensionFromJava);
-                }
+            if (mExtension != nullptr) {
+                b.get()->setExtension(mExtension);
             }
             mBinder = b;
             ALOGV("Creating JavaBinder %p (refs %p) for Object %p, weakCount=%" PRId32 "\n",
@@ -521,12 +516,21 @@
         mVintf = false;
     }
 
-    void setExtension(const sp<IBinder>& extension) {
+    sp<IBinder> getExtension() {
         AutoMutex _l(mLock);
-        mSetExtensionCalled = true;
         sp<JavaBBinder> b = mBinder.promote();
         if (b != nullptr) {
-            b.get()->setExtension(extension);
+            return b.get()->getExtension();
+        }
+        return mExtension;
+    }
+
+    void setExtension(const sp<IBinder>& extension) {
+        AutoMutex _l(mLock);
+        mExtension = extension;
+        sp<JavaBBinder> b = mBinder.promote();
+        if (b != nullptr) {
+            b.get()->setExtension(mExtension);
         }
     }
 
@@ -538,7 +542,8 @@
     // is too much binder state here, we can think about making JavaBBinder an
     // sp here (avoid recreating it)
     bool            mVintf = false;
-    bool            mSetExtensionCalled = false;
+
+    sp<IBinder>     mExtension;
 };
 
 // ----------------------------------------------------------------------------
@@ -1244,6 +1249,10 @@
     return IPCThreadState::self()->blockUntilThreadAvailable();
 }
 
+static jobject android_os_Binder_getExtension(JNIEnv* env, jobject obj) {
+    JavaBBinderHolder* jbh = (JavaBBinderHolder*) env->GetLongField(obj, gBinderOffsets.mObject);
+    return javaObjectForIBinder(env, jbh->getExtension());
+}
 
 static void android_os_Binder_setExtension(JNIEnv* env, jobject obj, jobject extensionObject) {
     JavaBBinderHolder* jbh = (JavaBBinderHolder*) env->GetLongField(obj, gBinderOffsets.mObject);
@@ -1286,7 +1295,8 @@
     { "getNativeBBinderHolder", "()J", (void*)android_os_Binder_getNativeBBinderHolder },
     { "getNativeFinalizer", "()J", (void*)android_os_Binder_getNativeFinalizer },
     { "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable },
-    { "setExtensionNative", "(Landroid/os/IBinder;)V", (void*)android_os_Binder_setExtension },
+    { "getExtension", "()Landroid/os/IBinder;", (void*)android_os_Binder_getExtension },
+    { "setExtension", "(Landroid/os/IBinder;)V", (void*)android_os_Binder_setExtension },
 };
 // clang-format on
 
@@ -1303,8 +1313,6 @@
     gBinderOffsets.mTransactionCallback =
             GetStaticMethodIDOrDie(env, clazz, "transactionCallback", "(IIII)V");
     gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J");
-    gBinderOffsets.mGetExtension = GetMethodIDOrDie(env, clazz, "getExtension",
-                                                        "()Landroid/os/IBinder;");
 
     return RegisterMethodsOrDie(
         env, kBinderPathName,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index aad8f8a..005c14d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8308,26 +8308,9 @@
         android:featureFlag="android.app.appfunctions.flags.enable_app_function_manager"
         android:protectionLevel="signature" />
 
-    <!-- Allows a trusted application to perform actions on behalf of users inside of
-         applications with privacy guarantees from the system.
-         <p>This permission is currently only granted to system packages in the
-         {@link android.app.role.SYSTEM_UI_INTELLIGENCE} role which complies with privacy
-         requirements outlined in the Android CDD section "9.8.6 Content Capture".
-         <p>Apps are not able to opt-out from caller having this permission.
-         <p>Protection level: internal|role
-         @SystemApi
-         @hide
-         @FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER)  -->
-    <permission android:name="android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED"
-        android:featureFlag="android.app.appfunctions.flags.enable_app_function_manager"
-        android:protectionLevel="internal|role" />
-
     <!-- Allows an application to perform actions on behalf of users inside of
          applications.
          <p>This permission is currently only granted to privileged system apps.
-         <p>Apps contributing app functions can opt to disallow callers with this permission,
-         limiting to only callers with {@link android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED}
-         instead.
          <p>Protection level: internal|privileged
          @FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER)  -->
     <permission android:name="android.permission.EXECUTE_APP_FUNCTIONS"
diff --git a/core/res/res/layout/notification_2025_conversation_header.xml b/core/res/res/layout/notification_2025_conversation_header.xml
index 1bde173..75bd244 100644
--- a/core/res/res/layout/notification_2025_conversation_header.xml
+++ b/core/res/res/layout/notification_2025_conversation_header.xml
@@ -29,7 +29,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
-        android:textSize="16sp"
+        android:textSize="@dimen/notification_2025_title_text_size"
         android:singleLine="true"
         android:layout_weight="1"
         />
diff --git a/core/res/res/layout/notification_2025_template_collapsed_base.xml b/core/res/res/layout/notification_2025_template_collapsed_base.xml
index d29b7af..05458329 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_base.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_base.xml
@@ -102,6 +102,7 @@
                     android:singleLine="true"
                     android:textAlignment="viewStart"
                     android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+                    android:textSize="@dimen/notification_2025_title_text_size"
                     />
 
                 <include layout="@layout/notification_2025_top_line_views" />
diff --git a/core/res/res/layout/notification_2025_template_collapsed_media.xml b/core/res/res/layout/notification_2025_template_collapsed_media.xml
index 5beab50..9959b66 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_media.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_media.xml
@@ -104,6 +104,7 @@
                     android:singleLine="true"
                     android:textAlignment="viewStart"
                     android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+                    android:textSize="@dimen/notification_2025_title_text_size"
                     />
 
                 <include layout="@layout/notification_2025_top_line_views" />
diff --git a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
index d7c3263..85ca124 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
@@ -130,6 +130,7 @@
                             android:singleLine="true"
                             android:textAlignment="viewStart"
                             android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+                            android:textSize="@dimen/notification_2025_title_text_size"
                             />
 
                         <include layout="@layout/notification_2025_top_line_views" />
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index d6b8704..5644cf9 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -577,6 +577,9 @@
     <dimen name="notification_text_size">14sp</dimen>
     <!-- Size of notification text titles (see TextAppearance.StatusBar.EventContent.Title) -->
     <dimen name="notification_title_text_size">14sp</dimen>
+    <!-- Size of notification text titles, 2025 redesign version (see TextAppearance.StatusBar.EventContent.Title) -->
+    <!-- TODO: b/378660052 - When inlining the redesign flag, this should be updated directly in TextAppearance.DeviceDefault.Notification.Title -->
+    <dimen name="notification_2025_title_text_size">16sp</dimen>
     <!-- Size of big notification text titles (see TextAppearance.StatusBar.EventContent.BigTitle) -->
     <dimen name="notification_big_title_text_size">16sp</dimen>
     <!-- Size of smaller notification text (see TextAppearance.StatusBar.EventContent.Line2, Info, Time) -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index aca9d30..8bb3c99 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -574,6 +574,7 @@
   <java-symbol type="dimen" name="notification_text_size" />
   <java-symbol type="dimen" name="notification_title_text_size" />
   <java-symbol type="dimen" name="notification_subtext_size" />
+  <java-symbol type="dimen" name="notification_2025_title_text_size" />
   <java-symbol type="dimen" name="notification_top_pad" />
   <java-symbol type="dimen" name="notification_top_pad_narrow" />
   <java-symbol type="dimen" name="notification_top_pad_large_text" />
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index dc2f0a6..8fa5103 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -307,10 +307,8 @@
         assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED,
                 mDisplayManagerGlobal
                         .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_ADDED, 0));
-        assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED,
-                mDisplayManagerGlobal
-                        .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_CHANGED,
-                                0));
+        assertEquals(DISPLAY_CHANGE_EVENTS, mDisplayManagerGlobal
+                .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_CHANGED, 0));
         assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
                 mDisplayManagerGlobal.mapFiltersToInternalEventFlag(
                         DisplayManager.EVENT_TYPE_DISPLAY_REMOVED, 0));
diff --git a/core/tests/coretests/src/android/os/BundleTest.java b/core/tests/coretests/src/android/os/BundleTest.java
index 31e0752..9aac02d 100644
--- a/core/tests/coretests/src/android/os/BundleTest.java
+++ b/core/tests/coretests/src/android/os/BundleTest.java
@@ -76,7 +76,7 @@
     /**
      * Create a test bundle, parcel it and return the parcel.
      */
-    private Parcel createBundleParcel(boolean withFd) throws Exception {
+    private Parcel createBundleParcel(boolean withFd, boolean hasIntent) throws Exception {
         final Bundle source = new Bundle();
         source.putString("string", "abc");
         source.putInt("int", 1);
@@ -85,13 +85,14 @@
             pipe[1].close();
             source.putParcelable("fd", pipe[0]);
         }
+        source.setHasIntent(hasIntent);
         return getParcelledBundle(source);
     }
 
     /**
      * Verify a bundle generated by {@link #createBundleParcel(boolean)}.
      */
-    private void checkBundle(Bundle b, boolean withFd) {
+    private void checkBundle(Bundle b, boolean withFd, boolean hasIntent) {
         // First, do the checks without actually unparceling the bundle.
         // (Note looking into the contents will unparcel a bundle, so we'll do it later.)
         assertTrue("mParcelledData shouldn't be null here.", b.isParcelled());
@@ -107,6 +108,8 @@
                     b.mFlags & (Bundle.FLAG_HAS_FDS | Bundle.FLAG_HAS_FDS_KNOWN));
         }
 
+        assertEquals(b.hasIntent(), hasIntent);
+
         // Then, check the contents.
         assertEquals("abc", b.getString("string"));
         assertEquals(1, b.getInt("int"));
@@ -139,42 +142,56 @@
         withFd = false;
 
         // new Bundle with p
-        p = createBundleParcel(withFd);
-        checkBundle(new Bundle(p), withFd);
+        p = createBundleParcel(withFd, false);
+        checkBundle(new Bundle(p), withFd, false);
         p.recycle();
 
         // new Bundle with p and length
-        p = createBundleParcel(withFd);
+        p = createBundleParcel(withFd, false);
         length = p.readInt();
-        checkBundle(new Bundle(p, length), withFd);
+        checkBundle(new Bundle(p, length), withFd, false);
         p.recycle();
 
         // readFromParcel()
-        p = createBundleParcel(withFd);
+        p = createBundleParcel(withFd, false);
         b = new Bundle();
         b.readFromParcel(p);
-        checkBundle(b, withFd);
+        checkBundle(b, withFd, false);
+        p.recycle();
+
+        // readFromParcel()
+        p = createBundleParcel(withFd, true);
+        b = new Bundle();
+        b.readFromParcel(p);
+        checkBundle(b, withFd, true);
         p.recycle();
 
         // Same test with FDs.
         withFd = true;
 
         // new Bundle with p
-        p = createBundleParcel(withFd);
-        checkBundle(new Bundle(p), withFd);
+        p = createBundleParcel(withFd, false);
+        checkBundle(new Bundle(p), withFd, false);
         p.recycle();
 
         // new Bundle with p and length
-        p = createBundleParcel(withFd);
+        p = createBundleParcel(withFd, false);
         length = p.readInt();
-        checkBundle(new Bundle(p, length), withFd);
+        checkBundle(new Bundle(p, length), withFd, false);
         p.recycle();
 
         // readFromParcel()
-        p = createBundleParcel(withFd);
+        p = createBundleParcel(withFd, false);
         b = new Bundle();
         b.readFromParcel(p);
-        checkBundle(b, withFd);
+        checkBundle(b, withFd, false);
+        p.recycle();
+
+        // readFromParcel()
+        p = createBundleParcel(withFd, true);
+        b = new Bundle();
+        b.readFromParcel(p);
+        checkBundle(b, withFd, true);
         p.recycle();
     }
 
@@ -486,6 +503,7 @@
         p.writeInt(131313); // Invalid type
         p.writeInt(0); // Anything, really
         int end = p.dataPosition();
+        p.writeBoolean(false);
         p.setDataPosition(0);
         return new Bundle(p, end - start);
     }
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index b8059d0..1edbffa 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -594,7 +594,6 @@
         <!-- Permission required for CTS test - FileIntegrityManagerTest -->
         <permission name="android.permission.SETUP_FSVERITY" />
         <!-- Permissions required for CTS test - AppFunctionManagerTest -->
-        <permission name="android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED" />
         <permission name="android.permission.EXECUTE_APP_FUNCTIONS" />
         <!-- Permission required for CTS test - CtsNfcTestCases -->
         <permission name="android.permission.NFC_SET_CONTROLLER_ALWAYS_ON" />
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
index 0ea3c2a..835456b 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
@@ -20,20 +20,23 @@
 import android.content.Context
 import android.window.DesktopModeFlags
 import com.android.internal.R
+import java.util.ArrayList
 
 /**
  * Class to decide whether to apply app compat policies in desktop mode.
  */
 // TODO(b/347289970): Consider replacing with API
-class DesktopModeCompatPolicy(context: Context) {
+class DesktopModeCompatPolicy(private val context: Context) {
 
     private val systemUiPackage: String = context.resources.getString(R.string.config_systemUi)
+    private val defaultHomePackage: String?
+        get() = context.getPackageManager().getHomeActivities(ArrayList())?.packageName
 
     /**
      * If the top activity should be exempt from desktop windowing and forced back to fullscreen.
-     * Currently includes all system ui activities and modal dialogs. However if the top activity is
-     * not being displayed, regardless of its configuration, we will not exempt it as to remain in
-     * the desktop windowing environment.
+     * Currently includes all system ui, default home and transparent stack activities. However if
+     * the top activity is not being displayed, regardless of its configuration, we will not exempt
+     * it as to remain in the desktop windowing environment.
      */
     fun isTopActivityExemptFromDesktopWindowing(task: TaskInfo) =
         isTopActivityExemptFromDesktopWindowing(task.baseActivity?.packageName,
@@ -43,6 +46,7 @@
         numActivities: Int, isTopActivityNoDisplay: Boolean, isActivityStackTransparent: Boolean) =
         DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue
                 && ((isSystemUiTask(packageName)
+                || isPartOfDefaultHomePackage(packageName)
                 || isTransparentTask(isActivityStackTransparent, numActivities))
                 && !isTopActivityNoDisplay)
 
@@ -57,4 +61,10 @@
         isActivityStackTransparent && numActivities > 0
 
     private fun isSystemUiTask(packageName: String?) = packageName == systemUiPackage
+
+    /**
+     * Returns true if the tasks base activity is part of the default home package.
+     */
+    private fun isPartOfDefaultHomePackage(packageName: String?) =
+        packageName != null && packageName == defaultHomePackage
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index b4ef9f0f..55ed5fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -168,7 +168,8 @@
 
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT,
+            @NonNull IBinder mergeTarget,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         mAnimationRunner.cancelAnimationFromMerge();
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 8dabd54..d1c7f7d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -1463,7 +1463,9 @@
 
         @Override
         public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-                @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+                @NonNull SurfaceControl.Transaction startT,
+                @NonNull SurfaceControl.Transaction finishT,
+                @NonNull IBinder mergeTarget,
                 @NonNull Transitions.TransitionFinishCallback finishCallback) {
             if (mClosePrepareTransition == transition) {
                 mClosePrepareTransition = null;
@@ -1476,7 +1478,7 @@
             if (info.getType() == TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION
                     && !mCloseTransitionRequested && info.getChanges().isEmpty() && mApps == null) {
                 finishCallback.onTransitionFinished(null);
-                t.apply();
+                startT.apply();
                 applyFinishOpenTransition();
                 return;
             }
@@ -1489,7 +1491,7 @@
             }
             // Handle the commit transition if this handler is running the open transition.
             finishCallback.onTransitionFinished(null);
-            t.apply();
+            startT.apply();
             if (mCloseTransitionRequested) {
                 if (mApps == null || mApps.length == 0) {
                     // animation was done
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
index 29fb1a2..48b83ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
@@ -248,7 +248,9 @@
 
         @Override
         public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-                @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+                @NonNull SurfaceControl.Transaction startT,
+                @NonNull SurfaceControl.Transaction finishT,
+                @NonNull IBinder mergeTarget,
                 @NonNull Transitions.TransitionFinishCallback finishCallback) {
         }
 
@@ -423,7 +425,9 @@
 
         @Override
         public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-                @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+                @NonNull SurfaceControl.Transaction startT,
+                @NonNull SurfaceControl.Transaction finishT,
+                @NonNull IBinder mergeTarget,
                 @NonNull Transitions.TransitionFinishCallback finishCallback) {
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index e69d60d..4c3bde9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -39,6 +39,7 @@
 import com.android.window.flags.Flags;
 import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellInit;
 
 import java.util.ArrayList;
@@ -91,7 +92,8 @@
                 onDisplayAdded(displayIds[i]);
             }
 
-            if (Flags.enableConnectedDisplaysWindowDrag()) {
+            if (Flags.enableConnectedDisplaysWindowDrag()
+                    && DesktopModeStatus.canEnterDesktopMode(mContext)) {
                 mDisplayManager.registerTopologyListener(mMainExecutor,
                         this::onDisplayTopologyChanged);
                 onDisplayTopologyChanged(mDisplayManager.getDisplayTopology());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/UserProfileContexts.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/UserProfileContexts.kt
index 0577f9e..1693864 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/UserProfileContexts.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/UserProfileContexts.kt
@@ -25,6 +25,7 @@
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.sysui.UserChangeListener
+import androidx.core.util.size
 
 /** Creates and manages contexts for all the profiles of the current user. */
 class UserProfileContexts(
@@ -35,6 +36,8 @@
     // Contexts for all the profiles of the current user.
     private val currentProfilesContext = SparseArray<Context>()
 
+    private val shellUserId = baseContext.userId
+
     lateinit var userContext: Context
         private set
 
@@ -49,6 +52,9 @@
                     currentProfilesContext.clear()
                     this@UserProfileContexts.userContext = userContext
                     currentProfilesContext.put(newUserId, userContext)
+                    if (newUserId != shellUserId) {
+                        currentProfilesContext.put(shellUserId, baseContext)
+                    }
                 }
 
                 override fun onUserProfilesChanged(profiles: List<UserInfo>) {
@@ -69,9 +75,9 @@
             currentProfilesContext.put(profile.id, profileContext)
         }
         val profilesToRemove = buildList<Int> {
-            for (i in 0..<currentProfilesContext.size()) {
+            for (i in 0..<currentProfilesContext.size) {
                 val userId = currentProfilesContext.keyAt(i)
-                if (profiles.none { it.id == userId }) {
+                if (userId != shellUserId && profiles.none { it.id == userId }) {
                     add(userId)
                 }
             }
@@ -80,4 +86,12 @@
     }
 
     operator fun get(userId: Int): Context? = currentProfilesContext.get(userId)
+
+    fun getOrCreate(userId: Int): Context {
+        val context = currentProfilesContext[userId]
+        if (context != null) return context
+        return baseContext.createContextAsUser(UserHandle.of(userId), /* flags= */ 0).also {
+            currentProfilesContext[userId] = it
+        }
+    }
 }
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 c81838f..0f232d5 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
@@ -111,6 +111,7 @@
 import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository;
 import com.android.wm.shell.desktopmode.education.data.AppToWebEducationDatastoreRepository;
 import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer;
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver;
 import com.android.wm.shell.desktopmode.multidesks.RootTaskDesksOrganizer;
 import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository;
 import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer;
@@ -760,6 +761,7 @@
             Optional<BubbleController> bubbleController,
             OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver,
             DesksOrganizer desksOrganizer,
+            DesksTransitionObserver desksTransitionObserver,
             UserProfileContexts userProfileContexts,
             DesktopModeCompatPolicy desktopModeCompatPolicy) {
         return new DesktopTasksController(
@@ -797,6 +799,7 @@
                 bubbleController,
                 overviewToDesktopTransitionObserver,
                 desksOrganizer,
+                desksTransitionObserver,
                 userProfileContexts,
                 desktopModeCompatPolicy);
     }
@@ -1134,6 +1137,7 @@
             Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
             Optional<BackAnimationController> backAnimationController,
             DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider,
+            @NonNull DesksTransitionObserver desksTransitionObserver,
             ShellInit shellInit) {
         return desktopUserRepositories.flatMap(
                 repository ->
@@ -1146,11 +1150,20 @@
                                         desktopMixedTransitionHandler.get(),
                                         backAnimationController.get(),
                                         desktopWallpaperActivityTokenProvider,
+                                        desksTransitionObserver,
                                         shellInit)));
     }
 
     @WMSingleton
     @Provides
+    static DesksTransitionObserver provideDesksTransitionObserver(
+            @NonNull @DynamicOverride DesktopUserRepositories desktopUserRepositories
+    ) {
+        return new DesksTransitionObserver(desktopUserRepositories);
+    }
+
+    @WMSingleton
+    @Provides
     static Optional<DesktopMixedTransitionHandler> provideDesktopMixedTransitionHandler(
             Context context,
             Transitions transitions,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
index 164d04b..b93d2e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -152,8 +152,8 @@
                 pw.println("Error: desk id should be an integer")
                 return false
             }
-        pw.println("Not implemented.")
-        return false
+        controller.removeDesk(deskId)
+        return true
     }
 
     private fun runRemoveAllDesks(args: Array<String>, pw: PrintWriter): Boolean {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index 4ff1a5f..043b353 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -174,6 +174,9 @@
     /** Returns the number of desks in the given display. */
     fun getNumberOfDesks(displayId: Int) = desktopData.getNumberOfDesks(displayId)
 
+    /** Returns the display the given desk is in. */
+    fun getDisplayForDesk(deskId: Int) = desktopData.getDisplayForDesk(deskId)
+
     /** Adds [regionListener] to inform about changes to exclusion regions for all Desktop tasks. */
     fun setExclusionRegionListener(regionListener: Consumer<Region>, executor: Executor) {
         desktopGestureExclusionListener = regionListener
@@ -207,6 +210,14 @@
         desktopData.createDesk(displayId, deskId)
     }
 
+    /** Returns the ids of the existing desks in the given display. */
+    @VisibleForTesting
+    fun getDeskIds(displayId: Int): Set<Int> =
+        desktopData.desksSequence(displayId).map { desk -> desk.deskId }.toSet()
+
+    /** Returns the id of the default desk in the given display. */
+    fun getDefaultDeskId(displayId: Int): Int? = getDefaultDesk(displayId)?.deskId
+
     /** Returns the default desk in the given display. */
     private fun getDefaultDesk(displayId: Int): Desk? = desktopData.getDefaultDesk(displayId)
 
@@ -716,17 +727,13 @@
         }
     }
 
-    /**
-     * Removes the active desk for the given [displayId] and returns the active tasks on that desk.
-     *
-     * TODO: b/389960283 - add explicit [deskId] argument.
-     */
-    fun removeDesk(displayId: Int): ArraySet<Int> {
-        val desk = desktopData.getActiveDesk(displayId)
-        if (desk == null) {
-            logW("Could not find desk to remove: displayId=%d", displayId)
-            return ArraySet()
-        }
+    /** Removes the given desk and returns the active tasks in that desk. */
+    fun removeDesk(deskId: Int): Set<Int> {
+        val desk =
+            desktopData.getDesk(deskId)
+                ?: return emptySet<Int>().also {
+                    logW("Could not find desk to remove: deskId=%d", deskId)
+                }
         val activeTasks = ArraySet(desk.activeTasks)
         desktopData.remove(desk.deskId)
         return activeTasks
@@ -1066,7 +1073,7 @@
         }
 
         override fun getDisplayForDesk(deskId: Int): Int =
-            getAllActiveDesks().find { it.deskId == deskId }?.displayId
+            desksSequence().find { it.deskId == deskId }?.displayId
                 ?: error("Display for desk=$deskId not found")
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 10f8705..3f88e7b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -103,7 +103,9 @@
 import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
 import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
 import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
+import com.android.wm.shell.desktopmode.multidesks.DeskTransition
 import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
 import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener
 import com.android.wm.shell.draganddrop.DragAndDropController
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
@@ -185,6 +187,7 @@
     private val bubbleController: Optional<BubbleController>,
     private val overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver,
     private val desksOrganizer: DesksOrganizer,
+    private val desksTransitionObserver: DesksTransitionObserver,
     private val userProfileContexts: UserProfileContexts,
     private val desktopModeCompatPolicy: DesktopModeCompatPolicy,
 ) :
@@ -1875,9 +1878,10 @@
         //  need updates in some cases.
         val baseActivity = callingTaskInfo.baseActivity ?: return
         val fillIn: Intent =
-            userProfileContexts[callingTaskInfo.userId]
-                ?.packageManager
-                ?.getLaunchIntentForPackage(baseActivity.packageName) ?: return
+            userProfileContexts
+                .getOrCreate(callingTaskInfo.userId)
+                .packageManager
+                .getLaunchIntentForPackage(baseActivity.packageName) ?: return
         fillIn.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
         val launchIntent =
             PendingIntent.getActivity(
@@ -2373,20 +2377,62 @@
         )
     }
 
-    fun removeDesktop(displayId: Int) {
-        if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return
-
-        val tasksToRemove = taskRepository.removeDesk(displayId)
-        val wct = WindowContainerTransaction()
-        tasksToRemove.forEach {
-            val task = shellTaskOrganizer.getRunningTaskInfo(it)
-            if (task != null) {
-                wct.removeTask(task.token)
-            } else {
-                recentTasksController?.removeBackgroundTask(it)
+    /** Removes the default desk in the given display. */
+    @Deprecated("Deprecated with multi-desks.", ReplaceWith("removeDesk()"))
+    fun removeDefaultDeskInDisplay(displayId: Int) {
+        val deskId =
+            checkNotNull(taskRepository.getDefaultDeskId(displayId)) {
+                "Expected a default desk to exist"
             }
+        removeDesk(displayId = displayId, deskId = deskId)
+    }
+
+    /** Removes the given desk. */
+    fun removeDesk(deskId: Int) {
+        val displayId = taskRepository.getDisplayForDesk(deskId)
+        removeDesk(displayId = displayId, deskId = deskId)
+    }
+
+    private fun removeDesk(displayId: Int, deskId: Int) {
+        if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return
+        logV("removeDesk deskId=%d from displayId=%d", deskId, displayId)
+
+        val tasksToRemove =
+            if (Flags.enableMultipleDesktopsBackend()) {
+                taskRepository.getActiveTaskIdsInDesk(deskId)
+            } else {
+                // TODO: 362720497 - make sure minimized windows are also removed in WM
+                //  and the repository.
+                taskRepository.removeDesk(deskId)
+            }
+
+        val wct = WindowContainerTransaction()
+        if (!Flags.enableMultipleDesktopsBackend()) {
+            tasksToRemove.forEach {
+                val task = shellTaskOrganizer.getRunningTaskInfo(it)
+                if (task != null) {
+                    wct.removeTask(task.token)
+                } else {
+                    recentTasksController?.removeBackgroundTask(it)
+                }
+            }
+        } else {
+            // TODO: 362720497 - double check background tasks are also removed.
+            desksOrganizer.removeDesk(wct, deskId)
         }
-        if (!wct.isEmpty) transitions.startTransition(TRANSIT_CLOSE, wct, null)
+        if (!Flags.enableMultipleDesktopsBackend() && wct.isEmpty) return
+        val transition = transitions.startTransition(TRANSIT_CLOSE, wct, /* handler= */ null)
+        if (Flags.enableMultipleDesktopsBackend()) {
+            desksTransitionObserver.addPendingTransition(
+                DeskTransition.RemoveDesk(
+                    token = transition,
+                    displayId = displayId,
+                    deskId = deskId,
+                    tasks = tasksToRemove,
+                    onDeskRemovedListener = onDeskRemovedListener,
+                )
+            )
+        }
     }
 
     /** Enter split by using the focused desktop task in given `displayId`. */
@@ -3090,7 +3136,7 @@
 
         override fun removeDesktop(displayId: Int) {
             executeRemoteCallWithTaskPermission(controller, "removeDesktop") { c ->
-                c.removeDesktop(displayId)
+                c.removeDefaultDeskInDisplay(displayId)
             }
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index b364869..3ada988 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -37,6 +37,7 @@
 import com.android.wm.shell.back.BackAnimationController
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.isExitDesktopModeTransition
 import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
 import com.android.wm.shell.shared.TransitionUtil
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
@@ -58,6 +59,7 @@
     private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
     private val backAnimationController: BackAnimationController,
     private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider,
+    private val desksTransitionObserver: DesksTransitionObserver,
     shellInit: ShellInit,
 ) : Transitions.TransitionObserver {
 
@@ -87,6 +89,7 @@
         finishTransaction: SurfaceControl.Transaction,
     ) {
         // TODO: b/332682201 Update repository state
+        desksTransitionObserver.onTransitionReady(transition, info)
         if (
             DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC
                 .isTrue() && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 91f10dc..cc3d86c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -458,7 +458,8 @@
     override fun mergeAnimation(
         transition: IBinder,
         info: TransitionInfo,
-        t: SurfaceControl.Transaction,
+        startT: SurfaceControl.Transaction,
+        finishT: SurfaceControl.Transaction,
         mergeTarget: IBinder,
         finishCallback: Transitions.TransitionFinishCallback,
     ) {
@@ -488,18 +489,18 @@
         if (isEndTransition) {
             setupEndDragToDesktop(
                 info,
-                startTransaction = t,
+                startTransaction = startT,
                 finishTransaction = startTransactionFinishT,
             )
             // Call finishCallback to merge animation before startTransitionFinishCb is called
             finishCallback.onTransitionFinished(/* wct= */ null)
-            animateEndDragToDesktop(startTransaction = t, startTransitionFinishCb)
+            animateEndDragToDesktop(startTransaction = startT, startTransitionFinishCb)
         } else if (isCancelTransition) {
             info.changes.forEach { change ->
-                t.show(change.leash)
+                startT.show(change.leash)
                 startTransactionFinishT.show(change.leash)
             }
-            t.apply()
+            startT.apply()
             finishCallback.onTransitionFinished(/* wct= */ null)
             startTransitionFinishCb.onTransitionFinished(/* wct= */ null)
             clearState()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt
new file mode 100644
index 0000000..47088c0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2025 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.desktopmode.multidesks
+
+import android.os.IBinder
+
+/** Represents shell-started transitions involving desks. */
+sealed class DeskTransition {
+    /** The transition token. */
+    abstract val token: IBinder
+
+    /** A transition to remove a desk and its tasks from a display. */
+    data class RemoveDesk(
+        override val token: IBinder,
+        val displayId: Int,
+        val deskId: Int,
+        val tasks: Set<Int>,
+        val onDeskRemovedListener: OnDeskRemovedListener?,
+    ) : DeskTransition()
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt
new file mode 100644
index 0000000..3e49b8a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2025 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.desktopmode.multidesks
+
+import android.os.IBinder
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.window.TransitionInfo
+import com.android.window.flags.Flags
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
+
+/**
+ * Observer of desk-related transitions, such as adding, removing or activating a whole desk. It
+ * tracks pending transitions and updates repository state once they finish.
+ */
+class DesksTransitionObserver(private val desktopUserRepositories: DesktopUserRepositories) {
+    private val deskTransitions = mutableMapOf<IBinder, DeskTransition>()
+
+    /** Adds a pending desk transition to be tracked. */
+    fun addPendingTransition(transition: DeskTransition) {
+        if (!Flags.enableMultipleDesktopsBackend()) return
+        deskTransitions[transition.token] = transition
+    }
+
+    /**
+     * Called when any transition is ready, which may include transitions not tracked by this
+     * observer.
+     */
+    fun onTransitionReady(transition: IBinder, info: TransitionInfo) {
+        if (!Flags.enableMultipleDesktopsBackend()) return
+        val deskTransition = deskTransitions.remove(transition) ?: return
+        val desktopRepository = desktopUserRepositories.current
+        when (deskTransition) {
+            is DeskTransition.RemoveDesk -> {
+                check(info.type == TRANSIT_CLOSE) { "Expected close transition for desk removal" }
+                // TODO: b/362720497 - consider verifying the desk was actually removed through the
+                //  DesksOrganizer. The transition info won't have changes if the desk was not
+                //  visible, such as when dismissing from Overview.
+                val deskId = deskTransition.deskId
+                val displayId = deskTransition.displayId
+                desktopRepository.removeDesk(deskTransition.deskId)
+                deskTransition.onDeskRemovedListener?.onDeskRemoved(displayId, deskId)
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index 52b6c62..31715f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -175,7 +175,9 @@
 
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
+            @NonNull IBinder mergeTarget,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         ArrayList<Animator> animations = mAnimations.get(mergeTarget);
         if (animations == null) return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index f8e6285..d666126 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -277,7 +277,8 @@
 
     @Override
     public void mergeAnimation(@NonNull IBinder nextTransition, @NonNull TransitionInfo nextInfo,
-            @NonNull SurfaceControl.Transaction nextT, @NonNull IBinder currentTransition,
+            @NonNull SurfaceControl.Transaction nextT, @NonNull SurfaceControl.Transaction finishT,
+            @NonNull IBinder currentTransition,
             @NonNull TransitionFinishCallback nextFinishCallback) {
         final StartedTransition playing = mStartedTransitions.get(currentTransition);
         if (playing == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 2f3c152..f0e6ae4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -372,7 +372,9 @@
 
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
+            @NonNull IBinder mergeTarget,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         end();
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index d3ae411..0fa6a11 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -653,7 +653,9 @@
 
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
+            @NonNull IBinder mergeTarget,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: merge animation", TAG);
         if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 2299624..78aa686 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -247,7 +247,9 @@
 
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
+            @NonNull IBinder mergeTarget,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         // Just jump-cut the current animation if any, but do not merge.
         if (info.getType() == TRANSIT_EXIT_PIP) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 5513378..8ad2e1d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -307,7 +307,9 @@
 
     @Override
     public void mergeAnimation(IBinder transition, TransitionInfo info,
-            SurfaceControl.Transaction t, IBinder mergeTarget,
+            @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
+            IBinder mergeTarget,
             Transitions.TransitionFinishCallback finishCallback) {
         final RecentsController controller = findController(mergeTarget);
         if (controller == null) {
@@ -315,7 +317,7 @@
                     "RecentsTransitionHandler.mergeAnimation: no controller found");
             return;
         }
-        controller.merge(info, t, mergeTarget, finishCallback);
+        controller.merge(info, startT, mergeTarget, finishCallback);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 3091be5..fed336b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -461,12 +461,14 @@
         return transition;
     }
 
-    void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
+    void mergeAnimation(IBinder transition, TransitionInfo info,
+            SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
             IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) {
         if (mergeTarget != mAnimatingTransition) return;
 
         if (mActiveRemoteHandler != null) {
-            mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+            mActiveRemoteHandler.mergeAnimation(transition, info, startT,
+                    finishT, mergeTarget, finishCallback);
         } else {
             for (int i = mAnimations.size() - 1; i >= 0; --i) {
                 final Animator anim = mAnimations.get(i);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 2174017..6783df8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -2977,10 +2977,13 @@
 
     @Override
     public void mergeAnimation(IBinder transition, TransitionInfo info,
-            SurfaceControl.Transaction t, IBinder mergeTarget,
+            @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
+            IBinder mergeTarget,
             Transitions.TransitionFinishCallback finishCallback) {
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "mergeAnimation: transition=%d", info.getDebugId());
-        mSplitTransitions.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+        mSplitTransitions.mergeAnimation(transition, info, startT, finishT, mergeTarget,
+                finishCallback);
     }
 
     /** Jump the current transition animation to the end. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index d8e7c2c..743bd05 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -176,7 +176,9 @@
 
         abstract void mergeAnimation(
                 @NonNull IBinder transition, @NonNull TransitionInfo info,
-                @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+                @NonNull SurfaceControl.Transaction startT,
+                @NonNull SurfaceControl.Transaction finishT,
+                @NonNull IBinder mergeTarget,
                 @NonNull Transitions.TransitionFinishCallback finishCallback);
 
         abstract void onTransitionConsumed(
@@ -691,7 +693,9 @@
 
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
+            @NonNull IBinder mergeTarget,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         for (int i = 0; i < mActiveTransitions.size(); ++i) {
             if (mActiveTransitions.get(i).mTransition != mergeTarget) continue;
@@ -701,7 +705,7 @@
                 // Already done, so no need to end it.
                 return;
             }
-            mixed.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+            mixed.mergeAnimation(transition, info, startT, finishT, mergeTarget, finishCallback);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
index 29a58d7..1853ffa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
@@ -384,7 +384,8 @@
     @Override
     void mergeAnimation(
             @NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT,
+            @NonNull IBinder mergeTarget,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         switch (mType) {
             case TYPE_DISPLAY_AND_SPLIT_CHANGE:
@@ -394,7 +395,7 @@
             case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
                 mPipHandler.end();
                 mActivityEmbeddingController.mergeAnimation(
-                        transition, info, t, mergeTarget, finishCallback);
+                        transition, info, startT, finishT, mergeTarget, finishCallback);
                 return;
             case TYPE_ENTER_PIP_FROM_SPLIT:
                 if (mAnimType == ANIM_TYPE_GOING_HOME) {
@@ -405,26 +406,28 @@
                     mPipHandler.end();
                     if (mLeftoversHandler != null) {
                         mLeftoversHandler.mergeAnimation(
-                                transition, info, t, mergeTarget, finishCallback);
+                                transition, info, startT, finishT, mergeTarget, finishCallback);
                     }
                 }
                 return;
             case TYPE_KEYGUARD:
-                mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+                mKeyguardHandler.mergeAnimation(transition, info, startT, finishT, mergeTarget,
+                        finishCallback);
                 return;
             case TYPE_OPTIONS_REMOTE_AND_PIP_OR_DESKTOP_CHANGE:
                 mPipHandler.end();
                 if (mLeftoversHandler != null) {
                     mLeftoversHandler.mergeAnimation(
-                            transition, info, t, mergeTarget, finishCallback);
+                            transition, info, startT, finishT, mergeTarget, finishCallback);
                 }
                 return;
             case TYPE_UNFOLD:
-                mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+                mUnfoldHandler.mergeAnimation(transition, info, startT, finishT, mergeTarget,
+                        finishCallback);
                 return;
             case TYPE_OPEN_IN_DESKTOP:
                 mDesktopTasksController.mergeAnimation(
-                        transition, info, t, mergeTarget, finishCallback);
+                        transition, info, startT, finishT, mergeTarget, finishCallback);
                 return;
             default:
                 throw new IllegalStateException("Playing a default mixed transition with unknown or"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index ac6e4c5..28bba2e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -708,7 +708,9 @@
 
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
+            @NonNull IBinder mergeTarget,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         ArrayList<Animator> anims = mAnimations.get(mergeTarget);
         if (anims == null) return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 209fc39..ec73738 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -96,7 +96,9 @@
 
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
+            @NonNull IBinder mergeTarget,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Merging registered One-shot remote"
                 + " transition %s for (#%d).", mRemote, info.getDebugId());
@@ -111,7 +113,7 @@
                 // process won't be cleared if the remote applied it. We don't actually know if the
                 // remote applied the transaction, but applying twice will break surfaceflinger
                 // so just assume the worst-case and clear the local transaction.
-                t.clear();
+                startT.clear();
                 mMainExecutor.execute(() -> {
                     finishCallback.onTransitionFinished(wct);
                 });
@@ -121,8 +123,8 @@
             // If the remote is actually in the same process, then make a copy of parameters since
             // remote impls assume that they have to clean-up native references.
             final SurfaceControl.Transaction remoteT =
-                    RemoteTransitionHandler.copyIfLocal(t, mRemote.getRemoteTransition());
-            final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
+                    RemoteTransitionHandler.copyIfLocal(startT, mRemote.getRemoteTransition());
+            final TransitionInfo remoteInfo = remoteT == startT ? info : info.localRemoteCopy();
             mRemote.getRemoteTransition().mergeAnimation(
                     transition, remoteInfo, remoteT, mergeTarget, cb);
         } catch (RemoteException e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
index 1847af0..f40dc8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -193,21 +193,24 @@
     @Override
     void mergeAnimation(
             @NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT,
+            @NonNull IBinder mergeTarget,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         switch (mType) {
             case TYPE_RECENTS_DURING_DESKTOP:
-                mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+                mLeftoversHandler.mergeAnimation(transition, info, startT, finishT, mergeTarget,
+                        finishCallback);
                 return;
             case TYPE_RECENTS_DURING_KEYGUARD:
                 if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
-                    handoverTransitionLeashes(mInfo, info, t, mFinishT);
+                    handoverTransitionLeashes(mInfo, info, startT, finishT);
                     if (animateKeyguard(
-                            this, info, t, mFinishT, mFinishCB, mKeyguardHandler, mPipHandler)) {
+                            this, info, startT, finishT, mFinishCB, mKeyguardHandler,
+                            mPipHandler)) {
                         finishCallback.onTransitionFinished(null);
                     }
                 }
-                mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+                mLeftoversHandler.mergeAnimation(transition, info, startT, finishT, mergeTarget,
                         finishCallback);
                 return;
             case TYPE_RECENTS_DURING_SPLIT:
@@ -216,7 +219,8 @@
                     // another pair.
                     mAnimType = DefaultMixedHandler.MixedTransition.ANIM_TYPE_PAIR_TO_PAIR;
                 }
-                mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+                mLeftoversHandler.mergeAnimation(transition, info, startT, finishT, mergeTarget,
+                        finishCallback);
                 return;
             default:
                 throw new IllegalStateException("Playing a Recents mixed transition with unknown or"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index dec28fe..c4a410b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -211,7 +211,9 @@
 
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
+            @NonNull IBinder mergeTarget,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         final RemoteTransition remoteTransition = mRequestedRemotes.get(mergeTarget);
         if (remoteTransition == null) return;
@@ -230,7 +232,7 @@
                 // process won't be cleared if the remote applied it. We don't actually know if the
                 // remote applied the transaction, but applying twice will break surfaceflinger
                 // so just assume the worst-case and clear the local transaction.
-                t.clear();
+                startT.clear();
                 mMainExecutor.execute(() -> {
                     if (!mRequestedRemotes.containsKey(mergeTarget)) {
                         Log.e(TAG, "Merged transition finished after it's mergeTarget (the "
@@ -245,8 +247,8 @@
         try {
             // If the remote is actually in the same process, then make a copy of parameters since
             // remote impls assume that they have to clean-up native references.
-            final SurfaceControl.Transaction remoteT = copyIfLocal(t, remote);
-            final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
+            final SurfaceControl.Transaction remoteT = copyIfLocal(startT, remote);
+            final TransitionInfo remoteInfo = remoteT == startT ? info : info.localRemoteCopy();
             remote.mergeAnimation(transition, remoteInfo, remoteT, mergeTarget, cb);
         } catch (RemoteException e) {
             Log.e(Transitions.TAG, "Error attempting to merge remote transition.", e);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index b83b7e2..72cbc47 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -922,7 +922,7 @@
                 + " %s is still animating. Notify the animating transition"
                 + " in case they can be merged", ready, playing);
         mTransitionTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
-        playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
+        playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT, ready.mFinishT,
                 playing.mToken, (wct) -> onMerged(playingToken, readyToken));
     }
 
@@ -1356,7 +1356,7 @@
             // fast-forward.
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt to merge sync %s"
                     + " into %s via a SLEEP proxy", nextSync, playing);
-            playing.mHandler.mergeAnimation(nextSync.mToken, dummyInfo, dummyT,
+            playing.mHandler.mergeAnimation(nextSync.mToken, dummyInfo, dummyT, dummyT,
                     playing.mToken, (wct) -> {});
             // it's possible to complete immediately. If that happens, just repeat the signal
             // loop until we either finish everything or start playing an animation that isn't
@@ -1404,7 +1404,9 @@
          * @param finishTransaction the transaction given to the handler to be applied after the
          *                       transition animation. Unlike startTransaction, the handler is NOT
          *                       expected to apply this transaction. The Transition system will
-         *                       apply it when finishCallback is called.
+         *                       apply it when finishCallback is called. If additional transitions
+         *                       are merged, then the finish transactions for those transitions
+         *                       will be applied after this transaction.
          * @param finishCallback Call this when finished. This MUST be called on main thread.
          * @return true if transition was handled, false if not (falls-back to default).
          */
@@ -1414,6 +1416,17 @@
                 @NonNull TransitionFinishCallback finishCallback);
 
         /**
+         * See {@link #mergeAnimation(IBinder, TransitionInfo, SurfaceControl.Transaction, SurfaceControl.Transaction, IBinder, TransitionFinishCallback)}
+         *
+         * This deprecated method header is provided until downstream implementation can migrate to
+         * the call that takes both start & finish transactions.
+         */
+        @Deprecated
+        default void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+                @NonNull SurfaceControl.Transaction startTransaction,
+                @NonNull IBinder mergeTarget, @NonNull TransitionFinishCallback finishCallback) { }
+
+        /**
          * Attempts to merge a different transition's animation into an animation that this handler
          * is currently playing. If a merge is not possible/supported, this should be a no-op.
          *
@@ -1430,14 +1443,25 @@
          *
          * @param transition This is the transition that wants to be merged.
          * @param info Information about what is changing in the transition.
-         * @param t Contains surface changes that resulted from the transition.
+         * @param startTransaction The start transaction containing surface changes that resulted
+         *                         from the incoming transition. This should be applied by this
+         *                         active handler only if it chooses to merge the transition.
+         * @param finishTransaction The finish transaction for the incoming transition. Unlike
+         *                          startTransaction, the handler is NOT expected to apply this
+         *                          transaction. If the transition is merged, the Transition system
+         *                          will apply after finishCallback is called following the finish
+         *                          transaction provided in `#startAnimation()`.
          * @param mergeTarget This is the transition that we are attempting to merge with (ie. the
          *                    one this handler is currently already animating).
          * @param finishCallback Call this if merged. This MUST be called on main thread.
          */
         default void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-                @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
-                @NonNull TransitionFinishCallback finishCallback) { }
+                @NonNull SurfaceControl.Transaction startTransaction,
+                @NonNull SurfaceControl.Transaction finishTransaction,
+                @NonNull IBinder mergeTarget, @NonNull TransitionFinishCallback finishCallback) {
+            // Call the legacy implementation by default
+            mergeAnimation(transition, info, startTransaction, mergeTarget, finishCallback);
+        }
 
         /**
          * Checks whether this handler is capable of taking over a transition matching `info`.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index 3e0e15a..7fd19a7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -225,7 +225,9 @@
 
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
+            @NonNull IBinder mergeTarget,
             @NonNull TransitionFinishCallback finishCallback) {
         if (info.getType() != TRANSIT_CHANGE) {
             return;
@@ -246,7 +248,7 @@
             }
         }
         // Apply changes happening during the unfold animation immediately
-        t.apply();
+        startT.apply();
         finishCallback.onTransitionFinished(null);
 
         if (getDefaultDisplayChange(info) == DefaultDisplayChange.DEFAULT_DISPLAY_FOLD) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 195e819..6a2a7b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -50,7 +50,6 @@
 import android.app.ActivityTaskManager;
 import android.app.IActivityManager;
 import android.app.IActivityTaskManager;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Point;
@@ -1654,9 +1653,6 @@
         if (mDesktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(taskInfo)) {
             return false;
         }
-        if (isPartOfDefaultHomePackage(taskInfo)) {
-            return false;
-        }
         final boolean isOnLargeScreen = taskInfo.getConfiguration().smallestScreenWidthDp
                 >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
         if (!DesktopModeStatus.canEnterDesktopMode(mContext)
@@ -1672,14 +1668,6 @@
                 && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop();
     }
 
-    private boolean isPartOfDefaultHomePackage(RunningTaskInfo taskInfo) {
-        final ComponentName currentDefaultHome =
-                mContext.getPackageManager().getHomeActivities(new ArrayList<>());
-        return currentDefaultHome != null && taskInfo.baseActivity != null
-                && currentDefaultHome.getPackageName()
-                .equals(taskInfo.baseActivity.getPackageName());
-    }
-
     private void createWindowDecoration(
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index b6765c4..c1a6240 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -1364,7 +1364,7 @@
         updateGenericLink();
         final boolean supportsMultiInstance = mMultiInstanceHelper
                 .supportsMultiInstanceSplit(mTaskInfo.baseActivity, mTaskInfo.userId)
-                && Flags.enableDesktopWindowingMultiInstanceFeatures();
+                && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES.isTrue();
         final boolean shouldShowManageWindowsButton = supportsMultiInstance
                 && mMinimumInstancesFound;
         final boolean shouldShowChangeAspectRatioButton = HandleMenu.Companion
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt
index 1bc48f8..801048a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt
@@ -153,9 +153,7 @@
     private fun loadAppResources(taskInfo: RunningTaskInfo): AppResources {
         Trace.beginSection("$TAG#loadAppResources")
         try {
-            val pm = checkNotNull(userProfilesContexts[taskInfo.userId]?.packageManager) {
-                "Could not get context for user ${taskInfo.userId}"
-            }
+            val pm = userProfilesContexts.getOrCreate(taskInfo.userId).packageManager
             val activityInfo = getActivityInfo(taskInfo, pm)
             val appName = pm.getApplicationLabel(activityInfo.applicationInfo)
             val appIconDrawable = iconProvider.getIcon(activityInfo)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
index 56948d4..9f29ef7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -271,7 +271,9 @@
         mController.startAnimation(mTransition, info, mStartTransaction,
                 mFinishTransaction, mFinishCallback);
         verify(mFinishCallback, never()).onTransitionFinished(any());
-        mController.mergeAnimation(mTransition, info, new SurfaceControl.Transaction(),
+        mController.mergeAnimation(mTransition, info,
+                new SurfaceControl.Transaction(),
+                new SurfaceControl.Transaction(),
                 mTransition, (wct) -> {});
         verify(mFinishCallback).onTransitionFinished(any());
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index e63db9a..05750a5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -666,7 +666,7 @@
         Transitions.TransitionFinishCallback mergeCallback =
                 mock(Transitions.TransitionFinishCallback.class);
         mBackTransitionHandler.mergeAnimation(
-                mock(IBinder.class), tInfo2, st, mock(IBinder.class), mergeCallback);
+                mock(IBinder.class), tInfo2, st, ft, mock(IBinder.class), mergeCallback);
         mBackTransitionHandler.onAnimationFinished();
         verify(callback).onTransitionFinished(any());
         verify(mergeCallback).onTransitionFinished(any());
@@ -701,7 +701,7 @@
         mBackTransitionHandler.mClosePrepareTransition = mock(IBinder.class);
         mergeCallback = mock(Transitions.TransitionFinishCallback.class);
         mBackTransitionHandler.mergeAnimation(mBackTransitionHandler.mClosePrepareTransition,
-                tInfo2, st, mock(IBinder.class), mergeCallback);
+                tInfo2, st, ft, mock(IBinder.class), mergeCallback);
         assertTrue("Change should be consumed", tInfo2.getChanges().isEmpty());
         verify(callback).onTransitionFinished(any());
     }
@@ -747,7 +747,7 @@
         final TransitionInfo closeInfo = createTransitionInfo(TRANSIT_CLOSE, close);
         Transitions.TransitionFinishCallback mergeCallback =
                 mock(Transitions.TransitionFinishCallback.class);
-        mBackTransitionHandler.mergeAnimation(mock(IBinder.class), closeInfo, ft,
+        mBackTransitionHandler.mergeAnimation(mock(IBinder.class), closeInfo, st, ft,
                 mock(IBinder.class), mergeCallback);
         verify(callback2).onTransitionFinished(any());
         verify(mergeCallback, never()).onTransitionFinished(any());
@@ -766,7 +766,7 @@
                 openTaskId2, TRANSIT_OPEN, FLAG_MOVED_TO_TOP);
         final TransitionInfo openInfo = createTransitionInfo(TRANSIT_OPEN, open2, close);
         mergeCallback = mock(Transitions.TransitionFinishCallback.class);
-        mBackTransitionHandler.mergeAnimation(mock(IBinder.class), openInfo, ft,
+        mBackTransitionHandler.mergeAnimation(mock(IBinder.class), openInfo, st, ft,
                 mock(IBinder.class), mergeCallback);
         verify(callback3).onTransitionFinished(any());
         verify(mergeCallback, never()).onTransitionFinished(any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/UserProfileContextsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/UserProfileContextsTest.kt
index ef0b8ab..56d4017 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/UserProfileContextsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/UserProfileContextsTest.kt
@@ -69,6 +69,7 @@
             }
             .whenever(baseContext)
             .createContextAsUser(any<UserHandle>(), anyInt())
+        doReturn(DEFAULT_USER).whenever(baseContext).userId
         // Define users and profiles
         val currentUser = ActivityManager.getCurrentUser()
         whenever(userManager.getProfiles(eq(currentUser)))
@@ -136,6 +137,25 @@
         assertThat(userProfilesContexts[SECOND_PROFILE]?.userId).isEqualTo(SECOND_PROFILE)
     }
 
+    @Test
+    fun onUserProfilesChanged_keepDefaultUser() {
+        val userChangeListener = retrieveUserChangeListener()
+        val newUserContext = createContextForUser(SECOND_USER)
+
+        userChangeListener.onUserChanged(SECOND_USER, newUserContext)
+        userChangeListener.onUserProfilesChanged(SECOND_PROFILES)
+
+        assertThat(userProfilesContexts[DEFAULT_USER]).isEqualTo(baseContext)
+    }
+
+    @Test
+    fun getOrCreate_newUser_shouldCreateTheUser() {
+        val newContext = userProfilesContexts.getOrCreate(SECOND_USER)
+
+        assertThat(newContext).isNotNull()
+        assertThat(userProfilesContexts[SECOND_USER]).isEqualTo(newContext)
+    }
+
     private fun retrieveUserChangeListener(): UserChangeListener {
         val captor = argumentCaptor<UserChangeListener>()
 
@@ -155,6 +175,7 @@
         const val MAIN_PROFILE = 11
         const val SECOND_PROFILE = 15
         const val SECOND_PROFILE_2 = 17
+        const val DEFAULT_USER = 25
 
         val SECOND_PROFILES =
             listOf(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index 90f342f..6a343c5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -27,6 +27,7 @@
 import com.android.window.flags.Flags
 import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE
 import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP
+import com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.TestShellExecutor
 import com.android.wm.shell.common.ShellExecutor
@@ -35,6 +36,7 @@
 import com.android.wm.shell.sysui.ShellInit
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.fail
+import kotlin.test.assertEquals
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -1076,13 +1078,37 @@
         repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 1, isVisible = true)
         repo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2)
 
-        val tasksBeforeRemoval = repo.removeDesk(displayId = DEFAULT_DISPLAY)
+        val tasksBeforeRemoval = repo.removeDesk(deskId = DEFAULT_DISPLAY)
 
         assertThat(tasksBeforeRemoval).containsExactly(1, 2, 3).inOrder()
         assertThat(repo.getActiveTasks(displayId = DEFAULT_DISPLAY)).isEmpty()
     }
 
     @Test
+    @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+    fun removeDesk_multipleDesks_active_removes() {
+        repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 2)
+        repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+        repo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+
+        repo.removeDesk(deskId = 3)
+
+        assertThat(repo.getDeskIds(displayId = DEFAULT_DISPLAY)).doesNotContain(3)
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+    fun removeDesk_multipleDesks_inactive_removes() {
+        repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 2)
+        repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+        repo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+
+        repo.removeDesk(deskId = 2)
+
+        assertThat(repo.getDeskIds(displayId = DEFAULT_DISPLAY)).doesNotContain(2)
+    }
+
+    @Test
     fun getTaskInFullImmersiveState_byDisplay() {
         repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
         repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
@@ -1164,6 +1190,26 @@
         assertThat(repo.getActiveTaskIdsInDesk(999)).contains(6)
     }
 
+    @Test
+    @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+    fun getDisplayForDesk() {
+        repo.addDesk(SECOND_DISPLAY, SECOND_DISPLAY)
+
+        assertEquals(SECOND_DISPLAY, repo.getDisplayForDesk(deskId = SECOND_DISPLAY))
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+    fun getDisplayForDesk_multipleDesks() {
+        repo.addDesk(DEFAULT_DISPLAY, deskId = 6)
+        repo.addDesk(DEFAULT_DISPLAY, deskId = 7)
+        repo.addDesk(SECOND_DISPLAY, deskId = 8)
+        repo.addDesk(SECOND_DISPLAY, deskId = 9)
+
+        assertEquals(DEFAULT_DISPLAY, repo.getDisplayForDesk(deskId = 7))
+        assertEquals(SECOND_DISPLAY, repo.getDisplayForDesk(deskId = 8))
+    }
+
     class TestListener : DesktopRepository.ActiveTasksListener {
         var activeChangesOnDefaultDisplay = 0
         var activeChangesOnSecondaryDisplay = 0
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index e7fe57d..8e7545c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -35,6 +35,7 @@
 import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
 import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
 import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
+import android.content.pm.PackageManager
 import android.content.res.Configuration.ORIENTATION_LANDSCAPE
 import android.content.res.Configuration.ORIENTATION_PORTRAIT
 import android.content.res.Resources
@@ -117,7 +118,9 @@
 import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
 import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
 import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
+import com.android.wm.shell.desktopmode.multidesks.DeskTransition
 import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
 import com.android.wm.shell.desktopmode.persistence.Desktop
 import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
 import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
@@ -179,6 +182,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.kotlin.any
 import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argThat
 import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.atLeastOnce
 import org.mockito.kotlin.capture
@@ -250,6 +254,7 @@
     private lateinit var overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver
     @Mock private lateinit var desksOrganizer: DesksOrganizer
     @Mock private lateinit var userProfileContexts: UserProfileContexts
+    @Mock private lateinit var desksTransitionsObserver: DesksTransitionObserver
 
     private lateinit var controller: DesktopTasksController
     private lateinit var shellInit: ShellInit
@@ -347,6 +352,7 @@
             .thenReturn(ExitResult.NoExit)
         whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken)
         whenever(userProfileContexts[anyInt()]).thenReturn(context)
+        whenever(userProfileContexts.getOrCreate(anyInt())).thenReturn(context)
 
         controller = createController()
         controller.setSplitScreenController(splitScreenController)
@@ -404,6 +410,7 @@
             Optional.of(bubbleController),
             overviewToDesktopTransitionObserver,
             desksOrganizer,
+            desksTransitionsObserver,
             userProfileContexts,
             desktopModeCompatPolicy,
         )
@@ -1454,6 +1461,41 @@
             .isEqualTo(WINDOWING_MODE_FREEFORM)
     }
 
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+    fun moveRunningTaskToDesktop_defaultHomePackageWithDisplay_doesNothing() {
+        val packageManager: PackageManager = org.mockito.kotlin.mock()
+        val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+        val task =
+            setUpFullscreenTask().apply {
+                baseActivity = homeActivities
+                isTopActivityNoDisplay = false
+            }
+        mContext.setMockPackageManager(packageManager)
+        whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+
+        controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+        verifyEnterDesktopWCTNotExecuted()
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+    fun moveRunningTaskToDesktop_defaultHomePackageWithoutDisplay_doesNothing() {
+        val packageManager: PackageManager = org.mockito.kotlin.mock()
+        val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+        val task =
+            setUpFullscreenTask().apply {
+                baseActivity = homeActivities
+                isTopActivityNoDisplay = false
+            }
+        mContext.setMockPackageManager(packageManager)
+        whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+
+        controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+
+        val wct = getLatestEnterDesktopWct()
+        assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+            .isEqualTo(WINDOWING_MODE_FREEFORM)
+    }
+
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun moveBackgroundTaskToDesktop_remoteTransition_usesOneShotHandler() {
@@ -3091,6 +3133,46 @@
             .isEqualTo(WINDOWING_MODE_FREEFORM)
     }
 
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+    fun handleRequest_defaultHomePackageWithDisplay_returnSwitchToFullscreenWCT() {
+        val freeformTask = setUpFreeformTask()
+        markTaskVisible(freeformTask)
+
+        val packageManager: PackageManager = org.mockito.kotlin.mock()
+        val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+        val task =
+            setUpFullscreenTask().apply {
+                baseActivity = homeActivities
+                isTopActivityNoDisplay = false
+            }
+        mContext.setMockPackageManager(packageManager)
+        whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+
+        val result = controller.handleRequest(Binder(), createTransition(task))
+        assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+            .isEqualTo(WINDOWING_MODE_FREEFORM)
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+    fun handleRequest_defaultHomePackageWithoutDisplay_returnSwitchToFreeformWCT() {
+        val freeformTask = setUpFreeformTask()
+        markTaskVisible(freeformTask)
+
+        val packageManager: PackageManager = org.mockito.kotlin.mock()
+        val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+        val task =
+            setUpFullscreenTask().apply {
+                baseActivity = homeActivities
+                isTopActivityNoDisplay = false
+            }
+        mContext.setMockPackageManager(packageManager)
+        whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+
+        val result = controller.handleRequest(Binder(), createTransition(task))
+        assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+            .isEqualTo(WINDOWING_MODE_FREEFORM)
+    }
+
     @Test
     fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT_enforcedDesktop() {
         whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
@@ -3542,13 +3624,14 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
-    fun removeDesktop_multipleTasks_removesAll() {
+    @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+    fun removeDesk_multipleTasks_removesAll() {
         val task1 = setUpFreeformTask()
         val task2 = setUpFreeformTask()
         val task3 = setUpFreeformTask()
         taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
 
-        controller.removeDesktop(displayId = DEFAULT_DISPLAY)
+        controller.removeDefaultDeskInDisplay(displayId = DEFAULT_DISPLAY)
 
         val wct = getLatestWct(TRANSIT_CLOSE)
         assertThat(wct.hierarchyOps).hasSize(3)
@@ -3559,14 +3642,15 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
-    fun removeDesktop_multipleTasksWithBackgroundTask_removesAll() {
+    @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+    fun removeDesk_multipleTasksWithBackgroundTask_removesAll() {
         val task1 = setUpFreeformTask()
         val task2 = setUpFreeformTask()
         val task3 = setUpFreeformTask()
         taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
         whenever(shellTaskOrganizer.getRunningTaskInfo(task3.taskId)).thenReturn(null)
 
-        controller.removeDesktop(displayId = DEFAULT_DISPLAY)
+        controller.removeDefaultDeskInDisplay(displayId = DEFAULT_DISPLAY)
 
         val wct = getLatestWct(TRANSIT_CLOSE)
         assertThat(wct.hierarchyOps).hasSize(2)
@@ -3576,6 +3660,30 @@
     }
 
     @Test
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+    )
+    fun removeDesk_multipleDesks_addsPendingTransition() {
+        val transition = Binder()
+        whenever(transitions.startTransition(eq(TRANSIT_CLOSE), any(), anyOrNull()))
+            .thenReturn(transition)
+        taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 2)
+
+        controller.removeDesk(deskId = 2)
+
+        verify(desksOrganizer).removeDesk(any(), eq(2))
+        verify(desksTransitionsObserver)
+            .addPendingTransition(
+                argThat {
+                    this is DeskTransition.RemoveDesk &&
+                        this.token == transition &&
+                        this.deskId == 2
+                }
+            )
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
     fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
         val spyController = spy(controller)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index 091159c..c29edec 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -47,11 +47,13 @@
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
 import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP
 import com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP
+import com.android.wm.shell.util.StubTransaction
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Before
@@ -92,6 +94,7 @@
     private val backAnimationController = mock<BackAnimationController>()
     private val desktopWallpaperActivityTokenProvider =
         mock<DesktopWallpaperActivityTokenProvider>()
+    private val desksTransitionObserver = mock<DesksTransitionObserver>()
     private val wallpaperToken = MockToken().token()
 
     private lateinit var transitionObserver: DesktopTasksTransitionObserver
@@ -115,6 +118,7 @@
                 mixedHandler,
                 backAnimationController,
                 desktopWallpaperActivityTokenProvider,
+                desksTransitionObserver,
                 shellInit,
             )
     }
@@ -411,6 +415,21 @@
         verify(taskRepository).setTaskInPip(task.displayId, task.taskId, enterPip = false)
     }
 
+    @Test
+    fun onTransitionReady_forwardsToDesksTransitionObserver() {
+        val transition = Binder()
+        val info = TransitionInfo(TRANSIT_CLOSE, /* flags= */ 0)
+
+        transitionObserver.onTransitionReady(
+            transition = transition,
+            info = info,
+            StubTransaction(),
+            StubTransaction(),
+        )
+
+        verify(desksTransitionObserver).onTransitionReady(transition, info)
+    }
+
     private fun createBackNavigationTransition(
         task: RunningTaskInfo?,
         type: Int = TRANSIT_TO_BACK,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index bf9cf00..33dc1aa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -284,6 +284,7 @@
             cancelToken,
             TransitionInfo(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, 0),
             mock<SurfaceControl.Transaction>(),
+            mock<SurfaceControl.Transaction>(),
             startToken,
             mock<Transitions.TransitionFinishCallback>(),
         )
@@ -385,21 +386,23 @@
 
     @Test
     fun mergeAnimation_otherTransition_doesNotMerge() {
-        val transaction = mock<SurfaceControl.Transaction>()
+        val mergedStartTransaction = mock<SurfaceControl.Transaction>()
+        val mergedFinishTransaction = mock<SurfaceControl.Transaction>()
         val finishCallback = mock<Transitions.TransitionFinishCallback>()
         val task = createTask()
 
         startDrag(defaultHandler, task)
         defaultHandler.mergeAnimation(
-            transition = mock(),
+            transition = mock<IBinder>(),
             info = createTransitionInfo(type = TRANSIT_OPEN, draggedTask = task),
-            t = transaction,
-            mergeTarget = mock(),
+            startT = mergedStartTransaction,
+            finishT = mergedFinishTransaction,
+            mergeTarget = mock<IBinder>(),
             finishCallback = finishCallback,
         )
 
         // Should NOT have any transaction changes
-        verifyZeroInteractions(transaction)
+        verifyZeroInteractions(mergedStartTransaction)
         // Should NOT merge animation
         verify(finishCallback, never()).onTransitionFinished(any())
     }
@@ -408,6 +411,7 @@
     fun mergeAnimation_endTransition_mergesAnimation() {
         val playingFinishTransaction = mock<SurfaceControl.Transaction>()
         val mergedStartTransaction = mock<SurfaceControl.Transaction>()
+        val mergedFinishTransaction = mock<SurfaceControl.Transaction>()
         val finishCallback = mock<Transitions.TransitionFinishCallback>()
         val task = createTask()
         val startTransition =
@@ -415,13 +419,14 @@
         defaultHandler.onTaskResizeAnimationListener = mock()
 
         defaultHandler.mergeAnimation(
-            transition = mock(),
+            transition = mock<IBinder>(),
             info =
                 createTransitionInfo(
                     type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
                     draggedTask = task,
                 ),
-            t = mergedStartTransaction,
+            startT = mergedStartTransaction,
+            finishT = mergedFinishTransaction,
             mergeTarget = startTransition,
             finishCallback = finishCallback,
         )
@@ -440,6 +445,7 @@
         whenever(dragAnimator.computeCurrentVelocity()).thenReturn(PointF())
         val playingFinishTransaction = mock<SurfaceControl.Transaction>()
         val mergedStartTransaction = mock<SurfaceControl.Transaction>()
+        val mergedFinishTransaction = mock<SurfaceControl.Transaction>()
         val finishCallback = mock<Transitions.TransitionFinishCallback>()
         val task = createTask()
         val startTransition =
@@ -447,13 +453,14 @@
         springHandler.onTaskResizeAnimationListener = mock()
 
         springHandler.mergeAnimation(
-            transition = mock(),
+            transition = mock<IBinder>(),
             info =
                 createTransitionInfo(
                     type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
                     draggedTask = task,
                 ),
-            t = mergedStartTransaction,
+            startT = mergedStartTransaction,
+            finishT = mergedFinishTransaction,
             mergeTarget = startTransition,
             finishCallback = finishCallback,
         )
@@ -564,7 +571,8 @@
                     type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
                     draggedTask = task,
                 ),
-            t = mock<SurfaceControl.Transaction>(),
+            startT = mock<SurfaceControl.Transaction>(),
+            finishT = mock<SurfaceControl.Transaction>(),
             mergeTarget = startTransition,
             finishCallback = mock<Transitions.TransitionFinishCallback>(),
         )
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt
new file mode 100644
index 0000000..bfbaa84
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt
@@ -0,0 +1,124 @@
+/*
+ * 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.wm.shell.desktopmode.multidesks
+
+import android.os.Binder
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.AndroidTestingRunner
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.window.TransitionInfo
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
+import com.android.wm.shell.sysui.ShellInit
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+/**
+ * Tests for [DesksTransitionObserver].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:DesksTransitionObserverTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesksTransitionObserverTest : ShellTestCase() {
+
+    @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+    private lateinit var desktopUserRepositories: DesktopUserRepositories
+    private lateinit var observer: DesksTransitionObserver
+
+    private val repository: DesktopRepository
+        get() = desktopUserRepositories.current
+
+    @Before
+    fun setUp() {
+        desktopUserRepositories =
+            DesktopUserRepositories(
+                context,
+                ShellInit(TestShellExecutor()),
+                /* shellController= */ mock(),
+                /* persistentRepository= */ mock(),
+                /* repositoryInitializer= */ mock(),
+                /* mainCoroutineScope= */ mock(),
+                /* userManager= */ mock(),
+            )
+        observer = DesksTransitionObserver(desktopUserRepositories)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+    fun onTransitionReady_removeDesk_removesFromRepository() {
+        val transition = Binder()
+        val removeTransition =
+            DeskTransition.RemoveDesk(
+                transition,
+                displayId = DEFAULT_DISPLAY,
+                deskId = 5,
+                tasks = setOf(10, 11),
+                onDeskRemovedListener = null,
+            )
+        repository.addDesk(DEFAULT_DISPLAY, deskId = 5)
+
+        observer.addPendingTransition(removeTransition)
+        observer.onTransitionReady(
+            transition = transition,
+            info = TransitionInfo(TRANSIT_CLOSE, /* flags= */ 0),
+        )
+
+        assertThat(repository.getDeskIds(DEFAULT_DISPLAY)).doesNotContain(5)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+    fun onTransitionReady_removeDesk_invokesOnRemoveListener() {
+        class FakeOnDeskRemovedListener : OnDeskRemovedListener {
+            var lastDeskRemoved: Int? = null
+
+            override fun onDeskRemoved(lastDisplayId: Int, deskId: Int) {
+                lastDeskRemoved = deskId
+            }
+        }
+        val transition = Binder()
+        val removeListener = FakeOnDeskRemovedListener()
+        val removeTransition =
+            DeskTransition.RemoveDesk(
+                transition,
+                displayId = DEFAULT_DISPLAY,
+                deskId = 5,
+                tasks = setOf(10, 11),
+                onDeskRemovedListener = removeListener,
+            )
+        repository.addDesk(DEFAULT_DISPLAY, deskId = 5)
+
+        observer.addPendingTransition(removeTransition)
+        observer.onTransitionReady(
+            transition = transition,
+            info = TransitionInfo(TRANSIT_CLOSE, /* flags= */ 0),
+        )
+
+        assertThat(removeListener.lastDeskRemoved).isEqualTo(5)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
index 8c78deb..a8a7be8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
@@ -17,6 +17,7 @@
 package com.android.wm.shell.shared.desktopmode
 
 import android.content.ComponentName
+import android.content.pm.PackageManager
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.internal.R
@@ -27,6 +28,9 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
 
 /**
  * Tests for [@link DesktopModeCompatPolicy].
@@ -110,4 +114,32 @@
                     isTopActivityNoDisplay = true
                 }))
     }
+
+    @Test
+    fun testIsTopActivityExemptFromDesktopWindowing_defaultHomePackage() {
+        val packageManager: PackageManager = mock()
+        val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+        whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+        mContext.setMockPackageManager(packageManager)
+        assertTrue(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
+            createFreeformTask(/* displayId */ 0)
+                .apply {
+                    baseActivity = homeActivities
+                    isTopActivityNoDisplay = false
+                }))
+    }
+
+    @Test
+    fun testIsTopActivityExemptFromDesktopWindowing_defaultHomePackage_notDisplayed() {
+        val packageManager: PackageManager = mock()
+        val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+        whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+        mContext.setMockPackageManager(packageManager)
+        assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
+            createFreeformTask(/* displayId */ 0)
+                .apply {
+                    baseActivity = homeActivities
+                    isTopActivityNoDisplay = true
+                }))
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
index e540322..82392e0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
@@ -292,6 +292,7 @@
                 new Binder(),
                 new TransitionInfoBuilder(TRANSIT_SLEEP, FLAG_SYNC).build(),
                 MockTransactionPool.create(),
+                MockTransactionPool.create(),
                 token,
                 mock(Transitions.TransitionFinishCallback.class));
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index efbc533..6f73db0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -1759,7 +1759,9 @@
 
         @Override
         public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
-                @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+                @NonNull SurfaceControl.Transaction startT,
+                @NonNull SurfaceControl.Transaction finishT,
+                @NonNull IBinder mergeTarget,
                 @NonNull Transitions.TransitionFinishCallback finishCallback) {
             if (mFinishOnSync && info.getType() == TRANSIT_SLEEP) {
                 for (int i = 0; i < mFinishes.size(); ++i) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
index 5ba2f18..aad18cb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
@@ -170,7 +170,8 @@
         // Send fold transition request
         TransitionFinishCallback mergeFinishCallback = mock(TransitionFinishCallback.class);
         mUnfoldTransitionHandler.mergeAnimation(new Binder(), createFoldTransitionInfo(),
-                mock(SurfaceControl.Transaction.class), mTransition, mergeFinishCallback);
+                mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class),
+                mTransition, mergeFinishCallback);
         mTestLooper.dispatchAll();
 
         // Verify that fold transition is merged into unfold and that unfold is finished
@@ -388,6 +389,7 @@
                 new Binder(),
                 new TransitionInfoBuilder(TRANSIT_CHANGE, TRANSIT_FLAG_KEYGUARD_GOING_AWAY).build(),
                 mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
                 mTransition,
                 mergeCallback);
         verify(finishCallback, never()).onTransitionFinished(any());
@@ -397,6 +399,7 @@
                 new Binder(),
                 new TransitionInfoBuilder(TRANSIT_CHANGE).build(),
                 mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
                 mTransition,
                 mergeCallback);
         verify(mergeCallback).onTransitionFinished(any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index baccbee..737780e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -28,6 +28,7 @@
 import android.content.Context
 import android.content.Intent
 import android.content.Intent.ACTION_MAIN
+import android.content.pm.PackageManager
 import android.graphics.Rect
 import android.graphics.Region
 import android.hardware.display.DisplayManager
@@ -96,7 +97,6 @@
 import org.mockito.quality.Strictness
 import java.util.function.Consumer
 
-
 /**
  * Tests of [DesktopModeWindowDecorViewModel]
  * Usage: atest WMShellUnitTests:DesktopModeWindowDecorViewModelTests
@@ -307,6 +307,23 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+    fun testDecorationIsNotCreatedForDefaultHomePackage() {
+        val packageManager: PackageManager = org.mockito.kotlin.mock()
+        val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+        val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
+            baseActivity = homeActivities
+            isTopActivityNoDisplay = false
+        }
+        mContext.setMockPackageManager(packageManager)
+        whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+
+        onTaskOpening(task)
+
+        assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING)
     fun testInsetsStateChanged_notifiesAllDecorsInDisplay() {
         val task1 = createTask(windowingMode = WINDOWING_MODE_FREEFORM, displayId = 1)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index 541b19cf..7468c54 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -150,7 +150,6 @@
     val display = mock<Display>()
     protected lateinit var spyContext: TestableContext
     private lateinit var desktopModeEventLogger: DesktopModeEventLogger
-    private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
 
     private val transactionFactory = Supplier<SurfaceControl.Transaction> {
         SurfaceControl.Transaction()
@@ -165,6 +164,7 @@
             DisplayChangeController.OnDisplayChangingListener
     internal lateinit var desktopModeOnKeyguardChangedListener: DesktopModeKeyguardChangeListener
     protected lateinit var desktopModeWindowDecorViewModel: DesktopModeWindowDecorViewModel
+    protected lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
 
     fun setUpCommon() {
         spyContext = spy(mContext)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt
index 431de89..c61e0eb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt
@@ -86,6 +86,7 @@
         spyContext.setMockPackageManager(mockPackageManager)
         doReturn(spyContext).whenever(spyContext).createContextAsUser(any(), anyInt())
         doReturn(spyContext).whenever(mMockUserProfileContexts)[anyInt()]
+        doReturn(spyContext).whenever(mMockUserProfileContexts).getOrCreate(anyInt())
         loader =
             WindowDecorTaskResourceLoader(
                 shellInit = shellInit,
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index 139ccfd..7280b12 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -24,8 +24,8 @@
 
   public final class AppFunctionManager {
     ctor public AppFunctionManager(android.content.Context);
-    method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>);
-    method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
+    method @RequiresPermission(android.Manifest.permission.EXECUTE_APP_FUNCTIONS) public void executeAppFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>);
+    method @RequiresPermission(android.Manifest.permission.EXECUTE_APP_FUNCTIONS) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
     method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
     method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
     field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java
index 9eb66a3..1e31390 100644
--- a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java
@@ -104,12 +104,7 @@
      * <p>See {@link android.app.appfunctions.AppFunctionManager#executeAppFunction} for the
      * documented behaviour of this method.
      */
-    @RequiresPermission(
-            anyOf = {
-                Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
-                Manifest.permission.EXECUTE_APP_FUNCTIONS
-            },
-            conditional = true)
+    @RequiresPermission(Manifest.permission.EXECUTE_APP_FUNCTIONS)
     public void executeAppFunction(
             @NonNull ExecuteAppFunctionRequest sidecarRequest,
             @NonNull @CallbackExecutor Executor executor,
@@ -150,12 +145,7 @@
      * <p>See {@link android.app.appfunctions.AppFunctionManager#isAppFunctionEnabled} for the
      * documented behaviour of this method.
      */
-    @RequiresPermission(
-            anyOf = {
-                Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
-                Manifest.permission.EXECUTE_APP_FUNCTIONS
-            },
-            conditional = true)
+    @RequiresPermission(Manifest.permission.EXECUTE_APP_FUNCTIONS)
     public void isAppFunctionEnabled(
             @NonNull String functionIdentifier,
             @NonNull String targetPackage,
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index c488683..1a84371 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -48,6 +48,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
+import android.os.Trace;
 import android.view.Surface;
 
 import java.io.IOException;
@@ -3107,6 +3108,7 @@
             int index,
             int offset, int size, long presentationTimeUs, int flags)
         throws CryptoException {
+        Trace.traceBegin(Trace.TRACE_TAG_VIDEO, "MediaCodec::queueInputBuffer#java");
         if ((flags & BUFFER_FLAG_DECODE_ONLY) != 0
                 && (flags & BUFFER_FLAG_END_OF_STREAM) != 0) {
             throw new InvalidBufferFlagsException(EOS_AND_DECODE_ONLY_ERROR_MESSAGE);
@@ -3126,6 +3128,8 @@
         } catch (CryptoException | IllegalStateException e) {
             revalidateByteBuffer(mCachedInputBuffers, index, true /* input */);
             throw e;
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIDEO);
         }
     }
 
@@ -3167,6 +3171,7 @@
     public final void queueInputBuffers(
             int index,
             @NonNull ArrayDeque<BufferInfo> bufferInfos) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIDEO, "MediaCodec::queueInputBuffers#java");
         synchronized(mBufferLock) {
             if (mBufferMode == BUFFER_MODE_BLOCK) {
                 throw new IncompatibleWithBlockModelException("queueInputBuffers() "
@@ -3182,6 +3187,8 @@
         } catch (CryptoException | IllegalStateException | IllegalArgumentException e) {
             revalidateByteBuffer(mCachedInputBuffers, index, true /* input */);
             throw e;
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIDEO);
         }
     }
 
@@ -3442,6 +3449,7 @@
             @NonNull CryptoInfo info,
             long presentationTimeUs,
             int flags) throws CryptoException {
+        Trace.traceBegin(Trace.TRACE_TAG_VIDEO, "MediaCodec::queueSecureInputBuffer#java");
         if ((flags & BUFFER_FLAG_DECODE_ONLY) != 0
                 && (flags & BUFFER_FLAG_END_OF_STREAM) != 0) {
             throw new InvalidBufferFlagsException(EOS_AND_DECODE_ONLY_ERROR_MESSAGE);
@@ -3461,6 +3469,8 @@
         } catch (CryptoException | IllegalStateException e) {
             revalidateByteBuffer(mCachedInputBuffers, index, true /* input */);
             throw e;
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIDEO);
         }
     }
 
@@ -3491,6 +3501,7 @@
             int index,
             @NonNull ArrayDeque<BufferInfo> bufferInfos,
             @NonNull ArrayDeque<CryptoInfo> cryptoInfos) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIDEO, "MediaCodec::queueSecureInputBuffers#java");
         synchronized(mBufferLock) {
             if (mBufferMode == BUFFER_MODE_BLOCK) {
                 throw new IncompatibleWithBlockModelException("queueSecureInputBuffers() "
@@ -3506,6 +3517,8 @@
         } catch (CryptoException | IllegalStateException | IllegalArgumentException e) {
             revalidateByteBuffer(mCachedInputBuffers, index, true /* input */);
             throw e;
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIDEO);
         }
     }
 
@@ -3533,6 +3546,7 @@
      * @throws MediaCodec.CodecException upon codec error.
      */
     public final int dequeueInputBuffer(long timeoutUs) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIDEO, "MediaCodec::dequeueInputBuffer#java");
         synchronized (mBufferLock) {
             if (mBufferMode == BUFFER_MODE_BLOCK) {
                 throw new IncompatibleWithBlockModelException("dequeueInputBuffer() "
@@ -3546,6 +3560,7 @@
                 validateInputByteBufferLocked(mCachedInputBuffers, res);
             }
         }
+        Trace.traceEnd(Trace.TRACE_TAG_VIDEO);
         return res;
     }
 
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index 3b8cf3f..87bb6ea 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -85,8 +85,7 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface TransferReason {}
 
-    @NonNull
-    final String mId;
+    @NonNull final String mOriginalId;
     @Nullable
     final CharSequence mName;
     @Nullable
@@ -120,7 +119,7 @@
     RoutingSessionInfo(@NonNull Builder builder) {
         Objects.requireNonNull(builder, "builder must not be null.");
 
-        mId = builder.mId;
+        mOriginalId = builder.mOriginalId;
         mName = builder.mName;
         mOwnerPackageName = builder.mOwnerPackageName;
         mClientPackageName = builder.mClientPackageName;
@@ -148,8 +147,8 @@
     }
 
     RoutingSessionInfo(@NonNull Parcel src) {
-        mId = src.readString();
-        Preconditions.checkArgument(!TextUtils.isEmpty(mId));
+        mOriginalId = src.readString();
+        Preconditions.checkArgument(!TextUtils.isEmpty(mOriginalId));
 
         mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(src);
         mOwnerPackageName = src.readString();
@@ -221,9 +220,9 @@
     @NonNull
     public String getId() {
         if (!TextUtils.isEmpty(mProviderId)) {
-            return MediaRouter2Utils.toUniqueId(mProviderId, mId);
+            return MediaRouter2Utils.toUniqueId(mProviderId, mOriginalId);
         } else {
-            return mId;
+            return mOriginalId;
         }
     }
 
@@ -236,12 +235,16 @@
     }
 
     /**
-     * Gets the original id set by {@link Builder#Builder(String, String)}.
+     * Gets the original id as assigned by the {@link MediaRoute2ProviderService route provider}.
+     *
+     * <p>This may be different from {@link #getId()}, which may convert this original id into a
+     * unique one by adding information about the provider that created this session info.
+     *
      * @hide
      */
     @NonNull
     public String getOriginalId() {
-        return mId;
+        return mOriginalId;
     }
 
     /**
@@ -423,7 +426,7 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeString(mId);
+        dest.writeString(mOriginalId);
         dest.writeCharSequence(mName);
         dest.writeString(mOwnerPackageName);
         dest.writeString(mClientPackageName);
@@ -454,7 +457,7 @@
 
         String indent = prefix + "  ";
 
-        pw.println(indent + "mId=" + mId);
+        pw.println(indent + "mOriginalId=" + mOriginalId);
         pw.println(indent + "mName=" + mName);
         pw.println(indent + "mOwnerPackageName=" + mOwnerPackageName);
         pw.println(indent + "mClientPackageName=" + mClientPackageName);
@@ -485,7 +488,7 @@
         }
 
         RoutingSessionInfo other = (RoutingSessionInfo) obj;
-        return Objects.equals(mId, other.mId)
+        return Objects.equals(mOriginalId, other.mOriginalId)
                 && Objects.equals(mName, other.mName)
                 && Objects.equals(mOwnerPackageName, other.mOwnerPackageName)
                 && Objects.equals(mClientPackageName, other.mClientPackageName)
@@ -500,13 +503,13 @@
                 && (mTransferReason == other.mTransferReason)
                 && Objects.equals(mTransferInitiatorUserHandle, other.mTransferInitiatorUserHandle)
                 && Objects.equals(
-                mTransferInitiatorPackageName, other.mTransferInitiatorPackageName);
+                        mTransferInitiatorPackageName, other.mTransferInitiatorPackageName);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(
-                mId,
+                mOriginalId,
                 mName,
                 mOwnerPackageName,
                 mClientPackageName,
@@ -585,8 +588,7 @@
      * Builder class for {@link RoutingSessionInfo}.
      */
     public static final class Builder {
-        @NonNull
-        private final String mId;
+        @NonNull private final String mOriginalId;
         @Nullable
         private CharSequence mName;
         @Nullable
@@ -616,23 +618,22 @@
 
         /**
          * Constructor for builder to create {@link RoutingSessionInfo}.
-         * <p>
-         * In order to ensure ID uniqueness in {@link MediaRouter2} side, the value of
-         * {@link RoutingSessionInfo#getId()} can be different from what was set in
-         * {@link MediaRoute2ProviderService}.
-         * </p>
          *
-         * @param id ID of the session. Must not be empty.
-         * @param clientPackageName package name of the client app which uses this session.
-         *                          If is is unknown, then just use an empty string.
+         * <p>In order to ensure ID uniqueness in {@link MediaRouter2} side, the value of {@link
+         * RoutingSessionInfo#getId()} can be different from what was set in {@link
+         * MediaRoute2ProviderService}.
+         *
+         * @param originalId ID of the session. Must not be empty.
+         * @param clientPackageName package name of the client app which uses this session. If is is
+         *     unknown, then just use an empty string.
          * @see MediaRoute2Info#getId()
          */
-        public Builder(@NonNull String id, @NonNull String clientPackageName) {
-            if (TextUtils.isEmpty(id)) {
+        public Builder(@NonNull String originalId, @NonNull String clientPackageName) {
+            if (TextUtils.isEmpty(originalId)) {
                 throw new IllegalArgumentException("id must not be empty");
             }
 
-            mId = id;
+            mOriginalId = originalId;
             mClientPackageName =
                     Objects.requireNonNull(clientPackageName, "clientPackageName must not be null");
             mSelectedRoutes = new ArrayList<>();
@@ -650,7 +651,7 @@
         public Builder(@NonNull RoutingSessionInfo sessionInfo) {
             Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
 
-            mId = sessionInfo.mId;
+            mOriginalId = sessionInfo.mOriginalId;
             mName = sessionInfo.mName;
             mClientPackageName = sessionInfo.mClientPackageName;
             mProviderId = sessionInfo.mProviderId;
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 94454cf..405d292 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -125,6 +125,13 @@
 }
 
 flag {
+    name: "enable_output_switcher_session_grouping"
+    namespace: "media_better_together"
+    description: "Enables selected items in Output Switcher to be grouped together."
+    bug: "388347018"
+}
+
+flag {
     name: "enable_prevention_of_keep_alive_route_providers"
     namespace: "media_solutions"
     description: "Enables mechanisms to prevent route providers from keeping malicious apps alive."
diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java
index e558209..e4de3e4 100644
--- a/media/java/android/media/quality/MediaQualityContract.java
+++ b/media/java/android/media/quality/MediaQualityContract.java
@@ -341,6 +341,13 @@
         public static final String PARAMETER_FILM_MODE = "film_mode";
 
         /**
+         * Enable/disable black color auto stretch
+         *
+         * @hide
+         */
+        public static final String PARAMETER_BLACK_STRETCH = "black_stretch";
+
+        /**
          * Enable/disable blue color auto stretch
          *
          * <p>Type: BOOLEAN
@@ -457,6 +464,27 @@
          * @hide
          *
          */
+        public static final String PARAMETER_COLOR_TEMPERATURE_RED_GAIN =
+                "color_temperature_red_gain";
+
+        /**
+         * @hide
+         *
+         */
+        public static final String PARAMETER_COLOR_TEMPERATURE_GREEN_GAIN =
+                "color_temperature_green_gain";
+
+        /**
+         * @hide
+         *
+         */
+        public static final String PARAMETER_COLOR_TEMPERATURE_BLUE_GAIN =
+                "color_temperature_blue_gain";
+
+        /**
+         * @hide
+         *
+         */
         public static final String PARAMETER_COLOR_TEMPERATURE_RED_OFFSET =
                 "color_temperature_red_offset";
 
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 8419ce7..3bc238a 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -16,7 +16,9 @@
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "MediaCodec-JNI"
+#define ATRACE_TAG  ATRACE_TAG_VIDEO
 #include <utils/Log.h>
+#include <utils/Trace.h>
 
 #include <type_traits>
 
@@ -2106,7 +2108,7 @@
         jlong timestampUs,
         jint flags) {
     ALOGV("android_media_MediaCodec_queueInputBuffer");
-
+    ScopedTrace trace(ATRACE_TAG, "MediaCodec::queueInputBuffer#jni");
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL || codec->initCheck() != OK) {
@@ -2192,6 +2194,7 @@
         jint index,
         jobjectArray objArray) {
     ALOGV("android_media_MediaCodec_queueInputBuffers");
+    ScopedTrace trace(ATRACE_TAG, "MediaCodec::queueInputBuffers#jni");
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
     if (codec == NULL || codec->initCheck() != OK || objArray == NULL) {
         throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
@@ -2431,6 +2434,7 @@
         jobject cryptoInfoObj,
         jlong timestampUs,
         jint flags) {
+    ScopedTrace trace(ATRACE_TAG, "MediaCodec::queueSecureInputBuffer#jni");
     ALOGV("android_media_MediaCodec_queueSecureInputBuffer");
 
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
@@ -2641,6 +2645,7 @@
         jint index,
         jobjectArray bufferInfosObjs,
         jobjectArray cryptoInfoObjs) {
+    ScopedTrace trace(ATRACE_TAG, "MediaCodec::queueSecureInputBuffers#jni");
     ALOGV("android_media_MediaCodec_queueSecureInputBuffers");
 
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
@@ -2685,6 +2690,7 @@
 }
 
 static jobject android_media_MediaCodec_mapHardwareBuffer(JNIEnv *env, jclass, jobject bufferObj) {
+    ScopedTrace trace(ATRACE_TAG, "MediaCodec::mapHardwareBuffer#jni");
     ALOGV("android_media_MediaCodec_mapHardwareBuffer");
     AHardwareBuffer *hardwareBuffer = android_hardware_HardwareBuffer_getNativeHardwareBuffer(
             env, bufferObj);
@@ -3028,6 +3034,7 @@
 static void android_media_MediaCodec_native_queueLinearBlock(
         JNIEnv *env, jobject thiz, jint index, jobject bufferObj,
         jobjectArray cryptoInfoArray, jobjectArray objArray, jobject keys, jobject values) {
+    ScopedTrace trace(ATRACE_TAG, "MediaCodec::queueLinearBlock#jni");
     ALOGV("android_media_MediaCodec_native_queueLinearBlock");
 
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
@@ -3145,6 +3152,7 @@
         JNIEnv *env, jobject thiz, jint index, jobject bufferObj,
         jlong presentationTimeUs, jint flags, jobject keys, jobject values) {
     ALOGV("android_media_MediaCodec_native_queueHardwareBuffer");
+    ScopedTrace trace(ATRACE_TAG, "MediaCodec::queueHardwareBuffer#jni");
 
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
diff --git a/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.java b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.java
index cae8db2..428997e 100644
--- a/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.java
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.java
@@ -19,6 +19,7 @@
 import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE;
 
 import android.annotation.CurrentTimeMillisLong;
+import android.annotation.DurationMillisLong;
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
@@ -51,7 +52,8 @@
     private final long endTimeMs;
 
     /**
-     * Suspended time in milliseconds.
+     * The total duration of the period(s) during which the inference was
+     * suspended (i.e. not running), in milliseconds.
      */
     private final long suspendedTimeMs;
 
@@ -61,7 +63,7 @@
      * @param uid             Uid for the caller app.
      * @param startTimeMs     Inference start time (milliseconds from the epoch time).
      * @param endTimeMs       Inference end time (milliseconds from the epoch time).
-     * @param suspendedTimeMs Suspended time in milliseconds.
+     * @param suspendedTimeMs Suspended duration, in milliseconds.
      */
     InferenceInfo(int uid, long startTimeMs, long endTimeMs,
             long suspendedTimeMs) {
@@ -128,11 +130,12 @@
     }
 
     /**
-     * Returns the suspended time in milliseconds.
+     * Returns the suspended duration, in milliseconds.
      *
-     * @return the suspended time in milliseconds.
+     * @return the total duration of the period(s) during which the inference
+     *         was suspended (i.e. not running), in milliseconds.
      */
-    @CurrentTimeMillisLong
+    @DurationMillisLong
     public long getSuspendedTimeMillis() {
         return suspendedTimeMs;
     }
@@ -197,12 +200,14 @@
         }
 
         /**
-         * Sets the suspended time in milliseconds.
+         * Sets the suspended duration, in milliseconds.
          *
-         * @param suspendedTimeMs the suspended time in milliseconds.
+         * @param suspendedTimeMs the total duration of the period(s) in which
+         *                        the request was suspended (i.e. not running),
+        *                         in milliseconds.
          * @return the Builder instance.
          */
-        public @NonNull Builder setSuspendedTimeMillis(@CurrentTimeMillisLong long suspendedTimeMs) {
+        public @NonNull Builder setSuspendedTimeMillis(@DurationMillisLong long suspendedTimeMs) {
             this.suspendedTimeMs = suspendedTimeMs;
             return this;
         }
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.java b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.java
index cae8db2..64524fb 100644
--- a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.java
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.java
@@ -19,6 +19,7 @@
 import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE;
 
 import android.annotation.CurrentTimeMillisLong;
+import android.annotation.DurationMillisLong;
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
@@ -50,8 +51,9 @@
      */
     private final long endTimeMs;
 
-    /**
-     * Suspended time in milliseconds.
+   /**
+     * The total duration of the period(s) during which the inference was
+     * suspended (i.e. not running), in milliseconds.
      */
     private final long suspendedTimeMs;
 
@@ -61,7 +63,7 @@
      * @param uid             Uid for the caller app.
      * @param startTimeMs     Inference start time (milliseconds from the epoch time).
      * @param endTimeMs       Inference end time (milliseconds from the epoch time).
-     * @param suspendedTimeMs Suspended time in milliseconds.
+     * @param suspendedTimeMs Suspended duration, in milliseconds.
      */
     InferenceInfo(int uid, long startTimeMs, long endTimeMs,
             long suspendedTimeMs) {
@@ -128,11 +130,12 @@
     }
 
     /**
-     * Returns the suspended time in milliseconds.
+     * Returns the suspended duration, in milliseconds.
      *
-     * @return the suspended time in milliseconds.
+     * @return the total duration of the period(s) during which the inference
+     *         was suspended (i.e. not running), in milliseconds.
      */
-    @CurrentTimeMillisLong
+    @DurationMillisLong
     public long getSuspendedTimeMillis() {
         return suspendedTimeMs;
     }
@@ -197,12 +200,14 @@
         }
 
         /**
-         * Sets the suspended time in milliseconds.
+         * Sets the suspended duration, in milliseconds.
          *
-         * @param suspendedTimeMs the suspended time in milliseconds.
+         * @param suspendedTimeMs the total duration of the period(s) in which
+         *                        the request was suspended (i.e. not running),
+        *                         in milliseconds.
          * @return the Builder instance.
          */
-        public @NonNull Builder setSuspendedTimeMillis(@CurrentTimeMillisLong long suspendedTimeMs) {
+        public @NonNull Builder setSuspendedTimeMillis(@DurationMillisLong long suspendedTimeMs) {
             this.suspendedTimeMs = suspendedTimeMs;
             return this;
         }
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
index 60a9ebd..c82829d 100644
--- a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
@@ -116,8 +116,8 @@
 
     // Default attention level is High.
     private AttentionLevel mAttentionLevel = AttentionLevel.HIGH;
-    private String mSubtitle;
-    private String mHeader;
+    private CharSequence mSubtitle;
+    private CharSequence mHeader;
     private int mButtonOrientation;
 
     public BannerMessagePreference(Context context) {
@@ -351,7 +351,7 @@
     /**
      * Sets the text to be displayed in positive button.
      */
-    public BannerMessagePreference setPositiveButtonText(String positiveButtonText) {
+    public BannerMessagePreference setPositiveButtonText(CharSequence positiveButtonText) {
         if (!TextUtils.equals(positiveButtonText, mPositiveButtonInfo.mText)) {
             mPositiveButtonInfo.mText = positiveButtonText;
             notifyChanged();
@@ -369,7 +369,7 @@
     /**
      * Sets the text to be displayed in negative button.
      */
-    public BannerMessagePreference setNegativeButtonText(String negativeButtonText) {
+    public BannerMessagePreference setNegativeButtonText(CharSequence negativeButtonText) {
         if (!TextUtils.equals(negativeButtonText, mNegativeButtonInfo.mText)) {
             mNegativeButtonInfo.mText = negativeButtonText;
             notifyChanged();
@@ -401,7 +401,7 @@
      * Sets the subtitle.
      */
     @RequiresApi(Build.VERSION_CODES.S)
-    public BannerMessagePreference setSubtitle(String subtitle) {
+    public BannerMessagePreference setSubtitle(CharSequence subtitle) {
         if (!TextUtils.equals(subtitle, mSubtitle)) {
             mSubtitle = subtitle;
             notifyChanged();
@@ -421,8 +421,8 @@
      * Sets the header.
      */
     @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
-    public BannerMessagePreference setHeader(String header) {
-        if (!TextUtils.equals(header, mSubtitle)) {
+    public BannerMessagePreference setHeader(CharSequence header) {
+        if (!TextUtils.equals(header, mHeader)) {
             mHeader = header;
             notifyChanged();
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index ad196b8..4ee9ff0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -658,12 +658,9 @@
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
             RouteListingPreference routeListingPreference = getRouteListingPreference();
             if (routeListingPreference != null) {
-                final List<RouteListingPreference.Item> preferenceRouteListing =
-                        Api34Impl.composePreferenceRouteListing(
-                                routeListingPreference);
                 availableRoutes = Api34Impl.arrangeRouteListByPreference(selectedRoutes,
                         getAvailableRoutesFromRouter(),
-                                preferenceRouteListing);
+                        routeListingPreference);
             }
             return Api34Impl.filterDuplicatedIds(availableRoutes);
         } else {
@@ -760,11 +757,15 @@
         @DoNotInline
         static List<RouteListingPreference.Item> composePreferenceRouteListing(
                 RouteListingPreference routeListingPreference) {
+            boolean preferRouteListingOrdering =
+                    com.android.media.flags.Flags.enableOutputSwitcherSessionGrouping()
+                    && preferRouteListingOrdering(routeListingPreference);
             List<RouteListingPreference.Item> finalizedItemList = new ArrayList<>();
             List<RouteListingPreference.Item> itemList = routeListingPreference.getItems();
             for (RouteListingPreference.Item item : itemList) {
                 // Put suggested devices on the top first before further organization
-                if ((item.getFlags() & RouteListingPreference.Item.FLAG_SUGGESTED) != 0) {
+                if (!preferRouteListingOrdering
+                        && (item.getFlags() & RouteListingPreference.Item.FLAG_SUGGESTED) != 0) {
                     finalizedItemList.add(0, item);
                 } else {
                     finalizedItemList.add(item);
@@ -792,7 +793,7 @@
          * Returns an ordered list of available devices based on the provided {@code
          * routeListingPreferenceItems}.
          *
-         * <p>The result has the following order:
+         * <p>The resulting order if enableOutputSwitcherSessionGrouping is disabled is:
          *
          * <ol>
          *   <li>Selected routes.
@@ -800,22 +801,54 @@
          *   <li>Not-selected, non-system, available routes sorted by route listing preference.
          * </ol>
          *
+         * <p>The resulting order if enableOutputSwitcherSessionGrouping is enabled is:
+         *
+         * <ol>
+         *   <li>Selected routes sorted by route listing preference.
+         *   <li>Selected routes not defined by route listing preference.
+         *   <li>Not-selected system routes.
+         *   <li>Not-selected, non-system, available routes sorted by route listing preference.
+         * </ol>
+         *
+         *
          * @param selectedRoutes List of currently selected routes.
          * @param availableRoutes List of available routes that match the app's requested route
          *     features.
-         * @param routeListingPreferenceItems Ordered list of {@link RouteListingPreference.Item} to
-         *     sort routes with.
+         * @param routeListingPreference Preferences provided by the app to determine route order.
          */
         @DoNotInline
         static List<MediaRoute2Info> arrangeRouteListByPreference(
                 List<MediaRoute2Info> selectedRoutes,
                 List<MediaRoute2Info> availableRoutes,
-                List<RouteListingPreference.Item> routeListingPreferenceItems) {
+                RouteListingPreference routeListingPreference) {
+            final List<RouteListingPreference.Item> routeListingPreferenceItems =
+                    Api34Impl.composePreferenceRouteListing(routeListingPreference);
+
             Set<String> sortedRouteIds = new LinkedHashSet<>();
 
+            boolean addSelectedRlpItemsFirst =
+                    com.android.media.flags.Flags.enableOutputSwitcherSessionGrouping()
+                    && preferRouteListingOrdering(routeListingPreference);
+            Set<String> selectedRouteIds = new HashSet<>();
+
+            if (addSelectedRlpItemsFirst) {
+                // Add selected RLP items first
+                for (MediaRoute2Info selectedRoute : selectedRoutes) {
+                    selectedRouteIds.add(selectedRoute.getId());
+                }
+                for (RouteListingPreference.Item item: routeListingPreferenceItems) {
+                    if (selectedRouteIds.contains(item.getRouteId())) {
+                        sortedRouteIds.add(item.getRouteId());
+                    }
+                }
+            }
+
             // Add selected routes first.
-            for (MediaRoute2Info selectedRoute : selectedRoutes) {
-                sortedRouteIds.add(selectedRoute.getId());
+            if (com.android.media.flags.Flags.enableOutputSwitcherSessionGrouping()
+                    && sortedRouteIds.size() != selectedRoutes.size()) {
+                for (MediaRoute2Info selectedRoute : selectedRoutes) {
+                    sortedRouteIds.add(selectedRoute.getId());
+                }
             }
 
             // Add not-yet-added system routes.
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index e1447dc..1a83f0a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -48,15 +48,20 @@
 import android.media.RoutingSessionInfo;
 import android.media.session.MediaSessionManager;
 import android.os.Build;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 
+import com.android.media.flags.Flags;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.media.InfoMediaManager.Api34Impl;
 import com.android.settingslib.testutils.shadow.ShadowRouter2Manager;
 
 import com.google.common.collect.ImmutableList;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -122,6 +127,8 @@
                     .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
                     .build();
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Mock
     private MediaRouter2Manager mRouterManager;
     @Mock
@@ -377,21 +384,26 @@
     }
 
     private RouteListingPreference setUpPreferenceList(String packageName) {
+        return setUpPreferenceList(packageName, false);
+    }
+
+    private RouteListingPreference setUpPreferenceList(
+                String packageName, boolean useSystemOrdering) {
         ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT",
                 Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
         final List<RouteListingPreference.Item> preferenceItemList = new ArrayList<>();
-        RouteListingPreference.Item item1 =
+        RouteListingPreference.Item item1 = new RouteListingPreference.Item.Builder(
+                TEST_ID_3).build();
+        RouteListingPreference.Item item2 =
                 new RouteListingPreference.Item.Builder(TEST_ID_4)
                         .setFlags(RouteListingPreference.Item.FLAG_SUGGESTED)
                         .build();
-        RouteListingPreference.Item item2 = new RouteListingPreference.Item.Builder(
-                TEST_ID_3).build();
         preferenceItemList.add(item1);
         preferenceItemList.add(item2);
 
         RouteListingPreference routeListingPreference =
                 new RouteListingPreference.Builder().setItems(
-                        preferenceItemList).setUseSystemOrdering(false).build();
+                        preferenceItemList).setUseSystemOrdering(useSystemOrdering).build();
         when(mRouterManager.getRouteListingPreference(packageName))
                 .thenReturn(routeListingPreference);
         return routeListingPreference;
@@ -908,4 +920,66 @@
         assertThat(device.getState()).isEqualTo(STATE_SELECTED);
         assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(device);
     }
+
+    @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+    @Test
+    public void composePreferenceRouteListing_useSystemOrderingIsFalse() {
+        RouteListingPreference routeListingPreference =
+                setUpPreferenceList(TEST_PACKAGE_NAME, false);
+
+        List<RouteListingPreference.Item> routeOrder =
+                Api34Impl.composePreferenceRouteListing(routeListingPreference);
+
+        assertThat(routeOrder.get(0).getRouteId()).isEqualTo(TEST_ID_3);
+        assertThat(routeOrder.get(1).getRouteId()).isEqualTo(TEST_ID_4);
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+    @Test
+    public void composePreferenceRouteListing_useSystemOrderingIsTrue() {
+        RouteListingPreference routeListingPreference =
+                setUpPreferenceList(TEST_PACKAGE_NAME, true);
+
+        List<RouteListingPreference.Item> routeOrder =
+                Api34Impl.composePreferenceRouteListing(routeListingPreference);
+
+        assertThat(routeOrder.get(0).getRouteId()).isEqualTo(TEST_ID_4);
+        assertThat(routeOrder.get(1).getRouteId()).isEqualTo(TEST_ID_3);
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+    @Test
+    public void arrangeRouteListByPreference_useSystemOrderingIsFalse() {
+        RouteListingPreference routeListingPreference =
+                setUpPreferenceList(TEST_PACKAGE_NAME, false);
+        List<MediaRoute2Info> routes = setAvailableRoutesList(TEST_PACKAGE_NAME);
+        when(mRouterManager.getSelectedRoutes(any())).thenReturn(routes);
+
+        List<MediaRoute2Info> routeOrder =
+                Api34Impl.arrangeRouteListByPreference(
+                        routes, routes, routeListingPreference);
+
+        assertThat(routeOrder.get(0).getId()).isEqualTo(TEST_ID_3);
+        assertThat(routeOrder.get(1).getId()).isEqualTo(TEST_ID_4);
+        assertThat(routeOrder.get(2).getId()).isEqualTo(TEST_ID_2);
+        assertThat(routeOrder.get(3).getId()).isEqualTo(TEST_ID_1);
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+    @Test
+    public void arrangeRouteListByPreference_useSystemOrderingIsTrue() {
+        RouteListingPreference routeListingPreference =
+                setUpPreferenceList(TEST_PACKAGE_NAME, true);
+        List<MediaRoute2Info> routes = setAvailableRoutesList(TEST_PACKAGE_NAME);
+        when(mRouterManager.getSelectedRoutes(any())).thenReturn(routes);
+
+        List<MediaRoute2Info> routeOrder =
+                Api34Impl.arrangeRouteListByPreference(
+                        routes, routes, routeListingPreference);
+
+        assertThat(routeOrder.get(0).getId()).isEqualTo(TEST_ID_2);
+        assertThat(routeOrder.get(1).getId()).isEqualTo(TEST_ID_3);
+        assertThat(routeOrder.get(2).getId()).isEqualTo(TEST_ID_4);
+        assertThat(routeOrder.get(3).getId()).isEqualTo(TEST_ID_1);
+    }
 }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 2b4e65f..9ab8aa8 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -952,7 +952,6 @@
     <uses-permission android:name="android.permission.SETUP_FSVERITY" />
 
     <!-- Permissions required for CTS test - AppFunctionManagerTest -->
-    <uses-permission android:name="android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED" />
     <uses-permission android:name="android.permission.EXECUTE_APP_FUNCTIONS" />
 
     <!-- Permission required for CTS test - CtsNfcTestCases -->
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index bb0d5d7..6c96279 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1697,13 +1697,6 @@
 }
 
 flag {
-   name: "magic_portrait_wallpapers"
-   namespace: "systemui"
-   description: "Magic Portrait related changes in systemui"
-   bug: "370863642"
-}
-
-flag {
   name: "notes_role_qs_tile"
   namespace: "systemui"
   description: "Enables notes role qs tile which opens default notes role app in app bubbles"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/ContentDescription.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/ContentDescription.kt
index 4a5ad65..b254963 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/ContentDescription.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/ContentDescription.kt
@@ -17,11 +17,13 @@
 package com.android.systemui.common.ui.compose
 
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
 import androidx.compose.ui.res.stringResource
 import com.android.systemui.common.shared.model.ContentDescription
 
 /** Returns the loaded [String] or `null` if there isn't one. */
 @Composable
+@ReadOnlyComposable
 fun ContentDescription.load(): String? {
     return when (this) {
         is ContentDescription.Loaded -> description
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt
index 82d1436..8b0c005 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt
@@ -21,9 +21,8 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.asImageBitmap
 import androidx.compose.ui.res.painterResource
-import androidx.core.graphics.drawable.toBitmap
+import com.android.compose.ui.graphics.painter.rememberDrawablePainter
 import com.android.systemui.common.shared.model.Icon
 
 /**
@@ -36,7 +35,7 @@
     val contentDescription = icon.contentDescription?.load()
     when (icon) {
         is Icon.Loaded -> {
-            Icon(icon.drawable.toBitmap().asImageBitmap(), contentDescription, modifier, tint)
+            Icon(rememberDrawablePainter(icon.drawable), contentDescription, modifier, tint)
         }
         is Icon.Resource -> Icon(painterResource(icon.res), contentDescription, modifier, tint)
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt
index 4e8121f..19adba0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt
@@ -19,6 +19,7 @@
 
 import android.content.Context
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.AnnotatedString
 import com.android.systemui.common.shared.model.Text
@@ -26,6 +27,7 @@
 
 /** Returns the loaded [String] or `null` if there isn't one. */
 @Composable
+@ReadOnlyComposable
 fun Text.load(): String? {
     return when (this) {
         is Text.Loaded -> text
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 31aebc2..2c6d09a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -19,13 +19,11 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.Measurable
-import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntRect
@@ -35,6 +33,7 @@
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler
 import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection
+import com.android.systemui.communal.ui.compose.section.CommunalLockSection
 import com.android.systemui.communal.ui.compose.section.CommunalPopupSection
 import com.android.systemui.communal.ui.compose.section.CommunalToDreamButtonSection
 import com.android.systemui.communal.ui.compose.section.HubOnboardingSection
@@ -43,7 +42,6 @@
 import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
 import com.android.systemui.keyguard.ui.composable.section.LockSection
-import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import javax.inject.Inject
 import kotlin.math.min
@@ -58,6 +56,7 @@
     private val communalSettingsInteractor: CommunalSettingsInteractor,
     private val dialogFactory: SystemUIDialogFactory,
     private val lockSection: LockSection,
+    private val communalLockSection: CommunalLockSection,
     private val bottomAreaSection: BottomAreaSection,
     private val ambientStatusBarSection: AmbientStatusBarSection,
     private val communalPopupSection: CommunalPopupSection,
@@ -88,12 +87,9 @@
                         with(hubOnboardingSection) { BottomSheet() }
                     }
                     if (communalSettingsInteractor.isV2FlagEnabled()) {
-                        Icon(
-                            painter = painterResource(id = R.drawable.ic_lock),
-                            contentDescription = null,
-                            tint = MaterialTheme.colorScheme.onPrimaryContainer,
-                            modifier = Modifier.element(Communal.Elements.LockIcon),
-                        )
+                        with(communalLockSection) {
+                            LockIcon(modifier = Modifier.element(Communal.Elements.LockIcon))
+                        }
                     } else {
                         with(lockSection) {
                             LockIcon(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalLockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalLockSection.kt
new file mode 100644
index 0000000..eab2b87
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalLockSection.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2025 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.communal.ui.compose.section
+
+import android.content.Context
+import android.util.DisplayMetrics
+import android.view.WindowManager
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.ElementKey
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.communal.ui.binder.CommunalLockIconViewBinder
+import com.android.systemui.communal.ui.viewmodel.CommunalLockIconViewModel
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LongPressHandlingViewLogger
+import com.android.systemui.log.dagger.LongPressTouchLog
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import dagger.Lazy
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+class CommunalLockSection
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    private val windowManager: WindowManager,
+    private val authController: AuthController,
+    private val viewModel: Lazy<CommunalLockIconViewModel>,
+    private val falsingManager: Lazy<FalsingManager>,
+    private val vibratorHelper: Lazy<VibratorHelper>,
+    private val featureFlags: FeatureFlagsClassic,
+    @LongPressTouchLog private val logBuffer: LogBuffer,
+) {
+    @Composable
+    fun ContentScope.LockIcon(modifier: Modifier = Modifier) {
+        val context = LocalContext.current
+
+        AndroidView(
+            factory = { context ->
+                DeviceEntryIconView(
+                        context,
+                        null,
+                        logger = LongPressHandlingViewLogger(logBuffer, tag = TAG),
+                    )
+                    .apply {
+                        id = R.id.device_entry_icon_view
+                        CommunalLockIconViewBinder.bind(
+                            applicationScope,
+                            this,
+                            viewModel.get(),
+                            falsingManager.get(),
+                            vibratorHelper.get(),
+                        )
+                    }
+            },
+            modifier =
+                modifier.element(LockIconElementKey).layout { measurable, _ ->
+                    val lockIconBounds = lockIconBounds(context)
+                    val placeable =
+                        measurable.measure(
+                            Constraints.fixed(
+                                width = lockIconBounds.width,
+                                height = lockIconBounds.height,
+                            )
+                        )
+                    layout(
+                        width = placeable.width,
+                        height = placeable.height,
+                        alignmentLines =
+                            mapOf(
+                                BlueprintAlignmentLines.LockIcon.Left to lockIconBounds.left,
+                                BlueprintAlignmentLines.LockIcon.Top to lockIconBounds.top,
+                                BlueprintAlignmentLines.LockIcon.Right to lockIconBounds.right,
+                                BlueprintAlignmentLines.LockIcon.Bottom to lockIconBounds.bottom,
+                            ),
+                    ) {
+                        placeable.place(0, 0)
+                    }
+                },
+        )
+    }
+
+    /** Returns the bounds of the lock icon, in window view coordinates. */
+    private fun lockIconBounds(context: Context): IntRect {
+        val windowViewBounds = windowManager.currentWindowMetrics.bounds
+        var widthPx = windowViewBounds.right.toFloat()
+        if (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) {
+            val insets = windowManager.currentWindowMetrics.windowInsets
+            // Assumed to be initially neglected as there are no left or right insets in portrait.
+            // However, on landscape, these insets need to included when calculating the midpoint.
+            @Suppress("DEPRECATION")
+            widthPx -= (insets.systemWindowInsetLeft + insets.systemWindowInsetRight).toFloat()
+        }
+        val defaultDensity =
+            DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
+                DisplayMetrics.DENSITY_DEFAULT.toFloat()
+        val lockIconRadiusPx = (defaultDensity * 36).toInt()
+
+        val scaleFactor = authController.scaleFactor
+        val bottomPaddingPx =
+            context.resources.getDimensionPixelSize(
+                com.android.systemui.customization.R.dimen.lock_icon_margin_bottom
+            )
+        val heightPx = windowViewBounds.bottom.toFloat()
+        val (center, radius) =
+            Pair(
+                IntOffset(
+                    x = (widthPx / 2).toInt(),
+                    y = (heightPx - ((bottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt(),
+                ),
+                (lockIconRadiusPx * scaleFactor).toInt(),
+            )
+
+        return IntRect(center, radius)
+    }
+
+    companion object {
+        private const val TAG = "CommunalLockSection"
+    }
+}
+
+private val LockIconElementKey = ElementKey("CommunalLockIcon")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
index b4c6003..73b0750 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
@@ -17,11 +17,8 @@
 package com.android.systemui.scene.ui.composable.transitions
 
 import androidx.compose.animation.core.tween
-import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
-import com.android.compose.animation.scene.UserActionDistance
-import com.android.compose.animation.scene.UserActionDistanceScope
 import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys
 import com.android.systemui.notifications.ui.composable.Notifications
 import com.android.systemui.notifications.ui.composable.NotificationsShade
@@ -31,9 +28,6 @@
 
 fun TransitionBuilder.toNotificationsShadeTransition(durationScale: Double = 1.0) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
-    distance = UserActionDistance { _, shadeContentKey, _ ->
-        calculateShadePanelTargetPositionY(shadeContentKey)
-    }
 
     // Ensure the clock isn't clipped by the shade outline during the transition from lockscreen.
     sharedElement(
@@ -50,12 +44,4 @@
     fractionRange(start = .5f) { fade(Notifications.Elements.NotificationScrim) }
 }
 
-/** Returns the Y position of the bottom of the shade container panel within [shadeOverlayKey]. */
-fun UserActionDistanceScope.calculateShadePanelTargetPositionY(shadeOverlayKey: ContentKey): Float {
-    val marginTop = OverlayShade.Elements.Panel.targetOffset(shadeOverlayKey)?.y ?: 0f
-    val panelHeight =
-        OverlayShade.Elements.Panel.targetSize(shadeOverlayKey)?.height?.toFloat() ?: 0f
-    return marginTop + panelHeight
-}
-
 private val DefaultDuration = 300.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
index c9fbb4d..43aa358 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
@@ -19,18 +19,13 @@
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
-import com.android.compose.animation.scene.UserActionDistance
 import com.android.systemui.shade.ui.composable.OverlayShade
 import kotlin.time.Duration.Companion.milliseconds
 
 fun TransitionBuilder.toQuickSettingsShadeTransition(durationScale: Double = 1.0) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
-    distance = UserActionDistance { _, shadeContentKey, _ ->
-        calculateShadePanelTargetPositionY(shadeContentKey)
-    }
 
     translate(OverlayShade.Elements.Panel, Edge.Top)
-
     fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) }
 }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalLockIconViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalLockIconViewModelTest.kt
new file mode 100644
index 0000000..c535831
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalLockIconViewModelTest.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2025 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.communal.view.viewmodel
+
+import android.platform.test.flag.junit.FlagsParameterization
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.domain.interactor.accessibilityInteractor
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.communal.ui.viewmodel.CommunalLockIconViewModel
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntrySourceInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.advanceTimeBy
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+class CommunalLockIconViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+    private val Kosmos.underTest by
+        Kosmos.Fixture {
+            CommunalLockIconViewModel(
+                context = context,
+                configurationInteractor = configurationInteractor,
+                deviceEntryInteractor = deviceEntryInteractor,
+                keyguardInteractor = keyguardInteractor,
+                keyguardViewController = { statusBarKeyguardViewManager },
+                deviceEntrySourceInteractor = deviceEntrySourceInteractor,
+                accessibilityInteractor = accessibilityInteractor,
+            )
+        }
+
+    @Test
+    fun isLongPressEnabled_unlocked() =
+        kosmos.runTest {
+            val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled)
+            setLockscreenDismissible()
+            assertThat(isLongPressEnabled).isTrue()
+        }
+
+    @Test
+    fun isLongPressEnabled_lock() =
+        kosmos.runTest {
+            val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled)
+            if (!SceneContainerFlag.isEnabled) {
+                fakeKeyguardRepository.setKeyguardDismissible(false)
+            }
+            assertThat(isLongPressEnabled).isFalse()
+        }
+
+    @Test
+    fun iconType_locked() =
+        kosmos.runTest {
+            val viewAttributes by collectLastValue(underTest.viewAttributes)
+            if (!SceneContainerFlag.isEnabled) {
+                fakeKeyguardRepository.setKeyguardDismissible(false)
+            }
+            assertThat(viewAttributes?.type).isEqualTo(DeviceEntryIconView.IconType.LOCK)
+        }
+
+    @Test
+    fun iconType_unlocked() =
+        kosmos.runTest {
+            val viewAttributes by collectLastValue(underTest.viewAttributes)
+            setLockscreenDismissible()
+            assertThat(viewAttributes?.type).isEqualTo(DeviceEntryIconView.IconType.UNLOCK)
+        }
+
+    private suspend fun Kosmos.setLockscreenDismissible() {
+        if (SceneContainerFlag.isEnabled) {
+            // Need to set up a collection for the authentication to be propagated.
+            backgroundScope.launch { kosmos.deviceUnlockedInteractor.deviceUnlockStatus.collect {} }
+            assertThat(
+                    kosmos.authenticationInteractor.authenticate(
+                        FakeAuthenticationRepository.DEFAULT_PIN
+                    )
+                )
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
+        } else {
+            fakeKeyguardRepository.setKeyguardDismissible(true)
+        }
+        testScope.advanceTimeBy(
+            DeviceEntryIconViewModel.UNLOCKED_DELAY_MS * 2
+        ) // wait for unlocked delay
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 7478464..57ac906 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -117,8 +117,8 @@
                 LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
         mMediaDevices.add(mMediaDevice1);
         mMediaDevices.add(mMediaDevice2);
-        mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice1));
-        mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice2));
+        mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice1, true));
+        mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice2, false));
 
         mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
         mMediaOutputAdapter.updateItems();
@@ -779,4 +779,120 @@
                 mViewHolder.getDrawableId(false /* isInputDevice */, false /* isMutedVolumeIcon */))
                 .isEqualTo(R.drawable.media_output_icon_volume);
     }
+
+    @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+    @Test
+    public void multipleSelectedDevices_verifySessionView() {
+        initializeSession();
+
+        mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+                .onCreateViewHolder(
+                        new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+        assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mEndClickIcon.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_SESSION_NAME);
+        assertThat(mViewHolder.mSeekBar.getVolume()).isEqualTo(TEST_CURRENT_VOLUME);
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+    @Test
+    public void multipleSelectedDevices_verifyCollapsedView() {
+        initializeSession();
+
+        mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+                .onCreateViewHolder(
+                        new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+        assertThat(mViewHolder.mItemLayout.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+    @Test
+    public void multipleSelectedDevices_expandIconClicked_verifyInitialView() {
+        initializeSession();
+        mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+                .onCreateViewHolder(
+                        new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+        mViewHolder.mEndTouchArea.performClick();
+        mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+                .onCreateViewHolder(
+                        new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+        assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mEndClickIcon.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+    @Test
+    public void multipleSelectedDevices_expandIconClicked_verifyCollapsedView() {
+        initializeSession();
+        mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+                .onCreateViewHolder(
+                        new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+        mViewHolder.mEndTouchArea.performClick();
+        mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+                .onCreateViewHolder(
+                        new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+        assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mEndClickIcon.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2);
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+    @Test
+    public void deviceCanNotBeDeselected_verifyView() {
+        List<MediaDevice> selectedDevices = new ArrayList<>();
+        selectedDevices.add(mMediaDevice1);
+        when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(selectedDevices);
+        when(mMediaSwitchingController.getSelectedMediaDevice()).thenReturn(selectedDevices);
+        when(mMediaSwitchingController.getDeselectableMediaDevice()).thenReturn(new ArrayList<>());
+
+        mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+                .onCreateViewHolder(
+                        new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+        assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mEndClickIcon.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
+    }
+
+    private void initializeSession() {
+        when(mMediaSwitchingController.getSessionVolumeMax()).thenReturn(TEST_MAX_VOLUME);
+        when(mMediaSwitchingController.getSessionVolume()).thenReturn(TEST_CURRENT_VOLUME);
+        when(mMediaSwitchingController.getSessionName()).thenReturn(TEST_SESSION_NAME);
+
+        List<MediaDevice> selectedDevices = new ArrayList<>();
+        selectedDevices.add(mMediaDevice1);
+        selectedDevices.add(mMediaDevice2);
+        when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(selectedDevices);
+        when(mMediaSwitchingController.getSelectedMediaDevice()).thenReturn(selectedDevices);
+        when(mMediaSwitchingController.getDeselectableMediaDevice()).thenReturn(selectedDevices);
+
+        mMediaOutputAdapter.updateItems();
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
index ff00bfb5..63942072 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.mediaprojection.data.repository
 
 import android.hardware.display.displayManager
+import android.media.projection.MediaProjectionEvent
 import android.media.projection.MediaProjectionInfo
 import android.media.projection.StopReason
 import android.os.Binder
@@ -32,8 +33,11 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.mediaprojection.data.model.MediaProjectionState
 import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
@@ -42,7 +46,7 @@
 import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeTasksRepository
 import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
 import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
-import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -55,7 +59,7 @@
 @SmallTest
 class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
 
-    private val kosmos = taskSwitcherKosmos()
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
     private val testScope = kosmos.testScope
 
     private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
@@ -345,4 +349,40 @@
             verify(fakeMediaProjectionManager.mediaProjectionManager)
                 .stopActiveProjection(StopReason.STOP_QS_TILE)
         }
+
+    @Test
+    @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+    fun projectionStartedDuringCallAndActivePostCallEvent_flagEnabled_emitsUnit() =
+        kosmos.runTest {
+            val projectionStartedDuringCallAndActivePostCallEvent by
+                collectLastValue(repo.projectionStartedDuringCallAndActivePostCallEvent)
+
+            fakeMediaProjectionManager.dispatchEvent(
+                PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL_EVENT
+            )
+
+            assertThat(projectionStartedDuringCallAndActivePostCallEvent).isEqualTo(Unit)
+        }
+
+    @Test
+    @DisableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+    fun projectionStartedDuringCallAndActivePostCallEvent_flagDisabled_doesNotEmit() =
+        testScope.runTest {
+            val projectionStartedDuringCallAndActivePostCallEvent by
+                collectLastValue(repo.projectionStartedDuringCallAndActivePostCallEvent)
+
+            fakeMediaProjectionManager.dispatchEvent(
+                PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL_EVENT
+            )
+
+            assertThat(projectionStartedDuringCallAndActivePostCallEvent).isNull()
+        }
+
+    companion object {
+        private val PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL_EVENT =
+            MediaProjectionEvent(
+                MediaProjectionEvent.PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL,
+                /* timestampMillis= */ 100L,
+            )
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 2020d0d..3d31787 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -460,17 +460,17 @@
     }
 
     @Test
-    public void testOnDisplayReady() {
-        mCommandQueue.onDisplayReady(DEFAULT_DISPLAY);
+    public void testonDisplayAddSystemDecorations() {
+        mCommandQueue.onDisplayAddSystemDecorations(DEFAULT_DISPLAY);
         waitForIdleSync();
-        verify(mCallbacks).onDisplayReady(eq(DEFAULT_DISPLAY));
+        verify(mCallbacks).onDisplayAddSystemDecorations(eq(DEFAULT_DISPLAY));
     }
 
     @Test
-    public void testOnDisplayReadyForSecondaryDisplay() {
-        mCommandQueue.onDisplayReady(SECONDARY_DISPLAY);
+    public void testonDisplayAddSystemDecorationsForSecondaryDisplay() {
+        mCommandQueue.onDisplayAddSystemDecorations(SECONDARY_DISPLAY);
         waitForIdleSync();
-        verify(mCallbacks).onDisplayReady(eq(SECONDARY_DISPLAY));
+        verify(mCallbacks).onDisplayAddSystemDecorations(eq(SECONDARY_DISPLAY));
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 0a05649..a79f408 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -45,6 +45,7 @@
 import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
 import com.android.wm.shell.appzoomout.AppZoomOut
 import com.google.common.truth.Truth.assertThat
+import java.util.Optional
 import java.util.function.Consumer
 import org.junit.Before
 import org.junit.Rule
@@ -66,7 +67,6 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.junit.MockitoJUnit
-import java.util.Optional
 
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper
@@ -150,24 +150,23 @@
 
     @Test
     fun setupListeners() {
-        verify(dumpManager).registerCriticalDumpable(
-            anyString(), eq(notificationShadeDepthController)
-        )
+        verify(dumpManager)
+            .registerCriticalDumpable(anyString(), eq(notificationShadeDepthController))
     }
 
     @Test
     fun onPanelExpansionChanged_apliesBlur_ifShade() {
         notificationShadeDepthController.onPanelExpansionChanged(
-            ShadeExpansionChangeEvent(
-                fraction = 1f, expanded = true, tracking = false))
+            ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+        )
         verify(shadeAnimation).animateTo(eq(maxBlur))
     }
 
     @Test
     fun onPanelExpansionChanged_animatesBlurIn_ifShade() {
         notificationShadeDepthController.onPanelExpansionChanged(
-            ShadeExpansionChangeEvent(
-                fraction = 0.01f, expanded = false, tracking = false))
+            ShadeExpansionChangeEvent(fraction = 0.01f, expanded = false, tracking = false)
+        )
         verify(shadeAnimation).animateTo(eq(maxBlur))
     }
 
@@ -176,27 +175,27 @@
         onPanelExpansionChanged_animatesBlurIn_ifShade()
         clearInvocations(shadeAnimation)
         notificationShadeDepthController.onPanelExpansionChanged(
-            ShadeExpansionChangeEvent(
-                fraction = 0f, expanded = false, tracking = false))
+            ShadeExpansionChangeEvent(fraction = 0f, expanded = false, tracking = false)
+        )
         verify(shadeAnimation).animateTo(eq(0))
     }
 
     @Test
     fun onPanelExpansionChanged_animatesBlurOut_ifFlick() {
-        val event =
-            ShadeExpansionChangeEvent(
-                fraction = 1f, expanded = true, tracking = false)
+        val event = ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
         onPanelExpansionChanged_apliesBlur_ifShade()
         clearInvocations(shadeAnimation)
         notificationShadeDepthController.onPanelExpansionChanged(event)
         verify(shadeAnimation, never()).animateTo(anyInt())
 
         notificationShadeDepthController.onPanelExpansionChanged(
-            event.copy(fraction = 0.9f, tracking = true))
+            event.copy(fraction = 0.9f, tracking = true)
+        )
         verify(shadeAnimation, never()).animateTo(anyInt())
 
         notificationShadeDepthController.onPanelExpansionChanged(
-            event.copy(fraction = 0.8f, tracking = false))
+            event.copy(fraction = 0.8f, tracking = false)
+        )
         verify(shadeAnimation).animateTo(eq(0))
     }
 
@@ -205,16 +204,14 @@
         onPanelExpansionChanged_animatesBlurOut_ifFlick()
         clearInvocations(shadeAnimation)
         notificationShadeDepthController.onPanelExpansionChanged(
-            ShadeExpansionChangeEvent(
-                fraction = 0.6f, expanded = true, tracking = true))
+            ShadeExpansionChangeEvent(fraction = 0.6f, expanded = true, tracking = true)
+        )
         verify(shadeAnimation).animateTo(eq(maxBlur))
     }
 
     @Test
     fun onPanelExpansionChanged_respectsMinPanelPullDownFraction() {
-        val event =
-            ShadeExpansionChangeEvent(
-                fraction = 0.5f, expanded = true, tracking = true)
+        val event = ShadeExpansionChangeEvent(fraction = 0.5f, expanded = true, tracking = true)
         notificationShadeDepthController.panelPullDownMinFraction = 0.5f
         notificationShadeDepthController.onPanelExpansionChanged(event)
         assertThat(notificationShadeDepthController.shadeExpansion).isEqualTo(0f)
@@ -241,8 +238,8 @@
         statusBarState = StatusBarState.KEYGUARD
         notificationShadeDepthController.qsPanelExpansion = 1f
         notificationShadeDepthController.onPanelExpansionChanged(
-            ShadeExpansionChangeEvent(
-                fraction = 1f, expanded = true, tracking = false))
+            ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+        )
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
         verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
     }
@@ -252,8 +249,8 @@
         statusBarState = StatusBarState.KEYGUARD
         notificationShadeDepthController.qsPanelExpansion = 0.25f
         notificationShadeDepthController.onPanelExpansionChanged(
-            ShadeExpansionChangeEvent(
-                fraction = 1f, expanded = true, tracking = false))
+            ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+        )
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
         verify(wallpaperController)
             .setNotificationShadeZoom(eq(ShadeInterpolation.getNotificationScrimAlpha(0.25f)))
@@ -264,8 +261,8 @@
         enableSplitShade()
 
         notificationShadeDepthController.onPanelExpansionChanged(
-            ShadeExpansionChangeEvent(
-                fraction = 1f, expanded = true, tracking = false))
+            ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+        )
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
 
         verify(wallpaperController).setNotificationShadeZoom(0f)
@@ -276,8 +273,8 @@
         disableSplitShade()
 
         notificationShadeDepthController.onPanelExpansionChanged(
-            ShadeExpansionChangeEvent(
-                fraction = 1f, expanded = true, tracking = false))
+            ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+        )
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
 
         verify(wallpaperController).setNotificationShadeZoom(floatThat { it > 0 })
@@ -354,8 +351,8 @@
     @Test
     fun updateBlurCallback_setsBlur_whenExpanded() {
         notificationShadeDepthController.onPanelExpansionChanged(
-            ShadeExpansionChangeEvent(
-                fraction = 1f, expanded = true, tracking = false))
+            ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+        )
         `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
         verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
@@ -364,8 +361,8 @@
     @Test
     fun updateBlurCallback_ignoreShadeBlurUntilHidden_overridesZoom() {
         notificationShadeDepthController.onPanelExpansionChanged(
-            ShadeExpansionChangeEvent(
-                fraction = 1f, expanded = true, tracking = false))
+            ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+        )
         `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
         notificationShadeDepthController.blursDisabledForAppLaunch = true
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
@@ -373,7 +370,7 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_BOUNCER_UI_REVAMP)
+    @DisableFlags(Flags.FLAG_BOUNCER_UI_REVAMP, Flags.FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND)
     fun ignoreShadeBlurUntilHidden_schedulesFrame() {
         notificationShadeDepthController.blursDisabledForAppLaunch = true
         verify(blurUtils).prepareBlur(any(), anyInt())
@@ -391,8 +388,8 @@
     @Test
     fun ignoreBlurForUnlock_ignores() {
         notificationShadeDepthController.onPanelExpansionChanged(
-            ShadeExpansionChangeEvent(
-                fraction = 1f, expanded = true, tracking = false))
+            ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+        )
         `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
 
         notificationShadeDepthController.blursDisabledForAppLaunch = false
@@ -408,8 +405,8 @@
     @Test
     fun ignoreBlurForUnlock_doesNotIgnore() {
         notificationShadeDepthController.onPanelExpansionChanged(
-            ShadeExpansionChangeEvent(
-                fraction = 1f, expanded = true, tracking = false))
+            ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+        )
         `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
 
         notificationShadeDepthController.blursDisabledForAppLaunch = false
@@ -435,14 +432,14 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_BOUNCER_UI_REVAMP)
+    @DisableFlags(Flags.FLAG_BOUNCER_UI_REVAMP, Flags.FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND)
     fun brightnessMirror_hidesShadeBlur() {
         // Brightness mirror is fully visible
         `when`(brightnessSpring.ratio).thenReturn(1f)
         // And shade is blurred
         notificationShadeDepthController.onPanelExpansionChanged(
-            ShadeExpansionChangeEvent(
-                fraction = 1f, expanded = true, tracking = false))
+            ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+        )
         `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
 
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index 75262a4..03c0751 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
@@ -24,9 +24,9 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.res.R
@@ -47,6 +47,7 @@
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
+import com.android.systemui.testKosmos
 import com.android.systemui.util.time.fakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
@@ -60,7 +61,7 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CallChipViewModelTest : SysuiTestCase() {
-    private val kosmos = Kosmos()
+    private val kosmos = testKosmos()
     private val notificationListRepository = kosmos.activeNotificationListRepository
     private val testScope = kosmos.testScope
     private val repo = kosmos.ongoingCallRepository
@@ -162,25 +163,34 @@
 
     @Test
     @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
-    fun chip_zeroStartTime_cdFlagOff_iconIsNotifIcon() =
+    fun chip_zeroStartTime_cdFlagOff_iconIsNotifIcon_withContentDescription() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
 
             val notifIcon = createStatusBarIconViewOrNull()
-            repo.setOngoingCallState(inCallModel(startTimeMs = 0, notificationIcon = notifIcon))
+            repo.setOngoingCallState(
+                inCallModel(
+                    startTimeMs = 0,
+                    notificationIcon = notifIcon,
+                    appName = "Fake app name",
+                )
+            )
 
             assertThat((latest as OngoingActivityChipModel.Shown).icon)
                 .isInstanceOf(OngoingActivityChipModel.ChipIcon.StatusBarView::class.java)
             val actualIcon =
-                (((latest as OngoingActivityChipModel.Shown).icon)
-                        as OngoingActivityChipModel.ChipIcon.StatusBarView)
-                    .impl
-            assertThat(actualIcon).isEqualTo(notifIcon)
+                (latest as OngoingActivityChipModel.Shown).icon
+                    as OngoingActivityChipModel.ChipIcon.StatusBarView
+            assertThat(actualIcon.impl).isEqualTo(notifIcon)
+            assertThat(actualIcon.contentDescription.loadContentDescription(context))
+                .contains("Ongoing call")
+            assertThat(actualIcon.contentDescription.loadContentDescription(context))
+                .contains("Fake app name")
         }
 
     @Test
     @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
-    fun chip_zeroStartTime_cdFlagOn_iconIsNotifKeyIcon() =
+    fun chip_zeroStartTime_cdFlagOn_iconIsNotifKeyIcon_withContentDescription() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
 
@@ -189,11 +199,22 @@
                     startTimeMs = 0,
                     notificationIcon = createStatusBarIconViewOrNull(),
                     notificationKey = "notifKey",
+                    appName = "Fake app name",
                 )
             )
 
             assertThat((latest as OngoingActivityChipModel.Shown).icon)
-                .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon("notifKey"))
+                .isInstanceOf(
+                    OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon::class.java
+                )
+            val actualIcon =
+                (latest as OngoingActivityChipModel.Shown).icon
+                    as OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon
+            assertThat(actualIcon.notificationKey).isEqualTo("notifKey")
+            assertThat(actualIcon.contentDescription.loadContentDescription(context))
+                .contains("Ongoing call")
+            assertThat(actualIcon.contentDescription.loadContentDescription(context))
+                .contains("Fake app name")
         }
 
     @Test
@@ -216,7 +237,7 @@
 
     @Test
     @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
-    fun chip_notifIconFlagOn_butNullNotifIcon_iconNotifKey() =
+    fun chip_notifIconFlagOn_butNullNotifIcon_cdFlagOn_iconIsNotifKeyIcon_withContentDescription() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
 
@@ -225,11 +246,22 @@
                     startTimeMs = 1000,
                     notificationIcon = null,
                     notificationKey = "notifKey",
+                    appName = "Fake app name",
                 )
             )
 
             assertThat((latest as OngoingActivityChipModel.Shown).icon)
-                .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon("notifKey"))
+                .isInstanceOf(
+                    OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon::class.java
+                )
+            val actualIcon =
+                (latest as OngoingActivityChipModel.Shown).icon
+                    as OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon
+            assertThat(actualIcon.notificationKey).isEqualTo("notifKey")
+            assertThat(actualIcon.contentDescription.loadContentDescription(context))
+                .contains("Ongoing call")
+            assertThat(actualIcon.contentDescription.loadContentDescription(context))
+                .contains("Fake app name")
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
index dea3d1f..0dc2759 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
@@ -22,21 +22,26 @@
 import android.content.pm.PackageManager
 import android.content.pm.ResolveInfo
 import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags.FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.mediaprojection.data.model.MediaProjectionState
 import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
 import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
 import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mockito.doAnswer
 import org.mockito.kotlin.any
@@ -44,8 +49,9 @@
 import org.mockito.kotlin.whenever
 
 @SmallTest
+@RunWith(AndroidJUnit4::class)
 class MediaProjectionChipInteractorTest : SysuiTestCase() {
-    private val kosmos = Kosmos().also { it.testCase = this }
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
     private val testScope = kosmos.testScope
     private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository
 
@@ -57,6 +63,26 @@
     private val underTest = kosmos.mediaProjectionChipInteractor
 
     @Test
+    fun projectionStartedDuringCallAndActivePostCallEvent_eventEmitted_isUnit() =
+        kosmos.runTest {
+            val latest by
+                collectLastValue(underTest.projectionStartedDuringCallAndActivePostCallEvent)
+
+            fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+            assertThat(latest).isEqualTo(Unit)
+        }
+
+    @Test
+    fun projectionStartedDuringCallAndActivePostCallEvent_noEventEmitted_isNull() =
+        kosmos.runTest {
+            val latest by
+                collectLastValue(underTest.projectionStartedDuringCallAndActivePostCallEvent)
+
+            assertThat(latest).isNull()
+        }
+
+    @Test
     fun projection_notProjectingState_isNotProjecting() =
         testScope.runTest {
             val latest by collectLastValue(underTest.projection)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
index fe15eac..05f2585 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
@@ -49,6 +49,7 @@
             val startingNotif =
                 activeNotificationModel(
                     key = "notif1",
+                    appName = "Fake Name",
                     statusBarChipIcon = icon,
                     promotedContent = PROMOTED_CONTENT,
                 )
@@ -58,6 +59,7 @@
             val latest by collectLastValue(underTest.notificationChip)
 
             assertThat(latest!!.key).isEqualTo("notif1")
+            assertThat(latest!!.appName).isEqualTo("Fake Name")
             assertThat(latest!!.statusBarChipIconView).isEqualTo(icon)
             assertThat(latest!!.promotedContent).isEqualTo(PROMOTED_CONTENT)
         }
@@ -70,6 +72,7 @@
                 factory.create(
                     activeNotificationModel(
                         key = "notif1",
+                        appName = "Fake Name",
                         statusBarChipIcon = originalIconView,
                         promotedContent = PROMOTED_CONTENT,
                     ),
@@ -82,12 +85,14 @@
             underTest.setNotification(
                 activeNotificationModel(
                     key = "notif1",
+                    appName = "New Name",
                     statusBarChipIcon = newIconView,
                     promotedContent = PROMOTED_CONTENT,
                 )
             )
 
             assertThat(latest!!.key).isEqualTo("notif1")
+            assertThat(latest!!.appName).isEqualTo("New Name")
             assertThat(latest!!.statusBarChipIconView).isEqualTo(newIconView)
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index 942e6554..1f77ddc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.chips.notification.ui.viewmodel
 
+import android.content.Context
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.view.View
@@ -23,6 +24,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags.FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.kosmos.collectLastValue
@@ -125,7 +127,7 @@
 
     @Test
     @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
-    fun chips_onePromotedNotif_statusBarIconViewMatches() =
+    fun chips_onePromotedNotif_connectedDisplaysFlagDisabled_statusBarIconViewMatches() =
         kosmos.runTest {
             val latest by collectLastValue(underTest.chips)
 
@@ -134,6 +136,7 @@
                 listOf(
                     activeNotificationModel(
                         key = "notif",
+                        appName = "Fake App Name",
                         statusBarChipIcon = icon,
                         promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
                     )
@@ -142,7 +145,13 @@
 
             assertThat(latest).hasSize(1)
             val chip = latest!![0]
-            assertIsNotifChip(chip, icon, "notif")
+            assertIsNotifChip(
+                chip,
+                context,
+                icon,
+                expectedNotificationKey = "notif",
+                expectedContentDescriptionSubstrings = listOf("Ongoing", "Fake App Name"),
+            )
         }
 
     @Test
@@ -157,6 +166,7 @@
                 listOf(
                     activeNotificationModel(
                         key = notifKey,
+                        appName = "Fake App Name",
                         statusBarChipIcon = null,
                         promotedContent = PromotedNotificationContentModel.Builder(notifKey).build(),
                     )
@@ -165,9 +175,13 @@
 
             assertThat(latest).hasSize(1)
             val chip = latest!![0]
-            assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
-            assertThat(chip.icon)
-                .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(notifKey))
+            assertIsNotifChip(
+                chip,
+                context,
+                expectedIcon = null,
+                expectedNotificationKey = "notif",
+                expectedContentDescriptionSubstrings = listOf("Ongoing", "Fake App Name"),
+            )
         }
 
     @Test
@@ -230,8 +244,8 @@
             )
 
             assertThat(latest).hasSize(2)
-            assertIsNotifChip(latest!![0], firstIcon, "notif1")
-            assertIsNotifChip(latest!![1], secondIcon, "notif2")
+            assertIsNotifChip(latest!![0], context, firstIcon, "notif1")
+            assertIsNotifChip(latest!![1], context, secondIcon, "notif2")
         }
 
     @Test
@@ -590,7 +604,7 @@
             // THEN the "notif" chip keeps showing time
             val chip = latest!![0]
             assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
-            assertIsNotifChip(chip, icon, "notif")
+            assertIsNotifChip(chip, context, icon, "notif")
         }
 
     @Test
@@ -705,24 +719,41 @@
     companion object {
         fun assertIsNotifChip(
             latest: OngoingActivityChipModel?,
+            context: Context,
             expectedIcon: StatusBarIconView?,
-            notificationKey: String,
+            expectedNotificationKey: String,
+            expectedContentDescriptionSubstrings: List<String> = emptyList(),
         ) {
             val shown = latest as OngoingActivityChipModel.Shown
             if (StatusBarConnectedDisplays.isEnabled) {
                 assertThat(shown.icon)
-                    .isEqualTo(
-                        OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(notificationKey)
+                    .isInstanceOf(
+                        OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon::class.java
                     )
+                val icon = shown.icon as OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon
+
+                assertThat(icon.notificationKey).isEqualTo(expectedNotificationKey)
+                expectedContentDescriptionSubstrings.forEach {
+                    assertThat(icon.contentDescription.loadContentDescription(context)).contains(it)
+                }
             } else {
-                assertThat(latest.icon)
-                    .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(expectedIcon!!))
+                assertThat(shown.icon)
+                    .isInstanceOf(OngoingActivityChipModel.ChipIcon.StatusBarView::class.java)
+                val icon = shown.icon as OngoingActivityChipModel.ChipIcon.StatusBarView
+                assertThat(icon.impl).isEqualTo(expectedIcon!!)
+                expectedContentDescriptionSubstrings.forEach {
+                    assertThat(icon.contentDescription.loadContentDescription(context)).contains(it)
+                }
             }
         }
 
         fun assertIsNotifKey(latest: OngoingActivityChipModel?, expectedKey: String) {
-            assertThat((latest as OngoingActivityChipModel.Shown).icon)
-                .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(expectedKey))
+            assertThat(
+                    ((latest as OngoingActivityChipModel.Shown).icon
+                            as OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon)
+                        .notificationKey
+                )
+                .isEqualTo(expectedKey)
         }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
index 36fc5aa..5a66888 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
@@ -31,9 +31,10 @@
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.mediaprojection.data.model.MediaProjectionState
 import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
 import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
@@ -41,6 +42,7 @@
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.CAST_TO_OTHER_DEVICES_PACKAGE
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
+import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
 import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndGenericShareToAppDialogDelegate
 import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndShareScreenToAppDialogDelegate
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
@@ -51,6 +53,7 @@
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
 import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
+import com.android.systemui.testKosmos
 import com.android.systemui.util.time.fakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
@@ -58,6 +61,7 @@
 import org.junit.Before
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mockito.times
 import org.mockito.kotlin.any
 import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.eq
@@ -68,7 +72,7 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class ShareToAppChipViewModelTest : SysuiTestCase() {
-    private val kosmos = Kosmos().also { it.testCase = this }
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
     private val testScope = kosmos.testScope
     private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository
     private val systemClock = kosmos.fakeSystemClock
@@ -89,9 +93,11 @@
         mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
 
     private val underTest = kosmos.shareToAppChipViewModel
+    private val mockDialog = mock<SystemUIDialog>()
 
     @Before
     fun setUp() {
+        underTest.start()
         setUpPackageManagerForMediaProjection(kosmos)
 
         whenever(kosmos.mockSystemUIDialogFactory.create(any<EndShareScreenToAppDialogDelegate>()))
@@ -101,6 +107,196 @@
     }
 
     @Test
+    @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+    fun chip_flagEnabled_projectionStartedDuringCallAndActivePostCallEventEmitted_chipHidden() =
+        kosmos.runTest {
+            val latestChip by collectLastValue(underTest.chip)
+
+            // Set mediaProjectionState to Projecting
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+            // Verify the chip is initially shown
+            assertThat(latestChip).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+
+            fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+            // Verify the chip is hidden
+            assertThat(latestChip).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+        }
+
+    @Test
+    @DisableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+    fun chip_flagDisabled_projectionStartedDuringCallAndActivePostCallEventEmitted_chipRemainsVisible() =
+        kosmos.runTest {
+            val latestChip by collectLastValue(underTest.chip)
+
+            // Set mediaProjectionState to Projecting
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+            // Verify the chip is initially shown
+            assertThat(latestChip).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+
+            fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+            // Chip is still shown
+            assertThat(latestChip).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+        }
+
+    @Test
+    @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+    fun stopDialog_flagEnabled_initialState_isHidden() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.stopDialogToShow)
+
+            assertThat(latest).isEqualTo(MediaProjectionStopDialogModel.Hidden)
+        }
+
+    @Test
+    @DisableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+    fun stopDialog_flagDisabled_projectionStartedDuringCallAndActivePostCallEventEmitted_dialogRemainsHidden() =
+        kosmos.runTest {
+            val latestStopDialogModel by collectLastValue(underTest.stopDialogToShow)
+
+            // Set mediaProjectionRepo state to Projecting
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+            fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+            // Verify that no dialog is shown
+            assertThat(latestStopDialogModel).isEqualTo(MediaProjectionStopDialogModel.Hidden)
+        }
+
+    @Test
+    @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+    fun stopDialog_notProjectingState_flagEnabled_remainsHidden() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.stopDialogToShow)
+
+            // Set the state to not projecting
+            mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting
+
+            fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+            // Verify that the dialog remains hidden
+            assertThat(latest).isEqualTo(MediaProjectionStopDialogModel.Hidden)
+        }
+
+    @Test
+    @EnableFlags(
+        com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END,
+        FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP,
+    )
+    fun stopDialog_projectingAudio_flagEnabled_eventEmitted_showsGenericStopDialog() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.stopDialogToShow)
+
+            // Set the state to projecting audio
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.NoScreen(NORMAL_PACKAGE)
+
+            fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+            // Verify that the generic dialog is shown
+            assertThat(latest).isInstanceOf(MediaProjectionStopDialogModel.Shown::class.java)
+            val dialogDelegate = (latest as MediaProjectionStopDialogModel.Shown).dialogDelegate
+            assertThat(dialogDelegate).isInstanceOf(EndGenericShareToAppDialogDelegate::class.java)
+        }
+
+    @Test
+    @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+    fun stopDialog_projectingEntireScreen_flagEnabled_eventEmitted_showsShareScreenToAppStopDialog() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.stopDialogToShow)
+
+            // Set the state to projecting the entire screen
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+            assertThat(latest).isInstanceOf(MediaProjectionStopDialogModel.Hidden::class.java)
+
+            fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+            // Verify that the dialog is shown
+            assertThat(latest).isInstanceOf(MediaProjectionStopDialogModel.Shown::class.java)
+            val dialogDelegate = (latest as MediaProjectionStopDialogModel.Shown).dialogDelegate
+            assertThat(dialogDelegate).isInstanceOf(EndShareScreenToAppDialogDelegate::class.java)
+        }
+
+    @Test
+    @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+    fun stopDialog_projectingEntireScreen_eventEmitted_hasCancelBehaviour() =
+        kosmos.runTest {
+            val latestDialogModel by collectLastValue(underTest.stopDialogToShow)
+
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+            fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+            // Verify that the dialog is shown
+            assertThat(latestDialogModel)
+                .isInstanceOf(MediaProjectionStopDialogModel.Shown::class.java)
+
+            val dialogModel = latestDialogModel as MediaProjectionStopDialogModel.Shown
+
+            whenever(dialogModel.dialogDelegate.createDialog()).thenReturn(mockDialog)
+
+            dialogModel.createAndShowDialog()
+
+            // Verify dialog is shown
+            verify(mockDialog).show()
+
+            // Verify dialog is hidden when dialog is cancelled
+            argumentCaptor<DialogInterface.OnCancelListener>().apply {
+                verify(mockDialog).setOnCancelListener(capture())
+                firstValue.onCancel(mockDialog)
+            }
+            assertThat(underTest.stopDialogToShow.value)
+                .isEqualTo(MediaProjectionStopDialogModel.Hidden)
+
+            verify(mockDialog, times(1)).setOnCancelListener(any())
+        }
+
+    @Test
+    @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+    fun stopDialog_projectingEntireScreen_eventEmitted_hasDismissBehaviour() =
+        kosmos.runTest {
+            val latestDialogModel by collectLastValue(underTest.stopDialogToShow)
+
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+            fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+            // Verify that the dialog is shown
+            assertThat(latestDialogModel)
+                .isInstanceOf(MediaProjectionStopDialogModel.Shown::class.java)
+
+            val dialogModel = latestDialogModel as MediaProjectionStopDialogModel.Shown
+
+            whenever(dialogModel.dialogDelegate.createDialog()).thenReturn(mockDialog)
+
+            // Simulate showing the dialog
+            dialogModel.createAndShowDialog()
+
+            // Verify dialog is shown
+            verify(mockDialog).show()
+
+            // Verify dialog is hidden when dialog is dismissed
+            argumentCaptor<DialogInterface.OnDismissListener>().apply {
+                verify(mockDialog).setOnDismissListener(capture())
+                firstValue.onDismiss(mockDialog)
+            }
+            assertThat(underTest.stopDialogToShow.value)
+                .isEqualTo(MediaProjectionStopDialogModel.Hidden)
+
+            verify(mockDialog, times(1)).setOnDismissListener(any())
+        }
+
+    @Test
     fun chip_notProjectingState_isHidden() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index a4b6a84..7a33cbe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -289,7 +289,11 @@
     fun primaryChip_screenRecordStoppedViaDialog_chipHiddenWithoutAnimation() =
         testScope.runTest {
             screenRecordState.value = ScreenRecordModel.Recording
-            mediaProjectionState.value = MediaProjectionState.NotProjecting
+            mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen(
+                    NORMAL_PACKAGE,
+                    hostDeviceName = "Recording Display",
+                )
             callRepo.setOngoingCallState(OngoingCallModel.NoCall)
 
             val latest by collectLastValue(underTest.primaryChip)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
index 28f3601..78103a9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
@@ -311,7 +311,7 @@
                 )
             )
 
-            assertIsNotifChip(latest!!.primary, icon, "notif")
+            assertIsNotifChip(latest!!.primary, context, icon, "notif")
             assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
         }
 
@@ -339,8 +339,8 @@
                 )
             )
 
-            assertIsNotifChip(latest!!.primary, firstIcon, "firstNotif")
-            assertIsNotifChip(latest!!.secondary, secondIcon, "secondNotif")
+            assertIsNotifChip(latest!!.primary, context, firstIcon, "firstNotif")
+            assertIsNotifChip(latest!!.secondary, context, secondIcon, "secondNotif")
         }
 
     @Test
@@ -374,8 +374,8 @@
                 )
             )
 
-            assertIsNotifChip(latest!!.primary, firstIcon, "firstNotif")
-            assertIsNotifChip(latest!!.secondary, secondIcon, "secondNotif")
+            assertIsNotifChip(latest!!.primary, context, firstIcon, "firstNotif")
+            assertIsNotifChip(latest!!.secondary, context, secondIcon, "secondNotif")
         }
 
     @Test
@@ -407,7 +407,7 @@
             )
 
             assertIsCallChip(latest!!.primary, callNotificationKey)
-            assertIsNotifChip(latest!!.secondary, firstIcon, "firstNotif")
+            assertIsNotifChip(latest!!.secondary, context, firstIcon, "firstNotif")
         }
 
     @Test
@@ -456,7 +456,7 @@
 
             val latest by collectLastValue(underTest.primaryChip)
 
-            assertIsNotifChip(latest, notifIcon, "notif")
+            assertIsNotifChip(latest, context, notifIcon, "notif")
 
             // WHEN the higher priority call chip is added
             callRepo.setOngoingCallState(
@@ -527,7 +527,7 @@
             callRepo.setOngoingCallState(OngoingCallModel.NoCall)
 
             // THEN the lower priority notif is used
-            assertIsNotifChip(latest, notifIcon, "notif")
+            assertIsNotifChip(latest, context, notifIcon, "notif")
         }
 
     @Test
@@ -552,7 +552,7 @@
 
             val latest by collectLastValue(underTest.chips)
 
-            assertIsNotifChip(latest!!.primary, notifIcon, "notif")
+            assertIsNotifChip(latest!!.primary, context, notifIcon, "notif")
             assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
 
             // WHEN the higher priority call chip is added
@@ -563,7 +563,7 @@
             // THEN the higher priority call chip is used as primary and notif is demoted to
             // secondary
             assertIsCallChip(latest!!.primary, callNotificationKey)
-            assertIsNotifChip(latest!!.secondary, notifIcon, "notif")
+            assertIsNotifChip(latest!!.secondary, context, notifIcon, "notif")
 
             // WHEN the higher priority media projection chip is added
             mediaProjectionState.value =
@@ -590,13 +590,13 @@
 
             // THEN media projection and notif remain
             assertIsShareToAppChip(latest!!.primary)
-            assertIsNotifChip(latest!!.secondary, notifIcon, "notif")
+            assertIsNotifChip(latest!!.secondary, context, notifIcon, "notif")
 
             // WHEN media projection is dropped
             mediaProjectionState.value = MediaProjectionState.NotProjecting
 
             // THEN notif is promoted to primary
-            assertIsNotifChip(latest!!.primary, notifIcon, "notif")
+            assertIsNotifChip(latest!!.primary, context, notifIcon, "notif")
             assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
index c06da4b..dc65a9e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
@@ -18,6 +18,7 @@
 
 import android.platform.test.annotations.EnableFlags
 import android.view.Display
+import android.view.mockIWindowManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -31,11 +32,13 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.never
 import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -51,75 +54,110 @@
     private val fakeInitializerStore = kosmos.fakeStatusBarInitializerStore
     private val fakePrivacyDotStore = kosmos.fakePrivacyDotWindowControllerStore
     private val fakeLightBarStore = kosmos.fakeLightBarControllerStore
+    private val windowManager = kosmos.mockIWindowManager
+
     // Lazy, so that @EnableFlags is set before initializer is instantiated.
     private val underTest by lazy { kosmos.multiDisplayStatusBarStarter }
 
+    @Before
+    fun setup() {
+        whenever(windowManager.shouldShowSystemDecors(Display.DEFAULT_DISPLAY)).thenReturn(true)
+        whenever(windowManager.shouldShowSystemDecors(DISPLAY_1)).thenReturn(true)
+        whenever(windowManager.shouldShowSystemDecors(DISPLAY_2)).thenReturn(true)
+        whenever(windowManager.shouldShowSystemDecors(DISPLAY_3)).thenReturn(true)
+        whenever(windowManager.shouldShowSystemDecors(DISPLAY_4_NO_SYSTEM_DECOR)).thenReturn(false)
+    }
+
     @Test
     fun start_startsInitializersForCurrentDisplays() =
         testScope.runTest {
-            fakeDisplayRepository.addDisplay(displayId = 1)
-            fakeDisplayRepository.addDisplay(displayId = 2)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
 
             underTest.start()
             runCurrent()
 
             expect
-                .that(fakeInitializerStore.forDisplay(displayId = 1).startedByCoreStartable)
+                .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_1).startedByCoreStartable)
                 .isTrue()
             expect
-                .that(fakeInitializerStore.forDisplay(displayId = 2).startedByCoreStartable)
+                .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_2).startedByCoreStartable)
                 .isTrue()
+            expect
+                .that(
+                    fakeInitializerStore
+                        .forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
+                        .startedByCoreStartable
+                )
+                .isFalse()
         }
 
     @Test
     fun start_startsOrchestratorForCurrentDisplays() =
         testScope.runTest {
-            fakeDisplayRepository.addDisplay(displayId = 1)
-            fakeDisplayRepository.addDisplay(displayId = 2)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
 
             underTest.start()
             runCurrent()
 
-            verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 1)!!).start()
-            verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 2)!!).start()
+            verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_1)!!)
+                .start()
+            verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_2)!!)
+                .start()
+            assertThat(
+                    fakeOrchestratorFactory.createdOrchestratorForDisplay(
+                        displayId = DISPLAY_4_NO_SYSTEM_DECOR
+                    )
+                )
+                .isNull()
         }
 
     @Test
     fun start_startsPrivacyDotForCurrentDisplays() =
         testScope.runTest {
-            fakeDisplayRepository.addDisplay(displayId = 1)
-            fakeDisplayRepository.addDisplay(displayId = 2)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
 
             underTest.start()
             runCurrent()
 
-            verify(fakePrivacyDotStore.forDisplay(displayId = 1)).start()
-            verify(fakePrivacyDotStore.forDisplay(displayId = 2)).start()
+            verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_1)).start()
+            verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_2)).start()
+            verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
+                .start()
         }
 
     @Test
     fun start_doesNotStartLightBarControllerForCurrentDisplays() =
         testScope.runTest {
-            fakeDisplayRepository.addDisplay(displayId = 1)
-            fakeDisplayRepository.addDisplay(displayId = 2)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
 
             underTest.start()
             runCurrent()
 
-            verify(fakeLightBarStore.forDisplay(displayId = 1), never()).start()
-            verify(fakeLightBarStore.forDisplay(displayId = 2), never()).start()
+            verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_1), never()).start()
+            verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_2), never()).start()
+            verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
+                .start()
         }
 
     @Test
     fun start_createsLightBarControllerForCurrentDisplays() =
         testScope.runTest {
-            fakeDisplayRepository.addDisplay(displayId = 1)
-            fakeDisplayRepository.addDisplay(displayId = 2)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
 
             underTest.start()
             runCurrent()
 
-            assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(1, 2)
+            assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(1, DISPLAY_2)
         }
 
     @Test
@@ -135,121 +173,174 @@
         }
 
     @Test
-    fun displayAdded_orchestratorForNewDisplayIsStarted() =
+    fun displayAdded_orchestratorForNewDisplay() =
         testScope.runTest {
             underTest.start()
             runCurrent()
 
-            fakeDisplayRepository.addDisplay(displayId = 3)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
             runCurrent()
 
-            verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 3)!!).start()
+            verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_3)!!)
+                .start()
+            assertThat(
+                    fakeOrchestratorFactory.createdOrchestratorForDisplay(
+                        displayId = DISPLAY_4_NO_SYSTEM_DECOR
+                    )
+                )
+                .isNull()
         }
 
     @Test
-    fun displayAdded_initializerForNewDisplayIsStarted() =
+    fun displayAdded_initializerForNewDisplay() =
         testScope.runTest {
             underTest.start()
             runCurrent()
 
-            fakeDisplayRepository.addDisplay(displayId = 3)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
             runCurrent()
 
             expect
-                .that(fakeInitializerStore.forDisplay(displayId = 3).startedByCoreStartable)
+                .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_3).startedByCoreStartable)
                 .isTrue()
+            expect
+                .that(
+                    fakeInitializerStore
+                        .forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
+                        .startedByCoreStartable
+                )
+                .isFalse()
         }
 
     @Test
-    fun displayAdded_privacyDotForNewDisplayIsStarted() =
+    fun displayAdded_privacyDotForNewDisplay() =
         testScope.runTest {
             underTest.start()
             runCurrent()
 
-            fakeDisplayRepository.addDisplay(displayId = 3)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
             runCurrent()
 
-            verify(fakePrivacyDotStore.forDisplay(displayId = 3)).start()
+            verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_3)).start()
+            verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
+                .start()
         }
 
     @Test
-    fun displayAdded_lightBarForNewDisplayIsCreated() =
+    fun displayAdded_lightBarForNewDisplayCreate() =
         testScope.runTest {
             underTest.start()
             runCurrent()
 
-            fakeDisplayRepository.addDisplay(displayId = 3)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
             runCurrent()
 
-            assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(3)
+            assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(DISPLAY_3)
         }
 
     @Test
-    fun displayAdded_lightBarForNewDisplayIsNotStarted() =
+    fun displayAdded_lightBarForNewDisplayStart() =
         testScope.runTest {
             underTest.start()
             runCurrent()
 
-            fakeDisplayRepository.addDisplay(displayId = 3)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
             runCurrent()
 
-            verify(fakeLightBarStore.forDisplay(displayId = 3), never()).start()
+            verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_3), never()).start()
+            verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
+                .start()
         }
 
     @Test
-    fun displayAddedDuringStart_initializerForNewDisplayIsStarted() =
+    fun displayAddedDuringStart_initializerForNewDisplay() =
         testScope.runTest {
             underTest.start()
 
-            fakeDisplayRepository.addDisplay(displayId = 3)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
             runCurrent()
 
             expect
-                .that(fakeInitializerStore.forDisplay(displayId = 3).startedByCoreStartable)
+                .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_3).startedByCoreStartable)
                 .isTrue()
+            expect
+                .that(
+                    fakeInitializerStore
+                        .forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
+                        .startedByCoreStartable
+                )
+                .isFalse()
         }
 
     @Test
-    fun displayAddedDuringStart_orchestratorForNewDisplayIsStarted() =
+    fun displayAddedDuringStart_orchestratorForNewDisplay() =
         testScope.runTest {
             underTest.start()
 
-            fakeDisplayRepository.addDisplay(displayId = 3)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
             runCurrent()
 
-            verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 3)!!).start()
+            verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_3)!!)
+                .start()
+            assertThat(
+                    fakeOrchestratorFactory.createdOrchestratorForDisplay(
+                        displayId = DISPLAY_4_NO_SYSTEM_DECOR
+                    )
+                )
+                .isNull()
         }
 
     @Test
-    fun displayAddedDuringStart_privacyDotForNewDisplayIsStarted() =
+    fun displayAddedDuringStart_privacyDotForNewDisplay() =
         testScope.runTest {
             underTest.start()
 
-            fakeDisplayRepository.addDisplay(displayId = 3)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
             runCurrent()
 
-            verify(fakePrivacyDotStore.forDisplay(displayId = 3)).start()
+            verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_3)).start()
+            verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
+                .start()
         }
 
     @Test
-    fun displayAddedDuringStart_lightBarForNewDisplayIsCreated() =
+    fun displayAddedDuringStart_lightBarForNewDisplayCreate() =
         testScope.runTest {
             underTest.start()
 
-            fakeDisplayRepository.addDisplay(displayId = 3)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
             runCurrent()
 
-            assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(3)
+            assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(DISPLAY_3)
         }
 
     @Test
-    fun displayAddedDuringStart_lightBarForNewDisplayIsNotStarted() =
+    fun displayAddedDuringStart_lightBarForNewDisplayStart() =
         testScope.runTest {
             underTest.start()
 
-            fakeDisplayRepository.addDisplay(displayId = 3)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+            fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
             runCurrent()
 
-            verify(fakeLightBarStore.forDisplay(displayId = 3), never()).start()
+            verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_3), never()).start()
+            verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
+                .start()
         }
+
+    companion object {
+        const val DISPLAY_1 = 1
+        const val DISPLAY_2 = 2
+        const val DISPLAY_3 = 3
+        const val DISPLAY_4_NO_SYSTEM_DECOR = 4
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
index 5d9aa71..35b19c1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
@@ -47,7 +47,7 @@
     private val notifsRepository = kosmos.activeNotificationListRepository
     private val notifsInteractor = kosmos.activeNotificationsInteractor
     private val underTest =
-        RenderNotificationListInteractor(notifsRepository, sectionStyleProvider = mock())
+        RenderNotificationListInteractor(notifsRepository, sectionStyleProvider = mock(), context)
 
     @Test
     fun setRenderedList_preservesOrdering() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index a1c910d..0223484 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -20,6 +20,7 @@
 import android.graphics.Rect
 import android.view.View
 import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
 import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
@@ -46,6 +47,9 @@
 
     override val statusBarPopupChips = MutableStateFlow(emptyList<PopupChipModel.Shown>())
 
+    override val mediaProjectionStopDialogDueToCallEndedState =
+        MutableStateFlow(MediaProjectionStopDialogModel.Hidden)
+
     override val isHomeStatusBarAllowedByScene = MutableStateFlow(false)
 
     override val shouldHomeStatusBarBeVisible = MutableStateFlow(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
index e74d009..46f625f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
@@ -45,8 +45,8 @@
 import com.android.systemui.kosmos.collectLastValue
 import com.android.systemui.kosmos.collectValues
 import com.android.systemui.kosmos.runTest
-import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.log.assertLogsWtf
 import com.android.systemui.mediaprojection.data.model.MediaProjectionState
 import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
@@ -58,7 +58,9 @@
 import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
+import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsScreenRecordChip
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsShareToAppChip
@@ -89,7 +91,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -98,9 +100,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class HomeStatusBarViewModelImplTest : SysuiTestCase() {
-    private val kosmos by lazy {
-        testKosmos().also { it.testDispatcher = UnconfinedTestDispatcher() }
-    }
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
     private val Kosmos.underTest by Kosmos.Fixture { kosmos.homeStatusBarViewModel }
 
     @Before
@@ -112,6 +112,55 @@
     fun addDisplays() = runBlocking { kosmos.displayRepository.fake.addDisplay(DEFAULT_DISPLAY) }
 
     @Test
+    @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+    fun mediaProjectionStopDialogDueToCallEndedState_initiallyHidden() =
+        kosmos.runTest {
+            shareToAppChipViewModel.start()
+            val latest by collectLastValue(underTest.mediaProjectionStopDialogDueToCallEndedState)
+
+            // Verify that the stop dialog is initially hidden
+            assertThat(latest).isInstanceOf(MediaProjectionStopDialogModel.Hidden::class.java)
+        }
+
+    @Test
+    @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+    fun mediaProjectionStopDialogDueToCallEndedState_flagEnabled_mediaIsProjecting_projectionStartedDuringCallAndActivePostCallEventEmitted_isShown() =
+        kosmos.runTest {
+            shareToAppChipViewModel.start()
+
+            val latest by
+                collectLastValue(
+                    homeStatusBarViewModel.mediaProjectionStopDialogDueToCallEndedState
+                )
+
+            fakeMediaProjectionRepository.mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+            fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+            assertThat(latest).isInstanceOf(MediaProjectionStopDialogModel.Shown::class.java)
+        }
+
+    @Test
+    @DisableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+    fun mediaProjectionStopDialogDueToCallEndedState_flagDisabled_mediaIsProjecting_projectionStartedDuringCallAndActivePostCallEventEmitted_isHidden() =
+        kosmos.runTest {
+            shareToAppChipViewModel.start()
+
+            val latest by
+                collectLastValue(
+                    homeStatusBarViewModel.mediaProjectionStopDialogDueToCallEndedState
+                )
+
+            fakeMediaProjectionRepository.mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+            fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+            assertThat(latest).isInstanceOf(MediaProjectionStopDialogModel.Hidden::class.java)
+        }
+
+    @Test
     fun isTransitioningFromLockscreenToOccluded_started_isTrue() =
         kosmos.runTest {
             val latest by collectLastValue(underTest.isTransitioningFromLockscreenToOccluded)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
index 115edd0..2b16c00 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
@@ -25,16 +25,15 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.R
-import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardClockRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.res.R as SysUIR
 import com.android.systemui.shared.Flags as SharedFlags
 import com.android.systemui.user.data.model.SelectedUserModel
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.wallpapers.data.repository.WallpaperRepositoryImpl.Companion.MAGIC_PORTRAIT_CLASSNAME
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
@@ -72,9 +71,12 @@
         )
     }
 
+    lateinit var focalAreaTarget: String
+
     @Before
     fun setUp() {
         whenever(wallpaperManager.isWallpaperSupported).thenReturn(true)
+        focalAreaTarget = context.resources.getString(SysUIR.string.focal_area_target)
     }
 
     @Test
@@ -248,17 +250,17 @@
         }
 
     @Test
-    @EnableFlags(Flags.FLAG_MAGIC_PORTRAIT_WALLPAPERS)
-    fun shouldSendNotificationLayout_setMagicPortraitWallpaper_launchSendLayoutJob() =
+    @EnableFlags(SharedFlags.FLAG_EXTENDED_WALLPAPER_EFFECTS)
+    fun shouldSendNotificationLayout_setExtendedEffectsWallpaper_launchSendLayoutJob() =
         testScope.runTest {
             val latest by collectLastValue(underTest.shouldSendFocalArea)
-            val magicPortraitWallpaper =
+            val extedendEffectsWallpaper =
                 mock<WallpaperInfo>().apply {
-                    whenever(this.component)
-                        .thenReturn(ComponentName(context, MAGIC_PORTRAIT_CLASSNAME))
+                    whenever(this.component).thenReturn(ComponentName(context, focalAreaTarget))
                 }
+
             whenever(wallpaperManager.getWallpaperInfoForUser(any()))
-                .thenReturn(magicPortraitWallpaper)
+                .thenReturn(extedendEffectsWallpaper)
             fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
                 context,
                 Intent(Intent.ACTION_WALLPAPER_CHANGED),
@@ -269,13 +271,16 @@
         }
 
     @Test
-    @EnableFlags(Flags.FLAG_MAGIC_PORTRAIT_WALLPAPERS)
-    fun shouldSendNotificationLayout_setNotMagicPortraitWallpaper_cancelSendLayoutJob() =
+    @EnableFlags(SharedFlags.FLAG_EXTENDED_WALLPAPER_EFFECTS)
+    fun shouldSendNotificationLayout_setNotExtendedEffectsWallpaper_cancelSendLayoutJob() =
         testScope.runTest {
             val latest by collectLastValue(underTest.shouldSendFocalArea)
-            val magicPortraitWallpaper = MAGIC_PORTRAIT_WP
+            val extendedEffectsWallpaper =
+                mock<WallpaperInfo>().apply {
+                    whenever(this.component).thenReturn(ComponentName("", focalAreaTarget))
+                }
             whenever(wallpaperManager.getWallpaperInfoForUser(any()))
-                .thenReturn(magicPortraitWallpaper)
+                .thenReturn(extendedEffectsWallpaper)
             fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
                 context,
                 Intent(Intent.ACTION_WALLPAPER_CHANGED),
@@ -284,9 +289,7 @@
             assertThat(underTest.sendLockscreenLayoutJob).isNotNull()
             assertThat(underTest.sendLockscreenLayoutJob!!.isActive).isEqualTo(true)
 
-            val nonMagicPortraitWallpaper = UNSUPPORTED_WP
-            whenever(wallpaperManager.getWallpaperInfoForUser(any()))
-                .thenReturn(nonMagicPortraitWallpaper)
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP)
             fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
                 context,
                 Intent(Intent.ACTION_WALLPAPER_CHANGED),
@@ -303,10 +306,5 @@
         val USER_WITH_SUPPORTED_WP = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0)
         val SUPPORTED_WP =
             mock<WallpaperInfo>().apply { whenever(this.supportsAmbientMode()).thenReturn(true) }
-
-        val MAGIC_PORTRAIT_WP =
-            mock<WallpaperInfo>().apply {
-                whenever(this.component).thenReturn(ComponentName("", MAGIC_PORTRAIT_CLASSNAME))
-            }
     }
 }
diff --git a/packages/SystemUI/res/drawable/media_output_item_expand_group.xml b/packages/SystemUI/res/drawable/media_output_item_expand_group.xml
new file mode 100644
index 0000000..833843d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_output_item_expand_group.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2025 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,15.4 L6,9.4l1.4,-1.4 4.6,4.6 4.6,-4.6 1.4,1.4 -6,6Z" />
+</vector>
diff --git a/packages/SystemUI/res/layout/notification_2025_hybrid.xml b/packages/SystemUI/res/layout/notification_2025_hybrid.xml
index 8c34cd4..8fd10fb 100644
--- a/packages/SystemUI/res/layout/notification_2025_hybrid.xml
+++ b/packages/SystemUI/res/layout/notification_2025_hybrid.xml
@@ -29,6 +29,7 @@
         android:layout_height="wrap_content"
         android:singleLine="true"
         android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
+        android:textSize="@*android:dimen/notification_2025_title_text_size"
         android:paddingEnd="4dp"
     />
     <TextView
diff --git a/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml b/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
index a338e4c..35f2ef9 100644
--- a/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
+++ b/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
@@ -54,6 +54,7 @@
         android:singleLine="true"
         android:paddingEnd="4dp"
         android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
+        android:textSize="@*android:dimen/notification_2025_title_text_size"
     />
 
     <TextView
diff --git a/packages/SystemUI/res/values-xlarge-land/config.xml b/packages/SystemUI/res/values-xlarge-land/config.xml
index 6d8b64a..4c77f30 100644
--- a/packages/SystemUI/res/values-xlarge-land/config.xml
+++ b/packages/SystemUI/res/values-xlarge-land/config.xml
@@ -16,5 +16,5 @@
 
 <resources>
     <item name="shortcut_helper_screen_width_fraction"  format="float" type="dimen">0.8</item>
-    <bool name="center_align_magic_portrait_shape">true</bool>
+    <bool name="center_align_focal_area_shape">true</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 68e33f2..9b8926e 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -1107,7 +1107,7 @@
     <!-- The dream component used when the device is low light environment. -->
     <string translatable="false" name="config_lowLightDreamComponent"/>
 
-    <!--Whether we should position magic portrait shape effects in the center of lockscreen
-    it's false by default, and only be true in tablet landscape -->
-    <bool name="center_align_magic_portrait_shape">false</bool>
+    <!-- Configuration for wallpaper focal area -->
+    <bool name="center_align_focal_area_shape">false</bool>
+    <string name="focal_area_target" translatable="false" />
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 84c859c..7b2e812 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -588,6 +588,12 @@
     <!-- Content description of the cast label showing what we are connected to. [CHAR LIMIT=NONE] -->
     <string name="accessibility_cast_name">Connected to <xliff:g id="cast" example="TV">%s</xliff:g>.</string>
 
+    <!-- Content description of the button to expand the group of devices. [CHAR LIMIT=NONE] -->
+    <string name="accessibility_expand_group">Expand group.</string>
+
+    <!-- Content description of the button to open the application . [CHAR LIMIT=NONE] -->
+    <string name="accessibility_open_application">Open application.</string>
+
     <!-- Content description of an item with no signal and no connection for accessibility (not shown on the screen) [CHAR LIMIT=NONE] -->
     <string name="accessibility_not_connected">Not connected.</string>
     <!-- Content description of the roaming data connection type. [CHAR LIMIT=NONE] -->
@@ -3395,6 +3401,8 @@
 
     <!-- Content description for a chip in the status bar showing that the user is currently on a call. [CHAR LIMIT=NONE] -->
     <string name="ongoing_call_content_description">Ongoing call</string>
+    <!-- Content description for a chip in the status bar showing that the user currently has an ongoing activity. [CHAR LIMIT=NONE]-->
+    <string name="ongoing_notification_extra_content_description">Ongoing</string>
 
     <!-- Provider Model: Default title of the mobile network in the mobile layout. [CHAR LIMIT=50] -->
     <string name="mobile_data_settings_title">Mobile data</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
index b43ffc5..10b9303 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
@@ -146,9 +146,9 @@
     void onUnbind(IRemoteCallback reply) = 35;
 
     /**
-     * Sent when {@link TaskbarDelegate#onDisplayReady} is called.
+     * Sent when {@link TaskbarDelegate#onDisplayAddSystemDecorations} is called.
      */
-    void onDisplayReady(int displayId) = 36;
+    void onDisplayAddSystemDecorations(int displayId) = 36;
 
     /**
      * Sent when {@link TaskbarDelegate#onDisplayRemoved} is called.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index b4e6e93..237a19c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -186,28 +186,32 @@
      * Whether communal hub should be shown automatically, depending on the user's [WhenToDream]
      * state.
      */
-    val shouldShowCommunal: Flow<Boolean> =
+    val shouldShowCommunal: StateFlow<Boolean> =
         allOf(
-            isCommunalAvailable,
-            communalSettingsInteractor.whenToDream
-                .flatMapLatest { whenToDream ->
-                    when (whenToDream) {
-                        WhenToDream.NEVER -> flowOf(false)
+                isCommunalAvailable,
+                communalSettingsInteractor.whenToDream
+                    .flatMapLatest { whenToDream ->
+                        when (whenToDream) {
+                            WhenToDream.NEVER -> flowOf(false)
 
-                        WhenToDream.WHILE_CHARGING -> batteryInteractor.isDevicePluggedIn
+                            WhenToDream.WHILE_CHARGING -> batteryInteractor.isDevicePluggedIn
 
-                        WhenToDream.WHILE_DOCKED ->
-                            allOf(
-                                batteryInteractor.isDevicePluggedIn,
-                                dockManager.retrieveIsDocked(),
-                            )
+                            WhenToDream.WHILE_DOCKED ->
+                                allOf(
+                                    batteryInteractor.isDevicePluggedIn,
+                                    dockManager.retrieveIsDocked(),
+                                )
 
-                        WhenToDream.WHILE_POSTURED ->
-                            allOf(batteryInteractor.isDevicePluggedIn, posturingInteractor.postured)
+                            WhenToDream.WHILE_POSTURED ->
+                                allOf(
+                                    batteryInteractor.isDevicePluggedIn,
+                                    posturingInteractor.postured,
+                                )
+                        }
                     }
-                }
-                .flowOn(bgDispatcher),
-        )
+                    .flowOn(bgDispatcher),
+            )
+            .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)
 
     private val _isDisclaimerDismissed = MutableStateFlow(false)
     val isDisclaimerDismissed: Flow<Boolean> = _isDisclaimerDismissed.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalLockIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalLockIconViewBinder.kt
new file mode 100644
index 0000000..b1407da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalLockIconViewBinder.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2023 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.communal.ui.binder
+
+import android.annotation.SuppressLint
+import android.content.res.ColorStateList
+import android.util.Log
+import android.util.StateSet
+import android.view.HapticFeedbackConstants
+import android.view.View
+import androidx.core.view.isInvisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.common.ui.view.LongPressHandlingView
+import com.android.systemui.communal.ui.viewmodel.CommunalLockIconViewModel
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.kotlin.DisposableHandles
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
+
+object CommunalLockIconViewBinder {
+    private const val TAG = "CommunalLockIconViewBinder"
+
+    /**
+     * Updates UI for:
+     * - device entry containing view (parent view for the below views)
+     *     - long-press handling view (transparent, no UI)
+     *     - foreground icon view (lock/unlock)
+     */
+    @SuppressLint("ClickableViewAccessibility")
+    @JvmStatic
+    fun bind(
+        applicationScope: CoroutineScope,
+        view: DeviceEntryIconView,
+        viewModel: CommunalLockIconViewModel,
+        falsingManager: FalsingManager,
+        vibratorHelper: VibratorHelper,
+    ): DisposableHandle {
+        val disposables = DisposableHandles()
+        val longPressHandlingView = view.longPressHandlingView
+        val fgIconView = view.iconView
+        val bgView = view.bgView
+        longPressHandlingView.listener =
+            object : LongPressHandlingView.Listener {
+                override fun onLongPressDetected(
+                    view: View,
+                    x: Int,
+                    y: Int,
+                    isA11yAction: Boolean,
+                ) {
+                    if (
+                        !isA11yAction && falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)
+                    ) {
+                        Log.d(
+                            TAG,
+                            "Long press rejected because it is not a11yAction " +
+                                "and it is a falseLongTap",
+                        )
+                        return
+                    }
+                    vibratorHelper.performHapticFeedback(view, HapticFeedbackConstants.CONFIRM)
+                    applicationScope.launch {
+                        view.clearFocus()
+                        view.clearAccessibilityFocus()
+                        viewModel.onUserInteraction()
+                    }
+                }
+            }
+
+        longPressHandlingView.isInvisible = false
+        view.isClickable = true
+        longPressHandlingView.longPressDuration = {
+            view.resources.getInteger(R.integer.config_lockIconLongPress).toLong()
+        }
+        bgView.visibility = View.GONE
+
+        disposables +=
+            view.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    launch("$TAG#viewModel.isLongPressEnabled") {
+                        viewModel.isLongPressEnabled.collect { isEnabled ->
+                            longPressHandlingView.setLongPressHandlingEnabled(isEnabled)
+                        }
+                    }
+                    launch("$TAG#viewModel.accessibilityDelegateHint") {
+                        viewModel.accessibilityDelegateHint.collect { hint ->
+                            view.accessibilityHintType = hint
+                            if (hint != DeviceEntryIconView.AccessibilityHintType.NONE) {
+                                view.setOnClickListener {
+                                    vibratorHelper.performHapticFeedback(
+                                        view,
+                                        HapticFeedbackConstants.CONFIRM,
+                                    )
+                                    applicationScope.launch {
+                                        view.clearFocus()
+                                        view.clearAccessibilityFocus()
+                                        viewModel.onUserInteraction()
+                                    }
+                                }
+                            } else {
+                                view.setOnClickListener(null)
+                            }
+                        }
+                    }
+                }
+            }
+
+        disposables +=
+            fgIconView.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                    // Start with an empty state
+                    fgIconView.setImageState(StateSet.NOTHING, /* merge */ false)
+                    launch("$TAG#fpIconView.viewModel") {
+                        viewModel.viewAttributes.collect { attributes ->
+                            if (attributes.type.contentDescriptionResId != -1) {
+                                fgIconView.contentDescription =
+                                    fgIconView.resources.getString(
+                                        attributes.type.contentDescriptionResId
+                                    )
+                            }
+                            fgIconView.imageTintList = ColorStateList.valueOf(attributes.tint)
+                            fgIconView.setPadding(
+                                attributes.padding,
+                                attributes.padding,
+                                attributes.padding,
+                                attributes.padding,
+                            )
+                            // Set image state at the end after updating other view state. This
+                            // method forces the ImageView to recompute the bounds of the drawable.
+                            fgIconView.setImageState(
+                                view.getIconState(attributes.type, false),
+                                /* merge */ false,
+                            )
+                            // Invalidate, just in case the padding changes just after icon changes
+                            fgIconView.invalidate()
+                        }
+                    }
+                }
+            }
+        return disposables
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalLockIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalLockIconViewModel.kt
new file mode 100644
index 0000000..19eeabd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalLockIconViewModel.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2025 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.communal.ui.viewmodel
+
+import android.content.Context
+import com.android.keyguard.KeyguardViewController
+import com.android.settingslib.Utils
+import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntrySourceInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import com.android.systemui.keyguard.ui.viewmodel.toAccessibilityHintType
+import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.ShadeDisplayAware
+import com.android.systemui.util.kotlin.emitOnStart
+import dagger.Lazy
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/**
+ * Simpler implementation of [DeviceEntryIconViewModel] for use in glanceable hub, where fingerprint
+ * is not supported.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class CommunalLockIconViewModel
+@Inject
+constructor(
+    @ShadeDisplayAware val context: Context,
+    @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
+    private val deviceEntryInteractor: DeviceEntryInteractor,
+    keyguardInteractor: KeyguardInteractor,
+    private val keyguardViewController: Lazy<KeyguardViewController>,
+    private val deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
+    accessibilityInteractor: AccessibilityInteractor,
+) {
+
+    private val isUnlocked: Flow<Boolean> =
+        if (SceneContainerFlag.isEnabled) {
+                deviceEntryInteractor.isUnlocked
+            } else {
+                keyguardInteractor.isKeyguardDismissible
+            }
+            .flatMapLatest { isUnlocked ->
+                if (!isUnlocked) {
+                    flowOf(false)
+                } else {
+                    flow {
+                        // delay in case device ends up transitioning away from the lock screen;
+                        // we don't want to animate to the unlocked icon and just let the
+                        // icon fade with the transition to GONE
+                        delay(DeviceEntryIconViewModel.UNLOCKED_DELAY_MS)
+                        emit(true)
+                    }
+                }
+            }
+
+    private val iconType: Flow<DeviceEntryIconView.IconType> =
+        isUnlocked.map { unlocked ->
+            if (unlocked) {
+                DeviceEntryIconView.IconType.UNLOCK
+            } else {
+                DeviceEntryIconView.IconType.LOCK
+            }
+        }
+
+    val isLongPressEnabled: Flow<Boolean> =
+        iconType.map { deviceEntryStatus ->
+            when (deviceEntryStatus) {
+                DeviceEntryIconView.IconType.UNLOCK -> true
+                DeviceEntryIconView.IconType.LOCK,
+                DeviceEntryIconView.IconType.FINGERPRINT,
+                DeviceEntryIconView.IconType.NONE -> false
+            }
+        }
+
+    val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> =
+        accessibilityInteractor.isEnabled.flatMapLatest { touchExplorationEnabled ->
+            if (touchExplorationEnabled) {
+                iconType.map { it.toAccessibilityHintType() }
+            } else {
+                flowOf(DeviceEntryIconView.AccessibilityHintType.NONE)
+            }
+        }
+
+    private val padding: Flow<Int> =
+        configurationInteractor.scaleForResolution.map { scale ->
+            (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
+                .roundToInt()
+        }
+
+    private fun getColor() =
+        Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
+
+    private val color: Flow<Int> =
+        configurationInteractor.onAnyConfigurationChange
+            .emitOnStart()
+            .map { getColor() }
+            .distinctUntilChanged()
+
+    suspend fun onUserInteraction() {
+        if (SceneContainerFlag.isEnabled) {
+            deviceEntryInteractor.attemptDeviceEntry()
+        } else {
+            keyguardViewController.get().showPrimaryBouncer(/* scrim */ true)
+        }
+        deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon()
+    }
+
+    val viewAttributes: Flow<CommunalLockIconAttributes> =
+        combine(iconType, color, padding) { iconType, color, padding ->
+            CommunalLockIconAttributes(type = iconType, tint = color, padding = padding)
+        }
+}
+
+data class CommunalLockIconAttributes(
+    val type: DeviceEntryIconView.IconType,
+    val tint: Int,
+    val padding: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
index 02e1824..11b7e9d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
@@ -51,6 +52,7 @@
     private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
     private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor,
     private val communalInteractor: CommunalInteractor,
+    private val communalSettingsInteractor: CommunalSettingsInteractor,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val userTracker: UserTracker,
     dumpManager: DumpManager,
@@ -58,8 +60,12 @@
 
     fun startTransitionFromDream() {
         val showGlanceableHub =
-            communalInteractor.isCommunalEnabled.value &&
-                !keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
+            if (communalSettingsInteractor.isV2FlagEnabled()) {
+                communalInteractor.shouldShowCommunal.value
+            } else {
+                communalInteractor.isCommunalEnabled.value &&
+                    !keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
+            }
         fromDreamingTransitionInteractor.startToLockscreenOrGlanceableHubTransition(
             showGlanceableHub && !glanceableHubAllowKeyguardWhenDreaming()
         )
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 0054dd7..6395bb73 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
Binary files differ
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 621cc46..aaad101 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -326,10 +326,7 @@
 
     fun setShortcutAbsoluteTop(top: Float)
 
-    /**
-     * Set bottom of notifications from notification stack, and Magic Portrait will layout base on
-     * this value
-     */
+    /** Set bottom of notifications from notification stack */
     fun setNotificationStackAbsoluteBottom(bottom: Float)
 
     fun setWallpaperFocalAreaBounds(bounds: RectF)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 8429c23..0b116de 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -15,9 +15,11 @@
  */
 package com.android.systemui.keyguard.domain.interactor
 
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.keyguard.logging.ScrimLogger
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.data.repository.DEFAULT_REVEAL_DURATION
 import com.android.systemui.keyguard.data.repository.LightRevealScrimRepository
 import com.android.systemui.keyguard.shared.model.Edge
@@ -31,12 +33,13 @@
 import com.android.systemui.util.kotlin.sample
 import dagger.Lazy
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.flow.flowOn
 
 @SysUISingleton
 class LightRevealScrimInteractor
@@ -47,6 +50,7 @@
     @Application private val scope: CoroutineScope,
     private val scrimLogger: ScrimLogger,
     private val powerInteractor: Lazy<PowerInteractor>,
+    @Background backgroundDispatcher: CoroutineDispatcher,
 ) {
     init {
         listenForStartedKeyguardTransitionStep()
@@ -113,6 +117,7 @@
                     repository.maxAlpha
                 }
             }
+            .flowOn(backgroundDispatcher)
 
     val revealAmount =
         repository.revealAmount.filter {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt
index 934afe2..9c744d6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt
@@ -50,8 +50,7 @@
     keyguardClockRepository: KeyguardClockRepository,
     wallpaperRepository: WallpaperRepository,
 ) {
-    // When there's notifications in splitshade, magic portrait shape effects should be left
-    // aligned in foldable
+    // When there's notifications in splitshade, the focal area shape effect should be left aligned
     private val notificationInShadeWideLayout: Flow<Boolean> =
         combine(
             shadeRepository.isShadeLayoutWide,
@@ -104,7 +103,7 @@
                     )
                 val (left, right) =
                 // tablet landscape
-                if (context.resources.getBoolean(R.bool.center_align_magic_portrait_shape)) {
+                if (context.resources.getBoolean(R.bool.center_align_focal_area_shape)) {
                         Pair(
                             scaledBounds.centerX() - maxFocalAreaWidth / 2F,
                             scaledBounds.centerX() + maxFocalAreaWidth / 2F,
@@ -129,7 +128,7 @@
                         wallpaperZoomedInScale
                 val top =
                     // tablet landscape
-                    if (context.resources.getBoolean(R.bool.center_align_magic_portrait_shape)) {
+                    if (context.resources.getBoolean(R.bool.center_align_focal_area_shape)) {
                         // no strict constraints for top, use bottom margin to make it symmetric
                         // vertically
                         scaledBounds.top + scaledBottomMargin
@@ -169,8 +168,8 @@
             )
         }
 
-        // A max width for magic portrait shape effects bounds, to avoid it going too large
-        // in large screen portrait mode
+        // A max width for focal area shape effects bounds, to avoid
+        // it becoming too large in large screen portrait mode
         const val FOCAL_AREA_MAX_WIDTH_DP = 500
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
index 856e1d6..8b213be 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.AccessibilityActionsSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
+import com.android.systemui.keyguard.ui.view.layout.sections.AodPromotedNotificationSection
 import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
@@ -58,6 +59,7 @@
     defaultStatusBarSection: DefaultStatusBarSection,
     splitShadeNotificationStackScrollLayoutSection: SplitShadeNotificationStackScrollLayoutSection,
     splitShadeGuidelines: SplitShadeGuidelines,
+    aodPromotedNotificationSection: AodPromotedNotificationSection,
     aodNotificationIconsSection: AodNotificationIconsSection,
     aodBurnInSection: AodBurnInSection,
     clockSection: ClockSection,
@@ -76,6 +78,7 @@
             defaultStatusBarSection,
             splitShadeNotificationStackScrollLayoutSection,
             splitShadeGuidelines,
+            aodPromotedNotificationSection,
             aodNotificationIconsSection,
             smartspaceSection,
             aodBurnInSection,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt
index ed1bdb0..ea4acce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt
@@ -26,6 +26,7 @@
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.promoted.AODPromotedNotification
 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationLogger
 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod
@@ -36,6 +37,7 @@
 @Inject
 constructor(
     private val viewModelFactory: AODPromotedNotificationViewModel.Factory,
+    private val shadeInteractor: ShadeInteractor,
     private val logger: PromotedNotificationLogger,
 ) : KeyguardSection() {
     var view: ComposeView? = null
@@ -77,9 +79,12 @@
         checkNotNull(view)
 
         constraintSet.apply {
+            val isShadeLayoutWide = shadeInteractor.isShadeLayoutWide.value
+            val endGuidelineId = if (isShadeLayoutWide) R.id.split_shade_guideline else PARENT_ID
+
             connect(viewId, TOP, R.id.smart_space_barrier_bottom, BOTTOM, 0)
             connect(viewId, START, PARENT_ID, START, 0)
-            connect(viewId, END, PARENT_ID, END, 0)
+            connect(viewId, END, endGuidelineId, END, 0)
 
             constrainWidth(viewId, ConstraintSet.MATCH_CONSTRAINT)
             constrainHeight(viewId, ConstraintSet.WRAP_CONTENT)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 84fdc6e..13cd583 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -262,16 +262,6 @@
         deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon()
     }
 
-    private fun DeviceEntryIconView.IconType.toAccessibilityHintType():
-        DeviceEntryIconView.AccessibilityHintType {
-        return when (this) {
-            DeviceEntryIconView.IconType.FINGERPRINT,
-            DeviceEntryIconView.IconType.LOCK -> DeviceEntryIconView.AccessibilityHintType.BOUNCER
-            DeviceEntryIconView.IconType.UNLOCK -> DeviceEntryIconView.AccessibilityHintType.ENTER
-            DeviceEntryIconView.IconType.NONE -> DeviceEntryIconView.AccessibilityHintType.NONE
-        }
-    }
-
     companion object {
         const val UNLOCKED_DELAY_MS = 50L
     }
@@ -282,3 +272,13 @@
     val y: Int, // current y burn in offset based on the aodTransitionAmount
     val progress: Float, // current progress based on the aodTransitionAmount
 )
+
+fun DeviceEntryIconView.IconType.toAccessibilityHintType():
+    DeviceEntryIconView.AccessibilityHintType {
+    return when (this) {
+        DeviceEntryIconView.IconType.FINGERPRINT,
+        DeviceEntryIconView.IconType.LOCK -> DeviceEntryIconView.AccessibilityHintType.BOUNCER
+        DeviceEntryIconView.IconType.UNLOCK -> DeviceEntryIconView.AccessibilityHintType.ENTER
+        DeviceEntryIconView.IconType.NONE -> DeviceEntryIconView.AccessibilityHintType.NONE
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaItem.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaItem.java
index 4496b25..7b1c62e2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaItem.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaItem.java
@@ -36,6 +36,7 @@
     private final String mTitle;
     @MediaItemType
     private final int mMediaItemType;
+    private final boolean mIsFirstDeviceInGroup;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({
@@ -54,7 +55,18 @@
      * name.
      */
     public static MediaItem createDeviceMediaItem(@NonNull MediaDevice device) {
-        return new MediaItem(device, device.getName(), MediaItemType.TYPE_DEVICE);
+        return new MediaItem(device, device.getName(), MediaItemType.TYPE_DEVICE, false);
+    }
+
+    /**
+     * Returns a new {@link MediaItemType#TYPE_DEVICE} {@link MediaItem} with its {@link
+     * #getMediaDevice() media device} set to {@code device} and its title set to {@code device}'s
+     * name.
+     */
+    public static MediaItem createDeviceMediaItem(
+            @NonNull MediaDevice device, boolean isFirstDeviceInGroup) {
+        return new MediaItem(
+            device, device.getName(), MediaItemType.TYPE_DEVICE, isFirstDeviceInGroup);
     }
 
     /**
@@ -63,7 +75,10 @@
      */
     public static MediaItem createPairNewDeviceMediaItem() {
         return new MediaItem(
-                /* device */ null, /* title */ null, MediaItemType.TYPE_PAIR_NEW_DEVICE);
+                /* device */ null,
+                /* title */ null,
+                MediaItemType.TYPE_PAIR_NEW_DEVICE,
+                /* mIsFirstDeviceInGroup */ false);
     }
 
     /**
@@ -71,14 +86,22 @@
      * title and a {@code null} {@link #getMediaDevice() media device}.
      */
     public static MediaItem createGroupDividerMediaItem(@Nullable String title) {
-        return new MediaItem(/* device */ null, title, MediaItemType.TYPE_GROUP_DIVIDER);
+        return new MediaItem(
+            /* device */ null,
+            title,
+            MediaItemType.TYPE_GROUP_DIVIDER,
+            /* misFirstDeviceInGroup */ false);
     }
 
     private MediaItem(
-            @Nullable MediaDevice device, @Nullable String title, @MediaItemType int type) {
+            @Nullable MediaDevice device,
+            @Nullable String title,
+            @MediaItemType int type,
+            boolean isFirstDeviceInGroup) {
         this.mMediaDeviceOptional = Optional.ofNullable(device);
         this.mTitle = title;
         this.mMediaItemType = type;
+        this.mIsFirstDeviceInGroup = isFirstDeviceInGroup;
     }
 
     public Optional<MediaDevice> getMediaDevice() {
@@ -106,4 +129,8 @@
     public int getMediaItemType() {
         return mMediaItemType;
     }
+
+    public boolean isFirstDeviceInGroup() {
+        return mIsFirstDeviceInGroup;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 53f3b3a..52b3c3ec 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -21,6 +21,7 @@
 import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
 
 import android.annotation.DrawableRes;
+import android.annotation.StringRes;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.drawable.AnimatedVectorDrawable;
@@ -38,6 +39,7 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.media.flags.Flags;
 import com.android.settingslib.media.LocalMediaManager.MediaDeviceState;
 import com.android.settingslib.media.MediaDevice;
 import com.android.systemui.res.R;
@@ -55,6 +57,7 @@
     private static final float DEVICE_DISCONNECTED_ALPHA = 0.5f;
     private static final float DEVICE_CONNECTED_ALPHA = 1f;
     protected List<MediaItem> mMediaItemList = new CopyOnWriteArrayList<>();
+    private boolean mShouldGroupSelectedMediaItems = Flags.enableOutputSwitcherSessionGrouping();
 
     public MediaOutputAdapter(MediaSwitchingController controller) {
         super(controller);
@@ -65,6 +68,12 @@
     public void updateItems() {
         mMediaItemList.clear();
         mMediaItemList.addAll(mController.getMediaItemList());
+        if (mShouldGroupSelectedMediaItems) {
+            if (mController.getSelectedMediaDevice().size() == 1) {
+                // Don't group devices if initially there isn't more than one selected.
+                mShouldGroupSelectedMediaItems = false;
+            }
+        }
         notifyDataSetChanged();
     }
 
@@ -101,7 +110,7 @@
                 break;
             case MediaItem.MediaItemType.TYPE_DEVICE:
                 ((MediaDeviceViewHolder) viewHolder).onBind(
-                        currentMediaItem.getMediaDevice().get(),
+                        currentMediaItem,
                         position);
                 break;
             default:
@@ -141,8 +150,8 @@
             super(view);
         }
 
-        @Override
-        void onBind(MediaDevice device, int position) {
+        void onBind(MediaItem mediaItem, int position) {
+            MediaDevice device = mediaItem.getMediaDevice().get();
             super.onBind(device, position);
             boolean isMutingExpectedDeviceExist = mController.hasMutingExpectedDevice();
             final boolean currentlyConnected = isCurrentlyConnected(device);
@@ -150,6 +159,7 @@
             if (mCurrentActivePosition == position) {
                 mCurrentActivePosition = -1;
             }
+            mItemLayout.setVisibility(View.VISIBLE);
             mStatusIcon.setVisibility(View.GONE);
             enableFocusPropertyForView(mContainerLayout);
 
@@ -174,6 +184,30 @@
                     updateFullItemClickListener(v -> onItemClick(v, device));
                     setSingleLineLayout(device.getName());
                     initFakeActiveDevice(device);
+                } else if (mShouldGroupSelectedMediaItems
+                        && mController.getSelectedMediaDevice().size() > 1
+                        && isDeviceIncluded(mController.getSelectedMediaDevice(), device)) {
+                    if (!mediaItem.isFirstDeviceInGroup()) {
+                        mItemLayout.setVisibility(View.GONE);
+                        mEndTouchArea.setVisibility(View.GONE);
+                    } else {
+                        String sessionName = mController.getSessionName().toString();
+                        updateUnmutedVolumeIcon(null);
+                        updateEndClickAreaWithIcon(
+                                v -> {
+                                    mShouldGroupSelectedMediaItems = false;
+                                    notifyDataSetChanged();
+                                },
+                                R.drawable.media_output_item_expand_group,
+                                R.string.accessibility_expand_group);
+                        disableFocusPropertyForView(mContainerLayout);
+                        setUpContentDescriptionForView(mSeekBar, mContext.getString(
+                                R.string.accessibility_cast_name, sessionName));
+                        setSingleLineLayout(sessionName, true /* showSeekBar */,
+                                false /* showProgressBar */, false /* showCheckBox */,
+                                true /* showEndTouchArea */);
+                        initGroupSeekbar(isCurrentSeekbarInvisible);
+                    }
                 } else if (device.hasSubtext()) {
                     boolean isActiveWithOngoingSession =
                             (device.hasOngoingSession() && (currentlyConnected || isDeviceIncluded(
@@ -237,6 +271,8 @@
                     // selected device in group
                     boolean isDeviceDeselectable = isDeviceIncluded(
                             mController.getDeselectableMediaDevice(), device);
+                    boolean showEndArea = !Flags.enableOutputSwitcherSessionGrouping()
+                            || isDeviceDeselectable;
                     updateUnmutedVolumeIcon(device);
                     updateGroupableCheckBox(true, isDeviceDeselectable, device);
                     updateEndClickArea(device, isDeviceDeselectable);
@@ -244,7 +280,7 @@
                     setUpContentDescriptionForView(mSeekBar, device);
                     setSingleLineLayout(device.getName(), true /* showSeekBar */,
                             false /* showProgressBar */, true /* showCheckBox */,
-                            true /* showEndTouchArea */);
+                            showEndArea /* showEndTouchArea */);
                     initSeekbar(device, isCurrentSeekbarInvisible);
                 } else if (!mController.hasAdjustVolumeUserRestriction()
                         && currentlyConnected) {
@@ -335,19 +371,29 @@
         }
 
         private void updateEndClickAreaAsSessionEditing(MediaDevice device, @DrawableRes int id) {
-            mEndClickIcon.setOnClickListener(null);
-            mEndTouchArea.setOnClickListener(null);
+            updateEndClickAreaWithIcon(
+                    v -> mController.tryToLaunchInAppRoutingIntent(device.getId(), v),
+                    id,
+                    R.string.accessibility_open_application);
+        }
+
+        private void updateEndClickAreaWithIcon(View.OnClickListener clickListener,
+                @DrawableRes int iconDrawableId,
+                @StringRes int accessibilityStringId) {
             updateEndClickAreaColor(mController.getColorSeekbarProgress());
             mEndClickIcon.setImageTintList(
                     ColorStateList.valueOf(mController.getColorItemContent()));
-            mEndClickIcon.setOnClickListener(
-                    v -> mController.tryToLaunchInAppRoutingIntent(device.getId(), v));
+            mEndClickIcon.setOnClickListener(clickListener);
             mEndTouchArea.setOnClickListener(v -> mEndClickIcon.performClick());
-            Drawable drawable = mContext.getDrawable(id);
+            Drawable drawable = mContext.getDrawable(iconDrawableId);
             mEndClickIcon.setImageDrawable(drawable);
             if (drawable instanceof AnimatedVectorDrawable) {
                 ((AnimatedVectorDrawable) drawable).start();
             }
+            if (Flags.enableOutputSwitcherSessionGrouping()) {
+                setUpContentDescriptionForView(
+                        mEndClickIcon, mContext.getString(accessibilityStringId));
+            }
         }
 
         public void updateEndClickAreaColor(int color) {
@@ -479,12 +525,17 @@
         }
 
         private void setUpContentDescriptionForView(View view, MediaDevice device) {
-            view.setContentDescription(
+            setUpContentDescriptionForView(
+                    view,
                     mContext.getString(device.getDeviceType()
                             == MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE
                             ? R.string.accessibility_bluetooth_name
                             : R.string.accessibility_cast_name, device.getName()));
         }
+
+        protected void setUpContentDescriptionForView(View view, String description) {
+            view.setContentDescription(description);
+        }
     }
 
     class MediaGroupDividerViewHolder extends RecyclerView.ViewHolder {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 9b24c69..ee2d8aa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -42,6 +42,7 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.media.flags.Flags;
 import com.android.settingslib.media.InputMediaDevice;
 import com.android.settingslib.media.MediaDevice;
 import com.android.settingslib.utils.ThreadUtils;
@@ -211,6 +212,10 @@
             mTitleText.setText(title);
             mCheckBox.setVisibility(showCheckBox ? View.VISIBLE : View.GONE);
             mEndTouchArea.setVisibility(showEndTouchArea ? View.VISIBLE : View.GONE);
+            if (Flags.enableOutputSwitcherSessionGrouping()) {
+                mEndClickIcon.setVisibility(
+                        !showCheckBox && showEndTouchArea ? View.VISIBLE : View.GONE);
+            }
             ViewGroup.MarginLayoutParams params =
                     (ViewGroup.MarginLayoutParams) mItemLayout.getLayoutParams();
             params.rightMargin = showEndTouchArea ? mController.getItemMarginEndSelectable()
@@ -265,14 +270,8 @@
                             mController.getActiveRadius(), 0, 0});
         }
 
-        void initSeekbar(MediaDevice device, boolean isCurrentSeekbarInvisible) {
-            if (!mController.isVolumeControlEnabled(device)) {
-                disableSeekBar();
-            } else {
-                enableSeekBar(device);
-            }
-            mSeekBar.setMaxVolume(device.getMaxVolume());
-            final int currentVolume = device.getCurrentVolume();
+        private void initializeSeekbarVolume(
+                MediaDevice device, int currentVolume, boolean isCurrentSeekbarInvisible) {
             if (!mIsDragging) {
                 if (mSeekBar.getVolume() != currentVolume && (mLatestUpdateVolume == -1
                         || currentVolume == mLatestUpdateVolume)) {
@@ -307,54 +306,75 @@
             if (mIsInitVolumeFirstTime) {
                 mIsInitVolumeFirstTime = false;
             }
-            mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
-                boolean mStartFromMute = false;
+        }
+
+        void initSeekbar(MediaDevice device, boolean isCurrentSeekbarInvisible) {
+            SeekBarVolumeControl volumeControl = new SeekBarVolumeControl() {
                 @Override
-                public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-                    if (device == null || !fromUser) {
-                        return;
-                    }
-
-                    final String percentageString = mContext.getResources().getString(
-                            R.string.media_output_dialog_volume_percentage,
-                            mSeekBar.getPercentage());
-                    mVolumeValueText.setText(percentageString);
-
-                    if (mStartFromMute) {
-                        updateUnmutedVolumeIcon(device);
-                        mStartFromMute = false;
-                    }
-                    int seekBarVolume = MediaOutputSeekbar.scaleProgressToVolume(progress);
-                    if (seekBarVolume != device.getCurrentVolume()) {
-                        mLatestUpdateVolume = seekBarVolume;
-                        mController.adjustVolume(device, seekBarVolume);
-                    }
+                public int getVolume() {
+                    return device.getCurrentVolume();
+                }
+                @Override
+                public void setVolume(int volume) {
+                    mController.adjustVolume(device, volume);
                 }
 
                 @Override
-                public void onStartTrackingTouch(SeekBar seekBar) {
-                    mTitleIcon.setVisibility(View.INVISIBLE);
-                    mVolumeValueText.setVisibility(View.VISIBLE);
-                    int currentVolume = MediaOutputSeekbar.scaleProgressToVolume(
-                            seekBar.getProgress());
-                    mStartFromMute = (currentVolume == 0);
-                    mIsDragging = true;
+                public void onMute() {
+                    mController.logInteractionUnmuteDevice(device);
                 }
+            };
 
+            if (!mController.isVolumeControlEnabled(device)) {
+                disableSeekBar();
+            } else {
+                enableSeekBar(volumeControl);
+            }
+            mSeekBar.setMaxVolume(device.getMaxVolume());
+            final int currentVolume = device.getCurrentVolume();
+            initializeSeekbarVolume(device, currentVolume, isCurrentSeekbarInvisible);
+
+            mSeekBar.setOnSeekBarChangeListener(new MediaSeekBarChangedListener(
+                    device, volumeControl) {
                 @Override
-                public void onStopTrackingTouch(SeekBar seekBar) {
-                    int currentVolume = MediaOutputSeekbar.scaleProgressToVolume(
-                            seekBar.getProgress());
-                    if (currentVolume == 0) {
-                        seekBar.setProgress(0);
-                        updateMutedVolumeIcon(device);
-                    } else {
-                        updateUnmutedVolumeIcon(device);
-                    }
-                    mTitleIcon.setVisibility(View.VISIBLE);
-                    mVolumeValueText.setVisibility(View.GONE);
+                public void onStopTrackingTouch(SeekBar seekbar) {
+                    super.onStopTrackingTouch(seekbar);
                     mController.logInteractionAdjustVolume(device);
-                    mIsDragging = false;
+                }
+            });
+        }
+
+        // Initializes the seekbar for a group of devices.
+        void initGroupSeekbar(boolean isCurrentSeekbarInvisible) {
+            SeekBarVolumeControl volumeControl = new SeekBarVolumeControl() {
+                @Override
+                public int getVolume() {
+                    return mController.getSessionVolume();
+                }
+
+                @Override
+                public void setVolume(int volume) {
+                    mController.adjustSessionVolume(volume);
+                }
+
+                @Override
+                public void onMute() {}
+            };
+
+            if (!mController.isVolumeControlEnabledForSession()) {
+                disableSeekBar();
+            } else {
+                enableSeekBar(volumeControl);
+            }
+            mSeekBar.setMaxVolume(mController.getSessionVolumeMax());
+
+            final int currentVolume = mController.getSessionVolume();
+            initializeSeekbarVolume(null, currentVolume, isCurrentSeekbarInvisible);
+            mSeekBar.setOnSeekBarChangeListener(new MediaSeekBarChangedListener(
+                    null, volumeControl) {
+                @Override
+                protected boolean shouldHandleProgressChanged() {
+                    return true;
                 }
             });
         }
@@ -385,7 +405,7 @@
         int getDrawableId(boolean isInputDevice, boolean isMutedVolumeIcon) {
             // Returns the microphone icon when the flag is enabled and the device is an input
             // device.
-            if (com.android.media.flags.Flags.enableAudioInputDeviceRoutingAndVolumeControl()
+            if (Flags.enableAudioInputDeviceRoutingAndVolumeControl()
                     && isInputDevice) {
                 return isMutedVolumeIcon ? R.drawable.ic_mic_off : R.drawable.ic_mic_26dp;
             }
@@ -452,27 +472,28 @@
             updateIconAreaClickListener(null);
         }
 
-        private void enableSeekBar(MediaDevice device) {
+        private void enableSeekBar(SeekBarVolumeControl volumeControl) {
             mSeekBar.setEnabled(true);
+
             mSeekBar.setOnTouchListener((v, event) -> false);
             updateIconAreaClickListener((v) -> {
-                if (device.getCurrentVolume() == 0) {
-                    mController.logInteractionUnmuteDevice(device);
+                if (volumeControl.getVolume() == 0) {
                     mSeekBar.setVolume(UNMUTE_DEFAULT_VOLUME);
-                    mController.adjustVolume(device, UNMUTE_DEFAULT_VOLUME);
-                    updateUnmutedVolumeIcon(device);
+                    volumeControl.setVolume(UNMUTE_DEFAULT_VOLUME);
+                    updateUnmutedVolumeIcon(null);
                     mIconAreaLayout.setOnTouchListener(((iconV, event) -> false));
                 } else {
-                    mController.logInteractionMuteDevice(device);
+                    volumeControl.onMute();
                     mSeekBar.resetVolume();
-                    mController.adjustVolume(device, 0);
-                    updateMutedVolumeIcon(device);
+                    volumeControl.setVolume(0);
+                    updateMutedVolumeIcon(null);
                     mIconAreaLayout.setOnTouchListener(((iconV, event) -> {
                         mSeekBar.dispatchTouchEvent(event);
                         return false;
                     }));
                 }
             });
+
         }
 
         protected void setUpDeviceIcon(MediaDevice device) {
@@ -488,5 +509,74 @@
                 });
             });
         }
+
+        interface SeekBarVolumeControl {
+            int getVolume();
+            void setVolume(int volume);
+            void onMute();
+        }
+
+        private abstract class MediaSeekBarChangedListener
+                implements SeekBar.OnSeekBarChangeListener {
+            boolean mStartFromMute = false;
+            private MediaDevice mMediaDevice;
+            private SeekBarVolumeControl mVolumeControl;
+
+            MediaSeekBarChangedListener(MediaDevice device, SeekBarVolumeControl volumeControl) {
+                mMediaDevice = device;
+                mVolumeControl = volumeControl;
+            }
+
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                if (!shouldHandleProgressChanged() || !fromUser) {
+                    return;
+                }
+
+                final String percentageString = mContext.getResources().getString(
+                        R.string.media_output_dialog_volume_percentage,
+                        mSeekBar.getPercentage());
+                mVolumeValueText.setText(percentageString);
+
+                if (mStartFromMute) {
+                    updateUnmutedVolumeIcon(mMediaDevice);
+                    mStartFromMute = false;
+                }
+
+                int seekBarVolume = MediaOutputSeekbar.scaleProgressToVolume(progress);
+                if (seekBarVolume != mVolumeControl.getVolume()) {
+                    mLatestUpdateVolume = seekBarVolume;
+                    mVolumeControl.setVolume(seekBarVolume);
+                }
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+                mTitleIcon.setVisibility(View.INVISIBLE);
+                mVolumeValueText.setVisibility(View.VISIBLE);
+                int currentVolume = MediaOutputSeekbar.scaleProgressToVolume(
+                        seekBar.getProgress());
+                mStartFromMute = (currentVolume == 0);
+                mIsDragging = true;
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+                int currentVolume = MediaOutputSeekbar.scaleProgressToVolume(
+                        seekBar.getProgress());
+                if (currentVolume == 0) {
+                    seekBar.setProgress(0);
+                    updateMutedVolumeIcon(mMediaDevice);
+                } else {
+                    updateUnmutedVolumeIcon(mMediaDevice);
+                }
+                mTitleIcon.setVisibility(View.VISIBLE);
+                mVolumeValueText.setVisibility(View.GONE);
+                mIsDragging = false;
+            }
+            protected boolean shouldHandleProgressChanged() {
+                return mMediaDevice != null;
+            }
+        };
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
index 15afd22..35c872f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
@@ -760,14 +760,26 @@
         if (connectedMediaDevice != null) {
             selectedDevicesIds.add(connectedMediaDevice.getId());
         }
+        boolean groupSelectedDevices =
+                com.android.media.flags.Flags.enableOutputSwitcherSessionGrouping();
+        int nextSelectedItemIndex = 0;
         boolean suggestedDeviceAdded = false;
         boolean displayGroupAdded = false;
+        boolean selectedDeviceAdded = false;
         for (MediaDevice device : devices) {
             if (needToHandleMutingExpectedDevice && device.isMutingExpectedDevice()) {
                 finalMediaItems.add(0, MediaItem.createDeviceMediaItem(device));
+                nextSelectedItemIndex++;
             } else if (!needToHandleMutingExpectedDevice && selectedDevicesIds.contains(
                     device.getId())) {
-                finalMediaItems.add(0, MediaItem.createDeviceMediaItem(device));
+                if (groupSelectedDevices) {
+                    finalMediaItems.add(
+                            nextSelectedItemIndex++,
+                            MediaItem.createDeviceMediaItem(device, !selectedDeviceAdded));
+                    selectedDeviceAdded = true;
+                } else {
+                    finalMediaItems.add(0, MediaItem.createDeviceMediaItem(device));
+                }
             } else {
                 if (device.isSuggestedDevice() && !suggestedDeviceAdded) {
                     addSuggestedDeviceGroupDivider(finalMediaItems);
@@ -1331,6 +1343,10 @@
         return !device.isVolumeFixed();
     }
 
+    boolean isVolumeControlEnabledForSession() {
+        return mLocalMediaManager.isMediaSessionAvailableForVolumeControl();
+    }
+
     private void startActivity(Intent intent, ActivityTransitionAnimator.Controller controller) {
         // Media Output dialog can be shown from the volume panel. This makes sure the panel is
         // closed when navigating to another activity, so it doesn't stays on top of it
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
index ea0f63c..d503fb7 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
@@ -18,6 +18,7 @@
 
 import android.app.ActivityManager.RunningTaskInfo
 import android.hardware.display.DisplayManager
+import android.media.projection.MediaProjectionEvent
 import android.media.projection.MediaProjectionInfo
 import android.media.projection.MediaProjectionManager
 import android.media.projection.StopReason
@@ -43,6 +44,9 @@
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.withContext
@@ -83,48 +87,59 @@
         }
     }
 
-    override val mediaProjectionState: Flow<MediaProjectionState> =
-        conflatedCallbackFlow {
-                val callback =
-                    object : MediaProjectionManager.Callback() {
-                        override fun onStart(info: MediaProjectionInfo?) {
-                            logger.log(
-                                TAG,
-                                LogLevel.DEBUG,
-                                {},
-                                { "MediaProjectionManager.Callback#onStart" },
-                            )
-                            trySendWithFailureLogging(CallbackEvent.OnStart(info), TAG)
-                        }
+    private val callbackEventsFlow = conflatedCallbackFlow {
+        val callback =
+            object : MediaProjectionManager.Callback() {
+                override fun onStart(info: MediaProjectionInfo?) {
+                    logger.log(TAG, LogLevel.DEBUG, {}, { "Callback#onStart" })
+                    trySendWithFailureLogging(CallbackEvent.OnStart(info), TAG)
+                }
 
-                        override fun onStop(info: MediaProjectionInfo?) {
-                            logger.log(
-                                TAG,
-                                LogLevel.DEBUG,
-                                {},
-                                { "MediaProjectionManager.Callback#onStop" },
-                            )
-                            trySendWithFailureLogging(CallbackEvent.OnStop, TAG)
-                        }
+                override fun onStop(info: MediaProjectionInfo?) {
+                    logger.log(TAG, LogLevel.DEBUG, {}, { "Callback#onStop" })
+                    trySendWithFailureLogging(CallbackEvent.OnStop, TAG)
+                }
 
-                        override fun onRecordingSessionSet(
-                            info: MediaProjectionInfo,
-                            session: ContentRecordingSession?,
-                        ) {
-                            logger.log(
-                                TAG,
-                                LogLevel.DEBUG,
-                                { str1 = session.toString() },
-                                { "MediaProjectionManager.Callback#onSessionStarted: $str1" },
-                            )
-                            trySendWithFailureLogging(
-                                CallbackEvent.OnRecordingSessionSet(info, session),
-                                TAG,
-                            )
-                        }
+                override fun onRecordingSessionSet(
+                    info: MediaProjectionInfo,
+                    session: ContentRecordingSession?,
+                ) {
+                    logger.log(
+                        TAG,
+                        LogLevel.DEBUG,
+                        { str1 = session.toString() },
+                        { "Callback#onSessionSet: $str1" },
+                    )
+                    trySendWithFailureLogging(
+                        CallbackEvent.OnRecordingSessionSet(info, session),
+                        TAG,
+                    )
+                }
+
+                override fun onMediaProjectionEvent(
+                    event: MediaProjectionEvent,
+                    info: MediaProjectionInfo?,
+                    session: ContentRecordingSession?,
+                ) {
+                    if (com.android.media.projection.flags.Flags.showStopDialogPostCallEnd()) {
+                        logger.log(
+                            TAG,
+                            LogLevel.DEBUG,
+                            { str1 = event.toString() },
+                            { "Callback#onMediaProjectionEvent : $str1" },
+                        )
+                        trySendWithFailureLogging(CallbackEvent.OnMediaProjectionEvent(event), TAG)
                     }
-                mediaProjectionManager.addCallback(callback, handler)
-                awaitClose { mediaProjectionManager.removeCallback(callback) }
+                }
+            }
+        mediaProjectionManager.addCallback(callback, handler)
+        awaitClose { mediaProjectionManager.removeCallback(callback) }
+    }
+
+    override val mediaProjectionState: Flow<MediaProjectionState> =
+        callbackEventsFlow
+            .filterNot {
+                it is CallbackEvent.OnMediaProjectionEvent // Exclude OnMediaProjectionEvent
             }
             // When we get an #onRecordingSessionSet event, we need to do some work in the
             // background before emitting the right state value. But when we get an #onStop
@@ -159,6 +174,11 @@
                     }
                     is CallbackEvent.OnStop -> MediaProjectionState.NotProjecting
                     is CallbackEvent.OnRecordingSessionSet -> stateForSession(it.info, it.session)
+                    is CallbackEvent.OnMediaProjectionEvent ->
+                        throw IllegalStateException(
+                            "Unexpected OnMediaProjectionEvent in mediaProjectionState flow. It " +
+                                "should have been filtered out."
+                        )
                 }
             }
             .stateIn(
@@ -167,6 +187,16 @@
                 initialValue = MediaProjectionState.NotProjecting,
             )
 
+    override val projectionStartedDuringCallAndActivePostCallEvent: Flow<Unit> =
+        callbackEventsFlow
+            .filter {
+                com.android.media.projection.flags.Flags.showStopDialogPostCallEnd() &&
+                    it is CallbackEvent.OnMediaProjectionEvent &&
+                    it.event.eventType ==
+                        MediaProjectionEvent.PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL
+            }
+            .map {}
+
     private suspend fun stateForSession(
         info: MediaProjectionInfo,
         session: ContentRecordingSession?,
@@ -206,6 +236,8 @@
             val info: MediaProjectionInfo,
             val session: ContentRecordingSession?,
         ) : CallbackEvent
+
+        data class OnMediaProjectionEvent(val event: MediaProjectionEvent) : CallbackEvent
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepository.kt
index a01d8c2..826ee58 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepository.kt
@@ -32,4 +32,10 @@
 
     /** Represents the current [MediaProjectionState]. */
     val mediaProjectionState: Flow<MediaProjectionState>
+
+    /**
+     * Emits each time a call ends but media projection is still active and media projection was
+     * starting during the call.
+     */
+    val projectionStartedDuringCallAndActivePostCallEvent: Flow<Unit>
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index ebda376..babb640 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -284,7 +284,10 @@
         }
 
         @Override
-        public void onDisplayReady(int displayId) {
+        public void onDisplayAddSystemDecorations(int displayId) {
+            if (enableDisplayContentModeManagement()) {
+                mHasNavBar.put(displayId, true);
+            }
             Display display = mDisplayManager.getDisplay(displayId);
             mIsLargeScreen = isLargeScreen(mContext);
             createNavigationBar(display, null /* savedState */, null /* result */);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 9d89430..c4d847f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -238,16 +238,16 @@
     }
 
     @Override
-    public void onDisplayReady(int displayId) {
-        CommandQueue.Callbacks.super.onDisplayReady(displayId);
+    public void onDisplayAddSystemDecorations(int displayId) {
+        CommandQueue.Callbacks.super.onDisplayAddSystemDecorations(displayId);
         if (mLauncherProxyService.getProxy() == null) {
             return;
         }
 
         try {
-            mLauncherProxyService.getProxy().onDisplayReady(displayId);
+            mLauncherProxyService.getProxy().onDisplayAddSystemDecorations(displayId);
         } catch (RemoteException e) {
-            Log.e(TAG, "onDisplayReady() failed", e);
+            Log.e(TAG, "onDisplayAddSystemDecorations() failed", e);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index a3893bc..85b677b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -311,7 +311,7 @@
         SceneTransitionLayout(state = sceneState, modifier = Modifier.fillMaxSize()) {
             scene(QuickSettings) {
                 LaunchedEffect(Unit) { viewModel.onQSOpen() }
-                QuickSettingsElement()
+                QuickSettingsElement(Modifier.element(QuickSettings.rootElementKey))
             }
 
             scene(QuickQuickSettings) {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 6844f05..eae0ba6 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.settings.brightness;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.content.Intent.EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -157,6 +159,7 @@
     }
 
     void setBrightnessDialogViewAttributes(View container) {
+        Configuration configuration = getResources().getConfiguration();
         // The brightness mirror container is INVISIBLE by default.
         container.setVisibility(View.VISIBLE);
         ViewGroup.MarginLayoutParams lp =
@@ -171,9 +174,16 @@
                         R.dimen.notification_guts_option_vertical_padding);
 
         lp.topMargin = verticalMargin;
+        // If in multi-window or freeform, increase the top margin so the brightness dialog
+        // doesn't get cut off.
+        final int windowingMode = configuration.windowConfiguration.getWindowingMode();
+        if (windowingMode == WINDOWING_MODE_MULTI_WINDOW
+                || windowingMode == WINDOWING_MODE_FREEFORM) {
+            lp.topMargin += 50;
+        }
+
         lp.bottomMargin = verticalMargin;
 
-        Configuration configuration = getResources().getConfiguration();
         int orientation = configuration.orientation;
         int windowWidth = getWindowAvailableWidth();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index dcea8d8..1720898 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -111,7 +111,7 @@
     private static final int MSG_COLLAPSE_PANELS                   = 4 << MSG_SHIFT;
     private static final int MSG_EXPAND_SETTINGS                   = 5 << MSG_SHIFT;
     private static final int MSG_SYSTEM_BAR_CHANGED                = 6 << MSG_SHIFT;
-    private static final int MSG_DISPLAY_READY                     = 7 << MSG_SHIFT;
+    private static final int MSG_DISPLAY_ADD_SYSTEM_DECORATIONS    = 7 << MSG_SHIFT;
     private static final int MSG_SHOW_IME_BUTTON                   = 8 << MSG_SHIFT;
     private static final int MSG_TOGGLE_RECENT_APPS                = 9 << MSG_SHIFT;
     private static final int MSG_PRELOAD_RECENT_APPS               = 10 << MSG_SHIFT;
@@ -415,9 +415,9 @@
         }
 
         /**
-         * @see IStatusBar#onDisplayReady(int)
+         * @see IStatusBar#onDisplayAddSystemDecorations(int)
          */
-        default void onDisplayReady(int displayId) {
+        default void onDisplayAddSystemDecorations(int displayId) {
         }
 
         /**
@@ -1205,9 +1205,9 @@
     }
 
     @Override
-    public void onDisplayReady(int displayId) {
+    public void onDisplayAddSystemDecorations(int displayId) {
         synchronized (mLock) {
-            mHandler.obtainMessage(MSG_DISPLAY_READY, displayId, 0).sendToTarget();
+            mHandler.obtainMessage(MSG_DISPLAY_ADD_SYSTEM_DECORATIONS, displayId, 0).sendToTarget();
         }
     }
 
@@ -1851,9 +1851,9 @@
                         mCallbacks.get(i).showPinningEscapeToast();
                     }
                     break;
-                case MSG_DISPLAY_READY:
+                case MSG_DISPLAY_ADD_SYSTEM_DECORATIONS:
                     for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).onDisplayReady(msg.arg1);
+                        mCallbacks.get(i).onDisplayAddSystemDecorations(msg.arg1);
                     }
                     break;
                 case MSG_DISPLAY_REMOVE_SYSTEM_DECORATIONS:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index 541a07c..98b7521 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.chips.call.ui.viewmodel
 
+import android.content.Context
 import android.view.View
 import com.android.internal.jank.Cuj
 import com.android.systemui.animation.ActivityTransitionAnimator
@@ -23,6 +24,7 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.plugins.ActivityStarter
@@ -52,6 +54,7 @@
 open class CallChipViewModel
 @Inject
 constructor(
+    @Main private val context: Context,
     @Application private val scope: CoroutineScope,
     interactor: CallChipInteractor,
     systemClock: SystemClock,
@@ -65,15 +68,18 @@
                     is OngoingCallModel.NoCall,
                     is OngoingCallModel.InCallWithVisibleApp -> OngoingActivityChipModel.Hidden()
                     is OngoingCallModel.InCall -> {
+                        val contentDescription = getContentDescription(state.appName)
                         val icon =
                             if (state.notificationIconView != null) {
                                 StatusBarConnectedDisplays.assertInLegacyMode()
                                 OngoingActivityChipModel.ChipIcon.StatusBarView(
-                                    state.notificationIconView
+                                    state.notificationIconView,
+                                    contentDescription,
                                 )
                             } else if (StatusBarConnectedDisplays.isEnabled) {
                                 OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(
-                                    state.notificationKey
+                                    state.notificationKey,
+                                    contentDescription,
                                 )
                             } else {
                                 OngoingActivityChipModel.ChipIcon.SingleColorIcon(phoneIcon)
@@ -155,6 +161,17 @@
             )
         }
 
+    private fun getContentDescription(appName: String): ContentDescription {
+        val ongoingCallDescription = context.getString(R.string.ongoing_call_content_description)
+        return ContentDescription.Loaded(
+            context.getString(
+                R.string.accessibility_desc_notification_icon,
+                appName,
+                ongoingCallDescription,
+            )
+        )
+    }
+
     companion object {
         private val phoneIcon =
             Icon.Resource(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
index 49c4479..49d69f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
@@ -53,6 +54,9 @@
     private val packageManager: PackageManager,
     @StatusBarChipsLog private val logger: LogBuffer,
 ) {
+    val projectionStartedDuringCallAndActivePostCallEvent: Flow<Unit> =
+        mediaProjectionRepository.projectionStartedDuringCallAndActivePostCallEvent
+
     val projection: StateFlow<ProjectionChipModel> =
         mediaProjectionRepository.mediaProjectionState
             .map { state ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/MediaProjectionStopDialogModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/MediaProjectionStopDialogModel.kt
new file mode 100644
index 0000000..b37c762
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/MediaProjectionStopDialogModel.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2025 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.statusbar.chips.mediaprojection.domain.model
+
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+/** Represents the visibility state of a media projection stop dialog. */
+sealed interface MediaProjectionStopDialogModel {
+    /** The dialog is hidden and not visible to the user. */
+    data object Hidden : MediaProjectionStopDialogModel
+
+    /** The dialog is shown to the user. */
+    data class Shown(
+        val dialogDelegate: SystemUIDialog.Delegate,
+        private val onDismissAction: () -> Unit,
+    ) : MediaProjectionStopDialogModel {
+        /**
+         * Creates and shows the dialog. Ensures that onDismissAction callback is invoked when the
+         * dialog is canceled or dismissed.
+         */
+        fun createAndShowDialog() {
+            val dialog = dialogDelegate.createDialog()
+            dialog.setOnCancelListener { onDismissAction.invoke() }
+            dialog.setOnDismissListener { onDismissAction.invoke() }
+            dialog.show()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
index cece521..a933888 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
@@ -138,7 +138,7 @@
             }
         }
 
-        return NotificationChipModel(key, statusBarChipIconView, promotedContent)
+        return NotificationChipModel(key, appName, statusBarChipIconView, promotedContent)
     }
 
     @AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
index c6759da..e7a9080 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
@@ -22,6 +22,8 @@
 /** Modeling all the data needed to render a status bar notification chip. */
 data class NotificationChipModel(
     val key: String,
+    /** The user-readable name of the app that posted the call notification. */
+    val appName: String,
     val statusBarChipIconView: StatusBarIconView?,
     val promotedContent: PromotedNotificationContentModel,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index 46456b8..b0da642 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
@@ -16,10 +16,14 @@
 
 package com.android.systemui.statusbar.chips.notification.ui.viewmodel
 
+import android.content.Context
 import android.view.View
 import com.android.systemui.Flags
+import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor
 import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
@@ -43,6 +47,7 @@
 class NotifChipsViewModel
 @Inject
 constructor(
+    @Main private val context: Context,
     @Application private val applicationScope: CoroutineScope,
     private val notifChipsInteractor: StatusBarNotificationChipsInteractor,
     headsUpNotificationInteractor: HeadsUpNotificationInteractor,
@@ -65,13 +70,20 @@
         headsUpState: TopPinnedState
     ): OngoingActivityChipModel.Shown {
         StatusBarNotifChips.assertInNewMode()
+        val contentDescription = getContentDescription(this.appName)
         val icon =
             if (this.statusBarChipIconView != null) {
                 StatusBarConnectedDisplays.assertInLegacyMode()
-                OngoingActivityChipModel.ChipIcon.StatusBarView(this.statusBarChipIconView)
+                OngoingActivityChipModel.ChipIcon.StatusBarView(
+                    this.statusBarChipIconView,
+                    contentDescription,
+                )
             } else {
                 StatusBarConnectedDisplays.assertInNewMode()
-                OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(this.key)
+                OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(
+                    this.key,
+                    contentDescription,
+                )
             }
         val colors = this.promotedContent.toCustomColorsModel()
 
@@ -79,6 +91,7 @@
             // The notification pipeline needs everything to run on the main thread, so keep
             // this event on the main thread.
             applicationScope.launch {
+                // TODO(b/364653005): Move accessibility focus to the HUN when chip is tapped.
                 notifChipsInteractor.onPromotedNotificationChipTapped(this@toActivityChipModel.key)
             }
         }
@@ -173,4 +186,16 @@
             }
         }
     }
+
+    private fun getContentDescription(appName: String): ContentDescription {
+        val ongoingDescription =
+            context.getString(R.string.ongoing_notification_extra_content_description)
+        return ContentDescription.Loaded(
+            context.getString(
+                R.string.accessibility_desc_notification_icon,
+                appName,
+                ongoingDescription,
+            )
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
index 6654d4a..7a46fff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import androidx.annotation.DrawableRes
 import com.android.internal.jank.Cuj
+import com.android.systemui.CoreStartable
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.common.shared.model.ContentDescription
@@ -31,6 +32,7 @@
 import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
 import com.android.systemui.statusbar.chips.StatusBarChipsLog
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractor
+import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
 import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
 import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
 import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndGenericShareToAppDialogDelegate
@@ -41,13 +43,18 @@
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickCallback
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
+import com.android.systemui.util.kotlin.sample
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 /**
  * View model for the share-to-app chip, shown when sharing your phone screen content to another app
@@ -64,7 +71,59 @@
     private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
     private val dialogTransitionAnimator: DialogTransitionAnimator,
     @StatusBarChipsLog private val logger: LogBuffer,
-) : OngoingActivityChipViewModel {
+) : OngoingActivityChipViewModel, CoreStartable {
+
+    private val _stopDialogToShow: MutableStateFlow<MediaProjectionStopDialogModel> =
+        MutableStateFlow(MediaProjectionStopDialogModel.Hidden)
+
+    /**
+     * Represents the current state of the media projection stop dialog. Emits
+     * [MediaProjectionStopDialogModel.Shown] when the dialog should be displayed, and
+     * [MediaProjectionStopDialogModel.Hidden] when it is dismissed.
+     */
+    val stopDialogToShow: StateFlow<MediaProjectionStopDialogModel> =
+        _stopDialogToShow.asStateFlow()
+
+    /**
+     * Emits a [MediaProjectionStopDialogModel] based on the current projection state when a
+     * projectionStartedDuringCallAndActivePostCallEvent event is emitted. If projecting, determines
+     * the appropriate dialog type to show. Otherwise, emits a hidden dialog state.
+     */
+    private val stopDialogDueToCallEndedState: StateFlow<MediaProjectionStopDialogModel> =
+        mediaProjectionChipInteractor.projectionStartedDuringCallAndActivePostCallEvent
+            .sample(mediaProjectionChipInteractor.projection) { _, currentProjection ->
+                when (currentProjection) {
+                    is ProjectionChipModel.NotProjecting -> MediaProjectionStopDialogModel.Hidden
+                    is ProjectionChipModel.Projecting -> {
+                        when (currentProjection.receiver) {
+                            ProjectionChipModel.Receiver.ShareToApp -> {
+                                when (currentProjection.contentType) {
+                                    ProjectionChipModel.ContentType.Screen ->
+                                        createShareScreenToAppStopDialog(currentProjection)
+                                    ProjectionChipModel.ContentType.Audio ->
+                                        createGenericShareScreenToAppStopDialog()
+                                }
+                            }
+                            ProjectionChipModel.Receiver.CastToOtherDevice ->
+                                MediaProjectionStopDialogModel.Hidden
+                        }
+                    }
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), MediaProjectionStopDialogModel.Hidden)
+
+    /**
+     * Initializes background flow collector during SysUI startup for events determining the
+     * visibility of media projection stop dialogs.
+     */
+    override fun start() {
+        if (com.android.media.projection.flags.Flags.showStopDialogPostCallEnd()) {
+            scope.launch {
+                stopDialogDueToCallEndedState.collect { event -> _stopDialogToShow.value = event }
+            }
+        }
+    }
+
     private val internalChip =
         mediaProjectionChipInteractor.projection
             .map { projectionModel ->
@@ -92,7 +151,25 @@
     private val chipTransitionHelper = ChipTransitionHelper(scope)
 
     override val chip: StateFlow<OngoingActivityChipModel> =
-        chipTransitionHelper.createChipFlow(internalChip)
+        combine(chipTransitionHelper.createChipFlow(internalChip), stopDialogToShow) {
+                currentChip,
+                stopDialog ->
+                if (
+                    com.android.media.projection.flags.Flags.showStopDialogPostCallEnd() &&
+                        stopDialog is MediaProjectionStopDialogModel.Shown
+                ) {
+                    logger.log(
+                        TAG,
+                        LogLevel.INFO,
+                        {},
+                        { "Hiding the chip as stop dialog is being shown" },
+                    )
+                    OngoingActivityChipModel.Hidden()
+                } else {
+                    currentChip
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden())
 
     /**
      * Notifies this class that the user just stopped a screen recording from the dialog that's
@@ -108,6 +185,12 @@
         chipTransitionHelper.onActivityStoppedFromDialog()
     }
 
+    /** Called when the stop dialog is dismissed or cancelled. */
+    private fun onStopDialogDismissed() {
+        logger.log(TAG, LogLevel.INFO, {}, { "The media projection stop dialog was dismissed" })
+        _stopDialogToShow.value = MediaProjectionStopDialogModel.Hidden
+    }
+
     /** Stops the currently active projection. */
     private fun stopProjectingFromDialog() {
         logger.log(TAG, LogLevel.INFO, {}, { "Stop sharing requested from dialog" })
@@ -115,6 +198,24 @@
         mediaProjectionChipInteractor.stopProjecting()
     }
 
+    private fun createShareScreenToAppStopDialog(
+        projectionModel: ProjectionChipModel.Projecting
+    ): MediaProjectionStopDialogModel {
+        val dialogDelegate = createShareScreenToAppDialogDelegate(projectionModel)
+        return MediaProjectionStopDialogModel.Shown(
+            dialogDelegate,
+            onDismissAction = ::onStopDialogDismissed,
+        )
+    }
+
+    private fun createGenericShareScreenToAppStopDialog(): MediaProjectionStopDialogModel {
+        val dialogDelegate = createGenericShareToAppDialogDelegate()
+        return MediaProjectionStopDialogModel.Shown(
+            dialogDelegate,
+            onDismissAction = ::onStopDialogDismissed,
+        )
+    }
+
     private fun createShareScreenToAppChip(
         state: ProjectionChipModel.Projecting
     ): OngoingActivityChipModel.Shown {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
index f5764d5..de9d497 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
@@ -26,6 +26,8 @@
 import android.widget.ImageView
 import android.widget.TextView
 import androidx.annotation.UiThread
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder
 import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView
@@ -187,7 +189,13 @@
             }
             is OngoingActivityChipModel.ChipIcon.StatusBarView -> {
                 StatusBarConnectedDisplays.assertInLegacyMode()
-                setStatusBarIconView(defaultIconView, icon.impl, iconTint, backgroundView)
+                setStatusBarIconView(
+                    defaultIconView,
+                    icon.impl,
+                    icon.contentDescription,
+                    iconTint,
+                    backgroundView,
+                )
             }
             is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon -> {
                 StatusBarConnectedDisplays.assertInNewMode()
@@ -196,7 +204,13 @@
                     // This means that the notification key doesn't exist anymore.
                     return
                 }
-                setStatusBarIconView(defaultIconView, iconView, iconTint, backgroundView)
+                setStatusBarIconView(
+                    defaultIconView,
+                    iconView,
+                    icon.contentDescription,
+                    iconTint,
+                    backgroundView,
+                )
             }
         }
     }
@@ -215,6 +229,7 @@
     private fun setStatusBarIconView(
         defaultIconView: ImageView,
         iconView: StatusBarIconView,
+        iconContentDescription: ContentDescription,
         iconTint: Int,
         backgroundView: ChipBackgroundContainer,
     ) {
@@ -224,9 +239,12 @@
         // 1. Set up the right visual params.
         with(iconView) {
             id = CUSTOM_ICON_VIEW_ID
-            // TODO(b/354930838): For RON chips, use the app name for the content description.
-            contentDescription =
-                context.resources.getString(R.string.ongoing_call_content_description)
+            if (StatusBarNotifChips.isEnabled) {
+                ContentDescriptionViewBinder.bind(iconContentDescription, this)
+            } else {
+                contentDescription =
+                    context.resources.getString(R.string.ongoing_call_content_description)
+            }
             tintView(iconTint)
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
index 647f3bd..816f291 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
@@ -37,11 +37,14 @@
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
 import com.android.compose.animation.Expandable
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.common.ui.compose.load
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
@@ -82,11 +85,25 @@
     val isClickable = onClick != {}
     val hasEmbeddedIcon = model.icon is OngoingActivityChipModel.ChipIcon.StatusBarView
 
+    val contentDescription =
+        when (val icon = model.icon) {
+            is OngoingActivityChipModel.ChipIcon.StatusBarView -> icon.contentDescription.load()
+            is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon ->
+                icon.contentDescription.load()
+            is OngoingActivityChipModel.ChipIcon.SingleColorIcon -> null
+            null -> null
+        }
+
     // Use a Box with `fillMaxHeight` to create a larger click surface for the chip. The visible
     // height of the chip is determined by the height of the background of the Row below.
     Box(
         contentAlignment = Alignment.Center,
-        modifier = modifier.fillMaxHeight().clickable(enabled = isClickable, onClick = onClick),
+        modifier =
+            modifier.fillMaxHeight().clickable(enabled = isClickable, onClick = onClick).semantics {
+                if (contentDescription != null) {
+                    this.contentDescription = contentDescription
+                }
+            },
     ) {
         Row(
             horizontalArrangement = Arrangement.Center,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index e0c7645..d44646c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -18,6 +18,7 @@
 
 import android.view.View
 import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
@@ -140,7 +141,10 @@
          * The icon is a custom icon, which is set on [impl]. The icon was likely created by an
          * external app.
          */
-        data class StatusBarView(val impl: StatusBarIconView) : ChipIcon {
+        data class StatusBarView(
+            val impl: StatusBarIconView,
+            val contentDescription: ContentDescription,
+        ) : ChipIcon {
             init {
                 StatusBarConnectedDisplays.assertInLegacyMode()
             }
@@ -150,7 +154,10 @@
          * The icon is a custom icon, which is set on a notification, and can be looked up using the
          * provided [notificationKey]. The icon was likely created by an external app.
          */
-        data class StatusBarNotificationIcon(val notificationKey: String) : ChipIcon {
+        data class StatusBarNotificationIcon(
+            val notificationKey: String,
+            val contentDescription: ContentDescription,
+        ) : ChipIcon {
             init {
                 StatusBarConnectedDisplays.assertInNewMode()
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
index b057fb0..eeb7a40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.core
 
 import android.view.Display
+import android.view.IWindowManager
 import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
@@ -54,6 +55,7 @@
     private val autoHideControllerStore: AutoHideControllerStore,
     private val privacyDotWindowControllerStore: PrivacyDotWindowControllerStore,
     private val lightBarControllerStore: LightBarControllerStore,
+    private val windowManager: IWindowManager,
 ) : CoreStartable {
 
     init {
@@ -68,7 +70,13 @@
                 }
                 .onStart { emit(displayRepository.displays.value) }
                 .collect { newDisplays ->
-                    newDisplays.forEach { createAndStartComponentsForDisplay(it) }
+                    newDisplays.forEach {
+                        // TODO(b/393191204): Split navbar, status bar, etc. functionality
+                        // from WindowManager#shouldShowSystemDecors.
+                        if (windowManager.shouldShowSystemDecors(it.displayId)) {
+                            createAndStartComponentsForDisplay(it)
+                        }
+                    }
                 }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index 351cdc8..b5a781e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.data.StatusBarDataLayerModule
 import com.android.systemui.statusbar.data.repository.LightBarControllerStore
@@ -140,6 +141,20 @@
         @Provides
         @SysUISingleton
         @IntoMap
+        @ClassKey(ShareToAppChipViewModel::class)
+        fun providesShareToAppChipViewModel(
+            shareToAppChipViewModelLazy: Lazy<ShareToAppChipViewModel>
+        ): CoreStartable {
+            return if (com.android.media.projection.flags.Flags.showStopDialogPostCallEnd()) {
+                shareToAppChipViewModelLazy.get()
+            } else {
+                CoreStartable.NOP
+            }
+        }
+
+        @Provides
+        @SysUISingleton
+        @IntoMap
         @ClassKey(MultiDisplayStatusBarWindowControllerStore::class)
         fun multiDisplayControllerStoreAsCoreStartable(
             storeLazy: Lazy<MultiDisplayStatusBarWindowControllerStore>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
index fd5973e..bde3c4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -21,10 +21,12 @@
 import android.app.Notification.CallStyle.CALL_TYPE_UNKNOWN
 import android.app.Notification.EXTRA_CALL_TYPE
 import android.app.PendingIntent
+import android.content.Context
 import android.graphics.drawable.Icon
 import android.service.notification.StatusBarNotification
 import android.util.ArrayMap
 import com.android.app.tracing.traceSection
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -50,6 +52,7 @@
 constructor(
     private val repository: ActiveNotificationListRepository,
     private val sectionStyleProvider: SectionStyleProvider,
+    @Main private val context: Context,
 ) {
     /**
      * Sets the current list of rendered notification entries as displayed in the notification list.
@@ -57,7 +60,7 @@
     fun setRenderedList(entries: List<ListEntry>) {
         traceSection("RenderNotificationListInteractor.setRenderedList") {
             repository.activeNotifications.update { existingModels ->
-                buildActiveNotificationsStore(existingModels, sectionStyleProvider) {
+                buildActiveNotificationsStore(existingModels, sectionStyleProvider, context) {
                     entries.forEach(::addListEntry)
                     setRankingsMap(entries)
                 }
@@ -69,13 +72,17 @@
 private fun buildActiveNotificationsStore(
     existingModels: ActiveNotificationsStore,
     sectionStyleProvider: SectionStyleProvider,
+    context: Context,
     block: ActiveNotificationsStoreBuilder.() -> Unit,
 ): ActiveNotificationsStore =
-    ActiveNotificationsStoreBuilder(existingModels, sectionStyleProvider).apply(block).build()
+    ActiveNotificationsStoreBuilder(existingModels, sectionStyleProvider, context)
+        .apply(block)
+        .build()
 
 private class ActiveNotificationsStoreBuilder(
     private val existingModels: ActiveNotificationsStore,
     private val sectionStyleProvider: SectionStyleProvider,
+    private val context: Context,
 ) {
     private val builder = ActiveNotificationsStore.Builder()
 
@@ -154,6 +161,7 @@
             statusBarChipIconView = icons.statusBarChipIcon,
             uid = sbn.uid,
             packageName = sbn.packageName,
+            appName = sbn.notification.loadHeaderAppName(context),
             contentIntent = sbn.notification.contentIntent,
             instanceId = sbn.instanceId?.id,
             isGroupSummary = sbn.notification.isGroupSummary,
@@ -180,6 +188,7 @@
     statusBarChipIconView: StatusBarIconView?,
     uid: Int,
     packageName: String,
+    appName: String,
     contentIntent: PendingIntent?,
     instanceId: Int?,
     isGroupSummary: Boolean,
@@ -206,6 +215,7 @@
             instanceId = instanceId,
             isGroupSummary = isGroupSummary,
             packageName = packageName,
+            appName = appName,
             contentIntent = contentIntent,
             bucket = bucket,
             callType = callType,
@@ -230,6 +240,7 @@
             instanceId = instanceId,
             isGroupSummary = isGroupSummary,
             packageName = packageName,
+            appName = appName,
             contentIntent = contentIntent,
             bucket = bucket,
             callType = callType,
@@ -253,6 +264,7 @@
     statusBarChipIconView: StatusBarIconView?,
     uid: Int,
     packageName: String,
+    appName: String,
     contentIntent: PendingIntent?,
     instanceId: Int?,
     isGroupSummary: Boolean,
@@ -278,6 +290,7 @@
         instanceId != this.instanceId -> false
         isGroupSummary != this.isGroupSummary -> false
         packageName != this.packageName -> false
+        appName != this.appName -> false
         contentIntent != this.contentIntent -> false
         bucket != this.bucket -> false
         callType != this.callType -> false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
index ab8be30..f00c3ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -68,6 +68,8 @@
     val uid: Int,
     /** The notifying app's packageName. */
     val packageName: String,
+    /** The notifying app's display name. */
+    val appName: String,
     /** The intent to execute if UI related to this notification is clicked. */
     val contentIntent: PendingIntent?,
     /** A small per-notification ID, used for statsd logging. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index f740144..ece1803 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shared.Flags.extendedWallpaperEffects
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
@@ -143,7 +144,7 @@
                     }
 
                     if (!SceneContainerFlag.isEnabled) {
-                        if (Flags.magicPortraitWallpapers()) {
+                        if (extendedWallpaperEffects()) {
                             launch {
                                 combine(
                                         viewModel.getNotificationStackAbsoluteBottom(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index a29934fa..949cb0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -160,6 +160,7 @@
                 notificationIconView = currentInfo.notificationIconView,
                 intent = currentInfo.intent,
                 notificationKey = currentInfo.key,
+                appName = currentInfo.appName,
                 promotedContent = currentInfo.promotedContent,
             )
         } else {
@@ -217,6 +218,7 @@
                     notifModel.statusBarChipIconView,
                     notifModel.contentIntent,
                     notifModel.uid,
+                    notifModel.appName,
                     notifModel.promotedContent,
                     isOngoing = true,
                     statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false,
@@ -337,6 +339,7 @@
         val notificationIconView: StatusBarIconView?,
         val intent: PendingIntent?,
         val uid: Int,
+        val appName: String,
         /**
          * If the call notification also meets promoted notification criteria, this field is filled
          * in with the content related to promotion. Otherwise null.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt
index ba7628f..2fd7d82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt
@@ -163,6 +163,7 @@
                     notificationIconView = model.statusBarChipIconView,
                     intent = model.contentIntent,
                     notificationKey = model.key,
+                    appName = model.appName,
                     promotedContent = model.promotedContent,
                 )
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
index 7d00e9d..6507b72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
@@ -42,6 +42,7 @@
      * @property notificationIconView the [android.app.Notification.getSmallIcon] that's set on the
      *   call notification. We may use this icon in the chip instead of the default phone icon.
      * @property intent the intent associated with the call notification.
+     * @property appName the user-readable name of the app that posted the call notification.
      * @property promotedContent if the call notification also meets promoted notification criteria,
      *   this field is filled in with the content related to promotion. Otherwise null.
      */
@@ -50,6 +51,7 @@
         val notificationIconView: StatusBarIconView?,
         val intent: PendingIntent?,
         val notificationKey: String,
+        val appName: String,
         val promotedContent: PromotedNotificationContentModel?,
     ) : OngoingCallModel
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
index 7e76d77..bd69060 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipBinder
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
@@ -115,6 +116,17 @@
                     }
                 }
 
+                if (com.android.media.projection.flags.Flags.showStopDialogPostCallEnd()) {
+                    launch {
+                        viewModel.mediaProjectionStopDialogDueToCallEndedState.collect { stopDialog
+                            ->
+                            if (stopDialog is MediaProjectionStopDialogModel.Shown) {
+                                stopDialog.createAndShowDialog()
+                            }
+                        }
+                    }
+                }
+
                 if (!StatusBarNotifChips.isEnabled && !StatusBarChipsModernization.isEnabled) {
                     val primaryChipViewBinding =
                         OngoingActivityChipBinder.createBinding(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index a59d95f..b116b47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -36,7 +36,9 @@
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel
 import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel
@@ -96,6 +98,12 @@
     val transitionFromLockscreenToDreamStartedEvent: Flow<Unit>
 
     /**
+     * The current media projection stop dialog to be shown, or
+     * `MediaProjectionStopDialogModel.Hidden` if no dialog is visible.
+     */
+    val mediaProjectionStopDialogDueToCallEndedState: StateFlow<MediaProjectionStopDialogModel>
+
+    /**
      * The ongoing activity chip that should be primarily shown on the left-hand side of the status
      * bar. If there are multiple ongoing activity chips, this one should take priority.
      */
@@ -180,6 +188,7 @@
     sceneInteractor: SceneInteractor,
     sceneContainerOcclusionInteractor: SceneContainerOcclusionInteractor,
     shadeInteractor: ShadeInteractor,
+    shareToAppChipViewModel: ShareToAppChipViewModel,
     ongoingActivityChipsViewModel: OngoingActivityChipsViewModel,
     statusBarPopupChipsViewModel: StatusBarPopupChipsViewModel,
     animations: SystemStatusEventAnimationInteractor,
@@ -206,6 +215,9 @@
             .filter { it.transitionState == TransitionState.STARTED }
             .map {}
 
+    override val mediaProjectionStopDialogDueToCallEndedState =
+        shareToAppChipViewModel.stopDialogToShow
+
     override val primaryOngoingActivityChip = ongoingActivityChipsViewModel.primaryChip
 
     override val ongoingActivityChips = ongoingActivityChipsViewModel.chips
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
index 9794c61..79a9630 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
@@ -27,12 +27,13 @@
 import androidx.annotation.VisibleForTesting
 import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.internal.R
-import com.android.systemui.Flags
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.res.R as SysUIR
 import com.android.systemui.shared.Flags.ambientAod
+import com.android.systemui.shared.Flags.extendedWallpaperEffects
 import com.android.systemui.user.data.model.SelectedUserModel
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.UserRepository
@@ -66,7 +67,7 @@
     /** Set rootView to get its windowToken afterwards */
     var rootView: View?
 
-    /** when we use magic portrait wallpapers, we should always get its bounds from keyguard */
+    /** some wallpapers require bounds to be sent from keyguard */
     val shouldSendFocalArea: StateFlow<Boolean>
 }
 
@@ -80,7 +81,7 @@
     userRepository: UserRepository,
     keyguardRepository: KeyguardRepository,
     private val wallpaperManager: WallpaperManager,
-    context: Context,
+    private val context: Context,
 ) : WallpaperRepository {
     private val wallpaperChanged: Flow<Unit> =
         broadcastDispatcher
@@ -125,8 +126,8 @@
     override val shouldSendFocalArea =
         wallpaperInfo
             .map {
-                val shouldSendNotificationLayout =
-                    it?.component?.className == MAGIC_PORTRAIT_CLASSNAME
+                val focalAreaTarget = context.resources.getString(SysUIR.string.focal_area_target)
+                val shouldSendNotificationLayout = it?.component?.className == focalAreaTarget
                 if (shouldSendNotificationLayout) {
                     sendLockscreenLayoutJob =
                         scope.launch {
@@ -167,9 +168,8 @@
             }
             .stateIn(
                 scope,
-                // Always be listening for wallpaper changes when magic portrait flag is on
-                if (Flags.magicPortraitWallpapers()) SharingStarted.Eagerly else WhileSubscribed(),
-                initialValue = Flags.magicPortraitWallpapers(),
+                if (extendedWallpaperEffects()) SharingStarted.Eagerly else WhileSubscribed(),
+                initialValue = extendedWallpaperEffects(),
             )
 
     private suspend fun getWallpaper(selectedUser: SelectedUserModel): WallpaperInfo? {
@@ -177,9 +177,4 @@
             wallpaperManager.getWallpaperInfoForUser(selectedUser.userInfo.id)
         }
     }
-
-    companion object {
-        const val MAGIC_PORTRAIT_CLASSNAME =
-            "com.google.android.apps.magicportrait.service.MagicPortraitWallpaperService"
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
index 86063ac..2715cb3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
@@ -1512,6 +1512,60 @@
         assertThat(getNumberOfConnectDeviceButtons()).isEqualTo(1);
     }
 
+    @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+    @Test
+    public void selectedDevicesAddedInSameOrder() {
+        when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true);
+        doReturn(mMediaDevices)
+                .when(mLocalMediaManager)
+                .getSelectedMediaDevice();
+        mMediaSwitchingController.start(mCb);
+        reset(mCb);
+        mMediaSwitchingController.getMediaItemList().clear();
+
+        mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+
+        List<MediaItem> items = mMediaSwitchingController.getMediaItemList();
+        assertThat(items.get(0).getMediaDevice().get()).isEqualTo(mMediaDevice1);
+        assertThat(items.get(1).getMediaDevice().get()).isEqualTo(mMediaDevice2);
+    }
+
+    @DisableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+    @Test
+    public void selectedDevicesAddedInReverseOrder() {
+        when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true);
+        doReturn(mMediaDevices)
+                .when(mLocalMediaManager)
+                .getSelectedMediaDevice();
+        mMediaSwitchingController.start(mCb);
+        reset(mCb);
+        mMediaSwitchingController.getMediaItemList().clear();
+
+        mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+
+        List<MediaItem> items = mMediaSwitchingController.getMediaItemList();
+        assertThat(items.get(0).getMediaDevice().get()).isEqualTo(mMediaDevice2);
+        assertThat(items.get(1).getMediaDevice().get()).isEqualTo(mMediaDevice1);
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+    @Test
+    public void firstSelectedDeviceIsFirstDeviceInGroupIsTrue() {
+        when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true);
+        doReturn(mMediaDevices)
+                .when(mLocalMediaManager)
+                .getSelectedMediaDevice();
+        mMediaSwitchingController.start(mCb);
+        reset(mCb);
+        mMediaSwitchingController.getMediaItemList().clear();
+
+        mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+
+        List<MediaItem> items = mMediaSwitchingController.getMediaItemList();
+        assertThat(items.get(0).isFirstDeviceInGroup()).isTrue();
+        assertThat(items.get(1).isFirstDeviceInGroup()).isFalse();
+    }
+
     private int getNumberOfConnectDeviceButtons() {
         int numberOfConnectDeviceButtons = 0;
         for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
diff --git a/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
index 025f556..80254d5 100644
--- a/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
@@ -24,4 +24,6 @@
 
 val Kosmos.mockWindowManager: WindowManager by Kosmos.Fixture { mock(WindowManager::class.java) }
 
+val Kosmos.mockIWindowManager: IWindowManager by Kosmos.Fixture { mock(IWindowManager::class.java) }
+
 var Kosmos.windowManager: WindowManager by Kosmos.Fixture { mockWindowManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
index ef9bd82..5793695 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
@@ -62,6 +62,7 @@
             defaultSettingsPopupMenuSection = mock(),
             defaultStatusBarSection = mock(),
             defaultNotificationStackScrollLayoutSection = mock(),
+            aodPromotedNotificationSection = mock(),
             aodNotificationIconsSection = mock(),
             aodBurnInSection = mock(),
             clockSection = keyguardClockSection,
@@ -69,7 +70,6 @@
             keyguardSliceViewSection = mock(),
             udfpsAccessibilityOverlaySection = mock(),
             accessibilityActionsSection = mock(),
-            aodPromotedNotificationSection = mock(),
         )
     }
 
@@ -84,6 +84,7 @@
             defaultStatusBarSection = mock(),
             splitShadeNotificationStackScrollLayoutSection = mock(),
             splitShadeGuidelines = mock(),
+            aodPromotedNotificationSection = mock(),
             aodNotificationIconsSection = mock(),
             aodBurnInSection = mock(),
             clockSection = keyguardClockSection,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt
index d5bdbdb..1b1fe59 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.keyguard.data.lightRevealScrimRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.power.domain.interactor.powerInteractor
 
 val Kosmos.lightRevealScrimInteractor by
@@ -30,5 +31,6 @@
             applicationCoroutineScope,
             scrimLogger,
             { powerInteractor },
+            backgroundDispatcher = testDispatcher,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/FakeMediaProjectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/FakeMediaProjectionRepository.kt
index e6256a5..6c52d54 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/FakeMediaProjectionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/FakeMediaProjectionRepository.kt
@@ -19,6 +19,8 @@
 import android.app.ActivityManager
 import android.media.projection.StopReason
 import com.android.systemui.mediaprojection.data.model.MediaProjectionState
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 
 class FakeMediaProjectionRepository : MediaProjectionRepository {
@@ -27,9 +29,18 @@
     override val mediaProjectionState: MutableStateFlow<MediaProjectionState> =
         MutableStateFlow(MediaProjectionState.NotProjecting)
 
+    private val _projectionStartedDuringCallAndActivePostCallEvent = MutableSharedFlow<Unit>()
+
+    override val projectionStartedDuringCallAndActivePostCallEvent: Flow<Unit> =
+        _projectionStartedDuringCallAndActivePostCallEvent
+
     var stopProjectingInvoked = false
 
     override suspend fun stopProjecting(@StopReason stopReason: Int) {
         stopProjectingInvoked = true
     }
+
+    suspend fun emitProjectionStartedDuringCallAndActivePostCallEvent() {
+        _projectionStartedDuringCallAndActivePostCallEvent.emit(Unit)
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
index 2b6032c..9b60051 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.mediaprojection.taskswitcher
 
+import android.media.projection.MediaProjectionEvent
 import android.media.projection.MediaProjectionInfo
 import android.media.projection.MediaProjectionManager
 import android.os.Binder
@@ -61,14 +62,22 @@
 
     fun dispatchOnSessionSet(
         info: MediaProjectionInfo = DEFAULT_INFO,
-        session: ContentRecordingSession?
+        session: ContentRecordingSession?,
     ) {
         callbacks.forEach { it.onRecordingSessionSet(info, session) }
     }
 
+    fun dispatchEvent(
+        event: MediaProjectionEvent,
+        info: MediaProjectionInfo? = DEFAULT_INFO,
+        session: ContentRecordingSession? = null,
+    ) {
+        callbacks.forEach { it.onMediaProjectionEvent(event, info, session) }
+    }
+
     companion object {
         fun createDisplaySession(): ContentRecordingSession =
-            ContentRecordingSession.createDisplaySession(/* displayToMirror = */ 123)
+            ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123)
 
         fun createSingleTaskSession(token: IBinder = Binder()): ContentRecordingSession =
             ContentRecordingSession.createTaskSession(token)
@@ -76,10 +85,6 @@
         private const val DEFAULT_PACKAGE_NAME = "com.media.projection.test"
         private val DEFAULT_USER_HANDLE = UserHandle.getUserHandleForUid(UserHandle.myUserId())
         private val DEFAULT_INFO =
-            MediaProjectionInfo(
-                DEFAULT_PACKAGE_NAME,
-                DEFAULT_USER_HANDLE,
-                /* launchCookie = */ null
-            )
+            MediaProjectionInfo(DEFAULT_PACKAGE_NAME, DEFAULT_USER_HANDLE, /* launchCookie= */ null)
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt
index 1e304d9..ab61a3e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.chips.call.ui.viewmodel
 
+import android.content.applicationContext
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.plugins.activityStarter
@@ -26,6 +27,7 @@
 val Kosmos.callChipViewModel: CallChipViewModel by
     Kosmos.Fixture {
         CallChipViewModel(
+            applicationContext,
             scope = applicationCoroutineScope,
             interactor = callChipInteractor,
             systemClock = fakeSystemClock,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
index d0c80c7..878c2de 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.chips.notification.ui.viewmodel
 
+import android.content.applicationContext
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
@@ -24,6 +25,7 @@
 val Kosmos.notifChipsViewModel: NotifChipsViewModel by
     Kosmos.Fixture {
         NotifChipsViewModel(
+            applicationContext,
             applicationCoroutineScope,
             statusBarNotificationChipsInteractor,
             headsUpNotificationInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
index 8c37bd7..bdcab5f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.core
 
 import android.content.testableContext
+import android.view.mockIWindowManager
 import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
 import com.android.systemui.display.data.repository.displayRepository
 import com.android.systemui.display.data.repository.displayScopeRepository
@@ -84,5 +85,6 @@
             multiDisplayAutoHideControllerStore,
             privacyDotWindowControllerStore,
             lightBarControllerStore,
+            mockIWindowManager,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
index c6ae15d..63085e1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
@@ -43,6 +43,7 @@
     instanceId: Int? = null,
     isGroupSummary: Boolean = false,
     packageName: String = "pkg",
+    appName: String = "appName",
     contentIntent: PendingIntent? = null,
     bucket: Int = BUCKET_UNKNOWN,
     callType: CallType = CallType.None,
@@ -64,6 +65,7 @@
         statusBarChipIconView = statusBarChipIcon,
         uid = uid,
         packageName = packageName,
+        appName = appName,
         contentIntent = contentIntent,
         instanceId = instanceId,
         isGroupSummary = isGroupSummary,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
index f7acae9..0acf98f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
@@ -16,11 +16,16 @@
 
 package com.android.systemui.statusbar.notification.domain.interactor
 
+import android.content.applicationContext
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.statusbar.notification.collection.provider.sectionStyleProvider
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 
 val Kosmos.renderNotificationListInteractor by
     Kosmos.Fixture {
-        RenderNotificationListInteractor(activeNotificationListRepository, sectionStyleProvider)
+        RenderNotificationListInteractor(
+            activeNotificationListRepository,
+            sectionStyleProvider,
+            applicationContext,
+        )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt
index f4e74fe..923b36d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt
@@ -26,5 +26,14 @@
     notificationIcon: StatusBarIconView? = null,
     intent: PendingIntent? = null,
     notificationKey: String = "test",
+    appName: String = "",
     promotedContent: PromotedNotificationContentModel? = null,
-) = OngoingCallModel.InCall(startTimeMs, notificationIcon, intent, notificationKey, promotedContent)
+) =
+    OngoingCallModel.InCall(
+        startTimeMs,
+        notificationIcon,
+        intent,
+        notificationKey,
+        appName,
+        promotedContent,
+    )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
index f8bf3c3..1626904 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
 import com.android.systemui.statusbar.events.domain.interactor.systemStatusEventAnimationInteractor
 import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.statusBarPopupChipsViewModel
@@ -53,6 +54,7 @@
             sceneInteractor,
             sceneContainerOcclusionInteractor,
             shadeInteractor,
+            shareToAppChipViewModel,
             ongoingActivityChipsViewModel,
             statusBarPopupChipsViewModel,
             systemStatusEventAnimationInteractor,
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
index 6191767..98ef974 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
@@ -60,9 +60,7 @@
      * Validates that the caller can execute the specified app function.
      *
      * <p>The caller can execute if the app function's package name is the same as the caller's
-     * package or the caller has either {@link Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} or
-     * {@link Manifest.permission#EXECUTE_APP_FUNCTIONS} granted. In some cases, app functions can
-     * still opt-out of caller having {@link Manifest.permission#EXECUTE_APP_FUNCTIONS}.
+     * package or the caller has the {@link Manifest.permission#EXECUTE_APP_FUNCTIONS} granted.
      *
      * @param callingUid The calling uid.
      * @param callingPid The calling pid.
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
index 69481c3..fe163d7 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
@@ -18,7 +18,6 @@
 
 import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB;
 import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_NAMESPACE;
-import static android.app.appfunctions.AppFunctionStaticMetadataHelper.STATIC_PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS;
 import static android.app.appfunctions.AppFunctionStaticMetadataHelper.getDocumentIdForAppFunction;
 
 import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR;
@@ -84,12 +83,7 @@
     }
 
     @Override
-    @RequiresPermission(
-            anyOf = {
-                Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
-                Manifest.permission.EXECUTE_APP_FUNCTIONS
-            },
-            conditional = true)
+    @RequiresPermission(Manifest.permission.EXECUTE_APP_FUNCTIONS)
     public AndroidFuture<Boolean> verifyCallerCanExecuteAppFunction(
             int callingUid,
             int callingPid,
@@ -101,17 +95,6 @@
             return AndroidFuture.completedFuture(true);
         }
 
-        boolean hasTrustedExecutionPermission =
-                mContext.checkPermission(
-                                Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
-                                callingPid,
-                                callingUid)
-                        == PackageManager.PERMISSION_GRANTED;
-
-        if (hasTrustedExecutionPermission) {
-            return AndroidFuture.completedFuture(true);
-        }
-
         boolean hasExecutionPermission =
                 mContext.checkPermission(
                                 Manifest.permission.EXECUTE_APP_FUNCTIONS, callingPid, callingUid)
@@ -138,7 +121,8 @@
                                 .build())
                 .thenApply(
                         batchResult -> getGenericDocumentFromBatchResult(batchResult, documentId))
-                .thenApply(document -> !getRestrictCallersWithExecuteAppFunctionsProperty(document))
+                // At this point, already checked the app has the permission.
+                .thenApply(document -> true)
                 .whenComplete(
                         (result, throwable) -> {
                             futureAppSearchSession.close();
@@ -160,12 +144,6 @@
                         + failedResult.getErrorMessage());
     }
 
-    private static boolean getRestrictCallersWithExecuteAppFunctionsProperty(
-            GenericDocument genericDocument) {
-        return genericDocument.getPropertyBoolean(
-                STATIC_PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS);
-    }
-
     @Override
     public boolean verifyEnterprisePolicyIsAllowed(
             @NonNull UserHandle callingUser, @NonNull UserHandle targetUser) {
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
index cc73288..9d13e37 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -78,7 +78,6 @@
     // Hidden constants in {@link SetSchemaRequest} that restricts runtime metadata visibility
     // by permissions.
     public static final int EXECUTE_APP_FUNCTIONS = 9;
-    public static final int EXECUTE_APP_FUNCTIONS_TRUSTED = 10;
 
     public MetadataSyncAdapter(
             @NonNull PackageManager packageManager, @NonNull AppSearchManager appSearchManager) {
@@ -281,8 +280,6 @@
                     new PackageIdentifier(packageName, packageCert));
             setSchemaRequestBuilder.addRequiredPermissionsForSchemaTypeVisibility(
                     runtimeMetadataSchema.getSchemaType(), Set.of(EXECUTE_APP_FUNCTIONS));
-            setSchemaRequestBuilder.addRequiredPermissionsForSchemaTypeVisibility(
-                    runtimeMetadataSchema.getSchemaType(), Set.of(EXECUTE_APP_FUNCTIONS_TRUSTED));
         }
         return setSchemaRequestBuilder.build();
     }
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index aef1c08..d47aab0 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -4673,12 +4673,6 @@
                         keep.add(providerId);
                         // Use the new AppWidgetProviderInfo.
                         provider.setPartialInfoLocked(info);
-                        // Clear old previews
-                        if (remoteViewsProto()) {
-                            clearGeneratedPreviewsAsync(provider);
-                        } else {
-                            provider.clearGeneratedPreviewsLocked();
-                        }
                         // If it's enabled
                         final int M = provider.widgets.size();
                         if (M > 0) {
@@ -5104,6 +5098,10 @@
         AndroidFuture<RemoteViews> result = new AndroidFuture<>();
         mSavePreviewsHandler.post(() -> {
             SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider);
+            if (previews.size() == 0 && provider.info.generatedPreviewCategories != 0) {
+                // Failed to read previews from file, clear the file and update providers.
+                saveGeneratedPreviews(provider, previews, /* notify= */ true);
+            }
             for (int i = 0; i < previews.size(); i++) {
                 if ((widgetCategory & previews.keyAt(i)) != 0) {
                     result.complete(previews.valueAt(i));
@@ -5222,8 +5220,14 @@
                 continue;
             }
             ProtoInputStream input = new ProtoInputStream(previewsFile.readFully());
-            provider.info.generatedPreviewCategories = readGeneratedPreviewCategoriesFromProto(
-                    input);
+            try {
+                provider.info.generatedPreviewCategories = readGeneratedPreviewCategoriesFromProto(
+                        input);
+            } catch (IOException e) {
+                Slog.e(TAG, "Failed to read generated previews from file for " + provider, e);
+                previewsFile.delete();
+                provider.info.generatedPreviewCategories = 0;
+            }
             if (DEBUG) {
                 Slog.i(TAG, TextUtils.formatSimple(
                         "loadGeneratedPreviewCategoriesLocked %d %s categories %d", profileId,
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 02a8f62..521f676 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -47,6 +47,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
+import android.app.KeyguardManager;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.ecm.EnhancedConfirmationManager;
@@ -302,8 +303,17 @@
             enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName,
                     "create associations");
 
-            mAssociationRequestsProcessor.processNewAssociationRequest(
-                    request, packageName, userId, callback);
+            if (request.isSkipRoleGrant()) {
+                checkCallerCanSkipRoleGrant();
+                mAssociationRequestsProcessor.createAssociation(userId, packageName,
+                        /* macAddress= */ null, request.getDisplayName(),
+                        request.getDeviceProfile(), /* associatedDevice= */ null,
+                        request.isSelfManaged(), callback, /* resultReceiver= */ null,
+                        request.getDeviceIcon(), /* skipRoleGrant= */ true);
+            } else {
+                mAssociationRequestsProcessor.processNewAssociationRequest(
+                        request, packageName, userId, callback);
+            }
         }
 
         @Override
@@ -669,7 +679,7 @@
 
             final MacAddress macAddressObj = MacAddress.fromString(macAddress);
             mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddressObj,
-                    null, null, null, false, null, null, null);
+                    null, null, null, false, null, null, null, false);
         }
 
         private void checkCanCallNotificationApi(String callingPackage, int userId) {
@@ -684,6 +694,19 @@
                     "App must have an association before calling this API");
         }
 
+        private void checkCallerCanSkipRoleGrant() {
+            final KeyguardManager keyguardManager =
+                    getContext().getSystemService(KeyguardManager.class);
+            if (keyguardManager != null && keyguardManager.isKeyguardSecure()) {
+                throw new SecurityException("Skipping CDM role grant requires insecure keyguard.");
+            }
+            if (getContext().checkCallingPermission(ASSOCIATE_COMPANION_DEVICES)
+                    != PERMISSION_GRANTED) {
+                throw new SecurityException(
+                        "Skipping CDM role grant requires ASSOCIATE_COMPANION_DEVICES permission.");
+            }
+        }
+
         @Override
         public boolean canPairWithoutPrompt(String packageName, String macAddress, int userId) {
             final AssociationInfo association =
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 3508f2f..e7d1460 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -106,8 +106,9 @@
                     boolean selfManaged = getNextBooleanArg();
                     final MacAddress macAddress = MacAddress.fromString(address);
                     mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
-                            deviceProfile, deviceProfile, /* associatedDevice */ null, selfManaged,
-                            /* callback */ null, /* resultReceiver */ null, /* deviceIcon */ null);
+                            deviceProfile, deviceProfile, /* associatedDevice= */ null, selfManaged,
+                            /* callback= */ null, /* resultReceiver= */ null,
+                            /* deviceIcon= */ null, /* skipRoleGrant= */ false);
                 }
                 break;
 
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index 899b302..8c2c63c 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -282,8 +282,8 @@
         Binder.withCleanCallingIdentity(() -> {
             createAssociation(userId, packageName, macAddress, request.getDisplayName(),
                     request.getDeviceProfile(), request.getAssociatedDevice(),
-                    request.isSelfManaged(),
-                    callback, resultReceiver, request.getDeviceIcon());
+                    request.isSelfManaged(), callback, resultReceiver, request.getDeviceIcon(),
+                    /* skipRoleGrant= */ false);
         });
     }
 
@@ -294,7 +294,8 @@
             @Nullable MacAddress macAddress, @Nullable CharSequence displayName,
             @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
             boolean selfManaged, @Nullable IAssociationRequestCallback callback,
-            @Nullable ResultReceiver resultReceiver, @Nullable Icon deviceIcon) {
+            @Nullable ResultReceiver resultReceiver, @Nullable Icon deviceIcon,
+            boolean skipRoleGrant) {
         final int id = mAssociationStore.getNextId();
         final long timestamp = System.currentTimeMillis();
 
@@ -303,8 +304,17 @@
                 selfManaged, /* notifyOnDeviceNearby */ false, /* revoked */ false,
                 /* pending */ false, timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0,
                 deviceIcon, /* deviceId */ null);
-        // Add role holder for association (if specified) and add new association to store.
-        maybeGrantRoleAndStoreAssociation(association, callback, resultReceiver);
+
+        if (skipRoleGrant) {
+            Slog.i(TAG, "Created association for " + association.getDeviceProfile() + " and userId="
+                    + association.getUserId() + ", packageName="
+                    + association.getPackageName() + " without granting role");
+            mAssociationStore.addAssociation(association);
+            sendCallbackAndFinish(association, callback, resultReceiver);
+        } else {
+            // Add role holder for association (if specified) and add new association to store.
+            maybeGrantRoleAndStoreAssociation(association, callback, resultReceiver);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/TradeInModeService.java b/services/core/java/com/android/server/TradeInModeService.java
index 70a0330..a696673 100644
--- a/services/core/java/com/android/server/TradeInModeService.java
+++ b/services/core/java/com/android/server/TradeInModeService.java
@@ -137,12 +137,13 @@
                 Slog.i(TAG, "Not starting trade-in mode, device is setup.");
                 return false;
             }
-            if (SystemProperties.getInt("ro.debuggable", 0) == 1) {
-                // We don't want to force adbd into TIM on debug builds.
-                Slog.e(TAG, "Not starting trade-in mode, device is debuggable.");
-                return false;
-            }
-            if (isAdbEnabled()) {
+            if (isDebuggable()) {
+                if (!isForceEnabledForTesting()) {
+                    // We don't want to force adbd into TIM on debug builds.
+                    Slog.e(TAG, "Not starting trade-in mode, device is debuggable.");
+                    return false;
+                }
+            } else if (isAdbEnabled()) {
                 Slog.e(TAG, "Not starting trade-in mode, adb is already enabled.");
                 return false;
             }
@@ -234,6 +235,10 @@
         return SystemProperties.getInt("ro.debuggable", 0) == 1;
     }
 
+    private boolean isForceEnabledForTesting() {
+        return SystemProperties.getInt("persist.adb.test_tradeinmode", 0) == 1;
+    }
+
     private boolean isAdbEnabled() {
         final ContentResolver cr = mContext.getContentResolver();
         return Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) == 1;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0603c45..6ece265 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19397,13 +19397,13 @@
         if (((intent.getExtendedFlags() & Intent.EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED) == 0)
                 && intent.getExtras() != null && intent.getExtras().hasIntent()) {
             Slog.wtf(TAG,
-                    "[IntentRedirect] The intent does not have its nested keys collected as a "
+                    "[IntentRedirect Hardening] The intent does not have its nested keys collected as a "
                             + "preparation for creating intent creator tokens. Intent: "
                             + intent + "; creatorPackage: " + creatorPackage);
             if (preventIntentRedirectShowToastIfNestedKeysNotCollectedRW()) {
                 UiThread.getHandler().post(
                         () -> Toast.makeText(mContext,
-                                "Nested keys not collected. go/report-bug-intentRedir to report a"
+                                "Nested keys not collected, activity launch won't be blocked. go/report-bug-intentRedir to report a"
                                         + " bug", Toast.LENGTH_LONG).show());
             }
             if (preventIntentRedirectThrowExceptionIfNestedKeysNotCollected()) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 8335998..e8a2226 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -237,6 +237,13 @@
      */
     private static final int CURRENT_VERSION = 1;
 
+    /**
+     * The upper limit of total number of attributed op entries that can be returned in a binder
+     * transaction to avoid TransactionTooLargeException
+     */
+    private static final int NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD = 2000;
+
+
     private SensorPrivacyManager mSensorPrivacyManager;
 
     // Write at most every 30 minutes.
@@ -1702,6 +1709,8 @@
                 Manifest.permission.GET_APP_OPS_STATS,
                 Binder.getCallingPid(), Binder.getCallingUid())
                 == PackageManager.PERMISSION_GRANTED;
+        int totalAttributedOpEntryCount = 0;
+
         if (ops == null) {
             resOps = new ArrayList<>();
             for (int j = 0; j < pkgOps.size(); j++) {
@@ -1709,7 +1718,12 @@
                 if (opRestrictsRead(curOp.op) && !shouldReturnRestrictedAppOps) {
                     continue;
                 }
-                resOps.add(getOpEntryForResult(curOp, persistentDeviceId));
+                if (totalAttributedOpEntryCount > NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD) {
+                    break;
+                }
+                OpEntry opEntry = getOpEntryForResult(curOp, persistentDeviceId);
+                resOps.add(opEntry);
+                totalAttributedOpEntryCount += opEntry.getAttributedOpEntries().size();
             }
         } else {
             for (int j = 0; j < ops.length; j++) {
@@ -1721,10 +1735,21 @@
                     if (opRestrictsRead(curOp.op) && !shouldReturnRestrictedAppOps) {
                         continue;
                     }
-                    resOps.add(getOpEntryForResult(curOp, persistentDeviceId));
+                    if (totalAttributedOpEntryCount > NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD) {
+                        break;
+                    }
+                    OpEntry opEntry = getOpEntryForResult(curOp, persistentDeviceId);
+                    resOps.add(opEntry);
+                    totalAttributedOpEntryCount += opEntry.getAttributedOpEntries().size();
                 }
             }
         }
+
+        if (totalAttributedOpEntryCount > NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD) {
+            Slog.w(TAG, "The number of attributed op entries has exceeded the threshold. This "
+                    + "could be due to DoS attack from malicious apps. The result is throttled.");
+        }
+
         return resOps;
     }
 
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 373287d..a1e8f08 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -61,7 +61,7 @@
 
     private final FlagState mDisplayTopology = new FlagState(
             Flags.FLAG_DISPLAY_TOPOLOGY,
-            Flags::displayTopology);
+            DesktopExperienceFlags.DISPLAY_TOPOLOGY::isTrue);
 
     private final FlagState mConnectedDisplayErrorHandlingFlagState = new FlagState(
             Flags.FLAG_ENABLE_CONNECTED_DISPLAY_ERROR_HANDLING,
@@ -267,7 +267,7 @@
 
     private final FlagState mBaseDensityForExternalDisplays = new FlagState(
             Flags.FLAG_BASE_DENSITY_FOR_EXTERNAL_DISPLAYS,
-            Flags::baseDensityForExternalDisplays
+            DesktopExperienceFlags.BASE_DENSITY_FOR_EXTERNAL_DISPLAYS::isTrue
     );
 
     private final FlagState mFramerateOverrideTriggersRrCallbacks = new FlagState(
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index bbd0e41..7890db1 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -509,3 +509,11 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "display_category_built_in"
+    namespace: "display_manager"
+    description: "Add a new category to get the built in displays."
+    bug: "293651324"
+    is_fixed_read_only: false
+}
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index 43fb8d0..d00ac4d 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -35,6 +35,7 @@
 import android.hardware.tv.mediaquality.ISoundProfileAdjustmentListener;
 import android.hardware.tv.mediaquality.ISoundProfileChangedListener;
 import android.hardware.tv.mediaquality.ParamCapability;
+import android.hardware.tv.mediaquality.ParameterRange;
 import android.hardware.tv.mediaquality.PictureParameter;
 import android.hardware.tv.mediaquality.PictureParameters;
 import android.hardware.tv.mediaquality.SoundParameter;
@@ -1133,7 +1134,39 @@
         @Override
         public List<ParameterCapability> getParameterCapabilities(
                 List<String> names, UserHandle user) {
-            return new ArrayList<>();
+            byte[] byteArray = MediaQualityUtils.convertParameterToByteArray(names);
+            ParamCapability[] caps = new ParamCapability[byteArray.length];
+            try {
+                mMediaQuality.getParamCaps(byteArray, caps);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to get parameter capabilities", e);
+            }
+
+            return getListParameterCapability(caps);
+        }
+
+        private List<ParameterCapability> getListParameterCapability(ParamCapability[] caps) {
+            List<ParameterCapability> pcList = new ArrayList<>();
+            for (ParamCapability pcHal : caps) {
+                String name = MediaQualityUtils.getParameterName(pcHal.name);
+                boolean isSupported = pcHal.isSupported;
+                int type = pcHal.defaultValue == null ? 0 : pcHal.defaultValue.getTag() + 1;
+                Bundle bundle = convertToCaps(pcHal.range);
+
+                pcList.add(new ParameterCapability(name, isSupported, type, bundle));
+            }
+            return pcList;
+        }
+
+        private Bundle convertToCaps(ParameterRange range) {
+            Bundle bundle = new Bundle();
+            bundle.putObject("INT_MIN_MAX", range.numRange.getIntMinMax());
+            bundle.putObject("INT_VALUES_SUPPORTED", range.numRange.getIntValuesSupported());
+            bundle.putObject("DOUBLE_MIN_MAX", range.numRange.getDoubleMinMax());
+            bundle.putObject("DOUBLE_VALUES_SUPPORTED", range.numRange.getDoubleValuesSupported());
+            bundle.putObject("LONG_MIN_MAX", range.numRange.getLongMinMax());
+            bundle.putObject("LONG_VALUES_SUPPORTED", range.numRange.getLongValuesSupported());
+            return bundle;
         }
 
         @GuardedBy("mPictureProfileLock")
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityUtils.java b/services/core/java/com/android/server/media/quality/MediaQualityUtils.java
index a74f977..5bd4420 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityUtils.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityUtils.java
@@ -20,6 +20,7 @@
 import android.database.Cursor;
 import android.hardware.tv.mediaquality.DolbyAudioProcessing;
 import android.hardware.tv.mediaquality.DtsVirtualX;
+import android.hardware.tv.mediaquality.ParameterName;
 import android.hardware.tv.mediaquality.PictureParameter;
 import android.hardware.tv.mediaquality.SoundParameter;
 import android.media.quality.MediaQualityContract.BaseParameters;
@@ -37,8 +38,11 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 /**
@@ -1012,6 +1016,495 @@
         );
     }
 
+    /**
+     * Convert parameter to byte array.
+     */
+    public static byte[] convertParameterToByteArray(List<String> names) {
+        /**
+         * TODO Add following to ParameterName & add conversion here.
+         * - PICTURE_QUALITY_EVENT_TYPE
+         * - PANEL_INIT_MAX_LUMINCE_NITS
+         */
+
+        HashSet<String> nameMap = new HashSet<>(names);
+
+        List<Byte> bytes = new ArrayList<>();
+        // Picture Quality parameters
+        if (nameMap.contains(PictureQuality.PARAMETER_BRIGHTNESS)) {
+            bytes.add(ParameterName.BRIGHTNESS);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_BRIGHTNESS)) {
+            bytes.add(ParameterName.BRIGHTNESS);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_CONTRAST)) {
+            bytes.add(ParameterName.CONTRAST);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_SHARPNESS)) {
+            bytes.add(ParameterName.SHARPNESS);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_SATURATION)) {
+            bytes.add(ParameterName.SATURATION);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_HUE)) {
+            bytes.add(ParameterName.HUE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_BRIGHTNESS)) {
+            bytes.add(ParameterName.BRIGHTNESS);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS)) {
+            bytes.add(ParameterName.COLOR_TUNER_BRIGHTNESS);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_SATURATION)) {
+            bytes.add(ParameterName.SATURATION);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION)) {
+            bytes.add(ParameterName.COLOR_TUNER_SATURATION);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_HUE)) {
+            bytes.add(ParameterName.HUE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE)) {
+            bytes.add(ParameterName.COLOR_TUNER_HUE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET)) {
+            bytes.add(ParameterName.COLOR_TUNER_RED_OFFSET);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET)) {
+            bytes.add(ParameterName.COLOR_TUNER_GREEN_OFFSET);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET)) {
+            bytes.add(ParameterName.COLOR_TUNER_BLUE_OFFSET);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)) {
+            bytes.add(ParameterName.COLOR_TUNER_RED_GAIN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)) {
+            bytes.add(ParameterName.COLOR_TUNER_GREEN_GAIN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)) {
+            bytes.add(ParameterName.COLOR_TUNER_BLUE_GAIN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_NOISE_REDUCTION)) {
+            bytes.add(ParameterName.NOISE_REDUCTION);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION)) {
+            bytes.add(ParameterName.MPEG_NOISE_REDUCTION);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_FLESH_TONE)) {
+            bytes.add(ParameterName.FLASH_TONE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_DECONTOUR)) {
+            bytes.add(ParameterName.DE_CONTOUR);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL)) {
+            bytes.add(ParameterName.DYNAMIC_LUMA_CONTROL);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_FILM_MODE)) {
+            bytes.add(ParameterName.FILM_MODE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_BLUE_STRETCH)) {
+            bytes.add(ParameterName.BLUE_STRETCH);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNE)) {
+            bytes.add(ParameterName.COLOR_TUNE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TEMPERATURE)) {
+            bytes.add(ParameterName.COLOR_TEMPERATURE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_GLOBAL_DIMMING)) {
+            bytes.add(ParameterName.GLOBE_DIMMING);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED)) {
+            bytes.add(ParameterName.AUTO_PICTUREQUALITY_ENABLED);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED)) {
+            bytes.add(ParameterName.AUTO_SUPER_RESOLUTION_ENABLED);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_LEVEL_RANGE)) {
+            bytes.add(ParameterName.LEVEL_RANGE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_GAMUT_MAPPING)) {
+            bytes.add(ParameterName.GAMUT_MAPPING);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_PC_MODE)) {
+            bytes.add(ParameterName.PC_MODE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_LOW_LATENCY)) {
+            bytes.add(ParameterName.LOW_LATENCY);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_VRR)) {
+            bytes.add(ParameterName.VRR);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_CVRR)) {
+            bytes.add(ParameterName.CVRR);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_HDMI_RGB_RANGE)) {
+            bytes.add(ParameterName.HDMI_RGB_RANGE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_SPACE)) {
+            bytes.add(ParameterName.COLOR_SPACE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID)) {
+            bytes.add(ParameterName.PANEL_INIT_MAX_LUMINCE_VALID);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_GAMMA)) {
+            bytes.add(ParameterName.GAMMA);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET)) {
+            bytes.add(ParameterName.COLOR_TEMPERATURE_RED_OFFSET);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET)) {
+            bytes.add(ParameterName.COLOR_TEMPERATURE_GREEN_OFFSET);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET)) {
+            bytes.add(ParameterName.COLOR_TEMPERATURE_BLUE_OFFSET);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_ELEVEN_POINT_RED)) {
+            bytes.add(ParameterName.ELEVEN_POINT_RED);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_ELEVEN_POINT_GREEN)) {
+            bytes.add(ParameterName.ELEVEN_POINT_GREEN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_ELEVEN_POINT_BLUE)) {
+            bytes.add(ParameterName.ELEVEN_POINT_BLUE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_LOW_BLUE_LIGHT)) {
+            bytes.add(ParameterName.LOW_BLUE_LIGHT);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_LD_MODE)) {
+            bytes.add(ParameterName.LD_MODE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_OSD_RED_GAIN)) {
+            bytes.add(ParameterName.OSD_RED_GAIN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_OSD_GREEN_GAIN)) {
+            bytes.add(ParameterName.OSD_GREEN_GAIN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_OSD_BLUE_GAIN)) {
+            bytes.add(ParameterName.OSD_BLUE_GAIN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_OSD_RED_OFFSET)) {
+            bytes.add(ParameterName.OSD_RED_OFFSET);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_OSD_GREEN_OFFSET)) {
+            bytes.add(ParameterName.OSD_GREEN_OFFSET);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_OSD_BLUE_OFFSET)) {
+            bytes.add(ParameterName.OSD_BLUE_OFFSET);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_OSD_HUE)) {
+            bytes.add(ParameterName.OSD_HUE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_OSD_SATURATION)) {
+            bytes.add(ParameterName.OSD_SATURATION);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_OSD_CONTRAST)) {
+            bytes.add(ParameterName.OSD_CONTRAST);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SWITCH)) {
+            bytes.add(ParameterName.COLOR_TUNER_SWITCH);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED)) {
+            bytes.add(ParameterName.COLOR_TUNER_HUE_RED);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN)) {
+            bytes.add(ParameterName.COLOR_TUNER_HUE_GREEN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE)) {
+            bytes.add(ParameterName.COLOR_TUNER_HUE_BLUE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN)) {
+            bytes.add(ParameterName.COLOR_TUNER_HUE_CYAN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA)) {
+            bytes.add(ParameterName.COLOR_TUNER_HUE_MAGENTA);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW)) {
+            bytes.add(ParameterName.COLOR_TUNER_HUE_YELLOW);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH)) {
+            bytes.add(ParameterName.COLOR_TUNER_HUE_FLESH);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED)) {
+            bytes.add(ParameterName.COLOR_TUNER_SATURATION_RED);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN)) {
+            bytes.add(ParameterName.COLOR_TUNER_SATURATION_GREEN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE)) {
+            bytes.add(ParameterName.COLOR_TUNER_SATURATION_BLUE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN)) {
+            bytes.add(ParameterName.COLOR_TUNER_SATURATION_CYAN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA)) {
+            bytes.add(ParameterName.COLOR_TUNER_SATURATION_MAGENTA);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW)) {
+            bytes.add(ParameterName.COLOR_TUNER_SATURATION_YELLOW);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH)) {
+            bytes.add(ParameterName.COLOR_TUNER_SATURATION_FLESH);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED)) {
+            bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_RED);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN)) {
+            bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_GREEN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE)) {
+            bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_BLUE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN)) {
+            bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_CYAN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA)) {
+            bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_MAGENTA);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW)) {
+            bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_YELLOW);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH)) {
+            bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_FLESH);
+        }
+
+        // Sound Quality parameters
+        if (nameMap.contains(SoundQuality.PARAMETER_BALANCE)) {
+            bytes.add(ParameterName.BALANCE);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_BASS)) {
+            bytes.add(ParameterName.BASS);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_TREBLE)) {
+            bytes.add(ParameterName.TREBLE);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_SURROUND_SOUND)) {
+            bytes.add(ParameterName.SURROUND_SOUND_ENABLED);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_EQUALIZER_DETAIL)) {
+            bytes.add(ParameterName.EQUALIZER_DETAIL);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_SPEAKERS)) {
+            bytes.add(ParameterName.SPEAKERS_ENABLED);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS)) {
+            bytes.add(ParameterName.SPEAKERS_DELAY_MS);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_EARC)) {
+            bytes.add(ParameterName.ENHANCED_AUDIO_RETURN_CHANNEL_ENABLED);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL)) {
+            bytes.add(ParameterName.AUTO_VOLUME_CONTROL);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_DOWN_MIX_MODE)) {
+            bytes.add(ParameterName.DOWNMIX_MODE);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_DTS_DRC)) {
+            bytes.add(ParameterName.DTS_DRC);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING)) {
+            bytes.add(ParameterName.DOLBY_AUDIO_PROCESSING);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_DIALOGUE_ENHANCER)) {
+            bytes.add(ParameterName.DOLBY_DIALOGUE_ENHANCER);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_DTS_VIRTUAL_X)) {
+            bytes.add(ParameterName.DTS_VIRTUAL_X);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS)) {
+            bytes.add(ParameterName.DIGITAL_OUTPUT_DELAY_MS);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE)) {
+            bytes.add(ParameterName.DIGITAL_OUTPUT);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_SOUND_STYLE)) {
+            bytes.add(ParameterName.SOUND_STYLE);
+        }
+
+        byte[] byteArray = new byte[bytes.size()];
+        for (int i = 0; i < bytes.size(); i++) {
+            byteArray[i] = bytes.get(i);
+        }
+        return byteArray;
+    }
+
+    /**
+     * Get Parameter Name based on byte.
+     */
+    public static String getParameterName(byte pn) {
+        Map<Byte, String> parameterNameMap = new HashMap<>();
+        parameterNameMap.put(ParameterName.BRIGHTNESS, PictureQuality.PARAMETER_BRIGHTNESS);
+        parameterNameMap.put(ParameterName.CONTRAST, PictureQuality.PARAMETER_CONTRAST);
+        parameterNameMap.put(ParameterName.SHARPNESS, PictureQuality.PARAMETER_SHARPNESS);
+        parameterNameMap.put(ParameterName.SATURATION, PictureQuality.PARAMETER_SATURATION);
+        parameterNameMap.put(ParameterName.HUE, PictureQuality.PARAMETER_HUE);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_BRIGHTNESS,
+                PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION,
+                PictureQuality.PARAMETER_COLOR_TUNER_SATURATION);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_HUE,
+                PictureQuality.PARAMETER_COLOR_TUNER_HUE);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_RED_OFFSET,
+                PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_GREEN_OFFSET,
+                PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_BLUE_OFFSET,
+                PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_RED_GAIN,
+                PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_GREEN_GAIN,
+                PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_BLUE_GAIN,
+                PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN);
+        parameterNameMap.put(ParameterName.NOISE_REDUCTION,
+                PictureQuality.PARAMETER_NOISE_REDUCTION);
+        parameterNameMap.put(ParameterName.MPEG_NOISE_REDUCTION,
+                PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION);
+        parameterNameMap.put(ParameterName.FLASH_TONE, PictureQuality.PARAMETER_FLESH_TONE);
+        parameterNameMap.put(ParameterName.DE_CONTOUR, PictureQuality.PARAMETER_DECONTOUR);
+        parameterNameMap.put(ParameterName.DYNAMIC_LUMA_CONTROL,
+                PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL);
+        parameterNameMap.put(ParameterName.FILM_MODE,
+                PictureQuality.PARAMETER_FILM_MODE);
+        parameterNameMap.put(ParameterName.BLACK_STRETCH,
+                PictureQuality.PARAMETER_BLACK_STRETCH);
+        parameterNameMap.put(ParameterName.BLUE_STRETCH,
+                PictureQuality.PARAMETER_BLUE_STRETCH);
+        parameterNameMap.put(ParameterName.COLOR_TUNE,
+                PictureQuality.PARAMETER_COLOR_TUNE);
+        parameterNameMap.put(ParameterName.COLOR_TEMPERATURE,
+                PictureQuality.PARAMETER_COLOR_TEMPERATURE);
+        parameterNameMap.put(ParameterName.GLOBE_DIMMING,
+                PictureQuality.PARAMETER_GLOBAL_DIMMING);
+        parameterNameMap.put(ParameterName.AUTO_PICTUREQUALITY_ENABLED,
+                PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED);
+        parameterNameMap.put(ParameterName.AUTO_SUPER_RESOLUTION_ENABLED,
+                PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED);
+        parameterNameMap.put(ParameterName.LEVEL_RANGE, PictureQuality.PARAMETER_LEVEL_RANGE);
+        parameterNameMap.put(ParameterName.GAMUT_MAPPING,
+                PictureQuality.PARAMETER_GAMUT_MAPPING);
+        parameterNameMap.put(ParameterName.PC_MODE, PictureQuality.PARAMETER_PC_MODE);
+        parameterNameMap.put(ParameterName.LOW_LATENCY, PictureQuality.PARAMETER_LOW_LATENCY);
+        parameterNameMap.put(ParameterName.VRR, PictureQuality.PARAMETER_VRR);
+        parameterNameMap.put(ParameterName.CVRR, PictureQuality.PARAMETER_CVRR);
+        parameterNameMap.put(ParameterName.HDMI_RGB_RANGE,
+                PictureQuality.PARAMETER_HDMI_RGB_RANGE);
+        parameterNameMap.put(ParameterName.COLOR_SPACE, PictureQuality.PARAMETER_COLOR_SPACE);
+        parameterNameMap.put(ParameterName.PANEL_INIT_MAX_LUMINCE_VALID,
+                PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID);
+        parameterNameMap.put(ParameterName.GAMMA, PictureQuality.PARAMETER_GAMMA);
+        parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_RED_GAIN,
+                PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_GAIN);
+        parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_GREEN_GAIN,
+                PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_GAIN);
+        parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_BLUE_GAIN,
+                PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_GAIN);
+        parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_RED_OFFSET,
+                PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET);
+        parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_GREEN_OFFSET,
+                PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET);
+        parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_BLUE_OFFSET,
+                PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET);
+        parameterNameMap.put(ParameterName.ELEVEN_POINT_RED,
+                PictureQuality.PARAMETER_ELEVEN_POINT_RED);
+        parameterNameMap.put(ParameterName.ELEVEN_POINT_GREEN,
+                PictureQuality.PARAMETER_ELEVEN_POINT_GREEN);
+        parameterNameMap.put(ParameterName.ELEVEN_POINT_BLUE,
+                PictureQuality.PARAMETER_ELEVEN_POINT_BLUE);
+        parameterNameMap.put(ParameterName.LOW_BLUE_LIGHT,
+                PictureQuality.PARAMETER_LOW_BLUE_LIGHT);
+        parameterNameMap.put(ParameterName.LD_MODE, PictureQuality.PARAMETER_LD_MODE);
+        parameterNameMap.put(ParameterName.OSD_RED_GAIN, PictureQuality.PARAMETER_OSD_RED_GAIN);
+        parameterNameMap.put(ParameterName.OSD_GREEN_GAIN,
+                PictureQuality.PARAMETER_OSD_GREEN_GAIN);
+        parameterNameMap.put(ParameterName.OSD_BLUE_GAIN,
+                PictureQuality.PARAMETER_OSD_BLUE_GAIN);
+        parameterNameMap.put(ParameterName.OSD_RED_OFFSET,
+                PictureQuality.PARAMETER_OSD_RED_OFFSET);
+        parameterNameMap.put(ParameterName.OSD_GREEN_OFFSET,
+                PictureQuality.PARAMETER_OSD_GREEN_OFFSET);
+        parameterNameMap.put(ParameterName.OSD_BLUE_OFFSET,
+                PictureQuality.PARAMETER_OSD_BLUE_OFFSET);
+        parameterNameMap.put(ParameterName.OSD_HUE, PictureQuality.PARAMETER_OSD_HUE);
+        parameterNameMap.put(ParameterName.OSD_SATURATION,
+                PictureQuality.PARAMETER_OSD_SATURATION);
+        parameterNameMap.put(ParameterName.OSD_CONTRAST,
+                PictureQuality.PARAMETER_OSD_CONTRAST);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_SWITCH,
+                PictureQuality.PARAMETER_COLOR_TUNER_SWITCH);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_RED,
+                PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_GREEN,
+                PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_BLUE,
+                PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_CYAN,
+                PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_MAGENTA,
+                PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_YELLOW,
+                PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_FLESH,
+                PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_RED,
+                PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_GREEN,
+                PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_BLUE,
+                PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_CYAN,
+                PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_MAGENTA,
+                PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_YELLOW,
+                PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_FLESH,
+                PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_RED,
+                PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_GREEN,
+                PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_BLUE,
+                PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_CYAN,
+                PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_MAGENTA,
+                PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_YELLOW,
+                PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_FLESH,
+                PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH);
+        parameterNameMap.put(ParameterName.BALANCE, SoundQuality.PARAMETER_BALANCE);
+        parameterNameMap.put(ParameterName.BASS, SoundQuality.PARAMETER_BASS);
+        parameterNameMap.put(ParameterName.TREBLE, SoundQuality.PARAMETER_TREBLE);
+        parameterNameMap.put(ParameterName.SURROUND_SOUND_ENABLED,
+                SoundQuality.PARAMETER_SURROUND_SOUND);
+        parameterNameMap.put(ParameterName.EQUALIZER_DETAIL,
+                SoundQuality.PARAMETER_EQUALIZER_DETAIL);
+        parameterNameMap.put(ParameterName.SPEAKERS_ENABLED, SoundQuality.PARAMETER_SPEAKERS);
+        parameterNameMap.put(ParameterName.SPEAKERS_DELAY_MS,
+                SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS);
+        parameterNameMap.put(ParameterName.ENHANCED_AUDIO_RETURN_CHANNEL_ENABLED,
+                SoundQuality.PARAMETER_EARC);
+        parameterNameMap.put(ParameterName.AUTO_VOLUME_CONTROL,
+                SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL);
+        parameterNameMap.put(ParameterName.DOWNMIX_MODE, SoundQuality.PARAMETER_DOWN_MIX_MODE);
+        parameterNameMap.put(ParameterName.DTS_DRC, SoundQuality.PARAMETER_DTS_DRC);
+        parameterNameMap.put(ParameterName.DOLBY_AUDIO_PROCESSING,
+                SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING);
+        parameterNameMap.put(ParameterName.DOLBY_DIALOGUE_ENHANCER,
+                SoundQuality.PARAMETER_DIALOGUE_ENHANCER);
+        parameterNameMap.put(ParameterName.DTS_VIRTUAL_X,
+                SoundQuality.PARAMETER_DTS_VIRTUAL_X);
+        parameterNameMap.put(ParameterName.DIGITAL_OUTPUT,
+                SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE);
+        parameterNameMap.put(ParameterName.DIGITAL_OUTPUT_DELAY_MS,
+                SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS);
+        parameterNameMap.put(ParameterName.SOUND_STYLE, SoundQuality.PARAMETER_SOUND_STYLE);
+
+        return parameterNameMap.get(pn);
+    }
+
     private static String getTempId(BiMap<Long, String> map, Cursor cursor) {
         int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_ID);
         Long dbId = colIndex != -1 ? cursor.getLong(colIndex) : null;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a16b122..6d565d2 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -679,6 +679,8 @@
     WorkerHandler mHandler;
     private final HandlerThread mRankingThread = new HandlerThread("ranker",
             Process.THREAD_PRIORITY_BACKGROUND);
+    @FlaggedApi(Flags.FLAG_NM_BINDER_PERF_THROTTLE_EFFECTS_SUPPRESSOR_BROADCAST)
+    private Handler mBroadcastsHandler;
 
     private final SparseArray<ArraySet<ComponentName>> mListenersDisablingEffects =
             new SparseArray<>();
@@ -2682,7 +2684,7 @@
 
     // TODO: All tests should use this init instead of the one-off setters above.
     @VisibleForTesting
-    void init(WorkerHandler handler, RankingHandler rankingHandler,
+    void init(WorkerHandler handler, RankingHandler rankingHandler, Handler broadcastsHandler,
             IPackageManager packageManager, PackageManager packageManagerClient,
             LightsManager lightsManager, NotificationListeners notificationListeners,
             NotificationAssistants notificationAssistants, ConditionProviders conditionProviders,
@@ -2702,6 +2704,9 @@
             ConnectivityManager connectivityManager,
             PostNotificationTrackerFactory postNotificationTrackerFactory) {
         mHandler = handler;
+        if (Flags.nmBinderPerfThrottleEffectsSuppressorBroadcast()) {
+            mBroadcastsHandler = broadcastsHandler;
+        }
         Resources resources = getContext().getResources();
         mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
                 Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
@@ -3045,13 +3050,22 @@
 
         WorkerHandler handler = new WorkerHandler(Looper.myLooper());
 
+        Handler broadcastsHandler;
+        if (Flags.nmBinderPerfThrottleEffectsSuppressorBroadcast()) {
+            HandlerThread broadcastsThread = new HandlerThread("NMS Broadcasts");
+            broadcastsThread.start();
+            broadcastsHandler = new Handler(broadcastsThread.getLooper());
+        } else {
+            broadcastsHandler = null;
+        }
+
         mShowReviewPermissionsNotification = getContext().getResources().getBoolean(
                 R.bool.config_notificationReviewPermissions);
 
         mDefaultUnsupportedAdjustments = getContext().getResources().getStringArray(
                 R.array.config_notificationDefaultUnsupportedAdjustments);
 
-        init(handler, new RankingHandlerWorker(mRankingThread.getLooper()),
+        init(handler, new RankingHandlerWorker(mRankingThread.getLooper()), broadcastsHandler,
                 AppGlobals.getPackageManager(), getContext().getPackageManager(),
                 getLocalService(LightsManager.class),
                 new NotificationListeners(getContext(), mNotificationLock, mUserProfiles,
@@ -3297,10 +3311,11 @@
      * so that e.g. rapidly changing some value A -> B -> C will only produce a broadcast for C
      * (instead of every time because the extras are different).
      */
+    @FlaggedApi(Flags.FLAG_NM_BINDER_PERF_THROTTLE_EFFECTS_SUPPRESSOR_BROADCAST)
     private void sendZenBroadcastWithDelay(Intent intent) {
         String token = "zen_broadcast:" + intent.getAction();
-        mHandler.removeCallbacksAndEqualMessages(token);
-        mHandler.postDelayed(() -> sendRegisteredOnlyBroadcast(intent), token,
+        mBroadcastsHandler.removeCallbacksAndEqualMessages(token);
+        mBroadcastsHandler.postDelayed(() -> sendRegisteredOnlyBroadcast(intent), token,
                 ZEN_BROADCAST_DELAY.toMillis());
     }
 
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 0ed5228..a80b1b2 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -171,11 +171,11 @@
     void onProposedRotationChanged(int displayId, int rotation, boolean isValid);
 
     /**
-     * Notifies System UI that the display is ready to show system decorations.
+     * Notifies System UI that the system decorations should be added on the display.
      *
      * @param displayId display ID
      */
-    void onDisplayReady(int displayId);
+    void onDisplayAddSystemDecorations(int displayId);
 
     /**
      * Notifies System UI that the system decorations should be removed from the display.
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 4301c93..c546388 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -770,10 +770,11 @@
         }
 
         @Override
-        public void onDisplayReady(int displayId) {
+        public void onDisplayAddSystemDecorations(int displayId) {
             if (isVisibleBackgroundUserOnDisplay(displayId)) {
                 if (SPEW) {
-                    Slog.d(TAG, "Skipping onDisplayReady for visible background user "
+                    Slog.d(TAG, "Skipping onDisplayAddSystemDecorations for visible background "
+                            + "user "
                             + mUserManagerInternal.getUserAssignedToDisplay(displayId));
                 }
                 return;
@@ -781,7 +782,7 @@
             IStatusBar bar = mBar;
             if (bar != null) {
                 try {
-                    bar.onDisplayReady(displayId);
+                    bar.onDisplayAddSystemDecorations(displayId);
                 } catch (RemoteException ex) {}
             }
         }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
index 872ab59..f413fe3 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
@@ -26,7 +26,7 @@
     /**
      * Notifies the display is ready for adding wallpaper on it.
      */
-    public abstract void onDisplayReady(int displayId);
+    public abstract void onDisplayAddSystemDecorations(int displayId);
 
     /** Notifies when display stop showing system decorations and wallpaper. */
     public abstract void onDisplayRemoveSystemDecorations(int displayId);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index db530e7..09b1073 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1636,8 +1636,8 @@
 
     private final class LocalService extends WallpaperManagerInternal {
         @Override
-        public void onDisplayReady(int displayId) {
-            onDisplayReadyInternal(displayId);
+        public void onDisplayAddSystemDecorations(int displayId) {
+            onDisplayAddSystemDecorationsInternal(displayId);
         }
 
         @Override
@@ -3944,7 +3944,7 @@
         return (wallpaper != null) ? wallpaper.allowBackup : false;
     }
 
-    private void onDisplayReadyInternal(int displayId) {
+    private void onDisplayAddSystemDecorationsInternal(int displayId) {
         synchronized (mLock) {
             if (mLastWallpaper == null) {
                 return;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index c7d4467..6f76618 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -3655,7 +3655,7 @@
     private static String getIntentRedirectPreventedLogMessage(@NonNull String message,
             @NonNull Intent intent, int intentCreatorUid, @Nullable String intentCreatorPackage,
             int callingUid, @Nullable String callingPackage) {
-        return "[IntentRedirect]" + message + " intentCreatorUid: " + intentCreatorUid
+        return "[IntentRedirect Hardening] " + message + " intentCreatorUid: " + intentCreatorUid
                 + "; intentCreatorPackage: " + intentCreatorPackage + "; callingUid: " + callingUid
                 + "; callingPackage: " + callingPackage + "; intent: " + intent;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 5b16199..5329e3b 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1887,17 +1887,17 @@
         mCanSystemBarsBeShownByUser = canBeShown;
     }
 
-    void notifyDisplayReady() {
+    void notifyDisplayAddSystemDecorations() {
         mHandler.post(() -> {
             final int displayId = getDisplayId();
             StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
             if (statusBar != null) {
-                statusBar.onDisplayReady(displayId);
+                statusBar.onDisplayAddSystemDecorations(displayId);
             }
             final WallpaperManagerInternal wpMgr = LocalServices
                     .getService(WallpaperManagerInternal.class);
             if (wpMgr != null) {
-                wpMgr.onDisplayReady(displayId);
+                wpMgr.onDisplayAddSystemDecorations(displayId);
             }
         });
     }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index abd26b5..cf464c7 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2845,7 +2845,13 @@
         }
 
         startHomeOnDisplay(mCurrentUser, reason, displayContent.getDisplayId());
-        displayContent.getDisplayPolicy().notifyDisplayReady();
+        if (enableDisplayContentModeManagement()) {
+            if (displayContent.isSystemDecorationsSupported()) {
+                displayContent.getDisplayPolicy().notifyDisplayAddSystemDecorations();
+            }
+        } else {
+            displayContent.getDisplayPolicy().notifyDisplayAddSystemDecorations();
+        }
     }
 
     @Override
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index bf4595c..b37bcc7 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -602,12 +602,14 @@
             return std::to_string(displayId.val());
         };
         dump += StringPrintf(INDENT "Display not interactive: %s\n",
-                             dumpSet(mLocked.nonInteractiveDisplays, streamableToString).c_str());
+                             dumpContainer(mLocked.nonInteractiveDisplays, streamableToString)
+                                     .c_str());
         dump += StringPrintf(INDENT "System UI Lights Out: %s\n",
                              toString(mLocked.systemUiLightsOut));
         dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed);
         dump += StringPrintf(INDENT "Display with Mouse Scaling Disabled: %s\n",
-                             dumpSet(mLocked.displaysWithMouseScalingDisabled, streamableToString)
+                             dumpContainer(mLocked.displaysWithMouseScalingDisabled,
+                                           streamableToString)
                                      .c_str());
         dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n",
                              toString(mLocked.pointerGesturesEnabled));
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
index ea85710..a96c477 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionService.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -177,23 +177,24 @@
         }
     }
 
-    /** Ensures that supervision is enabled when the supervision app is the profile owner. */
+    /**
+     * Ensures that supervision is enabled when the supervision app is the profile owner.
+     *
+     * <p>The state syncing with the DevicePolicyManager can only enable supervision and never
+     * disable. Supervision can only be disabled explicitly via calls to the
+     * {@link #setSupervisionEnabledForUser} method.
+     */
     private void syncStateWithDevicePolicyManager(@UserIdInt int userId) {
         final DevicePolicyManagerInternal dpmInternal = mInjector.getDpmInternal();
         final ComponentName po =
                 dpmInternal != null ? dpmInternal.getProfileOwnerAsUser(userId) : null;
 
         if (po != null && po.getPackageName().equals(getSystemSupervisionPackage())) {
-            setSupervisionEnabledForUserInternal(userId, true, po.getPackageName());
+            setSupervisionEnabledForUserInternal(userId, true, getSystemSupervisionPackage());
         } else if (po != null && po.equals(getSupervisionProfileOwnerComponent())) {
             // TODO(b/392071637): Consider not enabling supervision in case profile owner is given
             // to the legacy supervision profile owner component.
             setSupervisionEnabledForUserInternal(userId, true, po.getPackageName());
-        } else {
-            // TODO(b/381428475): Avoid disabling supervision when the app is not the profile owner.
-            // This might only be possible after introducing specific and public APIs to enable
-            // and disable supervision.
-            setSupervisionEnabledForUserInternal(userId, false, /* supervisionAppPackage= */ null);
         }
     }
 
@@ -279,7 +280,7 @@
         }
 
         @VisibleForTesting
-        @SuppressLint("MissingPermission") // not needed for a service
+        @SuppressLint("MissingPermission")  // not needed for a system service
         void registerProfileOwnerListener() {
             IntentFilter poIntentFilter = new IntentFilter();
             poIntentFilter.addAction(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED);
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index b92afc5..d80fd20 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -731,7 +731,7 @@
         // WHEN display ID, 2, is ready.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
                 WallpaperManagerInternal.class);
-        wallpaperManagerInternal.onDisplayReady(testDisplayId);
+        wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
 
         // Then there is a connection established for the system & lock wallpaper for display ID, 2.
         verify(mockIWallpaperService).attach(
@@ -771,7 +771,7 @@
         // WHEN display ID, 2, is ready.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
                 WallpaperManagerInternal.class);
-        wallpaperManagerInternal.onDisplayReady(testDisplayId);
+        wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
 
         // Then there is a connection established for the system wallpaper for display ID, 2.
         verify(mockIWallpaperService).attach(
@@ -818,7 +818,7 @@
         // WHEN display ID, 2, is ready.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
                 WallpaperManagerInternal.class);
-        wallpaperManagerInternal.onDisplayReady(testDisplayId);
+        wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
 
         // Then there is a connection established for the fallback wallpaper for display ID, 2.
         verify(mockIWallpaperService).attach(
@@ -856,7 +856,7 @@
         // GIVEN wallpaper connections have been established for display ID, 2.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
                 WallpaperManagerInternal.class);
-        wallpaperManagerInternal.onDisplayReady(testDisplayId);
+        wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
         // Save displayConnector for displayId 2 before display removal.
         WallpaperManagerService.DisplayConnector displayConnector =
                 wallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
@@ -894,7 +894,7 @@
         // GIVEN wallpaper connections have been established for display ID, 2.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
                 WallpaperManagerInternal.class);
-        wallpaperManagerInternal.onDisplayReady(testDisplayId);
+        wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
         // Save displayConnectors for display ID, 2, before display removal.
         WallpaperManagerService.DisplayConnector systemWallpaperDisplayConnector =
                 systemWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
@@ -930,7 +930,7 @@
         // GIVEN wallpaper connections have been established for display ID, 2.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
                 WallpaperManagerInternal.class);
-        wallpaperManagerInternal.onDisplayReady(testDisplayId);
+        wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
         // Save fallback wallpaper displayConnector for display ID, 2, before display removal.
         WallpaperManagerService.DisplayConnector fallbackWallpaperConnector =
                 mService.mFallbackWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
@@ -977,7 +977,7 @@
         // GIVEN wallpaper connections have been established for displayID, 2.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
                 WallpaperManagerInternal.class);
-        wallpaperManagerInternal.onDisplayReady(testDisplayId);
+        wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
         // Save displayConnector for displayId 2 before display removal.
         WallpaperManagerService.DisplayConnector displayConnector =
                 wallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
@@ -1011,7 +1011,7 @@
         // GIVEN wallpaper connections have been established for displayID, 2.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
                 WallpaperManagerInternal.class);
-        wallpaperManagerInternal.onDisplayReady(testDisplayId);
+        wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
         // Save displayConnectors for displayId 2 before display removal.
         WallpaperManagerService.DisplayConnector systemWallpaperDisplayConnector =
                 systemWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/OWNERS b/services/tests/servicestests/src/com/android/server/media/projection/OWNERS
index 832bcd9..3caf7fa 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/media/projection/OWNERS
@@ -1 +1,2 @@
+# Bug component: 1345447
 include /media/java/android/media/projection/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
index b150b14..da02278 100644
--- a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
+++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
@@ -161,6 +161,18 @@
 
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
+    fun profileOwnerChanged_supervisionAppIsNotProfileOwner_doesNotDisableSupervision() {
+        service.mInternal.setSupervisionEnabledForUser(USER_ID, true)
+        whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
+            .thenReturn(ComponentName("other.package", "MainActivity"))
+
+        broadcastProfileOwnerChanged(USER_ID)
+
+        assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
     fun profileOwnerChanged_supervisionAppIsNotProfileOwner_doesNotEnableSupervision() {
         whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
             .thenReturn(ComponentName("other.package", "MainActivity"))
@@ -258,7 +270,7 @@
 
     private companion object {
         const val USER_ID = 100
-        val APP_UID = USER_ID * UserHandle.PER_USER_RANGE
+        const val APP_UID = USER_ID * UserHandle.PER_USER_RANGE
     }
 }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 1a95984..f8c8a1d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -263,6 +263,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Parcel;
@@ -578,6 +579,7 @@
     NetworkCapabilities mWifiNetworkCapabilities;
 
     private NotificationManagerService.WorkerHandler mWorkerHandler;
+    private Handler mBroadcastsHandler;
 
     private class TestableToastCallback extends ITransientNotification.Stub {
         @Override
@@ -814,14 +816,16 @@
         when(mUmInternal.isUserInitialized(anyInt())).thenReturn(true);
 
         mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper()));
-        mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient,
-                mLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr,
-                mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm, mAtm,
-                mAppUsageStats, mDevicePolicyManager, mUgm, mUgmInternal,
-                mAppOpsManager, mUm, mHistoryManager, mStatsManager,
-                mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class),
-                mTelecomManager, mLogger, mTestFlagResolver, mPermissionManager,
-                mPowerManager, mConnectivityManager, mPostNotificationTrackerFactory);
+        mBroadcastsHandler = new Handler(mTestableLooper.getLooper());
+
+        mService.init(mWorkerHandler, mRankingHandler, mBroadcastsHandler, mPackageManager,
+                mPackageManagerClient, mLightsManager, mListeners, mAssistants, mConditionProviders,
+                mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager,
+                mGroupHelper, mAm, mAtm, mAppUsageStats, mDevicePolicyManager, mUgm, mUgmInternal,
+                mAppOpsManager, mUm, mHistoryManager, mStatsManager, mAmi, mToastRateLimiter,
+                mPermissionHelper, mock(UsageStatsManagerInternal.class), mTelecomManager, mLogger,
+                mTestFlagResolver, mPermissionManager, mPowerManager, mConnectivityManager,
+                mPostNotificationTrackerFactory);
 
         mService.setAttentionHelper(mAttentionHelper);
         mService.setLockPatternUtils(mock(LockPatternUtils.class));
@@ -1003,6 +1007,9 @@
             // problematic interactions with mocks when they're no longer working as expected).
             mWorkerHandler.removeCallbacksAndMessages(null);
         }
+        if (mBroadcastsHandler != null) {
+            mBroadcastsHandler.removeCallbacksAndMessages(null);
+        }
 
         if (mTestableLooper != null) {
             // Must remove static reference to this test object to prevent leak (b/261039202)
@@ -17647,8 +17654,19 @@
     }
 
     @Test
-    @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
-    public void testSetCanBePromoted_granted() throws Exception {
+    @EnableFlags({android.app.Flags.FLAG_API_RICH_ONGOING})
+    public void testSetCanBePromoted_granted_noui() throws Exception {
+        testSetCanBePromoted_granted();
+    }
+
+    @Test
+    @EnableFlags({android.app.Flags.FLAG_API_RICH_ONGOING,
+            android.app.Flags.FLAG_UI_RICH_ONGOING })
+    public void testSetCanBePromoted_granted_ui() throws Exception {
+        testSetCanBePromoted_granted();
+    }
+
+    private void testSetCanBePromoted_granted() throws Exception {
         // qualifying posted notification
         Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -17703,6 +17721,11 @@
         mService.addNotification(r);
         mService.addEnqueuedNotification(r1);
 
+        // GIVEN - make sure the promoted value does not depend on the default value.
+        mBinderService.setCanBePromoted(mPkg, mUid, false, true);
+        waitForIdle();
+        clearInvocations(mListeners);
+
         mBinderService.setCanBePromoted(mPkg, mUid, true, true);
 
         waitForIdle();
@@ -17725,7 +17748,18 @@
 
     @Test
     @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
-    public void testSetCanBePromoted_granted_onlyNotifiesOnce() throws Exception {
+    public void testSetCanBePromoted_granted_onlyNotifiesOnce_noui() throws Exception {
+        testSetCanBePromoted_granted_onlyNotifiesOnce();
+    }
+
+    @Test
+    @EnableFlags({android.app.Flags.FLAG_API_RICH_ONGOING,
+            android.app.Flags.FLAG_UI_RICH_ONGOING})
+    public void testSetCanBePromoted_granted_onlyNotifiesOnce_ui() throws Exception {
+        testSetCanBePromoted_granted_onlyNotifiesOnce();
+    }
+
+    private void testSetCanBePromoted_granted_onlyNotifiesOnce() throws Exception {
         // qualifying posted notification
         Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -17741,6 +17775,10 @@
         NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         mService.addNotification(r);
+        // GIVEN - make sure the promoted value does not depend on the default value.
+        mBinderService.setCanBePromoted(mPkg, mUid, false, true);
+        waitForIdle();
+        clearInvocations(mListeners);
 
         mBinderService.setCanBePromoted(mPkg, mUid, true, true);
         waitForIdle();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 82d87d4..ad900fe 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -48,13 +48,13 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
+import android.os.Handler;
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.permission.PermissionManager;
 import android.telecom.TelecomManager;
-import android.telephony.TelephonyManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
@@ -152,7 +152,7 @@
 
         try {
             mService.init(mService.new WorkerHandler(mTestableLooper.getLooper()),
-                    mock(RankingHandler.class),
+                    mock(RankingHandler.class), new Handler(mTestableLooper.getLooper()),
                     mock(IPackageManager.class), mock(PackageManager.class),
                     mock(LightsManager.class),
                     mock(NotificationListeners.class), mock(NotificationAssistants.class),