Merge "Clear leave shade open on bouncer exit" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index f267131..0404b79 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -17361,6 +17361,25 @@
     method public boolean setUseCompositingLayer(boolean, @Nullable android.graphics.Paint);
   }
 
+  @FlaggedApi("com.android.graphics.hwui.flags.runtime_color_filters_blenders") public class RuntimeColorFilter extends android.graphics.ColorFilter {
+    ctor public RuntimeColorFilter(@NonNull String);
+    method public void setColorUniform(@NonNull String, @ColorInt int);
+    method public void setColorUniform(@NonNull String, @ColorLong long);
+    method public void setColorUniform(@NonNull String, @NonNull android.graphics.Color);
+    method public void setFloatUniform(@NonNull String, float);
+    method public void setFloatUniform(@NonNull String, float, float);
+    method public void setFloatUniform(@NonNull String, float, float, float);
+    method public void setFloatUniform(@NonNull String, float, float, float, float);
+    method public void setFloatUniform(@NonNull String, @NonNull float[]);
+    method public void setInputColorFilter(@NonNull String, @NonNull android.graphics.ColorFilter);
+    method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader);
+    method public void setIntUniform(@NonNull String, int);
+    method public void setIntUniform(@NonNull String, int, int);
+    method public void setIntUniform(@NonNull String, int, int, int);
+    method public void setIntUniform(@NonNull String, int, int, int, int);
+    method public void setIntUniform(@NonNull String, @NonNull int[]);
+  }
+
   public class RuntimeShader extends android.graphics.Shader {
     ctor public RuntimeShader(@NonNull String);
     method public void setColorUniform(@NonNull String, @ColorInt int);
@@ -39815,7 +39834,7 @@
 
 package android.security.advancedprotection {
 
-  @FlaggedApi("android.security.aapm_api") public class AdvancedProtectionManager {
+  @FlaggedApi("android.security.aapm_api") public final class AdvancedProtectionManager {
     method @RequiresPermission(android.Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE) public boolean isAdvancedProtectionEnabled();
     method @RequiresPermission(android.Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE) public void registerAdvancedProtectionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.security.advancedprotection.AdvancedProtectionManager.Callback);
     method @RequiresPermission(android.Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE) public void unregisterAdvancedProtectionCallback(@NonNull android.security.advancedprotection.AdvancedProtectionManager.Callback);
@@ -41481,6 +41500,7 @@
     method public final void cancelNotification(String);
     method public final void cancelNotifications(String[]);
     method public final void clearRequestedListenerHints();
+    method @FlaggedApi("android.service.notification.notification_conversation_channel_management") @Nullable public final android.app.NotificationChannel createConversationNotificationChannelForPackage(@NonNull String, @NonNull android.os.UserHandle, @NonNull String, @NonNull String);
     method public android.service.notification.StatusBarNotification[] getActiveNotifications();
     method public android.service.notification.StatusBarNotification[] getActiveNotifications(String[]);
     method public final int getCurrentInterruptionFilter();
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 4c9ce07..bc73220 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -102,6 +102,7 @@
     method @NonNull public android.os.UserHandle getUser();
     field public static final String PAC_PROXY_SERVICE = "pac_proxy";
     field public static final String TEST_NETWORK_SERVICE = "test_network";
+    field @FlaggedApi("android.os.mainline_vcn_platform_api") public static final String VCN_MANAGEMENT_SERVICE = "vcn_management";
     field @FlaggedApi("android.webkit.update_service_ipc_wrapper") public static final String WEBVIEW_UPDATE_SERVICE = "webviewupdate";
   }
 
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 92785e3..b130f00 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1047,6 +1047,7 @@
     method public int getUserLockedFields();
     method public boolean isDeleted();
     method public void populateFromXml(org.xmlpull.v1.XmlPullParser);
+    method @FlaggedApi("android.service.notification.notification_conversation_channel_management") public void setImportantConversation(boolean);
     method public org.json.JSONObject toJson() throws org.json.JSONException;
     method public void writeXml(org.xmlpull.v1.XmlSerializer) throws java.io.IOException;
     field public static final int USER_LOCKED_SOUND = 32; // 0x20
@@ -7229,7 +7230,6 @@
     field @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public static final int USAGE_CALL_ASSISTANT = 17; // 0x11
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_EMERGENCY = 1000; // 0x3e8
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_SAFETY = 1001; // 0x3e9
-    field @FlaggedApi("android.media.audio.speaker_cleanup_usage") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_SPEAKER_CLEANUP = 1004; // 0x3ec
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_VEHICLE_STATUS = 1002; // 0x3ea
   }
 
@@ -12466,7 +12466,16 @@
 
 package android.security.advancedprotection {
 
-  @FlaggedApi("android.security.aapm_api") public class AdvancedProtectionManager {
+  @FlaggedApi("android.security.aapm_api") public final class AdvancedProtectionFeature implements android.os.Parcelable {
+    ctor public AdvancedProtectionFeature(@NonNull String);
+    method public int describeContents();
+    method @NonNull public String getId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.security.advancedprotection.AdvancedProtectionFeature> CREATOR;
+  }
+
+  @FlaggedApi("android.security.aapm_api") public final class AdvancedProtectionManager {
+    method @NonNull @RequiresPermission(android.Manifest.permission.SET_ADVANCED_PROTECTION_MODE) public java.util.List<android.security.advancedprotection.AdvancedProtectionFeature> getAdvancedProtectionFeatures();
     method @RequiresPermission(android.Manifest.permission.SET_ADVANCED_PROTECTION_MODE) public void setAdvancedProtectionEnabled(boolean);
   }
 
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a0c4fdb..98d6f58 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -369,6 +369,7 @@
   }
 
   public final class NotificationChannel implements android.os.Parcelable {
+    method @FlaggedApi("android.service.notification.notification_conversation_channel_management") @NonNull public android.app.NotificationChannel copy();
     method public int getOriginalImportance();
     method public boolean isImportanceLockedByCriticalDeviceFunction();
     method public void lockFields(int);
@@ -376,7 +377,7 @@
     method public void setDeletedTimeMs(long);
     method public void setDemoted(boolean);
     method public void setImportanceLockedByCriticalDeviceFunction(boolean);
-    method public void setImportantConversation(boolean);
+    method @FlaggedApi("android.service.notification.notification_conversation_channel_management") public void setImportantConversation(boolean);
     method public void setOriginalImportance(int);
     method public void setUserVisibleTaskShown(boolean);
     field @FlaggedApi("android.service.notification.notification_classification") public static final String NEWS_ID = "android.app.news";
@@ -1029,6 +1030,7 @@
   public abstract class Context {
     method @NonNull public java.io.File getCrateDir(@NonNull String);
     method public abstract int getDisplayId();
+    method @NonNull public java.util.List<android.content.IntentFilter> getRegisteredIntentFilters(@NonNull android.content.BroadcastReceiver);
     method @NonNull public android.os.UserHandle getUser();
     method public int getUserId();
     method public void setAutofillOptions(@Nullable android.content.AutofillOptions);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index ecef0db..2cf718e 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1962,6 +1962,24 @@
         }
     }
 
+    @Override
+    @NonNull
+    public List<IntentFilter> getRegisteredIntentFilters(@NonNull BroadcastReceiver receiver) {
+        if (mPackageInfo != null) {
+            IIntentReceiver rd = mPackageInfo.findRegisteredReceiverDispatcher(
+                    receiver, getOuterContext());
+            try {
+                final List<IntentFilter> filters = ActivityManager.getService()
+                        .getRegisteredIntentFilters(rd);
+                return filters == null ? new ArrayList<>() : filters;
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            throw new RuntimeException("Not supported in system context");
+        }
+    }
+
     private void validateServiceIntent(Intent service) {
         if (service.getComponent() == null && service.getPackage() == null) {
             if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index f880901..34a3ad1 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -181,6 +181,7 @@
             in IntentFilter filter, in String requiredPermission, int userId, int flags);
     @UnsupportedAppUsage
     void unregisterReceiver(in IIntentReceiver receiver);
+    List<IntentFilter> getRegisteredIntentFilters(in IIntentReceiver receiver);
     /** @deprecated Use {@link #broadcastIntentWithFeature} instead */
     @UnsupportedAppUsage(maxTargetSdk=29, publicAlternatives="Use {@link android.content.Context#sendBroadcast(android.content.Intent)} instead")
     int broadcastIntent(in IApplicationThread caller, in Intent intent,
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index a7597b4..a97fa18 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -175,6 +175,7 @@
     void setOnNotificationPostedTrimFromListener(in INotificationListener token, int trim);
     void setInterruptionFilter(String pkg, int interruptionFilter, boolean fromUser);
 
+    NotificationChannel createConversationNotificationChannelForPackageFromPrivilegedListener(in INotificationListener token, String pkg, in UserHandle user, String parentChannelId, String conversationId);
     void updateNotificationChannelGroupFromPrivilegedListener(in INotificationListener token, String pkg, in UserHandle user, in NotificationChannelGroup group);
     void updateNotificationChannelFromPrivilegedListener(in INotificationListener token, String pkg, in UserHandle user, in NotificationChannel channel);
     ParceledListSlice getNotificationChannelsFromPrivilegedListener(in INotificationListener token, String pkg, in UserHandle user);
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 1e45d6f..b8233bc 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1627,6 +1627,18 @@
         }
     }
 
+    IIntentReceiver findRegisteredReceiverDispatcher(BroadcastReceiver r, Context context) {
+        synchronized (mReceivers) {
+            final ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map =
+                    mReceivers.get(context);
+            if (map != null) {
+                final LoadedApk.ReceiverDispatcher rd = map.get(r);
+                return rd == null ? null : rd.getIIntentReceiver();
+            }
+            return null;
+        }
+    }
+
     public IIntentReceiver forgetReceiverDispatcher(Context context,
             BroadcastReceiver r) {
         synchronized (mReceivers) {
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index ebe7b3a..73d26b8 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -15,6 +15,8 @@
  */
 package android.app;
 
+import static android.service.notification.Flags.FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT;
+
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -475,9 +477,10 @@
         dest.writeBoolean(mImportanceLockedDefaultApp);
     }
 
-    /**
-     * @hide
-     */
+    /** @hide */
+    @TestApi
+    @NonNull
+    @FlaggedApi(FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT)
     public NotificationChannel copy() {
         NotificationChannel copy = new NotificationChannel(mId, mName, mImportance);
         copy.setDescription(mDesc);
@@ -548,10 +551,10 @@
         mDeletedTime = time;
     }
 
-    /**
-     * @hide
-     */
+    /** @hide */
     @TestApi
+    @SystemApi
+    @FlaggedApi(FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT)
     public void setImportantConversation(boolean importantConvo) {
         mImportantConvo = importantConvo;
     }
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index fd58377..3aaca25 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -341,3 +341,11 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+    name: "set_mte_policy_coexistence"
+    is_exported: true
+    namespace: "enterprise"
+    description: "Enables coexistence support for Setting MTE policy."
+    bug: "376213673"
+}
diff --git a/core/java/android/app/backup/NotificationLoggingConstants.java b/core/java/android/app/backup/NotificationLoggingConstants.java
index add4562..3601e86 100644
--- a/core/java/android/app/backup/NotificationLoggingConstants.java
+++ b/core/java/android/app/backup/NotificationLoggingConstants.java
@@ -23,10 +23,56 @@
     // Key under which the payload blob is stored
     public static final String KEY_NOTIFICATIONS = "notifications";
 
+    /**
+     * Events for android.service.notification.ZenModeConfig - the configuration for all modes
+     * settings for a single user
+     */
     @BackupRestoreEventLogger.BackupRestoreDataType
     public static final String DATA_TYPE_ZEN_CONFIG = KEY_NOTIFICATIONS + ":zen_config";
+    /**
+     * Events for android.service.notification.ZenModeConfig.ZenRule - a single mode within a
+     * ZenModeConfig
+     */
     @BackupRestoreEventLogger.BackupRestoreDataType
     public static final String DATA_TYPE_ZEN_RULES = KEY_NOTIFICATIONS + ":zen_rules";
+    /**
+     * Events for globally stored notifications data that aren't stored in settings, like whether
+     * to hide silent notification in the status bar
+     */
+    @BackupRestoreEventLogger.BackupRestoreDataType
+    public static final String DATA_TYPE_NOTIF_GLOBAL = KEY_NOTIFICATIONS + ":global";
+    /**
+     * Events for package specific notification settings, including app and
+     * android.app.NotificationChannel level settings.
+     */
+    @BackupRestoreEventLogger.BackupRestoreDataType
+    public static final String DATA_TYPE_NOTIF_PACKAGES = KEY_NOTIFICATIONS + ":packages";
+    /**
+     * Events for approved ManagedServices (NotificationListenerServices,
+     * NotificationAssistantService, ConditionProviderService).
+     */
+    @BackupRestoreEventLogger.BackupRestoreDataType
+    public static final String DATA_TYPE_MANAGED_SERVICE_PRIMARY_APPROVED = KEY_NOTIFICATIONS +
+            ":managed_service_primary_approved";
+    /**
+     * Events for what types of notifications NotificationListenerServices cannot see
+     */
+    @BackupRestoreEventLogger.BackupRestoreDataType
+    public static final String DATA_TYPE_NLS_RESTRICTED = KEY_NOTIFICATIONS +
+            ":nls_restricted";
+    /**
+     * Events for ManagedServices that are approved because they have a different primary
+     * ManagedService (ConditionProviderService).
+     */
+    @BackupRestoreEventLogger.BackupRestoreDataType
+    public static final String DATA_TYPE_MANAGED_SERVICE_SECONDARY_APPROVED = KEY_NOTIFICATIONS +
+            ":managed_service_secondary_approved";
+    /**
+     * Events for individual snoozed notifications.
+     */
+    @BackupRestoreEventLogger.BackupRestoreDataType
+    public static final String DATA_TYPE_SNOOZED = KEY_NOTIFICATIONS + ":snoozed";
+
 
     @BackupRestoreEventLogger.BackupRestoreError
     public static final String ERROR_XML_PARSING = KEY_NOTIFICATIONS + ":invalid_xml_parsing";
diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig
index 3b0c867..5e09517 100644
--- a/core/java/android/app/contextualsearch/flags.aconfig
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -12,4 +12,18 @@
   namespace: "machine_learning"
   description: "Flag to refresh the token to the callback"
   bug: "309689654"
+}
+
+flag {
+    name: "multi_window_screen_context"
+    namespace: "machine_learning"
+    description: "Report screen context and positions for all windows."
+    bug: "371065456"
+}
+
+flag {
+    name: "contextual_search_window_layer"
+    namespace: "machine_learning"
+    description: "Identify live contextual search UI to exclude from contextual search screenshot."
+    bug: "372510690"
 }
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index 9eb6d56..9af2016 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -145,3 +145,11 @@
     bug: "370657575"
     is_exported: true
 }
+
+flag {
+    namespace: "virtual_devices"
+    name: "default_device_camera_access_policy"
+    description: "API for default device camera access policy"
+    bug: "371173368"
+    is_exported: true
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 5893da3..1d26b77 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3658,6 +3658,27 @@
     public abstract void unregisterReceiver(BroadcastReceiver receiver);
 
     /**
+     * Returns the list of {@link IntentFilter} objects that have been registered for the given
+     * {@link BroadcastReceiver}.
+     *
+     * @param receiver The {@link BroadcastReceiver} whose registered intent filters
+     *                 should be retrieved.
+     *
+     * @return A list of registered intent filters, or an empty list if the given receiver is not
+     *         registered.
+     *
+     * @throws NullPointerException if the {@code receiver} is {@code null}.
+     *
+     * @hide
+     */
+    @SuppressLint("UnflaggedApi") // TestApi
+    @TestApi
+    @NonNull
+    public List<IntentFilter> getRegisteredIntentFilters(@NonNull BroadcastReceiver receiver) {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
+    /**
      * Request that a given application service be started.  The Intent
      * should either contain the complete class name of a specific service
      * implementation to start, or a specific package name to target.  If the
@@ -4873,6 +4894,8 @@
      * @see android.net.vcn.VcnManager
      * @hide
      */
+    @FlaggedApi(android.os.Flags.FLAG_MAINLINE_VCN_PLATFORM_API)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String VCN_MANAGEMENT_SERVICE = "vcn_management";
 
     /**
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 79fa6ea..23d17cb 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -843,6 +843,13 @@
         mBase.unregisterReceiver(receiver);
     }
 
+    /** @hide */
+    @Override
+    @NonNull
+    public List<IntentFilter> getRegisteredIntentFilters(@NonNull BroadcastReceiver receiver) {
+        return mBase.getRegisteredIntentFilters(receiver);
+    }
+
     @Override
     public @Nullable ComponentName startService(Intent service) {
         return mBase.startService(service);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 9bec598..6fa5a9b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -41,10 +41,7 @@
 import android.app.ActivityThread;
 import android.app.AppGlobals;
 import android.app.StatusBarManager;
-import android.app.compat.CompatChanges;
 import android.bluetooth.BluetoothDevice;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.Overridable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
@@ -673,12 +670,6 @@
 @android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Intent implements Parcelable, Cloneable {
     private static final String TAG = "Intent";
-
-    /** @hide */
-    @ChangeId
-    @Overridable
-    public static final long ENABLE_PREVENT_INTENT_REDIRECT = 29076063L;
-
     private static final String ATTR_ACTION = "action";
     private static final String TAG_CATEGORIES = "categories";
     private static final String ATTR_CATEGORY = "category";
@@ -908,7 +899,7 @@
         boolean isForeign = (intent.mLocalFlags & LOCAL_FLAG_FROM_PARCEL) != 0;
         boolean isWithoutTrustedCreatorToken =
                 (intent.mLocalFlags & Intent.LOCAL_FLAG_TRUSTED_CREATOR_TOKEN_PRESENT) == 0;
-        if (isForeign && isWithoutTrustedCreatorToken) {
+        if (isForeign && isWithoutTrustedCreatorToken && preventIntentRedirect()) {
             intent.addExtendedFlags(EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN);
         }
     }
@@ -12255,7 +12246,7 @@
      * @hide
      */
     public void collectExtraIntentKeys() {
-        if (!isPreventIntentRedirectEnabled()) return;
+        if (!preventIntentRedirect()) return;
 
         if (mExtras != null && !mExtras.isEmpty()) {
             for (String key : mExtras.keySet()) {
@@ -12272,14 +12263,6 @@
         }
     }
 
-    /**
-     * @hide
-     */
-    public static boolean isPreventIntentRedirectEnabled() {
-        return preventIntentRedirect() && CompatChanges.isChangeEnabled(
-                ENABLE_PREVENT_INTENT_REDIRECT);
-    }
-
     /** @hide */
     public void checkCreatorToken() {
         if (mExtras == null) return;
@@ -12368,7 +12351,7 @@
             out.writeInt(0);
         }
 
-        if (isPreventIntentRedirectEnabled()) {
+        if (preventIntentRedirect()) {
             if (mCreatorTokenInfo == null) {
                 out.writeInt(0);
             } else {
@@ -12435,7 +12418,7 @@
             mOriginalIntent = new Intent(in);
         }
 
-        if (isPreventIntentRedirectEnabled()) {
+        if (preventIntentRedirect()) {
             if (in.readInt() != 0) {
                 mCreatorTokenInfo = new CreatorTokenInfo();
                 mCreatorTokenInfo.mCreatorToken = in.readStrongBinder();
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 6db7dfe..ed965b3 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -78,8 +78,7 @@
 
     /**
      * Applications can use OverlayManager to create overlays to overlay on itself resources. The
-     * overlay target is itself, or the Android package, and the work range is only in caller
-     * application.
+     * overlay target is itself and the work range is only in caller application.
      *
      * <p>In {@link android.content.Context#getSystemService(String)}, it crashes because of {@link
      * java.lang.NullPointerException} if the parameter is OverlayManager. if the self-targeting is
@@ -402,7 +401,7 @@
     }
 
     /**
-     * Get the related information of self-targeting overlays for {@code targetPackageName}.
+     * Get the related information of overlays for {@code targetPackageName}.
      *
      * @param targetPackageName the target package name
      * @return a list of overlay information
diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java
index 87b2e93..becd0ea 100644
--- a/core/java/android/content/om/OverlayManagerTransaction.java
+++ b/core/java/android/content/om/OverlayManagerTransaction.java
@@ -209,7 +209,6 @@
      */
     public static final class Builder {
         private final List<Request> mRequests = new ArrayList<>();
-        private boolean mSelfTargeting = false;
 
         /**
          * Request that an overlay package be enabled and change its loading
@@ -247,18 +246,6 @@
         }
 
         /**
-         * Request that an overlay package be self-targeting. Self-targeting overlays enable
-         * applications to overlay on itself resources. The overlay target is itself, or the Android
-         * package, and the work range is only in caller application.
-         * @param selfTargeting whether the overlay is self-targeting, the default is false.
-         * @hide
-         */
-        public Builder setSelfTargeting(boolean selfTargeting) {
-            mSelfTargeting = selfTargeting;
-            return this;
-        }
-
-        /**
          * Registers the fabricated overlay with the overlay manager so it can be enabled and
          * disabled for any user.
          *
@@ -299,7 +286,7 @@
          */
         @NonNull
         public OverlayManagerTransaction build() {
-            return new OverlayManagerTransaction(mRequests, mSelfTargeting);
+            return new OverlayManagerTransaction(mRequests, false /* selfTargeting */);
         }
     }
 
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 35f9cff..528bde8 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -536,7 +536,7 @@
 
 flag {
   name: "ignore_restrictions_when_deleting_private_profile"
-  namespace: "multiuser"
+  namespace: "profile_experiences"
   description: "Ignore any user restrictions when deleting private profiles."
   bug: "350953833"
   metadata {
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 347bebd..6fd4d01 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -75,10 +75,7 @@
     private static final String TAG = "AssetManager";
     private static final boolean DEBUG_REFS = false;
 
-    /**
-     * @hide
-     */
-    public static final String FRAMEWORK_APK_PATH = getFrameworkApkPath();
+    private static final String FRAMEWORK_APK_PATH = getFrameworkApkPath();
     private static final String FRAMEWORK_APK_PATH_DEVICE = "/system/framework/framework-res.apk";
     private static final String FRAMEWORK_APK_PATH_RAVENWOOD = "ravenwood-data/framework-res.apk";
 
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index 0af2f25..e98fc0c 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -74,3 +74,12 @@
     description: "Feature flag for passing a dimension to create an frro"
     bug: "369672322"
 }
+
+flag {
+    name: "rro_control_for_android_no_overlayable"
+    is_exported: true
+    namespace: "resource_manager"
+    description: "Allow enabling and disabling RROs targeting android package with no overlayable"
+    bug: "364035303"
+}
+
diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java
index 830b7e0..b097bc0 100644
--- a/core/java/android/content/res/loader/ResourcesProvider.java
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -90,6 +90,8 @@
             throws IOException {
         Objects.requireNonNull(overlayInfo);
         Preconditions.checkArgument(overlayInfo.isFabricated(), "Not accepted overlay");
+        Preconditions.checkStringNotEmpty(
+                overlayInfo.getTargetOverlayableName(), "Without overlayable name");
         final String overlayName =
                 OverlayManagerImpl.checkOverlayNameValid(overlayInfo.getOverlayName());
         final String path =
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 22dbf5b..d38be9b 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -1457,7 +1457,7 @@
     /**
      * Set the mirroring mode for a surface belonging to this OutputConfiguration
      *
-     * <p>This function is identical to {@link #setMirroMode(int)} if {@code surface} is
+     * <p>This function is identical to {@link #setMirrorMode(int)} if {@code surface} is
      * the only surface belonging to this OutputConfiguration.</p>
      *
      * <p>If this OutputConfiguration contains a deferred surface, the application can either
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index 0f290d9..9d42b67 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -113,6 +113,10 @@
     public static final int KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS = 65;
     public static final int KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS = 66;
     public static final int KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS = 67;
+    public static final int KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW = 68;
+    public static final int KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW = 69;
+    public static final int KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW = 70;
+    public static final int KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE = 71;
 
     public static final int FLAG_CANCELLED = 1;
 
@@ -194,7 +198,11 @@
             KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS,
             KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS,
             KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS,
-            KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS
+            KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS,
+            KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
+            KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
+            KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
+            KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface KeyGestureType {
@@ -541,6 +549,14 @@
                 return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE;
             case KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION:
                 return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MULTI_WINDOW_NAVIGATION;
+            case KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW:
+                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SNAP_LEFT_FREEFORM_WINDOW;
+            case KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW:
+                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SNAP_RIGHT_FREEFORM_WINDOW;
+            case KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW:
+                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MAXIMIZE_FREEFORM_WINDOW;
+            case KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE:
+                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RESTORE_FREEFORM_WINDOW_SIZE;
             default:
                 return LOG_EVENT_UNSPECIFIED;
         }
@@ -749,6 +765,14 @@
                 return "KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS";
             case KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS:
                 return "KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS";
+            case KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW:
+                return "KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW";
+            case KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW:
+                return "KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW";
+            case KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW:
+                return "KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW";
+            case KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE:
+                return "KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE";
             default:
                 return Integer.toHexString(value);
         }
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index 3e5ac6f..6297318 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -85,11 +85,6 @@
     // queue for async messages when inserting a message at the tail.
     private int mLegacyAsyncMessageCount;
 
-    // The next barrier token.
-    // Barriers are indicated by messages with a null target whose arg1 field carries the token.
-    @UnsupportedAppUsage
-    private int mLegacyNextBarrierToken;
-
     /*
      * Select between two implementations of message queue. The legacy implementation is used
      * by default as it provides maximum compatibility with applications and tests that
@@ -139,7 +134,7 @@
         }
     }
 
-    private class MatchDeliverableMessages extends MessageCompare {
+    private static final class MatchDeliverableMessages extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -900,7 +895,12 @@
         // Enqueue a new sync barrier token.
         // We don't need to wake the queue because the purpose of a barrier is to stall it.
         if (sForceConcurrent) {
-            final int token = mNextBarrierToken.getAndIncrement();
+            final int token = mNextBarrierTokenAtomic.getAndIncrement();
+
+            // b/376573804: apps and tests may expect to be able to use reflection
+            // to read this value. Make some effort to support this legacy use case.
+            mNextBarrierToken = token + 1;
+
             final Message msg = Message.obtain();
 
             msg.markInUse();
@@ -915,7 +915,7 @@
         }
 
         synchronized (this) {
-            final int token = mLegacyNextBarrierToken++;
+            final int token = mNextBarrierToken++;
             final Message msg = Message.obtain();
             msg.markInUse();
             msg.when = when;
@@ -954,7 +954,7 @@
         }
     }
 
-    private class MatchBarrierToken extends MessageCompare {
+    private static final class MatchBarrierToken extends MessageCompare {
         int mBarrierToken;
 
         MatchBarrierToken(int token) {
@@ -1167,7 +1167,7 @@
         return true;
     }
 
-    private static class MatchHandlerWhatAndObject extends MessageCompare {
+    private static final class MatchHandlerWhatAndObject extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1199,7 +1199,7 @@
         }
     }
 
-    private static class MatchHandlerWhatAndObjectEquals extends MessageCompare {
+    private static final class MatchHandlerWhatAndObjectEquals extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1232,7 +1232,7 @@
         }
     }
 
-    private static class MatchHandlerRunnableAndObject extends MessageCompare {
+    private static final class MatchHandlerRunnableAndObject extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1266,7 +1266,7 @@
         }
     }
 
-    private static class MatchHandler extends MessageCompare {
+    private static final class MatchHandler extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1449,7 +1449,7 @@
         }
     }
 
-    private static class MatchHandlerRunnableAndObjectEquals extends MessageCompare {
+    private static final class MatchHandlerRunnableAndObjectEquals extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1512,7 +1512,7 @@
         }
     }
 
-    private static class MatchHandlerAndObject extends MessageCompare {
+    private static final class MatchHandlerAndObject extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1573,7 +1573,7 @@
         }
     }
 
-    private static class MatchHandlerAndObjectEquals extends MessageCompare {
+    private static final class MatchHandlerAndObjectEquals extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1680,7 +1680,7 @@
         }
     }
 
-    private static class MatchAllMessages extends MessageCompare {
+    private static final class MatchAllMessages extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1692,7 +1692,7 @@
         findOrRemoveMessages(null, -1, null, null, 0, mMatchAllMessages, true);
     }
 
-    private static class MatchAllFutureMessages extends MessageCompare {
+    private static final class MatchAllFutureMessages extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -2292,7 +2292,11 @@
 
     // The next barrier token.
     // Barriers are indicated by messages with a null target whose arg1 field carries the token.
-    private final AtomicInteger mNextBarrierToken = new AtomicInteger(1);
+    private final AtomicInteger mNextBarrierTokenAtomic = new AtomicInteger(1);
+
+    // Must retain this for compatibility reasons.
+    @UnsupportedAppUsage
+    private int mNextBarrierToken;
 
     /* Protects mNextIsDrainingStack */
     private final ReentrantLock mDrainingLock = new ReentrantLock();
diff --git a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
index 0efa02f..9db88d1 100644
--- a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
+++ b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
@@ -382,7 +382,7 @@
         }
     }
 
-    private class MatchDeliverableMessages extends MessageCompare {
+    private static final class MatchDeliverableMessages extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -984,7 +984,7 @@
         return token;
     }
 
-    private class MatchBarrierToken extends MessageCompare {
+    private static final class MatchBarrierToken extends MessageCompare {
         int mBarrierToken;
 
         MatchBarrierToken(int token) {
@@ -1165,7 +1165,7 @@
         return foundInStack || foundInQueue;
     }
 
-    private static class MatchHandlerWhatAndObject extends MessageCompare {
+    private static final class MatchHandlerWhatAndObject extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1185,7 +1185,7 @@
         return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, false);
     }
 
-    private static class MatchHandlerWhatAndObjectEquals extends MessageCompare {
+    private static final class MatchHandlerWhatAndObjectEquals extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1206,7 +1206,7 @@
                 false);
     }
 
-    private static class MatchHandlerRunnableAndObject extends MessageCompare {
+    private static final class MatchHandlerRunnableAndObject extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1227,7 +1227,7 @@
         return findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, false);
     }
 
-    private static class MatchHandler extends MessageCompare {
+    private static final class MatchHandler extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1266,7 +1266,7 @@
         findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, true);
     }
 
-    private static class MatchHandlerRunnableAndObjectEquals extends MessageCompare {
+    private static final class MatchHandlerRunnableAndObjectEquals extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1285,7 +1285,7 @@
         findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObjectEquals, true);
     }
 
-    private static class MatchHandlerAndObject extends MessageCompare {
+    private static final class MatchHandlerAndObject extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1303,7 +1303,7 @@
         findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObject, true);
     }
 
-    private static class MatchHandlerAndObjectEquals extends MessageCompare {
+    private static final class MatchHandlerAndObjectEquals extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1322,7 +1322,7 @@
         findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObjectEquals, true);
     }
 
-    private static class MatchAllMessages extends MessageCompare {
+    private static final class MatchAllMessages extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1334,7 +1334,7 @@
         findOrRemoveMessages(null, -1, null, null, 0, mMatchAllMessages, true);
     }
 
-    private static class MatchAllFutureMessages extends MessageCompare {
+    private static final class MatchAllFutureMessages extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
diff --git a/core/java/android/security/advancedprotection/AdvancedProtectionFeature.aidl b/core/java/android/security/advancedprotection/AdvancedProtectionFeature.aidl
new file mode 100644
index 0000000..3ecef02
--- /dev/null
+++ b/core/java/android/security/advancedprotection/AdvancedProtectionFeature.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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 android.security.advancedprotection;
+
+/**
+ * Represents an advanced protection feature providing protections
+ * @hide
+ */
+parcelable AdvancedProtectionFeature;
\ No newline at end of file
diff --git a/core/java/android/security/advancedprotection/AdvancedProtectionFeature.java b/core/java/android/security/advancedprotection/AdvancedProtectionFeature.java
new file mode 100644
index 0000000..a086bf7
--- /dev/null
+++ b/core/java/android/security/advancedprotection/AdvancedProtectionFeature.java
@@ -0,0 +1,77 @@
+/*
+ * 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 android.security.advancedprotection;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.security.Flags;
+
+/**
+ * An advanced protection feature providing protections.
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_AAPM_API)
+@SystemApi
+public final class AdvancedProtectionFeature implements Parcelable {
+    private final String mId;
+
+    /**
+     * Create an object identifying an Advanced Protection feature for AdvancedProtectionManager
+     * @param id A unique ID to identify this feature. It is used by Settings screens to display
+     *           information about this feature.
+     */
+    public AdvancedProtectionFeature(@NonNull String id) {
+        mId = id;
+    }
+
+    private AdvancedProtectionFeature(Parcel in) {
+        mId = in.readString8();
+    }
+
+    /**
+     * @return the unique ID representing this feature
+     */
+    @NonNull
+    public String getId() {
+        return mId;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mId);
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<AdvancedProtectionFeature> CREATOR =
+            new Parcelable.Creator<>() {
+                public AdvancedProtectionFeature createFromParcel(Parcel in) {
+                    return new AdvancedProtectionFeature(in);
+                }
+
+                public AdvancedProtectionFeature[] newArray(int size) {
+                    return new AdvancedProtectionFeature[size];
+                }
+            };
+}
diff --git a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
index 43b6ebe..6f3e3d8 100644
--- a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
+++ b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
@@ -29,6 +29,7 @@
 import android.security.Flags;
 import android.util.Log;
 
+import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 
@@ -41,7 +42,7 @@
  */
 @FlaggedApi(Flags.FLAG_AAPM_API)
 @SystemService(Context.ADVANCED_PROTECTION_SERVICE)
-public class AdvancedProtectionManager {
+public final class AdvancedProtectionManager {
     private static final String TAG = "AdvancedProtectionMgr";
 
     private final ConcurrentHashMap<Callback, IAdvancedProtectionCallback>
@@ -147,6 +148,22 @@
     }
 
     /**
+     * Returns the list of advanced protection features which are available on this device.
+     *
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    @RequiresPermission(Manifest.permission.SET_ADVANCED_PROTECTION_MODE)
+    public List<AdvancedProtectionFeature> getAdvancedProtectionFeatures() {
+        try {
+            return mService.getAdvancedProtectionFeatures();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * A callback class for monitoring changes to Advanced Protection state
      *
      * <p>To register a callback, implement this interface, and register it with
diff --git a/core/java/android/security/advancedprotection/IAdvancedProtectionService.aidl b/core/java/android/security/advancedprotection/IAdvancedProtectionService.aidl
index ef0abf4..6830763 100644
--- a/core/java/android/security/advancedprotection/IAdvancedProtectionService.aidl
+++ b/core/java/android/security/advancedprotection/IAdvancedProtectionService.aidl
@@ -16,6 +16,7 @@
 
 package android.security.advancedprotection;
 
+import android.security.advancedprotection.AdvancedProtectionFeature;
 import android.security.advancedprotection.IAdvancedProtectionCallback;
 
 /**
@@ -32,4 +33,6 @@
     void unregisterAdvancedProtectionCallback(IAdvancedProtectionCallback callback);
     @EnforcePermission("SET_ADVANCED_PROTECTION_MODE")
     void setAdvancedProtectionEnabled(boolean enabled);
+    @EnforcePermission("SET_ADVANCED_PROTECTION_MODE")
+    List<AdvancedProtectionFeature> getAdvancedProtectionFeatures();
 }
\ No newline at end of file
diff --git a/core/java/android/service/contextualsearch/OWNERS b/core/java/android/service/contextualsearch/OWNERS
index b723872..c435bd8 100644
--- a/core/java/android/service/contextualsearch/OWNERS
+++ b/core/java/android/service/contextualsearch/OWNERS
@@ -1,2 +1,3 @@
 srazdan@google.com
-hackz@google.com
+hyunyoungs@google.com
+awickham@google.com
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index bd9ab86..a8ab211 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -17,6 +17,7 @@
 package android.service.notification;
 
 import android.annotation.CurrentTimeMillisLong;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -882,6 +883,35 @@
         }
     }
 
+    /**
+     * Creates a conversation notification channel for a given package for a given user.
+     *
+     * <p>This method will throw a security exception if you don't have access to notifications
+     * for the given user.</p>
+     * <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated
+     * device} or be the notification assistant in order to use this method.
+     *
+     * @param pkg The package the channel belongs to.
+     * @param user The user the channel belongs to.
+     * @param parentChannelId The parent channel id of the conversation channel belongs to.
+     * @param conversationId The conversation id of the conversation channel.
+     *
+     * @return The created conversation channel.
+     */
+    @FlaggedApi(Flags.FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT)
+    public final @Nullable NotificationChannel createConversationNotificationChannelForPackage(
+        @NonNull String pkg, @NonNull UserHandle user, @NonNull String parentChannelId,
+        @NonNull String conversationId) {
+        if (!isBound()) return null;
+        try {
+            return getNotificationInterface()
+                    .createConversationNotificationChannelForPackageFromPrivilegedListener(
+                            mWrapper, pkg, user, parentChannelId, conversationId);
+        } catch (RemoteException e) {
+            Log.v(TAG, "Unable to contact notification manager", e);
+            throw e.rethrowFromSystemServer();
+        }
+    }
 
     /**
      * Updates a notification channel for a given package for a given user. This should only be used
@@ -890,7 +920,7 @@
      * <p>This method will throw a security exception if you don't have access to notifications
      * for the given user.</p>
      * <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated
-     * device} in order to use this method.
+     * device} or be the notification assistant in order to use this method.
      *
      * @param pkg The package the channel belongs to.
      * @param user The user the channel belongs to.
diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig
index 51961a8..34e311f 100644
--- a/core/java/android/service/notification/flags.aconfig
+++ b/core/java/android/service/notification/flags.aconfig
@@ -57,4 +57,12 @@
   namespace: "systemui"
   description: "Guards the new FLAG_SILENT Notification flag"
   bug: "336488844"
+}
+
+flag {
+   name: "notification_conversation_channel_management"
+   is_exported: true
+   namespace: "systemui"
+   description: "Allows the NAS to create and modify conversation notifications"
+   bug: "373599715"
 }
\ No newline at end of file
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 2ab16e9..7f54cf0 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -221,14 +221,14 @@
 
     /**
      * Wear products currently force a slight scaling transition to wallpapers
-     * when the QSS is opened. However, on Wear 6 (SDK 35) and above, 1P watch faces
+     * when the QSS is opened. However, on Wear 7 (SDK 37) and above, 1P watch faces
      * will be expected to either implement their own scaling, or to override this
      * method to allow the WallpaperController to continue to scale for them.
      *
      * @hide
      */
     @ChangeId
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)
     public static final long WEAROS_WALLPAPER_HANDLES_SCALING = 272527315L;
 
     static final class WallpaperCommand {
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 0d55544..b8b22e2 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -87,6 +87,7 @@
 import java.nio.ByteOrder;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
@@ -463,7 +464,7 @@
         /**
          * Called when new jank classifications are available.
          */
-        void onJankDataAvailable(JankData[] jankData);
+        void onJankDataAvailable(@NonNull List<JankData> jankData);
 
     }
 
@@ -2686,7 +2687,9 @@
      * Adds a callback to be informed about SF's jank classification for this surface.
      * @hide
      */
-    public OnJankDataListenerRegistration addJankDataListener(OnJankDataListener listener) {
+    @NonNull
+    public OnJankDataListenerRegistration addOnJankDataListener(
+            @NonNull OnJankDataListener listener) {
         return new OnJankDataListenerRegistration(this, listener);
     }
 
@@ -3459,15 +3462,15 @@
          * @return this This transaction for chaining
          * @hide
          */
-        public @NonNull Transaction setCrop(@NonNull SurfaceControl sc, float top, float left,
-                float bottom, float right) {
+        public @NonNull Transaction setCrop(@NonNull SurfaceControl sc, float left, float top,
+                float right, float bottom) {
             checkPreconditions(sc);
             if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
                 SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
                         "setCrop", this, sc, "crop={" + top + ", " + left + ", " +
                         bottom + ", " + right + "}");
             }
-            nativeSetCrop(mNativeObject, sc.mNativeObject, top, left, bottom, right);
+            nativeSetCrop(mNativeObject, sc.mNativeObject, left, top, right, bottom);
             return this;
         }
 
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 9cad3e5..4df7649 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -40,6 +40,7 @@
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.Region;
 import android.graphics.RenderNode;
 import android.hardware.input.InputManager;
@@ -1666,7 +1667,7 @@
     }
 
     private final Rect mRTLastReportedPosition = new Rect();
-    private final Rect mRTLastSetCrop = new Rect();
+    private final RectF mRTLastSetCrop = new RectF();
 
     private class SurfaceViewPositionUpdateListener implements RenderNode.PositionUpdateListener {
         private final int mRtSurfaceWidth;
@@ -1711,16 +1712,18 @@
 
         @Override
         public void positionChanged(long frameNumber, int left, int top, int right, int bottom,
-                int clipLeft, int clipTop, int clipRight, int clipBottom) {
+                int clipLeft, int clipTop, int clipRight, int clipBottom,
+                int nodeWidth, int nodeHeight) {
             try {
                 if (DEBUG_POSITION) {
                     Log.d(TAG, String.format(
                             "%d updateSurfacePosition RenderWorker, frameNr = %d, "
                                     + "position = [%d, %d, %d, %d] clip = [%d, %d, %d, %d] "
-                                    + "surfaceSize = %dx%d",
+                                    + "surfaceSize = %dx%d renderNodeSize = %d%d",
                             System.identityHashCode(SurfaceView.this), frameNumber,
                             left, top, right, bottom, clipLeft, clipTop, clipRight, clipBottom,
-                            mRtSurfaceWidth, mRtSurfaceHeight));
+                            mRtSurfaceWidth, mRtSurfaceHeight,
+                            nodeWidth, nodeHeight));
                 }
                 synchronized (mSurfaceControlLock) {
                     if (mSurfaceControl == null) return;
@@ -1735,14 +1738,29 @@
                             mRTLastReportedPosition.top /*positionTop*/,
                             postScaleX, postScaleY);
 
-                    mRTLastSetCrop.set(clipLeft, clipTop, clipRight, clipBottom);
+                    // The computed crop is in view-relative dimensions, however we need it to be
+                    // in buffer-relative dimensions. So scale the crop by the ratio between
+                    // the view's unscaled width/height (nodeWidth/Height), and the surface's
+                    // width/height
+                    // That is, if the Surface has a fixed size of 50x50, the SurfaceView is laid
+                    // out to a size of 100x100, and the SurfaceView is ultimately scaled to
+                    // 1000x1000, then we need to scale the crop by just the 2x from surface
+                    // domain to SV domain.
+                    final float surfaceToNodeScaleX = (float) mRtSurfaceWidth / (float) nodeWidth;
+                    final float surfaceToNodeScaleY = (float) mRtSurfaceHeight / (float) nodeHeight;
+                    mRTLastSetCrop.set(clipLeft * surfaceToNodeScaleX,
+                            clipTop * surfaceToNodeScaleY,
+                            clipRight * surfaceToNodeScaleX,
+                            clipBottom * surfaceToNodeScaleY);
+
                     if (DEBUG_POSITION) {
-                        Log.d(TAG, String.format("Setting layer crop = [%d, %d, %d, %d] "
+                        Log.d(TAG, String.format("Setting layer crop = [%f, %f, %f, %f] "
                                         + "from scale %f, %f", mRTLastSetCrop.left,
                                 mRTLastSetCrop.top, mRTLastSetCrop.right, mRTLastSetCrop.bottom,
-                                postScaleX, postScaleY));
+                                surfaceToNodeScaleX, surfaceToNodeScaleY));
                     }
-                    mPositionChangedTransaction.setCrop(mSurfaceControl, mRTLastSetCrop);
+                    mPositionChangedTransaction.setCrop(mSurfaceControl, mRTLastSetCrop.left,
+                            mRTLastSetCrop.top, mRTLastSetCrop.right, mRTLastSetCrop.bottom);
                     if (mRTLastSetCrop.isEmpty()) {
                         mPositionChangedTransaction.hide(mSurfaceControl);
                     } else {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d5fc262..d9092ee 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -40,7 +40,6 @@
 import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API;
 import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
 import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
-import static android.view.flags.Flags.calculateBoundsInParentFromBoundsInScreen;
 import static android.view.flags.Flags.enableUseMeasureCacheDuringForceLayout;
 import static android.view.flags.Flags.sensitiveContentAppProtection;
 import static android.view.flags.Flags.toolkitFrameRateAnimationBugfix25q1;
@@ -968,13 +967,6 @@
     private static boolean sAlwaysRemeasureExactly = false;
 
     /**
-     * When true calculates the bounds in parent from bounds in screen relative to its parents.
-     * This addresses the deprecated API (setBoundsInParent) in Compose, which causes empty
-     * getBoundsInParent call for Compose apps.
-     */
-    private static boolean sCalculateBoundsInParentFromBoundsInScreenFlagValue = false;
-
-    /**
      * When true makes it possible to use onMeasure caches also when the force layout flag is
      * enabled. This helps avoiding multiple measures in the same frame with the same dimensions.
      */
@@ -2564,8 +2556,6 @@
 
         sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
         sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision();
-        sCalculateBoundsInParentFromBoundsInScreenFlagValue =
-                calculateBoundsInParentFromBoundsInScreen();
         sUseMeasureCacheDuringForceLayoutFlagValue = enableUseMeasureCacheDuringForceLayout();
     }
 
@@ -9786,7 +9776,7 @@
             structure.setChildCount(1);
             final ViewStructure root = structure.newChild(0);
             if (info != null) {
-                populateVirtualStructure(root, provider, info, null, forAutofill);
+                populateVirtualStructure(root, provider, info, forAutofill);
                 info.recycle();
             } else {
                 Log.w(AUTOFILL_LOG_TAG, "AccessibilityNodeInfo is null.");
@@ -11085,19 +11075,11 @@
 
     private void populateVirtualStructure(ViewStructure structure,
             AccessibilityNodeProvider provider, AccessibilityNodeInfo info,
-            @Nullable AccessibilityNodeInfo parentInfo, boolean forAutofill) {
+            boolean forAutofill) {
         structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()),
                 null, null, info.getViewIdResourceName());
         Rect rect = structure.getTempRect();
-        // The bounds in parent for Jetpack Compose views aren't set as setBoundsInParent is
-        // deprecated, and only setBoundsInScreen is called.
-        // The bounds in parent can be calculated by diff'ing the child view's bounds in screen with
-        // the parent's.
-        if (sCalculateBoundsInParentFromBoundsInScreenFlagValue) {
-            getBoundsInParent(info, parentInfo, rect);
-        } else {
-            info.getBoundsInParent(rect);
-        }
+        info.getBoundsInParent(rect);
         structure.setDimens(rect.left, rect.top, 0, 0, rect.width(), rect.height());
         structure.setVisibility(VISIBLE);
         structure.setEnabled(info.isEnabled());
@@ -11181,32 +11163,13 @@
                         AccessibilityNodeInfo.getVirtualDescendantId(info.getChildId(i)));
                 if (cinfo != null) {
                     ViewStructure child = structure.newChild(i);
-                    populateVirtualStructure(child, provider, cinfo, info, forAutofill);
+                    populateVirtualStructure(child, provider, cinfo, forAutofill);
                     cinfo.recycle();
                 }
             }
         }
     }
 
-    private void getBoundsInParent(@NonNull AccessibilityNodeInfo info,
-            @Nullable AccessibilityNodeInfo parentInfo, @NonNull Rect rect) {
-        info.getBoundsInParent(rect);
-        // Fallback to calculate bounds in parent by diffing the bounds in
-        // screen if it's all 0.
-        if ((rect.left | rect.top | rect.right | rect.bottom) == 0) {
-            if (parentInfo != null) {
-                Rect parentBoundsInScreen = parentInfo.getBoundsInScreen();
-                Rect boundsInScreen = info.getBoundsInScreen();
-                rect.set(boundsInScreen.left - parentBoundsInScreen.left,
-                        boundsInScreen.top - parentBoundsInScreen.top,
-                        boundsInScreen.right - parentBoundsInScreen.left,
-                        boundsInScreen.bottom - parentBoundsInScreen.top);
-            } else {
-                info.getBoundsInScreen(rect);
-            }
-        }
-    }
-
     /**
      * Dispatch creation of {@link ViewStructure} down the hierarchy.  The default
      * implementation calls {@link #onProvideStructure} and
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index e6eec37..9b6311f 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -20,7 +20,6 @@
 import static android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO;
 import static android.appwidget.flags.Flags.drawDataParcel;
 import static android.appwidget.flags.Flags.remoteAdapterConversion;
-import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8;
 import static android.util.proto.ProtoInputStream.NO_MORE_FIELDS;
 import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
 
@@ -55,10 +54,6 @@
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.ServiceConnection;
-import android.content.om.FabricatedOverlay;
-import android.content.om.OverlayInfo;
-import android.content.om.OverlayManager;
-import android.content.om.OverlayManagerTransaction;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.ColorStateList;
@@ -84,12 +79,14 @@
 import android.os.CancellationSignal;
 import android.os.IBinder;
 import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.StrictMode;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.system.Os;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.DisplayMetrics;
@@ -131,8 +128,11 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -8552,8 +8552,12 @@
      * @hide
      */
     public static final class ColorResources {
-        private static final String OVERLAY_NAME = "remote_views_color_resources";
-        private static final String OVERLAY_TARGET_PACKAGE_NAME = "android";
+        // Set of valid colors resources.
+        private static final int FIRST_RESOURCE_COLOR_ID = android.R.color.system_neutral1_0;
+        private static final int LAST_RESOURCE_COLOR_ID =
+            android.R.color.system_error_1000;
+        // Size, in bytes, of an entry in the array of colors in an ARSC file.
+        private static final int ARSC_ENTRY_SIZE = 16;
 
         private final ResourcesLoader mLoader;
         private final SparseIntArray mColorMapping;
@@ -8587,6 +8591,44 @@
         }
 
         /**
+         * Creates the compiled resources content from the asset stored in the APK.
+         *
+         * The asset is a compiled resource with the correct resources name and correct ids, only
+         * the values are incorrect. The last value is at the very end of the file. The resources
+         * are in an array, the array's entries are 16 bytes each. We use this to work out the
+         * location of all the positions of the various resources.
+         */
+        @Nullable
+        private static byte[] createCompiledResourcesContent(Context context,
+                SparseIntArray colorResources) throws IOException {
+            byte[] content;
+            try (InputStream input = context.getResources().openRawResource(
+                    com.android.internal.R.raw.remote_views_color_resources)) {
+                ByteArrayOutputStream rawContent = readFileContent(input);
+                content = rawContent.toByteArray();
+            }
+            int valuesOffset =
+                    content.length - (LAST_RESOURCE_COLOR_ID & 0xffff) * ARSC_ENTRY_SIZE - 4;
+            if (valuesOffset < 0) {
+                Log.e(LOG_TAG, "ARSC file for theme colors is invalid.");
+                return null;
+            }
+            for (int colorRes = FIRST_RESOURCE_COLOR_ID; colorRes <= LAST_RESOURCE_COLOR_ID;
+                    colorRes++) {
+                // The last 2 bytes are the index in the color array.
+                int index = colorRes & 0xffff;
+                int offset = valuesOffset + index * ARSC_ENTRY_SIZE;
+                int value = colorResources.get(colorRes, context.getColor(colorRes));
+                // Write the 32 bit integer in little endian
+                for (int b = 0; b < 4; b++) {
+                    content[offset + b] = (byte) (value & 0xff);
+                    value >>= 8;
+                }
+            }
+            return content;
+        }
+
+        /**
          *  Adds a resource loader for theme colors to the given context.
          *
          * @param context Context of the view hosting the widget.
@@ -8597,38 +8639,31 @@
         @Nullable
         public static ColorResources create(Context context, SparseIntArray colorMapping) {
             try {
-                String owningPackage = context.getPackageName();
-                FabricatedOverlay overlay = new FabricatedOverlay.Builder(owningPackage,
-                        OVERLAY_NAME, OVERLAY_TARGET_PACKAGE_NAME).build();
-
-                for (int i = 0; i < colorMapping.size(); i++) {
-                    overlay.setResourceValue(
-                            context.getResources().getResourceName(colorMapping.keyAt(i)),
-                            TYPE_INT_COLOR_ARGB8, colorMapping.valueAt(i), null);
-                }
-                OverlayManager overlayManager = context.getSystemService(OverlayManager.class);
-                OverlayManagerTransaction.Builder transaction =
-                        new OverlayManagerTransaction.Builder()
-                                .registerFabricatedOverlay(overlay)
-                                .setSelfTargeting(true);
-                overlayManager.commit(transaction.build());
-
-                OverlayInfo overlayInfo =
-                        overlayManager.getOverlayInfosForTarget(OVERLAY_TARGET_PACKAGE_NAME)
-                                .stream()
-                                .filter(info -> TextUtils.equals(info.overlayName, OVERLAY_NAME)
-                                        && TextUtils.equals(info.packageName, owningPackage))
-                                .findFirst()
-                                .orElse(null);
-                if (overlayInfo == null) {
-                    Log.e(LOG_TAG, "Failed to get overlay info ", new Throwable());
+                byte[] contentBytes = createCompiledResourcesContent(context, colorMapping);
+                if (contentBytes == null) {
                     return null;
                 }
-                ResourcesLoader colorsLoader = new ResourcesLoader();
-                colorsLoader.addProvider(ResourcesProvider.loadOverlay(overlayInfo));
-                return new ColorResources(colorsLoader, colorMapping.clone());
-            } catch (Exception e) {
-                Log.e(LOG_TAG, "Failed to add theme color overlay into loader", e);
+                FileDescriptor arscFile = null;
+                try {
+                    arscFile = Os.memfd_create("remote_views_theme_colors.arsc", 0 /* flags */);
+                    // Note: This must not be closed through the OutputStream.
+                    try (OutputStream pipeWriter = new FileOutputStream(arscFile)) {
+                        pipeWriter.write(contentBytes);
+
+                        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(arscFile)) {
+                            ResourcesLoader colorsLoader = new ResourcesLoader();
+                            colorsLoader.addProvider(ResourcesProvider
+                                    .loadFromTable(pfd, null /* assetsProvider */));
+                            return new ColorResources(colorsLoader, colorMapping.clone());
+                        }
+                    }
+                } finally {
+                    if (arscFile != null) {
+                        Os.close(arscFile);
+                    }
+                }
+            } catch (Exception ex) {
+                Log.e(LOG_TAG, "Failed to setup the context for theme colors", ex);
             }
             return null;
         }
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index 255bd67..65cd190 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -45,6 +45,7 @@
 import com.android.internal.R;
 import com.android.internal.util.Preconditions;
 
+import java.time.DateTimeException;
 import java.time.Duration;
 import java.time.Instant;
 import java.time.ZoneId;
@@ -291,11 +292,26 @@
     }
 
     private void createTime(String timeZone) {
-        if (timeZone != null) {
-            mTime = Calendar.getInstance(TimeZone.getTimeZone(timeZone));
+        TimeZone tz = null;
+        if (timeZone == null) {
+            tz = TimeZone.getDefault();
+            // Note that mTimeZone should always be null if timeZone is.
         } else {
-            mTime = Calendar.getInstance();
+            tz = TimeZone.getTimeZone(timeZone);
+            try {
+                // Try converting this TZ to a zoneId to make sure it's valid. This
+                // performs a different set of checks than TimeZone.getTimeZone so
+                // we can avoid exceptions later when we do need this conversion.
+                tz.toZoneId();
+            } catch (DateTimeException ex) {
+                // If we're here, the user supplied timezone is invalid, so reset
+                // mTimeZone to something sane.
+                tz = TimeZone.getDefault();
+                mTimeZone = tz.getID();
+            }
         }
+
+        mTime = Calendar.getInstance(tz);
     }
 
     /**
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 22eec29..dae87dd 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -70,7 +70,12 @@
     ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false),
     ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS(Flags::enableDesktopWindowingExitTransitions, false),
     ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS(
-            Flags::enableWindowingTransitionHandlersObservers, false);
+            Flags::enableWindowingTransitionHandlersObservers, false),
+    ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS(
+            Flags::enableDesktopAppLaunchAlttabTransitions, false),
+    ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS(
+            Flags::enableDesktopAppLaunchTransitions, false),
+    ENABLE_DESKTOP_WINDOWING_PERSISTENCE(Flags::enableDesktopWindowingPersistence, false);
 
     private static final String TAG = "DesktopModeFlagsUtil";
     // Function called to obtain aconfig flag value.
diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
index be3f10a..091975c 100644
--- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
+++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
@@ -28,6 +28,7 @@
 import android.widget.Filter;
 import android.widget.Filterable;
 import android.widget.LinearLayout;
+import android.widget.RadioButton;
 import android.widget.TextView;
 
 import com.android.internal.R;
@@ -242,11 +243,7 @@
                 break;
             case TYPE_SYSTEM_LANGUAGE_FOR_APP_LANGUAGE_PICKER:
                 TextView title;
-                LocaleStore.LocaleInfo info = (LocaleStore.LocaleInfo) getItem(position);
-                if (info == null) {
-                    throw new NullPointerException("Non header locale cannot be null.");
-                }
-                if (info.isAppCurrentLocale()) {
+                if (mHasSpecificAppPackageName) {
                     title = itemView.findViewById(R.id.language_picker_item);
                 } else {
                     title = itemView.findViewById(R.id.locale);
@@ -254,11 +251,22 @@
                 title.setText(R.string.system_locale_title);
                 break;
             case TYPE_CURRENT_LOCALE:
-                updateTextView(itemView,
-                        itemView.findViewById(R.id.language_picker_item), position);
+                updateTextView(
+                        itemView, itemView.findViewById(R.id.language_picker_item), position);
                 break;
             default:
-                updateTextView(itemView, itemView.findViewById(R.id.locale), position);
+                LocaleStore.LocaleInfo localeInfo = (LocaleStore.LocaleInfo) getItem(position);
+                if (localeInfo == null) {
+                    throw new NullPointerException("Non header locale cannot be null.");
+                }
+                if (mHasSpecificAppPackageName && localeInfo.isSuggested() && !mCountryMode) {
+                    updateTextView(
+                            itemView,
+                            itemView.findViewById(R.id.language_picker_item),
+                            position);
+                } else {
+                    updateTextView(itemView, itemView.findViewById(R.id.locale), position);
+                }
                 break;
         }
         return itemView;
@@ -280,20 +288,21 @@
                 }
                 break;
             case TYPE_SYSTEM_LANGUAGE_FOR_APP_LANGUAGE_PICKER:
-                if (((LocaleStore.LocaleInfo) getItem(position)).isAppCurrentLocale()) {
-                    shouldReuseView = convertView instanceof LinearLayout
-                            && convertView.findViewById(R.id.language_picker_item) != null;
-                    if (!shouldReuseView) {
-                        updatedView = mInflater.inflate(
-                                R.layout.app_language_picker_current_locale_item,
-                                parent, false);
+                if (mHasSpecificAppPackageName) {
+                    updatedView = mInflater.inflate(
+                        R.layout.app_language_picker_locale_item, parent, false);
+                    RadioButton option = updatedView.findViewById(R.id.checkbox);
+                    if (((LocaleStore.LocaleInfo) getItem(position)).isAppCurrentLocale()) {
+                        option.setChecked(true);
+                    } else {
+                        option.setChecked(false);
                     }
                 } else {
                     shouldReuseView = convertView instanceof TextView
-                            && convertView.findViewById(R.id.locale) != null;
+                        && convertView.findViewById(R.id.locale) != null;
                     if (!shouldReuseView) {
-                        updatedView = mInflater.inflate(
-                                R.layout.language_picker_item, parent, false);
+                        updatedView =
+                            mInflater.inflate(R.layout.language_picker_item, parent, false);
                     }
                 }
                 break;
@@ -302,14 +311,30 @@
                         && convertView.findViewById(R.id.language_picker_item) != null;
                 if (!shouldReuseView) {
                     updatedView = mInflater.inflate(
-                            R.layout.app_language_picker_current_locale_item, parent, false);
+                            R.layout.app_language_picker_locale_item, parent, false);
+                    RadioButton option = updatedView.findViewById(R.id.checkbox);
+                    option.setChecked(true);
                 }
                 break;
             default:
-                shouldReuseView = convertView instanceof TextView
+                LocaleStore.LocaleInfo localeInfo = (LocaleStore.LocaleInfo) getItem(position);
+                if (mHasSpecificAppPackageName
+                        && localeInfo.isSuggested() && !mCountryMode) {
+                    shouldReuseView = convertView instanceof LinearLayout
+                        && convertView.findViewById(R.id.language_picker_item) != null;
+                    if ((!shouldReuseView)) {
+                        updatedView = mInflater.inflate(
+                            R.layout.app_language_picker_locale_item, parent, false);
+                        RadioButton option = updatedView.findViewById(R.id.checkbox);
+                        option.setChecked(false);
+                    }
+                } else {
+                    shouldReuseView = convertView instanceof TextView
                         && convertView.findViewById(R.id.locale) != null;
-                if (!shouldReuseView) {
-                    updatedView = mInflater.inflate(R.layout.language_picker_item, parent, false);
+                    if ((!shouldReuseView)) {
+                        updatedView = mInflater.inflate(
+                            R.layout.language_picker_item, parent, false);
+                    }
                 }
                 break;
         }
diff --git a/core/java/com/android/internal/content/om/OverlayConfig.java b/core/java/com/android/internal/content/om/OverlayConfig.java
index 38593b4..07e178c 100644
--- a/core/java/com/android/internal/content/om/OverlayConfig.java
+++ b/core/java/com/android/internal/content/om/OverlayConfig.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.pm.PackagePartitions;
-import android.content.res.AssetManager;
 import android.os.Build;
 import android.os.Trace;
 import android.util.ArrayMap;
@@ -534,7 +533,7 @@
      */
     @NonNull
     public String[] createImmutableFrameworkIdmapsInZygote() {
-        final String targetPath = AssetManager.FRAMEWORK_APK_PATH;
+        final String targetPath = "/system/framework/framework-res.apk";
         final ArrayList<String> idmapPaths = new ArrayList<>();
         final ArrayList<IdmapInvocation> idmapInvocations =
                 getImmutableFrameworkOverlayIdmapInvocations();
diff --git a/core/java/com/android/internal/content/om/OverlayManagerImpl.java b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
index fa5cf2a..c462449 100644
--- a/core/java/com/android/internal/content/om/OverlayManagerImpl.java
+++ b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
@@ -35,7 +35,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.parsing.FrameworkParsingPackageUtils;
-import android.content.res.AssetManager;
 import android.os.FabricatedOverlayInfo;
 import android.os.FabricatedOverlayInternal;
 import android.os.FabricatedOverlayInternalEntry;
@@ -61,8 +60,8 @@
 import java.util.Objects;
 
 /**
- * This class provides the functionalities for managing self-targeting overlays, including
- * registering an overlay, unregistering an overlay, and getting the list of overlays information.
+ * This class provides the functionalities of registering an overlay, unregistering an overlay, and
+ * getting the list of overlays information.
  */
 public class OverlayManagerImpl {
     private static final String TAG = "OverlayManagerImpl";
@@ -235,17 +234,14 @@
         Preconditions.checkArgument(!entryList.isEmpty(), "overlay entries shouldn't be empty");
         final String overlayName = checkOverlayNameValid(overlayInternal.overlayName);
         checkPackageName(overlayInternal.packageName);
-        Preconditions.checkStringNotEmpty(overlayInternal.targetPackageName);
+        checkPackageName(overlayInternal.targetPackageName);
+        Preconditions.checkStringNotEmpty(
+                overlayInternal.targetOverlayable,
+                "Target overlayable should be neither null nor empty string.");
 
         final ApplicationInfo applicationInfo = mContext.getApplicationInfo();
-        String targetPackage = null;
-        if (TextUtils.equals(overlayInternal.targetPackageName, "android")) {
-            targetPackage = AssetManager.FRAMEWORK_APK_PATH;
-        } else {
-            targetPackage = Preconditions.checkStringNotEmpty(
-                    applicationInfo.getBaseCodePath());
-        }
-
+        final String targetPackage = Preconditions.checkStringNotEmpty(
+                applicationInfo.getBaseCodePath());
         final Path frroPath = mBasePath.resolve(overlayName + FRRO_EXTENSION);
         final Path idmapPath = mBasePath.resolve(overlayName + IDMAP_EXTENSION);
 
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 0af4bea..44c0bd0 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -54,6 +54,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -448,7 +449,7 @@
     }
 
     @Override
-    public void onJankDataAvailable(SurfaceControl.JankData[] jankData) {
+    public void onJankDataAvailable(List<SurfaceControl.JankData> jankData) {
         postCallback(() -> {
             try {
                 Trace.beginSection("FrameTracker#onJankDataAvailable");
@@ -832,7 +833,7 @@
         /** adds the jank listener to the given surface */
         public SurfaceControl.OnJankDataListenerRegistration addJankStatsListener(
                 SurfaceControl.OnJankDataListener listener, SurfaceControl surfaceControl) {
-            return surfaceControl.addJankDataListener(listener);
+            return surfaceControl.addOnJankDataListener(listener);
         }
     }
 
diff --git a/core/java/com/android/server/FgThread.java b/core/java/com/android/server/FgThread.java
index f54ee5e..f8a6bb0 100644
--- a/core/java/com/android/server/FgThread.java
+++ b/core/java/com/android/server/FgThread.java
@@ -33,6 +33,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class FgThread extends ServiceThread {
     private static final long SLOW_DISPATCH_THRESHOLD_MS = 100;
     private static final long SLOW_DELIVERY_THRESHOLD_MS = 200;
diff --git a/core/java/com/android/server/ServiceThread.java b/core/java/com/android/server/ServiceThread.java
index 0eff1fc..86e507b 100644
--- a/core/java/com/android/server/ServiceThread.java
+++ b/core/java/com/android/server/ServiceThread.java
@@ -27,6 +27,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ServiceThread extends HandlerThread {
     private static final String TAG = "ServiceThread";
 
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 2283b88..2bb6e71 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -105,18 +105,7 @@
     ],
 
     shared_libs: [
-        "libbase",
-        "libcutils",
         "libtracing_perfetto",
-        "libharfbuzz_ng",
-        "liblog",
-        "libmediautils",
-        "libminikin",
-        "libz",
-        "server_configurable_flags",
-        "libaconfig_storage_read_api_cc",
-        "android.database.sqlite-aconfig-cc",
-        "android.media.audiopolicy-aconfig-cc",
     ],
 
     static_libs: [
@@ -303,6 +292,14 @@
             ],
 
             shared_libs: [
+                "libbase",
+                "libharfbuzz_ng",
+                "liblog",
+                "libmediautils",
+                "libminikin",
+                "libz",
+                "android.database.sqlite-aconfig-cc",
+                "android.media.audiopolicy-aconfig-cc",
                 "audioclient-types-aidl-cpp",
                 "audioflinger-aidl-cpp",
                 "audiopolicy-types-aidl-cpp",
@@ -412,20 +409,24 @@
                 "frameworks/native/libs/nativebase/include",
                 "frameworks/native/libs/nativewindow/include",
             ],
-            shared_libs: [
-                "libicui18n",
-                "libicuuc",
-            ],
             static_libs: [
                 "libandroidfw",
+                "libbase",
                 "libbinary_parse",
+                "libcutils",
                 "libdng_sdk",
                 "libft2",
+                "libharfbuzz_ng",
                 "libhostgraphics",
                 "libhwui",
+                "libicui18n",
+                "libicuuc",
+                "libicuuc_stubdata",
                 "libimage_type_recognition",
                 "libinput",
                 "libjpeg",
+                "liblog",
+                "libminikin",
                 "libnativehelper_jvm",
                 "libpiex",
                 "libpng",
@@ -435,11 +436,18 @@
                 "libwebp-decode",
                 "libwebp-encode",
                 "libwuffs_mirror_release_c",
+                "libz",
                 "libimage_io",
                 "libjpegdecoder",
                 "libjpegencoder",
                 "libultrahdr",
+                "server_configurable_flags",
             ],
+            export_static_lib_headers: [
+                "libnativehelper_jvm",
+                "libui-types",
+            ],
+            stl: "libc++_static",
         },
         host_linux: {
             srcs: [
@@ -465,14 +473,18 @@
                 "libbinderthreadstateutils",
                 "libsqlite",
                 "libgui_window_info_static",
-            ],
-            shared_libs: [
-                // libbinder needs to be shared since it has global state
-                // (e.g. gDefaultServiceManager)
                 "libbinder",
                 "libhidlbase", // libhwbinder is in here
             ],
         },
+        linux_glibc_x86_64: {
+            ldflags: ["-static-libgcc"],
+            dist: {
+                targets: ["layoutlib"],
+                dir: "layoutlib_native/linux",
+                tag: "stripped_all",
+            },
+        },
     },
 }
 
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 7fefe17..df87a69 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -298,6 +298,11 @@
     jmethodID ctor;
 } gFrameRateCategoryRateClassInfo;
 
+static struct {
+    jclass clazz;
+    jmethodID asList;
+} gUtilArrays;
+
 constexpr ui::Dataspace pickDataspaceFromColorMode(const ui::ColorMode colorMode) {
     switch (colorMode) {
         case ui::ColorMode::DISPLAY_P3:
@@ -2206,10 +2211,13 @@
             env->SetObjectArrayElement(jJankDataArray, i, jJankData);
             env->DeleteLocalRef(jJankData);
         }
-        env->CallVoidMethod(target,
-                gJankDataListenerClassInfo.onJankDataAvailable,
-                jJankDataArray);
+
+        jobject jJankDataList =
+                env->CallStaticObjectMethod(gUtilArrays.clazz, gUtilArrays.asList, jJankDataArray);
         env->DeleteLocalRef(jJankDataArray);
+
+        env->CallVoidMethod(target, gJankDataListenerClassInfo.onJankDataAvailable, jJankDataList);
+        env->DeleteLocalRef(jJankDataList);
         env->DeleteLocalRef(target);
 
         return true;
@@ -2858,7 +2866,7 @@
     gJankDataListenerClassInfo.clazz = MakeGlobalRefOrDie(env, onJankDataListenerClazz);
     gJankDataListenerClassInfo.onJankDataAvailable =
             GetMethodIDOrDie(env, onJankDataListenerClazz, "onJankDataAvailable",
-                             "([Landroid/view/SurfaceControl$JankData;)V");
+                             "(Ljava/util/List;)V");
 
     jclass transactionCommittedListenerClazz =
             FindClassOrDie(env, "android/view/SurfaceControl$TransactionCommittedListener");
@@ -2933,6 +2941,10 @@
     gStalledTransactionInfoClassInfo.frameNumber =
             GetFieldIDOrDie(env, stalledTransactionInfoClazz, "frameNumber", "J");
 
+    jclass utilArrays = FindClassOrDie(env, "java/util/Arrays");
+    gUtilArrays.clazz = MakeGlobalRefOrDie(env, utilArrays);
+    gUtilArrays.asList = GetStaticMethodIDOrDie(env, utilArrays, "asList",
+                                                "([Ljava/lang/Object;)Ljava/util/List;");
     return err;
 }
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 97682c8..5044a30 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8535,10 +8535,14 @@
         @hide
     -->
     <permission
-        android:name="android.permission.RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED"
+        android:name="android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED"
         android:protectionLevel="internal"
         android:featureFlag="android.content.pm.reduce_broadcasts_for_component_state_changes"/>
 
+    <uses-permission
+        android:name="android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED"
+        android:featureFlag="android.content.pm.reduce_broadcasts_for_component_state_changes"/>
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
diff --git a/core/res/res/layout/app_language_picker_current_locale_item.xml b/core/res/res/layout/app_language_picker_locale_item.xml
similarity index 73%
rename from core/res/res/layout/app_language_picker_current_locale_item.xml
rename to core/res/res/layout/app_language_picker_locale_item.xml
index edd6d64..bcad9ce 100644
--- a/core/res/res/layout/app_language_picker_current_locale_item.xml
+++ b/core/res/res/layout/app_language_picker_locale_item.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2022 The Android Open Source Project
+  ~ 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.
@@ -20,10 +20,27 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:gravity="center_vertical">
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:minHeight="?android:attr/listPreferredItemHeight"
+        android:layout_marginStart="20dip">
+
+      <RadioButton
+          android:id="@+id/checkbox"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:layout_gravity="center"
+          android:background="@null"
+          android:focusable="false"
+          android:clickable="false" />
+
+    </LinearLayout>
+
     <RelativeLayout
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginEnd="6dip"
         android:layout_marginTop="6dip"
         android:layout_marginBottom="6dip"
         android:layout_weight="1">
@@ -31,20 +48,4 @@
             android:id="@+id/language_picker_item"
             layout="@layout/language_picker_item" />
     </RelativeLayout>
-
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:gravity="center"
-        android:minHeight="?android:attr/listPreferredItemHeight">
-        <ImageView
-            android:id="@+id/imageView"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_centerVertical="true"
-            android:layout_marginHorizontal="16dp"
-            android:src="@drawable/ic_check_24dp"
-            app:tint="?attr/colorAccentPrimaryVariant"
-            android:contentDescription="@*android:string/checked"/>
-    </LinearLayout>
 </LinearLayout>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b32d4cf..515ebd5 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5264,7 +5264,7 @@
   <java-symbol type="string" name="system_locale_title" />
   <java-symbol type="layout" name="app_language_picker_system_default" />
   <java-symbol type="layout" name="app_language_picker_system_current" />
-  <java-symbol type="layout" name="app_language_picker_current_locale_item" />
+  <java-symbol type="layout" name="app_language_picker_locale_item" />
   <java-symbol type="id" name="system_locale_subtitle" />
   <java-symbol type="id" name="language_picker_item" />
   <java-symbol type="id" name="language_picker_header" />
diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
index 60b5a42..e7a6cb7 100644
--- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
@@ -67,6 +67,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
+import java.util.Arrays;
 import java.util.concurrent.TimeUnit;
 
 @SmallTest
@@ -690,10 +691,9 @@
             FrameTracker tracker, long durationMillis, long vsyncId, @JankType int jankType) {
         final ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
         doNothing().when(tracker).postCallback(captor.capture());
-        mListenerCapture.getValue().onJankDataAvailable(new JankData[] {
-                new JankData(vsyncId, jankType, FRAME_TIME_60Hz, FRAME_TIME_60Hz,
-                TimeUnit.MILLISECONDS.toNanos(durationMillis))
-        });
+        mListenerCapture.getValue().onJankDataAvailable(Arrays.asList(new JankData(
+                vsyncId, jankType, FRAME_TIME_60Hz, FRAME_TIME_60Hz,
+                TimeUnit.MILLISECONDS.toNanos(durationMillis))));
         captor.getValue().run();
     }
 }
diff --git a/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
index 28d6545..40d0bef 100644
--- a/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
+++ b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
@@ -210,6 +210,21 @@
     }
 
     @Test
+    public void registerOverlay_forAndroidPackage_shouldFail() {
+        FabricatedOverlayInternal overlayInternal =
+                createOverlayWithName(
+                        mOverlayName,
+                        SYSTEM_APP_OVERLAYABLE,
+                        "android",
+                        List.of(Pair.create("color/white", Pair.create(null, Color.BLACK))));
+
+        assertThrows(
+                "Wrong target package name",
+                IllegalArgumentException.class,
+                () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+    }
+
+    @Test
     public void getOverlayInfosForTarget_defaultShouldBeZero() {
         List<OverlayInfo> overlayInfos =
                 mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName());
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 211f74a..03a8b30 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -279,7 +279,8 @@
          * @hide
          */
         default void positionChanged(long frameNumber, int left, int top, int right, int bottom,
-                int clipLeft, int clipTop, int clipRight, int clipBottom) {
+                int clipLeft, int clipTop, int clipRight, int clipBottom,
+                int nodeWidth, int nodeHeight) {
             positionChanged(frameNumber, left, top, right, bottom);
         }
 
@@ -304,11 +305,12 @@
          * @hide */
         static boolean callPositionChanged2(WeakReference<PositionUpdateListener> weakListener,
                 long frameNumber, int left, int top, int right, int bottom,
-                int clipLeft, int clipTop, int clipRight, int clipBottom) {
+                int clipLeft, int clipTop, int clipRight, int clipBottom,
+                int nodeWidth, int nodeHeight) {
             final PositionUpdateListener listener = weakListener.get();
             if (listener != null) {
                 listener.positionChanged(frameNumber, left, top, right, bottom, clipLeft,
-                        clipTop, clipRight, clipBottom);
+                        clipTop, clipRight, clipBottom, nodeWidth, nodeHeight);
                 return true;
             } else {
                 return false;
@@ -401,10 +403,11 @@
 
         @Override
         public void positionChanged(long frameNumber, int left, int top, int right, int bottom,
-                int clipLeft, int clipTop, int clipRight, int clipBottom) {
+                int clipLeft, int clipTop, int clipRight, int clipBottom,
+                int nodeWidth, int nodeHeight) {
             for (PositionUpdateListener pul : mListeners) {
                 pul.positionChanged(frameNumber, left, top, right, bottom, clipLeft, clipTop,
-                        clipRight, clipBottom);
+                        clipRight, clipBottom, nodeWidth, nodeHeight);
             }
         }
 
diff --git a/graphics/java/android/graphics/RuntimeColorFilter.java b/graphics/java/android/graphics/RuntimeColorFilter.java
new file mode 100644
index 0000000..52724ce
--- /dev/null
+++ b/graphics/java/android/graphics/RuntimeColorFilter.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.ColorInt;
+import android.annotation.ColorLong;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+
+import com.android.graphics.hwui.flags.Flags;
+
+
+/**
+ * <p>A {@link RuntimeColorFilter} calculates a per-pixel color based on the output of a user
+ *  * defined Android Graphics Shading Language (AGSL) function.</p>
+ *
+ * <p>This AGSL function takes in an input color to be operated on. This color is in sRGB and the
+ *  * output is also interpreted as sRGB. The AGSL function signature expects a single input
+ *  * of color (packed as a half4 or float4 or vec4).</p>
+ *
+ * <pre class="prettyprint">
+ * vec4 main(half4 in_color);
+ * </pre>
+ */
+@FlaggedApi(Flags.FLAG_RUNTIME_COLOR_FILTERS_BLENDERS)
+public class RuntimeColorFilter extends ColorFilter {
+
+    private String mAgsl;
+
+    /**
+     * Creates a new RuntimeColorFilter.
+     *
+     * @param agsl The text of AGSL color filter program to run.
+     */
+    public RuntimeColorFilter(@NonNull String agsl) {
+        if (agsl == null) {
+            throw new NullPointerException("RuntimeColorFilter requires a non-null AGSL string");
+        }
+        mAgsl = agsl;
+        // call to parent class to register native RuntimeColorFilter
+        // TODO: find way to get super class to create native instance without requiring the storage
+        // of agsl string
+        getNativeInstance();
+
+    }
+    /**
+     * Sets the uniform color value corresponding to this color filter.  If the effect does not have
+     * a uniform with that name or if the uniform is declared with a type other than vec3 or vec4
+     * and corresponding layout(color) annotation then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the color uniform declared in the AGSL program
+     * @param color the provided sRGB color
+     */
+    public void setColorUniform(@NonNull String uniformName, @ColorInt int color) {
+        setUniform(uniformName, Color.valueOf(color).getComponents(), true);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to this color filter.  If the effect does not have
+     * a uniform with that name or if the uniform is declared with a type other than vec3 or vec4
+     * and corresponding layout(color) annotation then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the color uniform declared in the AGSL program
+     * @param color the provided sRGB color
+     */
+    public void setColorUniform(@NonNull String uniformName, @ColorLong long color) {
+        Color exSRGB = Color.valueOf(color).convert(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
+        setUniform(uniformName, exSRGB.getComponents(), true);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to this color filter.  If the effect does not have
+     * a uniform with that name or if the uniform is declared with a type other than vec3 or vec4
+     * and corresponding layout(color) annotation then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the color uniform declared in the AGSL program
+     * @param color the provided sRGB color
+     */
+    public void setColorUniform(@NonNull String uniformName, @NonNull Color color) {
+        if (color == null) {
+            throw new NullPointerException("The color parameter must not be null");
+        }
+        Color exSRGB = color.convert(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
+        setUniform(uniformName, exSRGB.getComponents(), true);
+    }
+
+    /**
+     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
+     * uniform with that name or if the uniform is declared with a type other than a float or
+     * float[1] then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL program
+     */
+    public void setFloatUniform(@NonNull String uniformName, float value) {
+        setFloatUniform(uniformName, value, 0.0f, 0.0f, 0.0f, 1);
+    }
+
+    /**
+     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
+     * uniform with that name or if the uniform is declared with a type other than a vec2 or
+     * float[2] then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL program
+     */
+    public void setFloatUniform(@NonNull String uniformName, float value1, float value2) {
+        setFloatUniform(uniformName, value1, value2, 0.0f, 0.0f, 2);
+    }
+
+    /**
+     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
+     * uniform with that name or if the uniform is declared with a type other than a vec3 or
+     * float[3] then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL program
+     */
+    public void setFloatUniform(@NonNull String uniformName, float value1, float value2,
+            float value3) {
+        setFloatUniform(uniformName, value1, value2, value3, 0.0f, 3);
+
+    }
+
+    /**
+     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
+     * uniform with that name or if the uniform is declared with a type other than a vec4 or
+     * float[4] then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL program
+     */
+    public void setFloatUniform(@NonNull String uniformName, float value1, float value2,
+            float value3, float value4) {
+        setFloatUniform(uniformName, value1, value2, value3, value4, 4);
+    }
+
+    /**
+     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
+     * uniform with that name or if the uniform is declared with a type other than a float
+     * (for N=1), vecN, or float[N] where N is the length of the values param then an
+     * IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL program
+     */
+    public void setFloatUniform(@NonNull String uniformName, @NonNull float[] values) {
+        setUniform(uniformName, values, false);
+    }
+
+    private void setFloatUniform(@NonNull String uniformName, float value1, float value2,
+            float value3, float value4, int count) {
+        if (uniformName == null) {
+            throw new NullPointerException("The uniformName parameter must not be null");
+        }
+        nativeUpdateUniforms(getNativeInstance(), uniformName, value1, value2, value3, value4,
+                count);
+    }
+
+    private void setUniform(@NonNull String uniformName, @NonNull float[] values, boolean isColor) {
+        if (uniformName == null) {
+            throw new NullPointerException("The uniformName parameter must not be null");
+        }
+        if (values == null) {
+            throw new NullPointerException("The uniform values parameter must not be null");
+        }
+        nativeUpdateUniforms(getNativeInstance(), uniformName, values, isColor);
+    }
+
+    /**
+     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
+     * uniform with that name or if the uniform is declared with a type other than an int or int[1]
+     * then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL program
+     */
+    public void setIntUniform(@NonNull String uniformName, int value) {
+        setIntUniform(uniformName, value, 0, 0, 0, 1);
+    }
+
+    /**
+     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
+     * uniform with that name or if the uniform is declared with a type other than an ivec2 or
+     * int[2] then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL program
+     */
+    public void setIntUniform(@NonNull String uniformName, int value1, int value2) {
+        setIntUniform(uniformName, value1, value2, 0, 0, 2);
+    }
+
+    /**
+     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
+     * uniform with that name or if the uniform is declared with a type other than an ivec3 or
+     * int[3] then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL program
+     */
+    public void setIntUniform(@NonNull String uniformName, int value1, int value2, int value3) {
+        setIntUniform(uniformName, value1, value2, value3, 0, 3);
+    }
+
+    /**
+     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
+     * uniform with that name or if the uniform is declared with a type other than an ivec4 or
+     * int[4] then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL program
+     */
+    public void setIntUniform(@NonNull String uniformName, int value1, int value2,
+            int value3, int value4) {
+        setIntUniform(uniformName, value1, value2, value3, value4, 4);
+    }
+
+    /**
+     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
+     * uniform with that name or if the uniform is declared with a type other than an int (for N=1),
+     * ivecN, or int[N] where N is the length of the values param then an IllegalArgumentException
+     * is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL program
+     */
+    public void setIntUniform(@NonNull String uniformName, @NonNull int[] values) {
+        if (uniformName == null) {
+            throw new NullPointerException("The uniformName parameter must not be null");
+        }
+        if (values == null) {
+            throw new NullPointerException("The uniform values parameter must not be null");
+        }
+        nativeUpdateUniforms(getNativeInstance(), uniformName, values);
+    }
+
+    private void setIntUniform(@NonNull String uniformName, int value1, int value2, int value3,
+            int value4, int count) {
+        if (uniformName == null) {
+            throw new NullPointerException("The uniformName parameter must not be null");
+        }
+        nativeUpdateUniforms(getNativeInstance(), uniformName, value1, value2, value3, value4,
+                count);
+    }
+
+    /**
+     * Assigns the uniform shader to the provided shader parameter.  If the shader program does not
+     * have a uniform shader with that name then an IllegalArgumentException is thrown.
+     *
+     * @param shaderName name matching the uniform declared in the AGSL program
+     * @param shader shader passed into the AGSL program for sampling
+     */
+    public void setInputShader(@NonNull String shaderName, @NonNull Shader shader) {
+        if (shaderName == null) {
+            throw new NullPointerException("The shaderName parameter must not be null");
+        }
+        if (shader == null) {
+            throw new NullPointerException("The shader parameter must not be null");
+        }
+        nativeUpdateChild(getNativeInstance(), shaderName, shader.getNativeInstance());
+    }
+
+    /**
+     * Assigns the uniform color filter to the provided color filter parameter.  If the shader
+     * program does not have a uniform color filter with that name then an IllegalArgumentException
+     * is thrown.
+     *
+     * @param filterName name matching the uniform declared in the AGSL program
+     * @param colorFilter filter passed into the AGSL program for sampling
+     */
+    public void setInputColorFilter(@NonNull String filterName, @NonNull ColorFilter colorFilter) {
+        if (filterName == null) {
+            throw new NullPointerException("The filterName parameter must not be null");
+        }
+        if (colorFilter == null) {
+            throw new NullPointerException("The colorFilter parameter must not be null");
+        }
+        nativeUpdateChild(getNativeInstance(), filterName, colorFilter.getNativeInstance());
+    }
+
+    /** @hide */
+    @Override
+    protected long createNativeInstance() {
+        return nativeCreateRuntimeColorFilter(mAgsl);
+    }
+
+    private static native long nativeCreateRuntimeColorFilter(String agsl);
+    private static native void nativeUpdateUniforms(
+            long colorFilter, String uniformName, float[] uniforms, boolean isColor);
+    private static native void nativeUpdateUniforms(
+            long colorFilter, String uniformName, float value1, float value2, float value3,
+            float value4, int count);
+    private static native void nativeUpdateUniforms(
+            long colorFilter, String uniformName, int[] uniforms);
+    private static native void nativeUpdateUniforms(
+            long colorFilter, String uniformName, int value1, int value2, int value3,
+            int value4, int count);
+    private static native void nativeUpdateChild(long colorFilter, String childName, long child);
+
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index ad194f7..6398c7a2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -39,6 +39,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.annotation.ColorInt;
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityThread;
@@ -1394,10 +1395,14 @@
         }
 
         private void showVeils(@NonNull SurfaceControl.Transaction t) {
-            final Color primaryVeilColor = getContainerBackgroundColor(
-                    mProperties.mPrimaryContainer, DEFAULT_PRIMARY_VEIL_COLOR);
-            final Color secondaryVeilColor = getContainerBackgroundColor(
-                    mProperties.mSecondaryContainer, DEFAULT_SECONDARY_VEIL_COLOR);
+            final Color primaryVeilColor = getVeilColor(
+                    mProperties.mDividerAttributes.getPrimaryVeilColor(),
+                    mProperties.mPrimaryContainer,
+                    DEFAULT_PRIMARY_VEIL_COLOR);
+            final Color secondaryVeilColor = getVeilColor(
+                    mProperties.mDividerAttributes.getSecondaryVeilColor(),
+                    mProperties.mSecondaryContainer,
+                    DEFAULT_SECONDARY_VEIL_COLOR);
             t.setColor(mPrimaryVeil, colorToFloatArray(primaryVeilColor))
                     .setColor(mSecondaryVeil, colorToFloatArray(secondaryVeilColor))
                     .setLayer(mDividerSurface, DIVIDER_LAYER)
@@ -1444,6 +1449,21 @@
             }
         }
 
+        /**
+         * Returns the veil color.
+         *
+         * If the configured color is not transparent, we use the configured color, otherwise we use
+         * the window background color of the top activity. If the background color of the top
+         * activity is unavailable, the default color is used.
+         */
+        @NonNull
+        private static Color getVeilColor(@ColorInt int configuredColor,
+                @NonNull TaskFragmentContainer container, @NonNull Color defaultColor) {
+            return configuredColor != Color.TRANSPARENT
+                    ? Color.valueOf(configuredColor)
+                    : getContainerBackgroundColor(container, defaultColor);
+        }
+
         private static float[] colorToFloatArray(@NonNull Color color) {
             return new float[]{color.red(), color.green(), color.blue()};
         }
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
index 61c09f2..7f54c75 100644
--- a/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
@@ -22,42 +22,6 @@
     default_team: "trendy_team_multitasking_windowing",
 }
 
-android_app {
-    name: "WMShellRobolectricScreenshotTestApp",
-    platform_apis: true,
-    certificate: "platform",
-    static_libs: [
-        "WindowManager-Shell",
-        "platform-screenshot-diff-core",
-        "ScreenshotComposeUtilsLib", // ComposableScreenshotTestRule & Theme.PlatformUi.Screenshot
-        "SystemUI-res", // Theme.SystemUI (dragged in by ScreenshotComposeUtilsLib)
-    ],
-    asset_dirs: ["goldens/robolectric"],
-    manifest: "AndroidManifestRobolectric.xml",
-    use_resource_processor: true,
-}
-
-android_robolectric_test {
-    name: "WMShellRobolectricScreenshotTests",
-    instrumentation_for: "WMShellRobolectricScreenshotTestApp",
-    upstream: true,
-    java_resource_dirs: [
-        "robolectric/config",
-    ],
-    srcs: [
-        "src/**/*.kt",
-    ],
-    static_libs: [
-        "junit",
-        "androidx.test.runner",
-        "androidx.test.rules",
-        "androidx.test.ext.junit",
-        "truth",
-        "platform-parametric-runner-lib",
-    ],
-    auto_gen_config: true,
-}
-
 android_test {
     name: "WMShellMultivalentScreenshotTestsOnDevice",
     srcs: [
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index 52ce8cb..0b515f5 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -23,14 +23,15 @@
 import android.graphics.Color
 import android.graphics.drawable.Icon
 import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
-import android.view.IWindowManager
 import android.view.WindowManager
-import android.view.WindowManagerGlobal
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.internal.protolog.ProtoLog
 import com.android.launcher3.icons.BubbleIconFactory
@@ -41,6 +42,7 @@
 import com.android.wm.shell.common.FloatingContentCoordinator
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
 import com.android.wm.shell.taskview.TaskView
 import com.android.wm.shell.taskview.TaskViewTaskController
 import com.google.common.truth.Truth.assertThat
@@ -51,9 +53,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.mock
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
-import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+import org.mockito.kotlin.verify
 import java.util.concurrent.Semaphore
 import java.util.concurrent.TimeUnit
 import java.util.function.Consumer
@@ -72,7 +72,7 @@
     private lateinit var expandedViewManager: FakeBubbleExpandedViewManager
     private lateinit var bubbleStackView: BubbleStackView
     private lateinit var shellExecutor: ShellExecutor
-    private lateinit var windowManager: IWindowManager
+    private lateinit var windowManager: WindowManager
     private lateinit var bubbleTaskViewFactory: BubbleTaskViewFactory
     private lateinit var bubbleData: BubbleData
     private lateinit var bubbleStackViewManager: FakeBubbleStackViewManager
@@ -83,9 +83,8 @@
         PhysicsAnimatorTestUtils.prepareForTest()
         // Disable protolog tool when running the tests from studio
         ProtoLog.REQUIRE_PROTOLOGTOOL = false
-        windowManager = WindowManagerGlobal.getWindowManagerService()!!
         shellExecutor = TestShellExecutor()
-        val windowManager = context.getSystemService(WindowManager::class.java)
+        windowManager = context.getSystemService(WindowManager::class.java)
         iconFactory =
             BubbleIconFactory(
                 context,
@@ -354,6 +353,16 @@
         assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isGreaterThan(-1)
     }
 
+    @Test
+    fun removeFromWindow_stopMonitoringSwipeUpGesture() {
+        spyOn(bubbleStackView)
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            // No way to add to window in the test environment right now so just pretend
+            bubbleStackView.onDetachedFromWindow()
+        }
+        verify(bubbleStackView).stopMonitoringSwipeUpGesture()
+    }
+
     private fun createAndInflateChatBubble(key: String): Bubble {
         val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button)
         val shortcutInfo = ShortcutInfo.Builder(context, "fakeId").setIcon(icon).build()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 35a0d07..88f55b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1704,6 +1704,7 @@
         getViewTreeObserver().removeOnPreDrawListener(mViewUpdater);
         getViewTreeObserver().removeOnDrawListener(mSystemGestureExcludeUpdater);
         getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+        stopMonitoringSwipeUpGesture();
     }
 
     @Override
@@ -2313,7 +2314,8 @@
     /**
      * Stop monitoring for swipe up gesture
      */
-    void stopMonitoringSwipeUpGesture() {
+    @VisibleForTesting
+    public void stopMonitoringSwipeUpGesture() {
         stopMonitoringSwipeUpGestureInternal();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
index e741892..f69aa6d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
@@ -224,9 +224,9 @@
         finishTransaction: SurfaceControl.Transaction,
         finishCallback: Transitions.TransitionFinishCallback
     ): Boolean {
-        logD("startAnimation transition=%s", transition)
         val state = requireState()
         if (transition != state.transition) return false
+        logD("startAnimation transition=%s", transition)
         animateResize(
             targetTaskId = state.taskId,
             info = info,
@@ -334,7 +334,6 @@
         startTransaction: SurfaceControl.Transaction,
         finishTransaction: SurfaceControl.Transaction,
     ) {
-        logD("onTransitionReady transition=%s", transition)
         // Check if this is a pending external exit transition.
         val pendingExit = pendingExternalExitTransitions
             .firstOrNull { pendingExit -> pendingExit.transition == transition }
@@ -402,7 +401,6 @@
     }
 
     override fun onTransitionMerged(merged: IBinder, playing: IBinder) {
-        logD("onTransitionMerged merged=%s playing=%s", merged, playing)
         val pendingExit = pendingExternalExitTransitions
             .firstOrNull { pendingExit -> pendingExit.transition == merged }
         if (pendingExit != null) {
@@ -415,7 +413,6 @@
     }
 
     override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
-        logD("onTransitionFinished transition=%s aborted=%b", transition, aborted)
         val pendingExit = pendingExternalExitTransitions
             .firstOrNull { pendingExit -> pendingExit.transition == transition }
         if (pendingExit != null) {
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 5648feb..fda709a 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
@@ -23,12 +23,12 @@
 import android.util.ArraySet
 import android.util.SparseArray
 import android.view.Display.INVALID_DISPLAY
+import android.window.DesktopModeFlags
 import android.window.WindowContainerToken
 import androidx.core.util.forEach
 import androidx.core.util.keyIterator
 import androidx.core.util.valueIterator
 import com.android.internal.protolog.ProtoLog
-import com.android.window.flags.Flags
 import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
 import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
@@ -294,7 +294,7 @@
                 taskId, isVisible, displayId)
             logD("VisibleTaskCount has changed from %d to %d", prevCount, newCount)
             notifyVisibleTaskListeners(displayId, newCount)
-            if (Flags.enableDesktopWindowingPersistence()) {
+            if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
                 updatePersistentRepository(displayId)
             }
         }
@@ -344,7 +344,7 @@
         desktopTaskDataByDisplayId.getOrCreate(displayId).freeformTasksInZOrder.add(0, taskId)
         // Unminimize the task if it is minimized.
         unminimizeTask(displayId, taskId)
-        if (Flags.enableDesktopWindowingPersistence()) {
+        if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
             updatePersistentRepository(displayId)
         }
     }
@@ -362,7 +362,7 @@
             desktopTaskDataByDisplayId.getOrCreate(displayId).minimizedTasks.add(taskId)
         }
         updateTask(displayId, taskId, isVisible = false)
-        if (Flags.enableDesktopWindowingPersistence()) {
+        if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
             updatePersistentRepository(displayId)
         }
     }
@@ -410,7 +410,7 @@
         unminimizeTask(displayId, taskId)
         removeActiveTask(taskId)
         removeVisibleTask(taskId)
-        if (Flags.enableDesktopWindowingPersistence()) {
+        if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
             updatePersistentRepository(displayId)
         }
     }
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 133f069..515b203d 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
@@ -1161,7 +1161,7 @@
                 if (runningTaskInfo != null) {
                     // Task is already running, reorder it to the front
                     wct.reorder(runningTaskInfo.token, /* onTop= */ true)
-                } else if (Flags.enableDesktopWindowingPersistence()) {
+                } else if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
                     // Task is not running, start it
                     wct.startTask(
                         taskId,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index dedd44f..d537da8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -60,7 +60,8 @@
  * entering and exiting freeform.
  */
 public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionHandler {
-    private static final int FULLSCREEN_ANIMATION_DURATION = 336;
+    @VisibleForTesting
+    static final int FULLSCREEN_ANIMATION_DURATION = 336;
 
     private final Context mContext;
     private final Transitions mTransitions;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt
index fc4ed15..d815656 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt
@@ -17,7 +17,7 @@
 package com.android.wm.shell.desktopmode.persistence
 
 import android.content.Context
-import com.android.window.flags.Flags
+import android.window.DesktopModeFlags
 import com.android.wm.shell.desktopmode.DesktopRepository
 import com.android.wm.shell.shared.annotations.ShellMainThread
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
@@ -36,7 +36,7 @@
     @ShellMainThread private val mainCoroutineScope: CoroutineScope,
 ) : DesktopRepositoryInitializer {
     override fun initialize(repository: DesktopRepository) {
-        if (!Flags.enableDesktopWindowingPersistence()) return
+        if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) return
         //  TODO: b/365962554 - Handle the case that user moves to desktop before it's initialized
         mainCoroutineScope.launch {
             val desktop = persistentRepository.readDesktop() ?: return@launch
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
index 245829e..371bdd5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
@@ -45,4 +45,7 @@
 
     /** A task has moved to front. */
     oneway void onTaskMovedToFront(in RunningTaskInfo taskInfo);
+
+    /** A task info has changed. */
+    oneway void onTaskInfoChanged(in RunningTaskInfo taskInfo);
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 6086801..faa2015 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -47,7 +47,6 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.protolog.ProtoLog;
-import com.android.window.flags.Flags;
 import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
@@ -289,6 +288,11 @@
     }
 
     @Override
+    public void onTaskChangedThroughTransition(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
+        notifyTaskInfoChanged(taskInfo);
+    }
+
+    @Override
     public void onTaskMovedToFrontThroughTransition(
             ActivityManager.RunningTaskInfo runningTaskInfo) {
         notifyTaskMovedToFront(runningTaskInfo);
@@ -355,6 +359,19 @@
         }
     }
 
+    private void notifyTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+        if (mListener == null
+                || !DesktopModeFlags.ENABLE_TASK_STACK_OBSERVER_IN_SHELL.isTrue()
+                || taskInfo.realActivity == null) {
+            return;
+        }
+        try {
+            mListener.onTaskInfoChanged(taskInfo);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed call onTaskInfoChanged", e);
+        }
+    }
+
     private void notifyTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
         if (mListener == null
                 || !DesktopModeFlags.ENABLE_TASK_STACK_OBSERVER_IN_SHELL.isTrue()
@@ -426,7 +443,7 @@
                 // If task has their app bounds set to null which happens after reboot, set the
                 // app bounds to persisted lastFullscreenBounds. Also set the position in parent
                 // to the top left of the bounds.
-                if (Flags.enableDesktopWindowingPersistence()
+                if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()
                         && taskInfo.configuration.windowConfiguration.getAppBounds() == null) {
                     taskInfo.configuration.windowConfiguration.setAppBounds(
                             taskInfo.lastNonFullscreenBounds);
@@ -636,6 +653,11 @@
             public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
                 mListener.call(l -> l.onTaskMovedToFront(taskInfo));
             }
+
+            @Override
+            public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+                mListener.call(l -> l.onTaskInfoChanged(taskInfo));
+            }
         };
 
         public IRecentTasksImpl(RecentTasksController controller) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
index 1af99f9..d28a462 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
@@ -20,8 +20,9 @@
 import android.os.IBinder
 import android.util.ArrayMap
 import android.view.SurfaceControl
-import android.window.TransitionInfo
+import android.view.WindowManager.TRANSIT_CHANGE
 import android.window.DesktopModeFlags
+import android.window.TransitionInfo
 import com.android.wm.shell.shared.TransitionUtil
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
@@ -69,8 +70,10 @@
                 // Find the first task that is opening, this should be the one at the front after
                 // the transition
                 if (TransitionUtil.isOpeningType(change.mode)) {
-                    notifyTaskStackTransitionObserverListeners(taskInfo)
+                    notifyOnTaskMovedToFront(taskInfo)
                     break
+                } else if (change.mode == TRANSIT_CHANGE) {
+                    notifyOnTaskChanged(taskInfo)
                 }
             }
         }
@@ -95,15 +98,23 @@
         taskStackTransitionObserverListeners.remove(taskStackTransitionObserverListener)
     }
 
-    private fun notifyTaskStackTransitionObserverListeners(taskInfo: RunningTaskInfo) {
+    private fun notifyOnTaskMovedToFront(taskInfo: RunningTaskInfo) {
         taskStackTransitionObserverListeners.forEach { (listener, executor) ->
             executor.execute { listener.onTaskMovedToFrontThroughTransition(taskInfo) }
         }
     }
 
+    private fun notifyOnTaskChanged(taskInfo: RunningTaskInfo) {
+        taskStackTransitionObserverListeners.forEach { (listener, executor) ->
+            executor.execute { listener.onTaskChangedThroughTransition(taskInfo) }
+        }
+    }
+
     /** Listener to use to get updates regarding task stack from this observer */
     interface TaskStackTransitionObserverListener {
         /** Called when a task is moved to front. */
         fun onTaskMovedToFrontThroughTransition(taskInfo: RunningTaskInfo) {}
+        /** Called when a task info has changed. */
+        fun onTaskChangedThroughTransition(taskInfo: RunningTaskInfo) {}
     }
 }
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 a3324cc6..29b8ddd 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
@@ -130,7 +130,6 @@
 import com.android.wm.shell.transition.FocusTransitionObserver;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
-import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer;
 import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
 import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
 import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
@@ -181,7 +180,6 @@
     private boolean mTransitionDragActive;
 
     private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
-    private DesktopStatusBarInputLayerSupplier mStatusBarInputLayerSupplier;
 
     private final ExclusionRegionListener mExclusionRegionListener =
             new ExclusionRegionListenerImpl();
@@ -420,11 +418,7 @@
                         return Unit.INSTANCE;
                     });
         }
-        if (Flags.enableHandleInputFix()) {
-            mStatusBarInputLayerSupplier =
-                    new DesktopStatusBarInputLayerSupplier(mContext, mMainHandler);
-            mFocusTransitionObserver.setLocalFocusTransitionListener(this, mMainExecutor);
-        }
+        mFocusTransitionObserver.setLocalFocusTransitionListener(this, mMainExecutor);
     }
 
     @Override
@@ -480,7 +474,6 @@
             removeTaskFromEventReceiver(oldTaskInfo.displayId);
             incrementEventReceiverTasks(taskInfo.displayId);
         }
-        decoration.setStatusBarInputLayer(getStatusBarInputLayer(taskInfo));
         decoration.relayout(taskInfo, decoration.mHasGlobalFocus);
         mActivityOrientationChangeHandler.ifPresent(handler ->
                 handler.handleActivityOrientationChange(oldTaskInfo, taskInfo));
@@ -519,7 +512,6 @@
         if (decoration == null) {
             createWindowDecoration(taskInfo, taskSurface, startT, finishT);
         } else {
-            decoration.setStatusBarInputLayer(getStatusBarInputLayer(taskInfo));
             decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
                     false /* shouldSetTaskPositionAndCrop */,
                     mFocusTransitionObserver.hasGlobalFocus(taskInfo));
@@ -673,7 +665,7 @@
         decoration.closeHandleMenu();
         // When the app enters split-select, the handle will no longer be visible, meaning
         // we shouldn't receive input for it any longer.
-        decoration.detachStatusBarInputLayer();
+        decoration.disposeStatusBarInputLayer();
         mDesktopTasksController.requestSplit(decoration.mTaskInfo, false /* leftOrTop */);
     }
 
@@ -1314,8 +1306,8 @@
                         // should not be receiving any input.
                         if (resultType == TO_SPLIT_LEFT_INDICATOR
                                 || resultType == TO_SPLIT_RIGHT_INDICATOR) {
-                            relevantDecor.detachStatusBarInputLayer();
-                            // We should also detach the other split task's input layer if
+                            relevantDecor.disposeStatusBarInputLayer();
+                            // We should also dispose the other split task's input layer if
                             // applicable.
                             final int splitPosition = mSplitScreenController
                                     .getSplitPosition(relevantDecor.mTaskInfo.taskId);
@@ -1328,7 +1320,7 @@
                                         mSplitScreenController.getTaskInfo(oppositePosition);
                                 if (oppositeTaskInfo != null) {
                                     mWindowDecorByTaskId.get(oppositeTaskInfo.taskId)
-                                            .detachStatusBarInputLayer();
+                                            .disposeStatusBarInputLayer();
                                 }
                             }
                         }
@@ -1578,7 +1570,6 @@
                 touchEventListener, touchEventListener, touchEventListener, touchEventListener);
         windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
         windowDecoration.setDragPositioningCallback(taskPositioner);
-        windowDecoration.setStatusBarInputLayer(getStatusBarInputLayer(taskInfo));
         windowDecoration.relayout(taskInfo, startT, finishT,
                 false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */,
                 mFocusTransitionObserver.hasGlobalFocus(taskInfo));
@@ -1587,18 +1578,6 @@
         }
     }
 
-    /** Decide which cached status bar input layer should be used for a decoration. */
-    private AdditionalSystemViewContainer getStatusBarInputLayer(
-            RunningTaskInfo taskInfo
-    ) {
-        if (mStatusBarInputLayerSupplier == null) return null;
-        return mStatusBarInputLayerSupplier.getStatusBarInputLayer(
-                taskInfo,
-                mSplitScreenController.getSplitPosition(taskInfo.taskId),
-                mSplitScreenController.isLeftRightSplit()
-        );
-    }
-
     private RunningTaskInfo getOtherSplitTask(int taskId) {
         @SplitPosition int remainingTaskPosition = mSplitScreenController
                 .getSplitPosition(taskId) == SPLIT_POSITION_BOTTOM_OR_RIGHT
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 9f37358..f930748 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
@@ -106,7 +106,6 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer;
 import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer;
 import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
 import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
 import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
@@ -206,7 +205,6 @@
     private final MultiInstanceHelper mMultiInstanceHelper;
     private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
     private final DesktopRepository mDesktopRepository;
-    private AdditionalSystemViewContainer mStatusBarInputLayer;
 
     DesktopModeWindowDecoration(
             Context context,
@@ -550,13 +548,13 @@
                 notifyNoCaptionHandle();
             }
             mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
-            detachStatusBarInputLayer();
+            disposeStatusBarInputLayer();
             Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces
             return;
         }
 
         if (oldRootView != mResult.mRootView) {
-            detachStatusBarInputLayer();
+            disposeStatusBarInputLayer();
             mWindowDecorViewHolder = createViewHolder();
         }
         Trace.beginSection("DesktopModeWindowDecoration#relayout-binding");
@@ -574,9 +572,6 @@
                     mTaskInfo, position, mResult.mCaptionWidth, mResult.mCaptionHeight,
                     isCaptionVisible()
             ));
-            if (mStatusBarInputLayer != null) {
-                asAppHandle(mWindowDecorViewHolder).bindStatusBarInputLayer(mStatusBarInputLayer);
-            }
         } else {
             mWindowDecorViewHolder.bindData(new AppHeaderViewHolder.HeaderData(
                     mTaskInfo,
@@ -790,15 +785,15 @@
     }
 
     /**
-     * Detach the status bar input layer from this decoration. Intended to be
+     * Dispose of the view used to forward inputs in status bar region. Intended to be
      * used any time handle is no longer visible.
      */
-    void detachStatusBarInputLayer() {
+    void disposeStatusBarInputLayer() {
         if (!isAppHandle(mWindowDecorViewHolder)
                 || !Flags.enableHandleInputFix()) {
             return;
         }
-        asAppHandle(mWindowDecorViewHolder).detachStatusBarInputLayer();
+        asAppHandle(mWindowDecorViewHolder).disposeStatusBarInputLayer();
     }
 
     private WindowDecorationViewHolder createViewHolder() {
@@ -1638,7 +1633,7 @@
         closeManageWindowsMenu();
         mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
         disposeResizeVeil();
-        detachStatusBarInputLayer();
+        disposeStatusBarInputLayer();
         clearCurrentViewHostRunnable();
         if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
             notifyNoCaptionHandle();
@@ -1755,16 +1750,6 @@
                 + "}";
     }
 
-    /**
-     * Set the view container to be used to forward input through status bar. Null in cases
-     * where input forwarding isn't needed.
-     */
-    public void setStatusBarInputLayer(
-            @Nullable AdditionalSystemViewContainer additionalSystemViewContainer
-    ) {
-        mStatusBarInputLayer = additionalSystemViewContainer;
-    }
-
     static class Factory {
 
         DesktopModeWindowDecoration create(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopStatusBarInputLayerSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopStatusBarInputLayerSupplier.kt
deleted file mode 100644
index 9c5215d..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopStatusBarInputLayerSupplier.kt
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * 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.windowdecor
-
-import android.app.ActivityManager.RunningTaskInfo
-import android.app.WindowConfiguration
-import android.content.Context
-import android.graphics.PixelFormat
-import android.os.Handler
-import android.view.Gravity
-import android.view.View
-import android.view.WindowManager
-import com.android.wm.shell.shared.annotations.ShellMainThread
-import com.android.wm.shell.shared.split.SplitScreenConstants
-import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
-
-/**
- * Supplier for [AdditionalSystemViewContainer] objects to be used for forwarding input
- * events through status bar to an app handle. Currently supports two simultaneous input layers.
- *
- * The supplier will pick one of two input layer view containers to use: one for tasks in
- * fullscreen or top/left split stage, and one for tasks in right split stage.
- */
-class DesktopStatusBarInputLayerSupplier(
-    private val context: Context,
-    @ShellMainThread handler: Handler
-) {
-    private val inputLayers: MutableList<AdditionalSystemViewContainer> = mutableListOf()
-
-    init {
-        // Post this as creation of the input layer views is a relatively expensive operation.
-        handler.post {
-            repeat(TOTAL_INPUT_LAYERS) {
-                inputLayers.add(createInputLayer())
-            }
-        }
-    }
-
-    private fun createInputLayer(): AdditionalSystemViewContainer {
-        val lp = WindowManager.LayoutParams(
-            WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
-            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
-            PixelFormat.TRANSPARENT
-        )
-        lp.title = "Desktop status bar input layer"
-        lp.gravity = Gravity.LEFT or Gravity.TOP
-        lp.setTrustedOverlay()
-
-        // Make this window a spy window to enable it to pilfer pointers from the system-wide
-        // gesture listener that receives events before window. This is to prevent notification
-        // shade gesture when we swipe down to enter desktop.
-        lp.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
-        lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-        val view = View(context)
-        view.visibility = View.GONE
-        return AdditionalSystemViewContainer(
-            WindowManagerWrapper(
-                context.getSystemService<WindowManager>(WindowManager::class.java)
-            ),
-            view,
-            lp
-        )
-    }
-
-    /**
-     * Decide which cached status bar input layer should be used for a decoration, if any.
-     *
-     * [splitPosition] and [isLeftRightSplit] are used to determine which input layer we use.
-     * The first one is reserved for fullscreen tasks or tasks in top/left split,
-     * while the second one is exclusively used for tasks in right split stage. Note we care about
-     * left-right vs top-bottom split as the bottom stage should not use an input layer.
-     */
-    fun getStatusBarInputLayer(
-        taskInfo: RunningTaskInfo,
-        @SplitScreenConstants.SplitPosition splitPosition: Int,
-        isLeftRightSplit: Boolean
-    ): AdditionalSystemViewContainer? {
-        if (!taskInfo.isVisibleRequested) return null
-        // Fullscreen and top/left split tasks will use the first input layer.
-        if (taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FULLSCREEN
-            || splitPosition == SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
-        ) {
-            return inputLayers[LEFT_TOP_INPUT_LAYER]
-        }
-        // Right split tasks will use the second one.
-        if (isLeftRightSplit && splitPosition == SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
-        ) {
-            return inputLayers[RIGHT_SPLIT_INPUT_LAYER]
-        }
-        // Which leaves bottom split and freeform tasks, which do not need an input layer
-        // as the status bar is not blocking them.
-        return null
-    }
-
-    companion object {
-        private const val TOTAL_INPUT_LAYERS = 2
-        // Input layer index for fullscreen tasks and tasks in top-left split
-        private const val LEFT_TOP_INPUT_LAYER = 0
-        // Input layer index for tasks in right split stage. Does not include bottom split as that
-        // stage is not blocked by status bar.
-        private const val RIGHT_SPLIT_INPUT_LAYER = 1
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
index 1451f36..8b6aaaf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
@@ -23,8 +23,8 @@
 import android.view.LayoutInflater
 import android.view.SurfaceControl
 import android.view.View
+import android.view.WindowInsets
 import android.view.WindowManager
-import android.view.WindowManager.LayoutParams
 import com.android.wm.shell.windowdecor.WindowManagerWrapper
 
 /**
@@ -33,11 +33,27 @@
  */
 class AdditionalSystemViewContainer(
     private val windowManagerWrapper: WindowManagerWrapper,
-    override val view: View,
-    val lp: LayoutParams
+    taskId: Int,
+    x: Int,
+    y: Int,
+    width: Int,
+    height: Int,
+    flags: Int,
+    @WindowInsets.Type.InsetsType forciblyShownTypes: Int = 0,
+    override val view: View
 ) : AdditionalViewContainer() {
+    val lp: WindowManager.LayoutParams = WindowManager.LayoutParams(
+        width, height, x, y,
+        WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
+        flags,
+        PixelFormat.TRANSPARENT
+    ).apply {
+        title = "Additional view container of Task=$taskId"
+        gravity = Gravity.LEFT or Gravity.TOP
+        setTrustedOverlay()
+        this.forciblyShownTypes = forciblyShownTypes
+    }
 
-    /** Provide a layout id of a view to inflate for this view container. */
     constructor(
         context: Context,
         windowManagerWrapper: WindowManagerWrapper,
@@ -50,30 +66,15 @@
         @LayoutRes layoutId: Int
     ) : this(
         windowManagerWrapper = windowManagerWrapper,
-        view = LayoutInflater.from(context).inflate(layoutId, null /* parent */),
-        lp = createLayoutParams(x, y, width, height, flags, taskId)
+        taskId = taskId,
+        x = x,
+        y = y,
+        width = width,
+        height = height,
+        flags = flags,
+        view = LayoutInflater.from(context).inflate(layoutId, null /* parent */)
     )
 
-    /** Provide a view directly for this view container */
-    constructor(
-        windowManagerWrapper: WindowManagerWrapper,
-        taskId: Int,
-        x: Int,
-        y: Int,
-        width: Int,
-        height: Int,
-        flags: Int,
-        view: View,
-        forciblyShownTypes: Int = 0
-    ) : this(
-        windowManagerWrapper = windowManagerWrapper,
-        view = view,
-        lp = createLayoutParams(x, y, width, height, flags, taskId).apply {
-            this.forciblyShownTypes = forciblyShownTypes
-        }
-    )
-
-    /** Do not supply a view at all, instead creating the view container with a basic view. */
     constructor(
         context: Context,
         windowManagerWrapper: WindowManagerWrapper,
@@ -85,7 +86,12 @@
         flags: Int
     ) : this(
         windowManagerWrapper = windowManagerWrapper,
-        lp = createLayoutParams(x, y, width, height, flags, taskId),
+        taskId = taskId,
+        x = x,
+        y = y,
+        width = width,
+        height = height,
+        flags = flags,
         view = View(context)
     )
 
@@ -98,7 +104,7 @@
     }
 
     override fun setPosition(t: SurfaceControl.Transaction, x: Float, y: Float) {
-        lp.apply {
+        val lp = (view.layoutParams as WindowManager.LayoutParams).apply {
             this.x = x.toInt()
             this.y = y.toInt()
         }
@@ -118,29 +124,13 @@
         ): AdditionalSystemViewContainer =
             AdditionalSystemViewContainer(
                 windowManagerWrapper = windowManagerWrapper,
-                view = view,
-                lp = createLayoutParams(x, y, width, height, flags, taskId)
+                taskId = taskId,
+                x = x,
+                y = y,
+                width = width,
+                height = height,
+                flags = flags,
+                view = view
             )
     }
-    companion object {
-        fun createLayoutParams(
-            x: Int,
-            y: Int,
-            width: Int,
-            height: Int,
-            flags: Int,
-            taskId: Int
-        ): LayoutParams {
-            return LayoutParams(
-                width, height, x, y,
-                LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
-                flags,
-                PixelFormat.TRANSPARENT
-            ).apply {
-                title = "Additional view container of Task=$taskId"
-                gravity = Gravity.LEFT or Gravity.TOP
-                setTrustedOverlay()
-            }
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
index ff418c6..e43c3a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
@@ -21,10 +21,13 @@
 import android.content.Context
 import android.graphics.Rect
 import android.util.SparseArray
+import android.window.DisplayAreaInfo
+import android.window.WindowContainerTransaction
 import androidx.core.util.valueIterator
 import com.android.internal.annotations.VisibleForTesting
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.DisplayChangeController
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.desktopmode.DesktopRepository
@@ -45,10 +48,16 @@
     private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
     private val returnToDragStartAnimator: ReturnToDragStartAnimator,
     private val taskRepository: DesktopRepository,
-) {
+) : DisplayChangeController.OnDisplayChangingListener {
     @VisibleForTesting
     var tilingTransitionHandlerByDisplayId = SparseArray<DesktopTilingWindowDecoration>()
 
+    init {
+        // TODO(b/374309287): Move this interface implementation to
+        // [DesktopModeWindowDecorViewModel] when the migration is done.
+        displayController.addDisplayChangingController(this)
+    }
+
     fun snapToHalfScreen(
         taskInfo: ActivityManager.RunningTaskInfo,
         desktopModeWindowDecoration: DesktopModeWindowDecoration,
@@ -102,7 +111,20 @@
 
     fun onUserChange() {
         for (tilingHandler in tilingTransitionHandlerByDisplayId.valueIterator()) {
-            tilingHandler.onUserChange()
+            tilingHandler.resetTilingSession()
         }
     }
+
+    override fun onDisplayChange(
+        displayId: Int,
+        fromRotation: Int,
+        toRotation: Int,
+        newDisplayAreaInfo: DisplayAreaInfo?,
+        t: WindowContainerTransaction?,
+    ) {
+        // Exit if the rotation hasn't changed or is changed by 180 degrees. [fromRotation] and
+        // [toRotation] can be one of the [@Surface.Rotation] values.
+        if ((fromRotation % 2 == toRotation % 2)) return
+        tilingTransitionHandlerByDisplayId.get(displayId)?.resetTilingSession()
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
index 9bf1304..209eb5e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
@@ -23,6 +23,7 @@
 import android.graphics.Region
 import android.os.Binder
 import android.view.LayoutInflater
+import android.view.RoundedCorner
 import android.view.SurfaceControl
 import android.view.SurfaceControlViewHost
 import android.view.View
@@ -53,12 +54,14 @@
     private val transitionHandler: DesktopTilingWindowDecoration,
     private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
     private var dividerBounds: Rect,
+    private val displayContext: Context,
 ) : WindowlessWindowManager(config, leash, null), DividerMoveCallback, View.OnLayoutChangeListener {
     private lateinit var viewHost: SurfaceControlViewHost
     private var tilingDividerView: TilingDividerView? = null
     private var dividerShown = false
     private var handleRegionWidth: Int = -1
     private var setTouchRegion = true
+    private val maxRoundedCornerRadius = getMaxRoundedCornerRadius()
 
     /**
      * Gets bounds of divider window with screen based coordinate on the param Rect.
@@ -93,7 +96,11 @@
         getDividerBounds(tmpDividerBounds)
         dividerView.setup(this, tmpDividerBounds)
         t.setRelativeLayer(leash, relativeLeash, 1)
-            .setPosition(leash, dividerBounds.left.toFloat(), dividerBounds.top.toFloat())
+            .setPosition(
+                leash,
+                dividerBounds.left.toFloat() - maxRoundedCornerRadius,
+                dividerBounds.top.toFloat(),
+            )
             .show(leash)
         syncQueue.runInSync { transaction ->
             transaction.merge(t)
@@ -144,7 +151,7 @@
      */
     override fun onDividerMove(pos: Int): Boolean {
         val t = transactionSupplier.get()
-        t.setPosition(leash, pos.toFloat(), dividerBounds.top.toFloat())
+        t.setPosition(leash, pos.toFloat() - maxRoundedCornerRadius, dividerBounds.top.toFloat())
         val dividerWidth = dividerBounds.width()
         dividerBounds.set(pos, dividerBounds.top, pos + dividerWidth, dividerBounds.bottom)
         return transitionHandler.onDividerHandleMoved(dividerBounds, t)
@@ -157,7 +164,7 @@
     override fun onDividerMovedEnd(pos: Int) {
         setSlippery(true)
         val t = transactionSupplier.get()
-        t.setPosition(leash, pos.toFloat(), dividerBounds.top.toFloat())
+        t.setPosition(leash, pos.toFloat() - maxRoundedCornerRadius, dividerBounds.top.toFloat())
         val dividerWidth = dividerBounds.width()
         dividerBounds.set(pos, dividerBounds.top, pos + dividerWidth, dividerBounds.bottom)
         transitionHandler.onDividerHandleDragEnd(dividerBounds, t)
@@ -166,7 +173,7 @@
     private fun getWindowManagerParams(): WindowManager.LayoutParams {
         val lp =
             WindowManager.LayoutParams(
-                dividerBounds.width(),
+                dividerBounds.width() + 2 * maxRoundedCornerRadius,
                 dividerBounds.height(),
                 TYPE_DOCK_DIVIDER,
                 FLAG_NOT_FOCUSABLE or
@@ -225,4 +232,15 @@
         }
         viewHost.relayout(lp)
     }
+
+    private fun getMaxRoundedCornerRadius(): Int {
+        val display = displayContext.display
+        return listOf(
+                RoundedCorner.POSITION_TOP_LEFT,
+                RoundedCorner.POSITION_TOP_RIGHT,
+                RoundedCorner.POSITION_BOTTOM_RIGHT,
+                RoundedCorner.POSITION_BOTTOM_LEFT,
+            )
+            .maxOf { position -> display.getRoundedCorner(position)?.getRadius() ?: 0 }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
index a7087ae..c46767c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
@@ -195,6 +195,7 @@
         val builder = SurfaceControl.Builder()
         rootTdaOrganizer.attachToDisplayArea(displayId, builder)
         val leash = builder.setName(TILING_DIVIDER_TAG).setContainerLayer().build()
+        val displayContext = displayController.getDisplayContext(displayId) ?: return null
         val tilingManager =
             displayLayout?.let {
                 dividerBounds = inflateDividerBounds(it)
@@ -207,6 +208,7 @@
                     this,
                     transactionSupplier,
                     dividerBounds,
+                    displayContext,
                 )
             }
         // a leash to present the divider on top of, without re-parenting.
@@ -483,7 +485,7 @@
         }
     }
 
-    fun onUserChange() {
+    fun resetTilingSession() {
         if (leftTaskResizingHelper != null) {
             removeTask(leftTaskResizingHelper, taskVanished = false, shouldDelayUpdate = true)
             leftTaskResizingHelper = null
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
index f8113c219..8922905 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
@@ -118,7 +118,7 @@
             val dividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width)
             val backgroundLeft = (width - dividerSize) / 2
             val backgroundTop = 0
-            val backgroundRight = left + dividerSize
+            val backgroundRight = backgroundLeft + dividerSize
             val backgroundBottom = height
             backgroundRect.set(backgroundLeft, backgroundTop, backgroundRight, backgroundBottom)
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index c4e4946..b5700ff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -36,10 +36,13 @@
 import android.widget.ImageButton
 import androidx.core.view.ViewCompat
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat
+import com.android.internal.policy.SystemBarUtils
+import com.android.window.flags.Flags
 import com.android.wm.shell.R
 import com.android.wm.shell.shared.animation.Interpolators
 import com.android.wm.shell.windowdecor.WindowManagerWrapper
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
+import com.android.wm.shell.windowdecor.viewholder.WindowDecorationViewHolder.Data
 
 /**
  * A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen/split).
@@ -66,12 +69,10 @@
     ) : Data()
 
     private lateinit var taskInfo: RunningTaskInfo
-    private val position: Point = Point()
-    private var width: Int = 0
-    private var height: Int = 0
     private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
     private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle)
     private val inputManager = context.getSystemService(InputManager::class.java)
+    private var statusBarInputLayerExists = false
 
     // An invisible View that takes up the same coordinates as captionHandle but is layered
     // above the status bar. The purpose of this View is to receive input intended for
@@ -111,54 +112,21 @@
     ) {
         captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
         this.taskInfo = taskInfo
-        this.position.set(position)
-        this.width = width
-        this.height = height
-        if (!isCaptionVisible && statusBarInputLayer != null) {
-            detachStatusBarInputLayer()
+        // If handle is not in status bar region(i.e., bottom stage in vertical split),
+        // do not create an input layer
+        if (position.y >= SystemBarUtils.getStatusBarHeight(context)) return
+        if (!isCaptionVisible && statusBarInputLayerExists) {
+            disposeStatusBarInputLayer()
             return
         }
-    }
-
-    fun bindStatusBarInputLayer(
-        statusBarLayer: AdditionalSystemViewContainer
-    ) {
-        // Input layer view modification takes a significant amount of time;
+        // Input layer view creation / modification takes a significant amount of time;
         // post them so we don't hold up DesktopModeWindowDecoration#relayout.
-        if (statusBarLayer == statusBarInputLayer) {
+        if (statusBarInputLayerExists) {
             handler.post { updateStatusBarInputLayer(position) }
-            return
-        }
-        // Remove the old input layer when changing to a new one.
-        if (statusBarInputLayer != null) detachStatusBarInputLayer()
-        if (statusBarLayer.view.visibility == View.GONE) {
-            statusBarLayer.view.visibility = View.VISIBLE
-        }
-        statusBarInputLayer = statusBarLayer
-        statusBarInputLayer?.let {
-            inputLayer -> setupAppHandleA11y(inputLayer.view)
-        }
-        handler.post {
-            val view = statusBarInputLayer?.view
-                ?: error("Unable to find statusBarInputLayer View")
-            // Caption handle is located within the status bar region, meaning the
-            // DisplayPolicy will attempt to transfer this input to status bar if it's
-            // a swipe down. Pilfer here to keep the gesture in handle alone.
-            view.setOnTouchListener { v, event ->
-                if (event.actionMasked == ACTION_DOWN) {
-                    inputManager.pilferPointers(v.viewRootImpl.inputToken)
-                }
-                captionHandle.dispatchTouchEvent(event)
-                return@setOnTouchListener true
-            }
-            view.setOnHoverListener { _, event ->
-                captionHandle.onHoverEvent(event)
-            }
-            val lp = statusBarInputLayer?.view?.layoutParams as WindowManager.LayoutParams
-            lp.x = position.x
-            lp.y = position.y
-            lp.width = width
-            lp.height = height
+        } else {
+            // Input layer is created on a delay; prevent multiple from being created.
+            statusBarInputLayerExists = true
+            handler.post { createStatusBarInputLayer(position, width, height) }
         }
     }
 
@@ -170,6 +138,40 @@
         animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
     }
 
+    private fun createStatusBarInputLayer(handlePosition: Point,
+                                          handleWidth: Int,
+                                          handleHeight: Int) {
+        if (!Flags.enableHandleInputFix()) return
+        statusBarInputLayer = AdditionalSystemViewContainer(context, windowManagerWrapper,
+            taskInfo.taskId, handlePosition.x, handlePosition.y, handleWidth, handleHeight,
+            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+        )
+        val view = statusBarInputLayer?.view ?: error("Unable to find statusBarInputLayer View")
+        val lp = statusBarInputLayer?.lp ?: error("Unable to find statusBarInputLayer " +
+                "LayoutParams")
+        lp.title = "Handle Input Layer of task " + taskInfo.taskId
+        lp.setTrustedOverlay()
+        // Make this window a spy window to enable it to pilfer pointers from the system-wide
+        // gesture listener that receives events before window. This is to prevent notification
+        // shade gesture when we swipe down to enter desktop.
+        lp.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
+        view.setOnHoverListener { _, event ->
+            captionHandle.onHoverEvent(event)
+        }
+        // Caption handle is located within the status bar region, meaning the
+        // DisplayPolicy will attempt to transfer this input to status bar if it's
+        // a swipe down. Pilfer here to keep the gesture in handle alone.
+        view.setOnTouchListener { v, event ->
+            if (event.actionMasked == ACTION_DOWN) {
+                inputManager.pilferPointers(v.viewRootImpl.inputToken)
+            }
+            captionHandle.dispatchTouchEvent(event)
+            return@setOnTouchListener true
+        }
+        setupAppHandleA11y(view)
+        windowManagerWrapper.updateViewLayout(view, lp)
+    }
+
     private fun setupAppHandleA11y(view: View) {
         view.accessibilityDelegate = object : View.AccessibilityDelegate() {
             override fun onInitializeAccessibilityNodeInfo(
@@ -222,12 +224,15 @@
     }
 
     /**
-     * Remove the input listeners from the input layer and remove it from this view holder.
+     * Remove the input layer from [WindowManager]. Should be used when caption handle
+     * is not visible.
      */
-    fun detachStatusBarInputLayer() {
-        statusBarInputLayer?.view?.setOnTouchListener(null)
-        statusBarInputLayer?.view?.setOnHoverListener(null)
-        statusBarInputLayer = null
+    fun disposeStatusBarInputLayer() {
+        statusBarInputLayerExists = false
+        handler.post {
+            statusBarInputLayer?.releaseView()
+            statusBarInputLayer = null
+        }
     }
 
     private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
index fefa933..a82e5e8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
@@ -19,8 +19,6 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
-import static androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread;
-
 import static com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN;
 
 import static org.junit.Assert.assertTrue;
@@ -28,6 +26,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.animation.AnimatorTestRule;
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.WindowConfiguration;
@@ -36,6 +35,8 @@
 import android.graphics.Point;
 import android.os.Handler;
 import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.util.DisplayMetrics;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
@@ -53,17 +54,23 @@
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.ArrayList;
 import java.util.function.Supplier;
 
 /** Tests of {@link com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler} */
 @SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
 public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase {
 
+    @Rule
+    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this);
+
     @Mock
     private Transitions mTransitions;
     @Mock
@@ -105,7 +112,7 @@
     }
 
     @Test
-    public void testTransitExitDesktopModeAnimation() throws Throwable {
+    public void testTransitExitDesktopModeAnimation() {
         final int transitionType = TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN;
         final int taskId = 1;
         WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -118,21 +125,16 @@
         TransitionInfo.Change change =
                 createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FULLSCREEN);
         TransitionInfo info = createTransitionInfo(TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN, change);
-        ArrayList<Exception> exceptions = new ArrayList<>();
-        runOnUiThread(() -> {
-            try {
-                assertTrue(mExitDesktopTaskTransitionHandler
-                        .startAnimation(mToken, info,
-                                new SurfaceControl.Transaction(),
-                                new SurfaceControl.Transaction(),
-                                mTransitionFinishCallback));
-            } catch (Exception e) {
-                exceptions.add(e);
-            }
-        });
-        if (!exceptions.isEmpty()) {
-            throw exceptions.get(0);
-        }
+
+        final boolean animated = mExitDesktopTaskTransitionHandler
+                .startAnimation(mToken, info,
+                        new SurfaceControl.Transaction(),
+                        new SurfaceControl.Transaction(),
+                        mTransitionFinishCallback);
+        mAnimatorTestRule.advanceTimeBy(
+                ExitDesktopTaskTransitionHandler.FULLSCREEN_ANIMATION_DURATION);
+
+        assertTrue(animated);
     }
 
     private TransitionInfo.Change createChange(@WindowManager.TransitionType int type, int taskId,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
index afdb687..efe4fb1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
@@ -34,6 +34,7 @@
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.TransitionInfoBuilder
 import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.windowdecor.extension.isFullscreen
 import com.google.common.truth.Truth.assertThat
 import dagger.Lazy
 import org.junit.Before
@@ -107,8 +108,8 @@
         callOnTransitionFinished()
         executor.flushAll()
 
-        assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(change.taskInfo?.taskId)
-        assertThat(listener.taskInfoToBeNotified.windowingMode)
+        assertThat(listener.taskInfoOnTaskMovedToFront.taskId).isEqualTo(change.taskInfo?.taskId)
+        assertThat(listener.taskInfoOnTaskMovedToFront.windowingMode)
             .isEqualTo(change.taskInfo?.windowingMode)
     }
 
@@ -130,8 +131,8 @@
         callOnTransitionFinished()
         executor.flushAll()
 
-        assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(1)
-        assertThat(listener.taskInfoToBeNotified.windowingMode)
+        assertThat(listener.taskInfoOnTaskMovedToFront.taskId).isEqualTo(1)
+        assertThat(listener.taskInfoOnTaskMovedToFront.windowingMode)
             .isEqualTo(WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
     }
 
@@ -161,9 +162,9 @@
         callOnTransitionFinished()
         executor.flushAll()
 
-        assertThat(listener.taskInfoToBeNotified.taskId)
+        assertThat(listener.taskInfoOnTaskMovedToFront.taskId)
             .isEqualTo(freeformOpenChange.taskInfo?.taskId)
-        assertThat(listener.taskInfoToBeNotified.windowingMode)
+        assertThat(listener.taskInfoOnTaskMovedToFront.windowingMode)
             .isEqualTo(freeformOpenChange.taskInfo?.windowingMode)
     }
 
@@ -199,9 +200,15 @@
         callOnTransitionFinished()
         executor.flushAll()
 
-        assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(change.taskInfo?.taskId)
-        assertThat(listener.taskInfoToBeNotified.windowingMode)
+        assertThat(listener.taskInfoOnTaskMovedToFront.taskId).isEqualTo(change.taskInfo?.taskId)
+        assertThat(listener.taskInfoOnTaskMovedToFront.windowingMode)
             .isEqualTo(change.taskInfo?.windowingMode)
+
+        assertThat(listener.taskInfoOnTaskChanged.size).isEqualTo(1)
+        with(listener.taskInfoOnTaskChanged.last()) {
+            assertThat(taskId).isEqualTo(mergedChange.taskInfo?.taskId)
+            assertThat(windowingMode).isEqualTo(mergedChange.taskInfo?.windowingMode)
+        }
     }
 
     @Test
@@ -236,18 +243,151 @@
         callOnTransitionFinished()
         executor.flushAll()
 
-        assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(mergedChange.taskInfo?.taskId)
-        assertThat(listener.taskInfoToBeNotified.windowingMode)
-                .isEqualTo(mergedChange.taskInfo?.windowingMode)
+        assertThat(listener.taskInfoOnTaskMovedToFront.taskId)
+            .isEqualTo(mergedChange.taskInfo?.taskId)
+        assertThat(listener.taskInfoOnTaskMovedToFront.windowingMode)
+            .isEqualTo(mergedChange.taskInfo?.windowingMode)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+    fun taskChange_freeformWindowToFullscreenWindow_listenerNotified() {
+        val listener = TestListener()
+        val executor = TestShellExecutor()
+        transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+        val freeformState =
+            createChange(
+                WindowManager.TRANSIT_OPEN,
+                createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM)
+            )
+        val transitionInfoOpen =
+            TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(freeformState).build()
+        callOnTransitionReady(transitionInfoOpen)
+        callOnTransitionFinished()
+        executor.flushAll()
+
+        assertThat(listener.taskInfoOnTaskMovedToFront.taskId)
+            .isEqualTo(freeformState.taskInfo?.taskId)
+        assertThat(listener.taskInfoOnTaskMovedToFront.windowingMode)
+            .isEqualTo(freeformState.taskInfo?.windowingMode)
+        assertThat(listener.taskInfoOnTaskMovedToFront.isFullscreen).isEqualTo(false)
+
+        // create change transition to update the windowing mode to full screen.
+        val fullscreenState =
+            createChange(
+                WindowManager.TRANSIT_CHANGE,
+                createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
+            )
+        val transitionInfoChange =
+            TransitionInfoBuilder(WindowManager.TRANSIT_CHANGE, 0)
+                .addChange(fullscreenState)
+                .build()
+
+        callOnTransitionReady(transitionInfoChange)
+        callOnTransitionFinished()
+        executor.flushAll()
+
+        // Asserting whether freeformState remains the same as before the change
+        assertThat(listener.taskInfoOnTaskMovedToFront.taskId)
+            .isEqualTo(freeformState.taskInfo?.taskId)
+        assertThat(listener.taskInfoOnTaskMovedToFront.isFullscreen).isEqualTo(false)
+
+        // Asserting changes
+        assertThat(listener.taskInfoOnTaskChanged.size).isEqualTo(1)
+        with(listener.taskInfoOnTaskChanged.last()) {
+            assertThat(taskId).isEqualTo(fullscreenState.taskInfo?.taskId)
+            assertThat(isFullscreen).isEqualTo(true)
+        }
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+    fun singleTransition_withOpenAndChange_onlyOpenIsNotified() {
+        val listener = TestListener()
+        val executor = TestShellExecutor()
+        transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+
+        // Creating multiple changes to be fired in a single transition
+        val freeformState =
+            createChange(
+                mode = WindowManager.TRANSIT_OPEN,
+                taskInfo = createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM)
+            )
+        val fullscreenState =
+            createChange(
+                mode = WindowManager.TRANSIT_CHANGE,
+                taskInfo = createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
+            )
+
+        val transitionInfoWithChanges =
+            TransitionInfoBuilder(WindowManager.TRANSIT_CHANGE, 0)
+                .addChange(freeformState)
+                .addChange(fullscreenState)
+                .build()
+
+        callOnTransitionReady(transitionInfoWithChanges)
+        callOnTransitionFinished()
+        executor.flushAll()
+
+        assertThat(listener.taskInfoOnTaskMovedToFront.taskId)
+            .isEqualTo(freeformState.taskInfo?.taskId)
+        assertThat(listener.taskInfoOnTaskMovedToFront.isFullscreen).isEqualTo(false)
+        assertThat(listener.taskInfoOnTaskChanged.size).isEqualTo(0)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+    fun singleTransition_withMultipleChanges_listenerNotified_forEachChange() {
+        val listener = TestListener()
+        val executor = TestShellExecutor()
+        transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+
+        val taskId = 1
+
+        // Creating multiple changes to be fired in a single transition
+        val changes =
+            listOf(
+                    WindowConfiguration.WINDOWING_MODE_FREEFORM,
+                    WindowConfiguration.WINDOW_CONFIG_DISPLAY_ROTATION,
+                    WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+                )
+                .map { change ->
+                    createChange(
+                        mode = WindowManager.TRANSIT_CHANGE,
+                        taskInfo = createTaskInfo(taskId, change)
+                    )
+                }
+
+        val transitionInfoWithChanges =
+            TransitionInfoBuilder(WindowManager.TRANSIT_CHANGE, 0)
+                .apply { changes.forEach { c -> this@apply.addChange(c) } }
+                .build()
+
+        callOnTransitionReady(transitionInfoWithChanges)
+        callOnTransitionFinished()
+        executor.flushAll()
+
+        assertThat(listener.taskInfoOnTaskChanged.size).isEqualTo(changes.size)
+        changes.forEachIndexed { index, change ->
+            assertThat(listener.taskInfoOnTaskChanged[index].taskId)
+                .isEqualTo(change.taskInfo?.taskId)
+            assertThat(listener.taskInfoOnTaskChanged[index].windowingMode)
+                .isEqualTo(change.taskInfo?.windowingMode)
+        }
     }
 
     class TestListener : TaskStackTransitionObserver.TaskStackTransitionObserverListener {
-        var taskInfoToBeNotified = ActivityManager.RunningTaskInfo()
+        var taskInfoOnTaskMovedToFront = ActivityManager.RunningTaskInfo()
+        var taskInfoOnTaskChanged = mutableListOf<ActivityManager.RunningTaskInfo>()
 
         override fun onTaskMovedToFrontThroughTransition(
             taskInfo: ActivityManager.RunningTaskInfo
         ) {
-            taskInfoToBeNotified = taskInfo
+            taskInfoOnTaskMovedToFront = taskInfo
+        }
+
+        override fun onTaskChangedThroughTransition(taskInfo: ActivityManager.RunningTaskInfo) {
+            taskInfoOnTaskChanged += taskInfo
         }
     }
 
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 c42be7f..36c5be1 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
@@ -946,7 +946,7 @@
     }
 
     @Test
-    fun testDecor_onClickToSplitScreen_detachesStatusBarInputLayer() {
+    fun testDecor_onClickToSplitScreen_disposesStatusBarInputLayer() {
         val toSplitScreenListenerCaptor = forClass(Function0::class.java)
                 as ArgumentCaptor<Function0<Unit>>
         val decor = createOpenTaskDecoration(
@@ -956,7 +956,7 @@
 
         toSplitScreenListenerCaptor.value.invoke()
 
-        verify(decor).detachStatusBarInputLayer()
+        verify(decor).disposeStatusBarInputLayer()
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 8a2c778..f6fed29 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -49,6 +49,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.mockito.kotlin.VerificationKt.times;
 
 import android.app.ActivityManager;
 import android.app.assist.AssistContent;
@@ -849,7 +850,8 @@
         ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
         spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
 
-        verify(mMockHandler).post(runnableArgument.capture());
+        // Once for view host, the other for the AppHandle input layer.
+        verify(mMockHandler, times(2)).post(runnableArgument.capture());
         runnableArgument.getValue().run();
         verify(mMockSurfaceControlViewHostFactory).create(any(), any(), any());
     }
@@ -876,7 +878,8 @@
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
         spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
-        verify(mMockHandler).post(runnableArgument.capture());
+        // Once for view host, the other for the AppHandle input layer.
+        verify(mMockHandler, times(2)).post(runnableArgument.capture());
 
         spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
 
@@ -890,7 +893,8 @@
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
         spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
-        verify(mMockHandler).post(runnableArgument.capture());
+        // Once for view host, the other for the AppHandle input layer.
+        verify(mMockHandler, times(2)).post(runnableArgument.capture());
 
         spyWindowDecor.close();
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
index 52e93bb..80ad1df 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
@@ -36,6 +36,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
@@ -145,7 +146,7 @@
     }
 
     @Test
-    fun userChange_starting_allTilingSessionsShouldBeDestroyed() {
+    fun onUserChange_allTilingSessionsShouldBeDestroyed() {
         desktopTilingDecorViewModel.tilingTransitionHandlerByDisplayId.put(
             1,
             desktopTilingDecoration,
@@ -157,7 +158,29 @@
 
         desktopTilingDecorViewModel.onUserChange()
 
-        verify(desktopTilingDecoration, times(2)).onUserChange()
+        verify(desktopTilingDecoration, times(2)).resetTilingSession()
+    }
+
+    @Test
+    fun displayOrientationChange_tilingForDisplayShouldBeDestroyed() {
+        desktopTilingDecorViewModel.tilingTransitionHandlerByDisplayId.put(
+            1,
+            desktopTilingDecoration,
+        )
+        desktopTilingDecorViewModel.tilingTransitionHandlerByDisplayId.put(
+            2,
+            desktopTilingDecoration,
+        )
+
+        desktopTilingDecorViewModel.onDisplayChange(1, 1, 2, null, null)
+
+        verify(desktopTilingDecoration, times(1)).resetTilingSession()
+        verify(displayControllerMock, times(1))
+            .addDisplayChangingController(eq(desktopTilingDecorViewModel))
+
+        desktopTilingDecorViewModel.onDisplayChange(1, 1, 3, null, null)
+        // No extra calls after 180 degree change.
+        verify(desktopTilingDecoration, times(1)).resetTilingSession()
     }
 
     companion object {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt
index 0ee3f46..3143946 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt
@@ -16,9 +16,12 @@
 
 package com.android.wm.shell.windowdecor.tiling
 
+import android.content.Context
 import android.content.res.Configuration
 import android.graphics.Rect
 import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.RoundedCorner
 import android.view.SurfaceControl
 import androidx.test.annotation.UiThreadTest
 import androidx.test.filters.SmallTest
@@ -29,6 +32,7 @@
 import org.junit.Before
 import org.junit.runner.RunWith
 import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
@@ -55,10 +59,17 @@
 
     private lateinit var desktopTilingWindowManager: DesktopTilingDividerWindowManager
 
+    private val context = mock<Context>()
+    private val display = mock<Display>()
+    private val roundedCorner = mock<RoundedCorner>()
+
     @Before
     fun setup() {
         config = Configuration()
         config.setToDefaults()
+        whenever(context.display).thenReturn(display)
+        whenever(display.getRoundedCorner(any())).thenReturn(roundedCorner)
+        whenever(roundedCorner.radius).thenReturn(CORNER_RADIUS)
         desktopTilingWindowManager =
             DesktopTilingDividerWindowManager(
                 config,
@@ -69,6 +80,7 @@
                 transitionHandlerMock,
                 transactionSupplierMock,
                 BOUNDS,
+                context,
             )
     }
 
@@ -85,7 +97,6 @@
 
         // Ensure a surfaceControl transaction runs to show the divider.
         verify(transactionSupplierMock, times(1)).get()
-        verify(syncQueueMock, times(1)).runInSync(any())
 
         desktopTilingWindowManager.release()
         verify(transaction, times(1)).hide(any())
@@ -93,7 +104,24 @@
         verify(transaction, times(1)).apply()
     }
 
+    @Test
+    @UiThreadTest
+    fun testWindowManager_accountsForRoundedCornerDimensions() {
+        whenever(transactionSupplierMock.get()).thenReturn(transaction)
+        whenever(transaction.setRelativeLayer(any(), any(), any())).thenReturn(transaction)
+        whenever(transaction.setRelativeLayer(any(), any(), any())).thenReturn(transaction)
+        whenever(transaction.setPosition(any(), any(), any())).thenReturn(transaction)
+        whenever(transaction.show(any())).thenReturn(transaction)
+
+        desktopTilingWindowManager.generateViewHost(surfaceControl)
+
+        // Ensure a surfaceControl transaction runs to show the divider.
+        verify(transaction, times(1))
+            .setPosition(any(), eq(BOUNDS.left.toFloat() - CORNER_RADIUS), any())
+    }
+
     companion object {
         private val BOUNDS = Rect(1, 2, 3, 4)
+        private val CORNER_RADIUS = 28
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
index 2ae2461..f371f52 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
@@ -475,7 +475,7 @@
         tilingDecoration.rightTaskResizingHelper = tiledTaskHelper
         tilingDecoration.desktopTilingDividerWindowManager = desktopTilingDividerWindowManager
 
-        tilingDecoration.onUserChange()
+        tilingDecoration.resetTilingSession()
 
         assertThat(tilingDecoration.leftTaskResizingHelper).isNull()
         assertThat(tilingDecoration.rightTaskResizingHelper).isNull()
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 266c236..fcb7efc 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -384,6 +384,7 @@
         "jni/ScopedParcel.cpp",
         "jni/Shader.cpp",
         "jni/RenderEffect.cpp",
+        "jni/RuntimeEffectUtils.cpp",
         "jni/Typeface.cpp",
         "jni/Utils.cpp",
         "jni/YuvToJpegEncoder.cpp",
@@ -579,6 +580,7 @@
         "utils/Color.cpp",
         "utils/LinearAllocator.cpp",
         "utils/StringUtils.cpp",
+        "utils/StatsUtils.cpp",
         "utils/TypefaceUtils.cpp",
         "utils/VectorDrawableUtils.cpp",
         "AnimationContext.cpp",
diff --git a/libs/hwui/ColorFilter.h b/libs/hwui/ColorFilter.h
index 3a3bfb47..f6b6be0 100644
--- a/libs/hwui/ColorFilter.h
+++ b/libs/hwui/ColorFilter.h
@@ -22,6 +22,7 @@
 #include <memory>
 
 #include "GraphicsJNI.h"
+#include "RuntimeEffectUtils.h"
 #include "SkColorFilter.h"
 
 namespace android {
@@ -113,6 +114,36 @@
     std::vector<float> mMatrix;
 };
 
+class RuntimeColorFilter : public ColorFilter {
+public:
+    RuntimeColorFilter(SkRuntimeEffectBuilder* builder) : mBuilder(builder) {}
+
+    void updateUniforms(JNIEnv* env, const char* name, const float vals[], int count,
+                        bool isColor) {
+        UpdateFloatUniforms(env, mBuilder, name, vals, count, isColor);
+        discardInstance();
+    }
+
+    void updateUniforms(JNIEnv* env, const char* name, const int vals[], int count) {
+        UpdateIntUniforms(env, mBuilder, name, vals, count);
+        discardInstance();
+    }
+
+    void updateChild(JNIEnv* env, const char* name, SkFlattenable* childEffect) {
+        UpdateChild(env, mBuilder, name, childEffect);
+        discardInstance();
+    }
+
+private:
+    sk_sp<SkColorFilter> createInstance() override {
+        // TODO: throw error if null
+        return mBuilder->makeColorFilter();
+    }
+
+private:
+    SkRuntimeEffectBuilder* mBuilder;
+};
+
 }  // namespace uirenderer
 }  // namespace android
 
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index e074a27..a9a5db8 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -27,8 +27,8 @@
 #include <SkColorSpace.h>
 #include <SkColorType.h>
 #include <SkEncodedOrigin.h>
-#include <SkImageInfo.h>
 #include <SkGainmapInfo.h>
+#include <SkImageInfo.h>
 #include <SkMatrix.h>
 #include <SkPaint.h>
 #include <SkPngChunkReader.h>
@@ -43,6 +43,8 @@
 
 #include <memory>
 
+#include "modules/skcms/src/skcms_public.h"
+
 using namespace android;
 
 sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const {
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 49a7f73..8b43f1d 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -10,6 +10,7 @@
 #include <stdint.h>
 #include <stdio.h>
 #include <sys/stat.h>
+#include <utils/StatsUtils.h>
 
 #include <memory>
 
@@ -630,6 +631,7 @@
         }
         bitmap::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied);
         outputBitmap.notifyPixelsChanged();
+        uirenderer::logBitmapDecode(*reuseBitmap);
         // If a java bitmap was passed in for reuse, pass it back
         return javaBitmap;
     }
@@ -650,6 +652,7 @@
             }
         }
 
+        uirenderer::logBitmapDecode(*hardwareBitmap);
         return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags,
                 ninePatchChunk, ninePatchInsets, -1);
     }
@@ -659,6 +662,7 @@
         heapBitmap->setGainmap(std::move(gainmap));
     }
 
+    uirenderer::logBitmapDecode(*heapBitmap);
     // now create the java bitmap
     return bitmap::createBitmap(env, heapBitmap, bitmapCreateFlags, ninePatchChunk, ninePatchInsets,
                                 -1);
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index f7e8e07..5ffd5b9 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -19,6 +19,7 @@
 #include <HardwareBitmapUploader.h>
 #include <androidfw/Asset.h>
 #include <sys/stat.h>
+#include <utils/StatsUtils.h>
 
 #include <memory>
 
@@ -376,6 +377,7 @@
             recycledBitmap->setGainmap(std::move(gainmap));
         }
         bitmap::reinitBitmap(env, javaBitmap, recycledBitmap->info(), !requireUnpremul);
+        uirenderer::logBitmapDecode(*recycledBitmap);
         return javaBitmap;
     }
 
@@ -392,12 +394,14 @@
                 hardwareBitmap->setGainmap(std::move(gm));
             }
         }
+        uirenderer::logBitmapDecode(*hardwareBitmap);
         return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags);
     }
     Bitmap* heapBitmap = heapAlloc.getStorageObjAndReset();
     if (hasGainmap && heapBitmap != nullptr) {
         heapBitmap->setGainmap(std::move(gainmap));
     }
+    uirenderer::logBitmapDecode(*heapBitmap);
     return android::bitmap::createBitmap(env, heapBitmap, bitmapCreateFlags);
 }
 
diff --git a/libs/hwui/jni/ColorFilter.cpp b/libs/hwui/jni/ColorFilter.cpp
index 0b95148..20301d2 100644
--- a/libs/hwui/jni/ColorFilter.cpp
+++ b/libs/hwui/jni/ColorFilter.cpp
@@ -18,7 +18,9 @@
 #include "ColorFilter.h"
 
 #include "GraphicsJNI.h"
+#include "RuntimeEffectUtils.h"
 #include "SkBlendMode.h"
+#include "include/effects/SkRuntimeEffect.h"
 
 namespace android {
 
@@ -89,6 +91,78 @@
             filter->setMatrix(getMatrixFromJFloatArray(env, jarray));
         }
     }
+
+    static jlong RuntimeColorFilter_createColorFilter(JNIEnv* env, jobject, jstring agsl) {
+        ScopedUtfChars strSksl(env, agsl);
+        auto result = SkRuntimeEffect::MakeForColorFilter(SkString(strSksl.c_str()),
+                                                          SkRuntimeEffect::Options{});
+        if (result.effect.get() == nullptr) {
+            doThrowIAE(env, result.errorText.c_str());
+            return 0;
+        }
+        auto builder = new SkRuntimeEffectBuilder(std::move(result.effect));
+        auto* runtimeColorFilter = new RuntimeColorFilter(builder);
+        runtimeColorFilter->incStrong(nullptr);
+        return static_cast<jlong>(reinterpret_cast<uintptr_t>(runtimeColorFilter));
+    }
+
+    static void RuntimeColorFilter_updateUniformsFloatArray(JNIEnv* env, jobject,
+                                                            jlong colorFilterPtr,
+                                                            jstring uniformName,
+                                                            jfloatArray uniforms,
+                                                            jboolean isColor) {
+        auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
+        ScopedUtfChars name(env, uniformName);
+        AutoJavaFloatArray autoValues(env, uniforms, 0, kRO_JNIAccess);
+        if (filter) {
+            filter->updateUniforms(env, name.c_str(), autoValues.ptr(), autoValues.length(),
+                                   isColor);
+        }
+    }
+
+    static void RuntimeColorFilter_updateUniformsFloats(JNIEnv* env, jobject, jlong colorFilterPtr,
+                                                        jstring uniformName, jfloat value1,
+                                                        jfloat value2, jfloat value3, jfloat value4,
+                                                        jint count) {
+        auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
+        ScopedUtfChars name(env, uniformName);
+        const float values[4] = {value1, value2, value3, value4};
+        if (filter) {
+            filter->updateUniforms(env, name.c_str(), values, count, false);
+        }
+    }
+
+    static void RuntimeColorFilter_updateUniformsIntArray(JNIEnv* env, jobject,
+                                                          jlong colorFilterPtr, jstring uniformName,
+                                                          jintArray uniforms) {
+        auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
+        ScopedUtfChars name(env, uniformName);
+        AutoJavaIntArray autoValues(env, uniforms, 0);
+        if (filter) {
+            filter->updateUniforms(env, name.c_str(), autoValues.ptr(), autoValues.length());
+        }
+    }
+
+    static void RuntimeColorFilter_updateUniformsInts(JNIEnv* env, jobject, jlong colorFilterPtr,
+                                                      jstring uniformName, jint value1, jint value2,
+                                                      jint value3, jint value4, jint count) {
+        auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
+        ScopedUtfChars name(env, uniformName);
+        const int values[4] = {value1, value2, value3, value4};
+        if (filter) {
+            filter->updateUniforms(env, name.c_str(), values, count);
+        }
+    }
+
+    static void RuntimeColorFilter_updateChild(JNIEnv* env, jobject, jlong colorFilterPtr,
+                                               jstring childName, jlong childPtr) {
+        auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
+        ScopedUtfChars name(env, childName);
+        auto* child = reinterpret_cast<SkFlattenable*>(childPtr);
+        if (filter && child) {
+            filter->updateChild(env, name.c_str(), child);
+        }
+    }
 };
 
 static const JNINativeMethod colorfilter_methods[] = {
@@ -107,6 +181,20 @@
         {"nativeColorMatrixFilter", "([F)J", (void*)ColorFilterGlue::CreateColorMatrixFilter},
         {"nativeSetColorMatrix", "(J[F)V", (void*)ColorFilterGlue::SetColorMatrix}};
 
+static const JNINativeMethod runtime_color_filter_methods[] = {
+        {"nativeCreateRuntimeColorFilter", "(Ljava/lang/String;)J",
+         (void*)ColorFilterGlue::RuntimeColorFilter_createColorFilter},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V",
+         (void*)ColorFilterGlue::RuntimeColorFilter_updateUniformsFloatArray},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V",
+         (void*)ColorFilterGlue::RuntimeColorFilter_updateUniformsFloats},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V",
+         (void*)ColorFilterGlue::RuntimeColorFilter_updateUniformsIntArray},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V",
+         (void*)ColorFilterGlue::RuntimeColorFilter_updateUniformsInts},
+        {"nativeUpdateChild", "(JLjava/lang/String;J)V",
+         (void*)ColorFilterGlue::RuntimeColorFilter_updateChild}};
+
 int register_android_graphics_ColorFilter(JNIEnv* env) {
     android::RegisterMethodsOrDie(env, "android/graphics/ColorFilter", colorfilter_methods,
                                   NELEM(colorfilter_methods));
@@ -118,7 +206,10 @@
                                   NELEM(lighting_methods));
     android::RegisterMethodsOrDie(env, "android/graphics/ColorMatrixColorFilter",
                                   colormatrix_methods, NELEM(colormatrix_methods));
-    
+    android::RegisterMethodsOrDie(env, "android/graphics/RuntimeColorFilter",
+                                  runtime_color_filter_methods,
+                                  NELEM(runtime_color_filter_methods));
+
     return 0;
 }
 
diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp
index aebc4db..90fd3d8 100644
--- a/libs/hwui/jni/ImageDecoder.cpp
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -37,6 +37,7 @@
 #include <hwui/Bitmap.h>
 #include <hwui/ImageDecoder.h>
 #include <sys/stat.h>
+#include <utils/StatsUtils.h>
 
 #include "Bitmap.h"
 #include "BitmapFactory.h"
@@ -485,6 +486,7 @@
                         hwBitmap->setGainmap(std::move(gm));
                     }
                 }
+                uirenderer::logBitmapDecode(*hwBitmap);
                 return bitmap::createBitmap(env, hwBitmap.release(), bitmapCreateFlags,
                                             ninePatchChunk, ninePatchInsets);
             }
@@ -498,6 +500,8 @@
 
         nativeBitmap->setImmutable();
     }
+
+    uirenderer::logBitmapDecode(*nativeBitmap);
     return bitmap::createBitmap(env, nativeBitmap.release(), bitmapCreateFlags, ninePatchChunk,
                                 ninePatchInsets);
 }
diff --git a/libs/hwui/jni/RuntimeEffectUtils.cpp b/libs/hwui/jni/RuntimeEffectUtils.cpp
new file mode 100644
index 0000000..46db863
--- /dev/null
+++ b/libs/hwui/jni/RuntimeEffectUtils.cpp
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ */
+
+#include "RuntimeEffectUtils.h"
+
+#include "include/effects/SkRuntimeEffect.h"
+
+namespace android {
+namespace uirenderer {
+
+static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) {
+    va_list args;
+    va_start(args, fmt);
+    int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args);
+    va_end(args);
+    return ret;
+}
+
+bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) {
+    switch (type) {
+        case SkRuntimeEffect::Uniform::Type::kFloat:
+        case SkRuntimeEffect::Uniform::Type::kFloat2:
+        case SkRuntimeEffect::Uniform::Type::kFloat3:
+        case SkRuntimeEffect::Uniform::Type::kFloat4:
+        case SkRuntimeEffect::Uniform::Type::kFloat2x2:
+        case SkRuntimeEffect::Uniform::Type::kFloat3x3:
+        case SkRuntimeEffect::Uniform::Type::kFloat4x4:
+            return false;
+        case SkRuntimeEffect::Uniform::Type::kInt:
+        case SkRuntimeEffect::Uniform::Type::kInt2:
+        case SkRuntimeEffect::Uniform::Type::kInt3:
+        case SkRuntimeEffect::Uniform::Type::kInt4:
+            return true;
+    }
+}
+
+void UpdateFloatUniforms(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* uniformName,
+                         const float values[], int count, bool isColor) {
+    SkRuntimeEffectBuilder::BuilderUniform uniform = builder->uniform(uniformName);
+    if (uniform.fVar == nullptr) {
+        ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
+    } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) {
+        if (isColor) {
+            jniThrowExceptionFmt(
+                    env, "java/lang/IllegalArgumentException",
+                    "attempting to set a color uniform using the non-color specific APIs: %s %x",
+                    uniformName, uniform.fVar->flags);
+        } else {
+            ThrowIAEFmt(env,
+                        "attempting to set a non-color uniform using the setColorUniform APIs: %s",
+                        uniformName);
+        }
+    } else if (isIntUniformType(uniform.fVar->type)) {
+        ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s",
+                    uniformName);
+    } else if (!uniform.set<float>(values, count)) {
+        ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
+                    uniform.fVar->sizeInBytes(), sizeof(float) * count);
+    }
+}
+
+void UpdateIntUniforms(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* uniformName,
+                       const int values[], int count) {
+    SkRuntimeEffectBuilder::BuilderUniform uniform = builder->uniform(uniformName);
+    if (uniform.fVar == nullptr) {
+        ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
+    } else if (!isIntUniformType(uniform.fVar->type)) {
+        ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s",
+                    uniformName);
+    } else if (!uniform.set<int>(values, count)) {
+        ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
+                    uniform.fVar->sizeInBytes(), sizeof(float) * count);
+    }
+}
+
+void UpdateChild(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* childName,
+                 SkFlattenable* childEffect) {
+    SkRuntimeShaderBuilder::BuilderChild builderChild = builder->child(childName);
+    if (builderChild.fChild == nullptr) {
+        ThrowIAEFmt(env, "unable to find shader named %s", childName);
+        return;
+    }
+
+    builderChild = sk_ref_sp(childEffect);
+}
+
+}  // namespace uirenderer
+}  // namespace android
\ No newline at end of file
diff --git a/libs/hwui/jni/RuntimeEffectUtils.h b/libs/hwui/jni/RuntimeEffectUtils.h
new file mode 100644
index 0000000..75623c0
--- /dev/null
+++ b/libs/hwui/jni/RuntimeEffectUtils.h
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+#ifndef RUNTIMEEFFECTUTILS_H
+#define RUNTIMEEFFECTUTILS_H
+
+#include "GraphicsJNI.h"
+#include "include/effects/SkRuntimeEffect.h"
+
+namespace android {
+namespace uirenderer {
+
+void UpdateFloatUniforms(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* uniformName,
+                         const float values[], int count, bool isColor);
+
+void UpdateIntUniforms(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* uniformName,
+                       const int values[], int count);
+
+void UpdateChild(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* childName,
+                 SkFlattenable* childEffect);
+}  // namespace uirenderer
+}  // namespace android
+
+#endif  // MAIN_RUNTIMEEFFECTUTILS_H
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index 6e03bbd..d9dc8eb 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -593,9 +593,9 @@
 
             Matrix4 transform;
             SkIRect clipBounds;
+            uirenderer::Rect initialClipBounds;
+            const auto clipFlags = props.getClippingFlags();
             if (enableClip) {
-                uirenderer::Rect initialClipBounds;
-                const auto clipFlags = props.getClippingFlags();
                 if (clipFlags) {
                     props.getClippingRectForFlags(clipFlags, &initialClipBounds);
                 } else {
@@ -659,8 +659,8 @@
                         static_cast<jint>(bounds.left), static_cast<jint>(bounds.top),
                         static_cast<jint>(bounds.right), static_cast<jint>(bounds.bottom),
                         static_cast<jint>(clipBounds.fLeft), static_cast<jint>(clipBounds.fTop),
-                        static_cast<jint>(clipBounds.fRight),
-                        static_cast<jint>(clipBounds.fBottom));
+                        static_cast<jint>(clipBounds.fRight), static_cast<jint>(clipBounds.fBottom),
+                        static_cast<jint>(props.getWidth()), static_cast<jint>(props.getHeight()));
             }
             if (!keepListening) {
                 env->DeleteGlobalRef(mListener);
@@ -891,7 +891,7 @@
     gPositionListener.callPositionChanged = GetStaticMethodIDOrDie(
             env, clazz, "callPositionChanged", "(Ljava/lang/ref/WeakReference;JIIII)Z");
     gPositionListener.callPositionChanged2 = GetStaticMethodIDOrDie(
-            env, clazz, "callPositionChanged2", "(Ljava/lang/ref/WeakReference;JIIIIIIII)Z");
+            env, clazz, "callPositionChanged2", "(Ljava/lang/ref/WeakReference;JIIIIIIIIII)Z");
     gPositionListener.callApplyStretch = GetStaticMethodIDOrDie(
             env, clazz, "callApplyStretch", "(Ljava/lang/ref/WeakReference;JFFFFFFFFFF)Z");
     gPositionListener.callPositionLost = GetStaticMethodIDOrDie(
diff --git a/libs/hwui/libhwui.map.txt b/libs/hwui/libhwui.map.txt
index 2414299..b559194 100644
--- a/libs/hwui/libhwui.map.txt
+++ b/libs/hwui/libhwui.map.txt
@@ -67,6 +67,7 @@
       SkFILEStream::SkFILEStream*;
       SkImageInfo::*;
       SkMemoryStream::SkMemoryStream*;
+      android::uirenderer::logBitmapDecode*;
     };
   local:
     *;
diff --git a/libs/hwui/utils/StatsUtils.cpp b/libs/hwui/utils/StatsUtils.cpp
new file mode 100644
index 0000000..5c4027e
--- /dev/null
+++ b/libs/hwui/utils/StatsUtils.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifdef __ANDROID__
+#include <dlfcn.h>
+#include <log/log.h>
+#include <statslog_hwui.h>
+#include <statssocket_lazy.h>
+#include <utils/Errors.h>
+
+#include <mutex>
+#endif
+
+#include <unistd.h>
+
+#include "StatsUtils.h"
+
+namespace android {
+namespace uirenderer {
+
+#ifdef __ANDROID__
+
+namespace {
+
+int32_t toStatsColorSpaceTransfer(skcms_TFType transferType) {
+    switch (transferType) {
+        case skcms_TFType_sRGBish:
+            return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_SRGBISH;
+        case skcms_TFType_PQish:
+            return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_PQISH;
+        case skcms_TFType_HLGish:
+            return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_HLGISH;
+        default:
+            return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_UNKNOWN;
+    }
+}
+
+int32_t toStatsBitmapFormat(SkColorType type) {
+    switch (type) {
+        case kAlpha_8_SkColorType:
+            return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_A_8;
+        case kRGB_565_SkColorType:
+            return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGB_565;
+        case kN32_SkColorType:
+            return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_ARGB_8888;
+        case kRGBA_F16_SkColorType:
+            return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGBA_F16;
+        case kRGBA_1010102_SkColorType:
+            return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGBA_1010102;
+        default:
+            return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_UNKNOWN;
+    }
+}
+
+}  // namespace
+
+#endif
+
+void logBitmapDecode(const SkImageInfo& info, bool hasGainmap) {
+#ifdef __ANDROID__
+
+    if (!statssocket::lazy::IsAvailable()) {
+        std::once_flag once;
+        std::call_once(once, []() { ALOGD("libstatssocket not available, dropping stats"); });
+        return;
+    }
+
+    skcms_TFType tfnType = skcms_TFType_Invalid;
+
+    if (info.colorSpace()) {
+        skcms_TransferFunction tfn;
+        info.colorSpace()->transferFn(&tfn);
+        tfnType = skcms_TransferFunction_getType(&tfn);
+    }
+
+    auto status =
+            stats::stats_write(uirenderer::stats::IMAGE_DECODED, static_cast<int32_t>(getuid()),
+                               uirenderer::toStatsColorSpaceTransfer(tfnType), hasGainmap,
+                               uirenderer::toStatsBitmapFormat(info.colorType()));
+    ALOGW_IF(status != OK, "Image decoding logging dropped!");
+#endif
+}
+
+void logBitmapDecode(const Bitmap& bitmap) {
+    logBitmapDecode(bitmap.info(), bitmap.hasGainmap());
+}
+
+}  // namespace uirenderer
+}  // namespace android
diff --git a/libs/hwui/utils/StatsUtils.h b/libs/hwui/utils/StatsUtils.h
new file mode 100644
index 0000000..0c247014
--- /dev/null
+++ b/libs/hwui/utils/StatsUtils.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <SkBitmap.h>
+#include <SkColorSpace.h>
+#include <SkColorType.h>
+#include <cutils/compiler.h>
+#include <hwui/Bitmap.h>
+
+namespace android {
+namespace uirenderer {
+
+ANDROID_API void logBitmapDecode(const SkImageInfo& info, bool hasGainmap);
+
+ANDROID_API void logBitmapDecode(const Bitmap& bitmap);
+
+}  // namespace uirenderer
+}  // namespace android
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 5a8eb3a..1024a55 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -16,15 +16,11 @@
 
 package android.media;
 
-import static android.media.audio.Flags.FLAG_SPEAKER_CLEANUP_USAGE;
-
-import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-// TODO switch from HIDL imports to AIDL
 import android.audio.policy.configuration.V7_0.AudioUsage;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.media.audiopolicy.AudioProductStrategy;
@@ -251,16 +247,6 @@
     public static final int USAGE_ANNOUNCEMENT = SYSTEM_USAGE_OFFSET + 3;
 
     /**
-     * @hide
-     * Usage value to use when a system application plays a signal intended to clean up the
-     * speaker transducers and free them of deposits of dust or water.
-     */
-    @FlaggedApi(FLAG_SPEAKER_CLEANUP_USAGE)
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
-    public static final int USAGE_SPEAKER_CLEANUP = SYSTEM_USAGE_OFFSET + 4;
-
-    /**
      * IMPORTANT: when adding new usage types, add them to SDK_USAGES and update SUPPRESSIBLE_USAGES
      *            if applicable, as well as audioattributes.proto.
      *            Also consider adding them to <aaudio/AAudio.h> for the NDK.
@@ -1536,8 +1522,6 @@
                 return "USAGE_VEHICLE_STATUS";
             case USAGE_ANNOUNCEMENT:
                 return "USAGE_ANNOUNCEMENT";
-            case USAGE_SPEAKER_CLEANUP:
-                return "USAGE_SPEAKER_CLEANUP";
             default:
                 return "unknown usage " + usage;
         }
@@ -1678,8 +1662,12 @@
     }
 
     /**
-     * Returns whether the given usage can only be used by system-privileged components
-     * @param usage one of {@link AttributeSystemUsage}.
+     * @param usage one of {@link AttributeSystemUsage},
+     *     {@link AttributeSystemUsage#USAGE_CALL_ASSISTANT},
+     *     {@link AttributeSystemUsage#USAGE_EMERGENCY},
+     *     {@link AttributeSystemUsage#USAGE_SAFETY},
+     *     {@link AttributeSystemUsage#USAGE_VEHICLE_STATUS},
+     *     {@link AttributeSystemUsage#USAGE_ANNOUNCEMENT}
      * @return boolean indicating if the usage is a system usage or not
      * @hide
      */
@@ -1689,8 +1677,7 @@
                 || usage == USAGE_EMERGENCY
                 || usage == USAGE_SAFETY
                 || usage == USAGE_VEHICLE_STATUS
-                || usage == USAGE_ANNOUNCEMENT
-                || usage == USAGE_SPEAKER_CLEANUP);
+                || usage == USAGE_ANNOUNCEMENT);
     }
 
     /**
@@ -1805,7 +1792,6 @@
             case USAGE_SAFETY:
             case USAGE_VEHICLE_STATUS:
             case USAGE_ANNOUNCEMENT:
-            case USAGE_SPEAKER_CLEANUP:
             case USAGE_UNKNOWN:
                 return AudioSystem.STREAM_MUSIC;
             default:
@@ -1845,8 +1831,7 @@
             USAGE_EMERGENCY,
             USAGE_SAFETY,
             USAGE_VEHICLE_STATUS,
-            USAGE_ANNOUNCEMENT,
-            USAGE_SPEAKER_CLEANUP
+            USAGE_ANNOUNCEMENT
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AttributeSystemUsage {}
@@ -1896,7 +1881,6 @@
         USAGE_SAFETY,
         USAGE_VEHICLE_STATUS,
         USAGE_ANNOUNCEMENT,
-        USAGE_SPEAKER_CLEANUP,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AttributeUsage {}
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 0fb3049..23dd9b7 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -48,7 +48,9 @@
         "libhwui_internal_headers",
     ],
 
-    static_libs: ["libarect"],
+    static_libs: [
+        "libarect",
+    ],
 
     host_supported: true,
     target: {
@@ -60,6 +62,11 @@
             shared_libs: [
                 "libandroid",
             ],
+            static_libs: [
+                "libstatslog_hwui",
+                "libstatspull_lazy",
+                "libstatssocket_lazy",
+            ],
             version_script: "libjnigraphics.map.txt",
         },
         host: {
diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp
index e18b4a9..cb95bcf 100644
--- a/native/graphics/jni/imagedecoder.cpp
+++ b/native/graphics/jni/imagedecoder.cpp
@@ -14,18 +14,9 @@
  * limitations under the License.
  */
 
-#include "aassetstreamadaptor.h"
-
-#include <android/asset_manager.h>
-#include <android/bitmap.h>
-#include <android/data_space.h>
-#include <android/imagedecoder.h>
 #include <MimeType.h>
-#include <android/rect.h>
-#include <hwui/ImageDecoder.h>
-#include <log/log.h>
-#include <SkAndroidCodec.h>
 #include <SkAlphaType.h>
+#include <SkAndroidCodec.h>
 #include <SkCodec.h>
 #include <SkCodecAnimation.h>
 #include <SkColorSpace.h>
@@ -35,14 +26,24 @@
 #include <SkRefCnt.h>
 #include <SkSize.h>
 #include <SkStream.h>
-#include <utils/Color.h>
-
+#include <android/asset_manager.h>
+#include <android/bitmap.h>
+#include <android/data_space.h>
+#include <android/imagedecoder.h>
+#include <android/rect.h>
 #include <fcntl.h>
-#include <limits>
-#include <optional>
+#include <hwui/ImageDecoder.h>
+#include <log/log.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
+#include <utils/Color.h>
+#include <utils/StatsUtils.h>
+
+#include <limits>
+#include <optional>
+
+#include "aassetstreamadaptor.h"
 
 using namespace android;
 
@@ -400,9 +401,7 @@
     return info.minRowBytes();
 }
 
-int AImageDecoder_decodeImage(AImageDecoder* decoder,
-                              void* pixels, size_t stride,
-                              size_t size) {
+int AImageDecoder_decodeImage(AImageDecoder* decoder, void* pixels, size_t stride, size_t size) {
     if (!decoder || !pixels || !stride) {
         return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
     }
@@ -419,7 +418,13 @@
         return ANDROID_IMAGE_DECODER_FINISHED;
     }
 
-    return ResultToErrorCode(imageDecoder->decode(pixels, stride));
+    const auto result = ResultToErrorCode(imageDecoder->decode(pixels, stride));
+
+    if (result == ANDROID_IMAGE_DECODER_SUCCESS) {
+        uirenderer::logBitmapDecode(imageDecoder->getOutputInfo(), false);
+    }
+
+    return result;
 }
 
 void AImageDecoder_delete(AImageDecoder* decoder) {
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/HandlerExecutor.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/HandlerExecutor.kt
index be08606..99d3c8d 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/HandlerExecutor.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/HandlerExecutor.kt
@@ -24,7 +24,7 @@
  * Adapter of [Handler] and [Executor], where the task is executed on handler with given looper.
  *
  * When current looper is same with the given looper, task passed to [Executor.execute] will be
- * executed immediately to improve better performance.
+ * executed immediately to achieve better performance.
  *
  * @param looper Looper of the handler.
  */
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
index 9cb0ebb..843d2aa 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
@@ -114,6 +114,10 @@
     fun notifyChange(key: K, reason: Int)
 }
 
+/** Delegation of [KeyedObservable]. */
+open class KeyedObservableDelegate<K>(delegate: KeyedObservable<K>) :
+    KeyedObservable<K> by delegate
+
 /** A thread safe implementation of [KeyedObservable]. */
 open class KeyedDataObservable<K> : KeyedObservable<K> {
     // Instead of @GuardedBy("this"), guarded by itself because KeyedDataObservable object could be
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
index 7af9c81..d75f3e8 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
@@ -237,7 +237,8 @@
                 } else {
                     preferences[preference.key]?.let {
                         preferenceBindingFactory.bind(preference, it)
-                        (it as? PersistentPreference<*>)?.storage(context)?.let { storage ->
+                        val metadata = it.metadata
+                        (metadata as? PersistentPreference<*>)?.storage(context)?.let { storage ->
                             preference.preferenceDataStore =
                                 storages.getOrPut(storage) { PreferenceDataStoreAdapter(storage) }
                         }
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index a56c2fe..83b7566 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1154,6 +1154,13 @@
 }
 
 flag {
+  name: "communal_hub_on_mobile"
+  namespace: "systemui"
+  description: "Brings the glanceable hub experience to mobile phones"
+  bug: "375689917"
+}
+
+flag {
     name: "dream_overlay_updated_font"
     namespace: "systemui"
     description: "Flag to enable updated font settings for dream overlay"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
index 9ca3ce6..e9e3e1b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -81,7 +81,7 @@
             this.fakeKeyguardTransitionRepository =
                 FakeKeyguardTransitionRepository(
                     // This test sends transition steps manually in the test cases.
-                    sendTransitionStepsOnStartTransition = false,
+                    initiallySendTransitionStepsOnStartTransition = false,
                     testScope = testScope,
                 )
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
index 9c2e631..b29a5f4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
@@ -27,15 +27,13 @@
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
-import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat as assertThatRepository
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.shade.data.repository.FlingInfo
 import com.android.systemui.shade.data.repository.fakeShadeRepository
@@ -48,6 +46,8 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat as assertThatRepository
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -55,7 +55,9 @@
 class FromLockscreenTransitionInteractorTest : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
-            this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
+            this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository(
+                testScope = testScope,
+            ))
         }
 
     private val testScope = kosmos.testScope
@@ -66,7 +68,7 @@
 
     @Before
     fun setup() {
-        transitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
+        transitionRepository = kosmos.fakeKeyguardTransitionRepository
     }
 
     @Test
@@ -302,4 +304,74 @@
                     to = KeyguardState.LOCKSCREEN,
                 )
         }
+
+    /**
+     * External signals can cause us to transition from PRIMARY_BOUNCER -> * while a manual
+     * transition is in progress. This test was added after a bug that caused the manual transition
+     * ID to get stuck in this scenario, preventing subsequent transitions to PRIMARY_BOUNCER.
+     */
+    @Test
+    fun testExternalTransitionAwayFromBouncer_transitionIdNotStuck() =
+        testScope.runTest {
+            underTest.start()
+            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+            keyguardRepository.setKeyguardDismissible(false)
+            shadeRepository.setLegacyShadeTracking(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            runCurrent()
+
+            reset(transitionRepository)
+
+            // Disable automatic sending of transition steps so we can send steps through RUNNING
+            // to simulate a cancellation.
+            transitionRepository.sendTransitionStepsOnStartTransition = false
+            shadeRepository.setLegacyShadeExpansion(0.5f)
+            runCurrent()
+
+            assertThatRepository(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                )
+
+            // Partially transition to PRIMARY_BOUNCER.
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.PRIMARY_BOUNCER,
+                throughTransitionState = TransitionState.RUNNING,
+                testScope = testScope,
+            )
+
+            // Start a transition to GONE, which will cancel LS -> BOUNCER.
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.PRIMARY_BOUNCER,
+                to = KeyguardState.GONE,
+                testScope = testScope,
+            )
+
+            // Go to AOD, then LOCKSCREEN.
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.AOD,
+                testScope = testScope,
+            )
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.LOCKSCREEN,
+                testScope = testScope,
+            )
+
+            reset(transitionRepository)
+
+            // Start a swipe up to the bouncer, and verify that we started a transition to
+            // PRIMARY_BOUNCER, verifying the transition ID did not get stuck.
+            shadeRepository.setLegacyShadeExpansion(0.25f)
+            runCurrent()
+
+            assertThatRepository(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                )
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt
index 2e2894d..b5fc52f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt
@@ -24,12 +24,16 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.media.controls.domain.pipeline.legacyMediaDataManagerImpl
+import com.android.systemui.media.controls.domain.pipeline.mediaDataManager
+import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment
 import com.android.systemui.testKosmos
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestResult
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import kotlinx.coroutines.test.setMain
 import org.junit.After
@@ -39,7 +43,7 @@
 @RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalCoroutinesApi::class)
 abstract class AbstractQSFragmentComposeViewModelTest : SysuiTestCase() {
-    protected val kosmos = testKosmos()
+    protected val kosmos = testKosmos().apply { mediaDataManager = legacyMediaDataManagerImpl }
 
     protected val lifecycleOwner =
         TestLifecycleOwner(
@@ -62,11 +66,15 @@
     }
 
     protected inline fun TestScope.testWithinLifecycle(
-        crossinline block: suspend TestScope.() -> TestResult
+        usingMedia: Boolean = true,
+        crossinline block: suspend TestScope.() -> TestResult,
     ): TestResult {
         return runTest {
+            kosmos.usingMediaInComposeFragment = usingMedia
+
             lifecycleOwner.setCurrentState(Lifecycle.State.RESUMED)
             underTest.activateIn(kosmos.testScope)
+            runCurrent()
             block().also { lifecycleOwner.setCurrentState(Lifecycle.State.DESTROYED) }
         }
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
index 3b00f86..9fe9ed2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
@@ -25,6 +25,15 @@
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.domain.pipeline.legacyMediaDataManagerImpl
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.controller.mediaCarouselController
+import com.android.systemui.media.controls.ui.view.MediaHostState
+import com.android.systemui.media.controls.ui.view.qqsMediaHost
+import com.android.systemui.media.controls.ui.view.qsMediaHost
+import com.android.systemui.qs.composefragment.viewmodel.MediaState.ACTIVE_MEDIA
+import com.android.systemui.qs.composefragment.viewmodel.MediaState.ANY_MEDIA
+import com.android.systemui.qs.composefragment.viewmodel.MediaState.NO_MEDIA
 import com.android.systemui.qs.fgsManagerController
 import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor
 import com.android.systemui.res.R
@@ -35,9 +44,11 @@
 import com.android.systemui.statusbar.sysuiStatusBarStateController
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -185,6 +196,92 @@
             }
         }
 
+    @Test
+    fun qqsMediaHost_initializedCorrectly() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                assertThat(underTest.qqsMediaHost.location)
+                    .isEqualTo(MediaHierarchyManager.LOCATION_QQS)
+                assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED)
+                assertThat(underTest.qqsMediaHost.showsOnlyActiveMedia).isTrue()
+                assertThat(underTest.qqsMediaHost.hostView).isNotNull()
+            }
+        }
+
+    @Test
+    fun qsMediaHost_initializedCorrectly() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                assertThat(underTest.qsMediaHost.location)
+                    .isEqualTo(MediaHierarchyManager.LOCATION_QS)
+                assertThat(underTest.qsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED)
+                assertThat(underTest.qsMediaHost.showsOnlyActiveMedia).isFalse()
+                assertThat(underTest.qsMediaHost.hostView).isNotNull()
+            }
+        }
+
+    @Test
+    fun qqsMediaVisible_onlyWhenActiveMedia() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                whenever(mediaCarouselController.isLockedAndHidden()).thenReturn(false)
+
+                assertThat(underTest.qqsMediaVisible).isEqualTo(underTest.qqsMediaHost.visible)
+
+                setMediaState(NO_MEDIA)
+                assertThat(underTest.qqsMediaVisible).isFalse()
+
+                setMediaState(ANY_MEDIA)
+                assertThat(underTest.qqsMediaVisible).isFalse()
+
+                setMediaState(ACTIVE_MEDIA)
+                assertThat(underTest.qqsMediaVisible).isTrue()
+            }
+        }
+
+    @Test
+    fun qsMediaVisible_onAnyMedia() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                whenever(mediaCarouselController.isLockedAndHidden()).thenReturn(false)
+
+                assertThat(underTest.qsMediaVisible).isEqualTo(underTest.qsMediaHost.visible)
+
+                setMediaState(NO_MEDIA)
+                assertThat(underTest.qsMediaVisible).isFalse()
+
+                setMediaState(ANY_MEDIA)
+                assertThat(underTest.qsMediaVisible).isTrue()
+
+                setMediaState(ACTIVE_MEDIA)
+                assertThat(underTest.qsMediaVisible).isTrue()
+            }
+        }
+
+    @Test
+    fun notUsingMedia_mediaNotVisible() =
+        with(kosmos) {
+            testScope.testWithinLifecycle(usingMedia = false) {
+                setMediaState(ACTIVE_MEDIA)
+
+                assertThat(underTest.qqsMediaVisible).isFalse()
+                assertThat(underTest.qsMediaVisible).isFalse()
+            }
+        }
+
+    private fun TestScope.setMediaState(state: MediaState) {
+        with(kosmos) {
+            val activeMedia = state == ACTIVE_MEDIA
+            val anyMedia = state != NO_MEDIA
+            whenever(legacyMediaDataManagerImpl.hasActiveMediaOrRecommendation())
+                .thenReturn(activeMedia)
+            whenever(legacyMediaDataManagerImpl.hasAnyMediaOrRecommendation()).thenReturn(anyMedia)
+            qqsMediaHost.updateViewVisibility()
+            qsMediaHost.updateViewVisibility()
+        }
+        runCurrent()
+    }
+
     companion object {
         private const val QS_DISABLE_FLAG = StatusBarManager.DISABLE2_QUICK_SETTINGS
 
@@ -195,3 +292,9 @@
         private const val epsilon = 0.001f
     }
 }
+
+private enum class MediaState {
+    ACTIVE_MEDIA,
+    ANY_MEDIA,
+    NO_MEDIA,
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt
index 2c894f9..ab5a049 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt
@@ -20,21 +20,25 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
-import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testCase
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.qs.panels.domain.interactor.qsPreferencesInteractor
 import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.res.R
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class QuickQuickSettingsViewModelTest : SysuiTestCase() {
@@ -65,7 +69,8 @@
             fakeConfigurationRepository.onConfigurationChange()
         }
 
-    private val underTest = kosmos.quickQuickSettingsViewModel
+    private val underTest =
+        kosmos.quickQuickSettingsViewModelFactory.create().apply { activateIn(kosmos.testScope) }
 
     @Before
     fun setUp() {
@@ -77,17 +82,15 @@
         with(kosmos) {
             testScope.runTest {
                 setRows(2)
-                val columns by collectLastValue(underTest.columns)
-                val tileViewModels by collectLastValue(underTest.tileViewModels)
 
-                assertThat(columns).isEqualTo(4)
+                assertThat(underTest.columns).isEqualTo(4)
                 // All tiles in 4 columns
                 // [1] [2] [3 3]
                 // [4] [5 5]
                 // [6 6] [7] [8]
                 // [9 9]
 
-                assertThat(tileViewModels!!.map { it.tile.spec }).isEqualTo(tiles.take(5))
+                assertThat(underTest.tileViewModels.map { it.tile.spec }).isEqualTo(tiles.take(5))
             }
         }
 
@@ -96,10 +99,8 @@
         with(kosmos) {
             testScope.runTest {
                 setRows(2)
-                val columns by collectLastValue(underTest.columns)
-                val tileViewModels by collectLastValue(underTest.tileViewModels)
 
-                assertThat(columns).isEqualTo(4)
+                assertThat(underTest.columns).isEqualTo(4)
                 // All tiles in 4 columns
                 // [1] [2] [3 3]
                 // [4] [5 5]
@@ -107,9 +108,9 @@
                 // [9 9]
 
                 setRows(3)
-                assertThat(tileViewModels!!.map { it.tile.spec }).isEqualTo(tiles.take(8))
+                assertThat(underTest.tileViewModels.map { it.tile.spec }).isEqualTo(tiles.take(8))
                 setRows(1)
-                assertThat(tileViewModels!!.map { it.tile.spec }).isEqualTo(tiles.take(3))
+                assertThat(underTest.tileViewModels.map { it.tile.spec }).isEqualTo(tiles.take(3))
             }
         }
 
@@ -118,10 +119,8 @@
         with(kosmos) {
             testScope.runTest {
                 setRows(2)
-                val columns by collectLastValue(underTest.columns)
-                val tileViewModels by collectLastValue(underTest.tileViewModels)
 
-                assertThat(columns).isEqualTo(4)
+                assertThat(underTest.columns).isEqualTo(4)
                 // All tiles in 4 columns
                 // [1] [2] [3 3]
                 // [4] [5 5]
@@ -130,8 +129,9 @@
 
                 // Remove tile small:4
                 currentTilesInteractor.removeTiles(setOf(tiles[3]))
+                runCurrent()
 
-                assertThat(tileViewModels!!.map { it.tile.spec })
+                assertThat(underTest.tileViewModels.map { it.tile.spec })
                     .isEqualTo(
                         listOf(
                                 "$PREFIX_SMALL:1",
@@ -149,12 +149,15 @@
         currentTilesInteractor.setTiles(tiles)
     }
 
-    private fun Kosmos.setRows(rows: Int) {
-        testCase.context.orCreateTestableResources.addOverride(
-            R.integer.quick_qs_paginated_grid_num_rows,
-            rows,
-        )
-        fakeConfigurationRepository.onConfigurationChange()
+    private fun TestScope.setRows(rows: Int) {
+        with(kosmos) {
+            testCase.context.orCreateTestableResources.addOverride(
+                R.integer.quick_qs_paginated_grid_num_rows,
+                rows,
+            )
+            fakeConfigurationRepository.onConfigurationChange()
+        }
+        runCurrent()
     }
 
     private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index c005743..657e9df 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -58,6 +58,7 @@
 import com.android.systemui.util.mockito.withArgCaptor
 import com.android.systemui.util.time.SystemClock
 import com.android.systemui.wmshell.BubblesManager
+import com.google.android.msdl.domain.MSDLPlayer
 import java.util.Optional
 import junit.framework.Assert
 import org.junit.After
@@ -110,6 +111,7 @@
     private val dismissibilityProvider: NotificationDismissibilityProvider = mock()
     private val statusBarService: IStatusBarService = mock()
     private val uiEventLogger: UiEventLogger = mock()
+    private val msdlPlayer: MSDLPlayer = mock()
     private lateinit var controller: ExpandableNotificationRowController
 
     @Before
@@ -150,7 +152,8 @@
                 dragController,
                 dismissibilityProvider,
                 statusBarService,
-                uiEventLogger
+                uiEventLogger,
+                msdlPlayer,
             )
         whenever(view.childrenContainer).thenReturn(childrenContainer)
 
@@ -268,14 +271,14 @@
         controller.mSettingsListener.onSettingChanged(
             BUBBLES_SETTING_URI,
             view.entry.sbn.userId,
-            "1"
+            "1",
         )
         verify(childView).setBubblesEnabledForUser(true)
 
         controller.mSettingsListener.onSettingChanged(
             BUBBLES_SETTING_URI,
             view.entry.sbn.userId,
-            "9"
+            "9",
         )
         verify(childView).setBubblesEnabledForUser(false)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt
index 48c2cc7..a008588 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt
@@ -20,6 +20,8 @@
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.flags.DisableSceneContainer
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.kosmos.testScope
@@ -113,4 +115,21 @@
 
             assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
         }
+
+    @Test
+    @DisableSceneContainer
+    fun entireScreenTouchable_communalVisible() =
+        testScope.runTest {
+            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
+
+            kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal)
+            runCurrent()
+
+            assertThat(underTest.shouldMakeEntireScreenTouchable()).isTrue()
+
+            kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Blank)
+            runCurrent()
+
+            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
index eddac4d..05ff0cc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
@@ -70,7 +70,7 @@
             },
             //  Change split screen focus to LHS:
             //   - Meta + Alt + Left arrow
-            shortcutInfo(resources.getString(R.string.system_multitasking_splitscreen_focus_rhs)) {
+            shortcutInfo(resources.getString(R.string.system_multitasking_splitscreen_focus_lhs)) {
                 command(META_META_ON or META_ALT_ON, KEYCODE_DPAD_LEFT)
             },
         )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index f3eeed2..8c60371 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -19,7 +19,6 @@
 import android.animation.ValueAnimator
 import android.util.MathUtils
 import com.android.app.animation.Interpolators
-import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.Flags.communalSceneKtfRefactor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.dagger.SysUISingleton
@@ -40,17 +39,19 @@
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.util.kotlin.sample
-import java.util.UUID
-import javax.inject.Inject
-import kotlin.time.Duration.Companion.milliseconds
-import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
+import java.util.UUID
+import javax.inject.Inject
+import com.android.app.tracing.coroutines.launchTraced as launch
 
 @SysUISingleton
 class FromLockscreenTransitionInteractor
@@ -258,6 +259,19 @@
                 }
             }
         }
+
+        // Ensure that transitionId is nulled out if external signals cause a PRIMARY_BOUNCER
+        // transition to be canceled.
+        scope.launch {
+            transitionInteractor.transitions
+                .filter {
+                    it.transitionState == TransitionState.CANCELED &&
+                        it.to == KeyguardState.PRIMARY_BOUNCER
+                }
+                .collect {
+                    transitionId = null
+                }
+        }
     }
 
     fun dismissKeyguard() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index 0b37b5b..1ca3927 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -1146,4 +1146,4 @@
             updateState();
         }
     }
-}
+}
\ No newline at end of file
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 9c8e84f..bacff99 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -38,12 +38,14 @@
 import androidx.compose.animation.fadeOut
 import androidx.compose.animation.togetherWith
 import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.layout.Arrangement.spacedBy
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.navigationBars
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.windowInsetsPadding
@@ -73,13 +75,14 @@
 import androidx.compose.ui.semantics.customActions
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.round
 import androidx.compose.ui.util.fastRoundToInt
+import androidx.compose.ui.viewinterop.AndroidView
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.ElementMatcher
@@ -98,10 +101,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.lifecycle.setSnapshotBinding
-import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
 import com.android.systemui.media.controls.ui.view.MediaHost
-import com.android.systemui.media.dagger.MediaModule.QS_PANEL
-import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.plugins.qs.QSContainerController
 import com.android.systemui.qs.composefragment.SceneKeys.QuickQuickSettings
@@ -127,7 +127,6 @@
 import java.io.PrintWriter
 import java.util.function.Consumer
 import javax.inject.Inject
-import javax.inject.Named
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.coroutineScope
@@ -135,6 +134,7 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
 
 @SuppressLint("ValidFragment")
 class QSFragmentCompose
@@ -142,11 +142,11 @@
 constructor(
     private val qsFragmentComposeViewModelFactory: QSFragmentComposeViewModel.Factory,
     private val dumpManager: DumpManager,
-    @Named(QUICK_QS_PANEL) private val qqsMediaHost: MediaHost,
-    @Named(QS_PANEL) private val qsMediaHost: MediaHost,
 ) : LifecycleFragment(), QS, Dumpable {
 
     private val scrollListener = MutableStateFlow<QS.ScrollListener?>(null)
+    private val collapsedMediaVisibilityChangedListener =
+        MutableStateFlow<(Consumer<Boolean>)?>(null)
     private val heightListener = MutableStateFlow<QS.HeightListener?>(null)
     private val qsContainerController = MutableStateFlow<QSContainerController?>(null)
 
@@ -183,8 +183,6 @@
         QSComposeFragment.isUnexpectedlyInLegacyMode()
         viewModel = qsFragmentComposeViewModelFactory.create(lifecycleScope)
 
-        qqsMediaHost.init(MediaHierarchyManager.LOCATION_QQS)
-        qsMediaHost.init(MediaHierarchyManager.LOCATION_QS)
         setListenerCollections()
         lifecycleScope.launch { viewModel.activate() }
     }
@@ -491,7 +489,7 @@
     }
 
     override fun setCollapsedMediaVisibilityChangedListener(listener: Consumer<Boolean>?) {
-        // TODO (b/353253280)
+        collapsedMediaVisibilityChangedListener.value = listener
     }
 
     override fun setScrollListener(scrollListener: QS.ScrollListener?) {
@@ -534,6 +532,7 @@
             lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                 this@QSFragmentCompose.view?.setSnapshotBinding {
                     scrollListener.value?.onQsPanelScrollChanged(scrollState.value)
+                    collapsedMediaVisibilityChangedListener.value?.accept(viewModel.qqsMediaVisible)
                 }
                 launch {
                     setListenerJob(
@@ -569,7 +568,7 @@
                 .squishiness
                 .collectAsStateWithLifecycle()
 
-        Column(modifier = Modifier.sysuiResTag("quick_qs_panel")) {
+        Column(modifier = Modifier.sysuiResTag(ResIdTags.quickQsPanel)) {
             Box(
                 modifier =
                     Modifier.fillMaxWidth()
@@ -581,6 +580,9 @@
                                 leftFromRoot + coordinates.size.width,
                                 topFromRoot + coordinates.size.height,
                             )
+                            if (squishiness == 1f) {
+                                viewModel.qqsHeight = coordinates.size.height
+                            }
                         }
                         // Use an approach layout to determien the height without squishiness, as
                         // that's the value that NPVC and QuickSettingsController care about
@@ -595,8 +597,7 @@
                         .padding(top = { qqsPadding }, bottom = { bottomPadding })
             ) {
                 if (viewModel.isQsEnabled) {
-                    QuickQuickSettings(
-                        viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel,
+                    Column(
                         modifier =
                             Modifier.collapseExpandSemanticAction(
                                     stringResource(
@@ -608,7 +609,16 @@
                                         QuickSettingsShade.Dimensions.Padding.roundToPx()
                                     }
                                 ),
-                    )
+                        verticalArrangement =
+                            spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)),
+                    ) {
+                        QuickQuickSettings(
+                            viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel
+                        )
+                        if (viewModel.qqsMediaVisible) {
+                            MediaObject(mediaHost = viewModel.qqsMediaHost)
+                        }
+                    }
                 }
             }
             Spacer(modifier = Modifier.weight(1f))
@@ -645,14 +655,27 @@
                                 }
                                 .onSizeChanged { viewModel.qsScrollHeight = it.height }
                                 .verticalScroll(scrollState)
+                                .sysuiResTag(ResIdTags.qsScroll)
                     ) {
                         Spacer(
                             modifier = Modifier.height { qqsPadding + qsExtraPadding.roundToPx() }
                         )
                         QuickSettingsLayout(
                             viewModel = viewModel.containerViewModel,
-                            modifier = Modifier.sysuiResTag("quick_settings_panel"),
+                            modifier = Modifier.sysuiResTag(ResIdTags.quickSettingsPanel),
                         )
+                        Spacer(modifier = Modifier.height(8.dp))
+                        if (viewModel.qsMediaVisible) {
+                            MediaObject(
+                                mediaHost = viewModel.qsMediaHost,
+                                modifier =
+                                    Modifier.padding(
+                                        horizontal = {
+                                            QuickSettingsShade.Dimensions.Padding.roundToPx()
+                                        }
+                                    ),
+                            )
+                        }
                     }
                 }
                 QuickSettingsTheme {
@@ -660,7 +683,7 @@
                         viewModel = viewModel.footerActionsViewModel,
                         qsVisibilityLifecycleOwner = this@QSFragmentCompose,
                         modifier =
-                            Modifier.sysuiResTag("qs_footer_actions")
+                            Modifier.sysuiResTag(ResIdTags.qsFooterActions)
                                 .element(ElementKeys.FooterActions),
                     )
                 }
@@ -914,3 +937,29 @@
     } else {
         this
     }
+
+@Composable
+private fun MediaObject(mediaHost: MediaHost, modifier: Modifier = Modifier) {
+    Box {
+        AndroidView(
+            modifier = modifier,
+            factory = {
+                mediaHost.hostView.apply {
+                    layoutParams =
+                        FrameLayout.LayoutParams(
+                            FrameLayout.LayoutParams.MATCH_PARENT,
+                            FrameLayout.LayoutParams.WRAP_CONTENT,
+                        )
+                }
+            },
+            onReset = {},
+        )
+    }
+}
+
+private object ResIdTags {
+    const val quickSettingsPanel = "quick_settings_panel"
+    const val quickQsPanel = "quick_qs_panel"
+    const val qsScroll = "expanded_qs_scroll_view"
+    const val qsFooterActions = "qs_footer_actions"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt
new file mode 100644
index 0000000..5127320
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.composefragment.dagger
+
+import android.content.Context
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.util.Utils
+import dagger.Module
+import dagger.Provides
+import javax.inject.Named
+
+@Module
+interface QSFragmentComposeModule {
+
+    companion object {
+        const val QS_USING_MEDIA_PLAYER = "compose_fragment_using_media_player"
+
+        @Provides
+        @SysUISingleton
+        @Named(QS_USING_MEDIA_PLAYER)
+        fun providesUsingMedia(@Application context: Context): Boolean {
+            return Utils.useQsMediaPlayer(context)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index d30c6be..0ca621d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -38,11 +38,17 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.lifecycle.Hydrator
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHostState
+import com.android.systemui.media.dagger.MediaModule.QS_PANEL
+import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.FooterActionsController
+import com.android.systemui.qs.composefragment.dagger.QSFragmentComposeModule
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.panels.domain.interactor.TileSquishinessInteractor
-import com.android.systemui.qs.panels.ui.viewmodel.PaginatedGridViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.InFirstPageViewModel
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Scenes
@@ -60,10 +66,14 @@
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import java.io.PrintWriter
+import javax.inject.Named
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 
@@ -83,7 +93,10 @@
     configurationInteractor: ConfigurationInteractor,
     private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
     private val squishinessInteractor: TileSquishinessInteractor,
-    private val paginatedGridViewModel: PaginatedGridViewModel,
+    private val inFirstPageViewModel: InFirstPageViewModel,
+    @Named(QUICK_QS_PANEL) val qqsMediaHost: MediaHost,
+    @Named(QS_PANEL) val qsMediaHost: MediaHost,
+    @Named(QSFragmentComposeModule.QS_USING_MEDIA_PLAYER) private val usingMedia: Boolean,
     @Assisted private val lifecycleScope: LifecycleCoroutineScope,
 ) : Dumpable, ExclusiveActivatable() {
 
@@ -191,7 +204,7 @@
     var collapseExpandAccessibilityAction: Runnable? = null
 
     val inFirstPage: Boolean
-        get() = paginatedGridViewModel.inFirstPage
+        get() = inFirstPageViewModel.inFirstPage
 
     var overScrollAmount by mutableStateOf(0)
 
@@ -222,6 +235,30 @@
         }
     }
 
+    val showingMirror: Boolean
+        get() = containerViewModel.brightnessSliderViewModel.showMirror
+
+    // The initial values in these two are not meaningful. The flow will emit on start the correct
+    // values. This is because we need to lazily fetch them after initMediaHosts.
+    val qqsMediaVisible by
+        hydrator.hydratedStateOf(
+            traceName = "qqsMediaVisible",
+            initialValue = usingMedia,
+            source =
+                if (usingMedia) {
+                    mediaHostVisible(qqsMediaHost)
+                } else {
+                    flowOf(false)
+                },
+        )
+
+    val qsMediaVisible by
+        hydrator.hydratedStateOf(
+            traceName = "qsMediaVisible",
+            initialValue = usingMedia,
+            source = if (usingMedia) mediaHostVisible(qsMediaHost) else flowOf(false),
+        )
+
     private var qsBounds by mutableStateOf(Rect())
 
     private val constrainedSquishinessFraction: Float
@@ -259,9 +296,6 @@
                     .onStart { emit(sysuiStatusBarStateController.state) },
         )
 
-    val showingMirror: Boolean
-        get() = containerViewModel.brightnessSliderViewModel.showMirror
-
     private val isKeyguardState: Boolean
         get() = statusBarState == StatusBarState.KEYGUARD
 
@@ -323,6 +357,7 @@
         )
 
     override suspend fun onActivated(): Nothing {
+        initMediaHosts() // init regardless of using media (same as current QS).
         coroutineScope {
             launch { hydrateSquishinessInteractor() }
             launch { hydrator.activate() }
@@ -331,6 +366,19 @@
         }
     }
 
+    private fun initMediaHosts() {
+        qqsMediaHost.apply {
+            expansion = MediaHostState.EXPANDED
+            showsOnlyActiveMedia = true
+            init(MediaHierarchyManager.LOCATION_QQS)
+        }
+        qsMediaHost.apply {
+            expansion = MediaHostState.EXPANDED
+            showsOnlyActiveMedia = false
+            init(MediaHierarchyManager.LOCATION_QS)
+        }
+    }
+
     private suspend fun hydrateSquishinessInteractor() {
         snapshotFlow { constrainedSquishinessFraction }
             .collect { squishinessInteractor.setSquishinessValue(it) }
@@ -373,6 +421,10 @@
                 println("qqsHeight", "${qqsHeight}px")
                 println("qsScrollHeight", "${qsScrollHeight}px")
             }
+            printSection("Media") {
+                println("qqsMediaVisible", qqsMediaVisible)
+                println("qsMediaVisible", qsMediaVisible)
+            }
         }
     }
 
@@ -390,3 +442,21 @@
 }
 
 private val SHORT_PARALLAX_AMOUNT = 0.1f
+
+/**
+ * Returns a flow to track the visibility of a [MediaHost]. The flow will emit on start the visible
+ * state of the view.
+ */
+private fun mediaHostVisible(mediaHost: MediaHost): Flow<Boolean> {
+    return callbackFlow {
+            val listener: (Boolean) -> Unit = { visible: Boolean -> trySend(visible) }
+            mediaHost.addVisibilityChangeListener(listener)
+
+            awaitClose { mediaHost.removeVisibilityChangeListener(listener) }
+        }
+        // Need to use this to set initial state because on creation of the media host, the
+        // view visibility is not in sync with [MediaHost.visible], which is what we track with
+        // the listener. The correct state is set as part of init, so we need to get the state
+        // lazily.
+        .onStart { emit(mediaHost.visible) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index 29bcad4..94b8a3a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -19,6 +19,7 @@
 import com.android.systemui.media.dagger.MediaModule;
 import com.android.systemui.qs.ReduceBrightColorsController;
 import com.android.systemui.qs.ReduceBrightColorsControllerImpl;
+import com.android.systemui.qs.composefragment.dagger.QSFragmentComposeModule;
 import com.android.systemui.qs.external.QSExternalModule;
 import com.android.systemui.qs.panels.dagger.PanelsModule;
 import com.android.systemui.qs.pipeline.dagger.QSPipelineModule;
@@ -40,6 +41,7 @@
         includes = {
                 MediaModule.class,
                 PanelsModule.class,
+                QSFragmentComposeModule.class,
                 QSExternalModule.class,
                 QSFlagsModule.class,
                 QSHostModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
index d55763a..6cc2cbc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
@@ -40,9 +40,9 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.qs.panels.dagger.PaginatedBaseLayoutType
 import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.FooterHeight
 import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.InterPageSpacing
@@ -54,7 +54,7 @@
 class PaginatedGridLayout
 @Inject
 constructor(
-    private val viewModel: PaginatedGridViewModel,
+    private val viewModelFactory: PaginatedGridViewModel.Factory,
     @PaginatedBaseLayoutType private val delegateGridLayout: PaginatableGridLayout,
 ) : GridLayout by delegateGridLayout {
     @Composable
@@ -63,13 +63,18 @@
         modifier: Modifier,
         editModeStart: () -> Unit,
     ) {
+        val viewModel =
+            rememberViewModel(traceName = "PaginatedGridLayout-TileGrid") {
+                viewModelFactory.create()
+            }
+
         DisposableEffect(tiles) {
             val token = Any()
             tiles.forEach { it.startListening(token) }
             onDispose { tiles.forEach { it.stopListening(token) } }
         }
-        val columns by viewModel.columns.collectAsStateWithLifecycle()
-        val rows by viewModel.rows.collectAsStateWithLifecycle()
+        val columns by viewModel.columns
+        val rows = viewModel.rows
 
         val pages =
             remember(tiles, columns, rows) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index 99a6cda..ca28ab3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -41,7 +41,8 @@
     viewModel: QuickQuickSettingsViewModel,
     modifier: Modifier = Modifier,
 ) {
-    val sizedTiles by viewModel.tileViewModels.collectAsStateWithLifecycle()
+
+    val sizedTiles = viewModel.tileViewModels
     val tiles = sizedTiles.fastMap { it.tile }
     val bounceables = remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } }
     val squishiness by viewModel.squishinessViewModel.squishiness.collectAsStateWithLifecycle()
@@ -52,7 +53,7 @@
         tiles.forEach { it.startListening(token) }
         onDispose { tiles.forEach { it.stopListening(token) } }
     }
-    val columns by viewModel.columns.collectAsStateWithLifecycle()
+    val columns = viewModel.columns
     var cellIndex = 0
     Box(modifier = modifier) {
         GridAnchor()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
index 91f2da2..19ab29e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
@@ -72,7 +72,7 @@
             rememberViewModel(traceName = "InfiniteGridLayout.TileGrid") {
                 viewModel.dynamicIconTilesViewModelFactory.create()
             }
-        val columns by viewModel.gridSizeViewModel.columns.collectAsStateWithLifecycle()
+        val columns by viewModel.gridSizeViewModel.columns
         val sizedTiles = tiles.map { SizedTileImpl(it, it.spec.width()) }
         val bounceables =
             remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } }
@@ -118,7 +118,7 @@
             rememberViewModel(traceName = "InfiniteGridLayout.EditTileGrid") {
                 viewModel.dynamicIconTilesViewModelFactory.create()
             }
-        val columns by viewModel.gridSizeViewModel.columns.collectAsStateWithLifecycle()
+        val columns by viewModel.gridSizeViewModel.columns
         val largeTiles by iconTilesViewModel.largeTiles.collectAsStateWithLifecycle()
 
         // Non-current tiles should always be displayed as icon tiles.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InFirstPageViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InFirstPageViewModel.kt
new file mode 100644
index 0000000..225f542
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InFirstPageViewModel.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/*
+ * Tracks whether the current HorizontalPager (using this viewmodel) is in the first page.
+ * This requires it to be a `@SysUISingleton` to be shared between viewmodels.
+ */
+@SysUISingleton
+class InFirstPageViewModel @Inject constructor() {
+    var inFirstPage = true
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModel.kt
index 0d12067..d687100 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.panels.ui.viewmodel
 
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.qs.panels.ui.dialog.QSResetDialogDelegate
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -27,12 +28,16 @@
     val gridSizeViewModel: QSColumnsViewModel,
     val squishinessViewModel: TileSquishinessViewModel,
     private val resetDialogDelegate: QSResetDialogDelegate,
-) {
+) : ExclusiveActivatable() {
 
     fun showResetDialog() {
         resetDialogDelegate.showDialog()
     }
 
+    override suspend fun onActivated(): Nothing {
+        gridSizeViewModel.activate()
+    }
+
     @AssistedFactory
     interface Factory {
         fun create(): InfiniteGridViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
index 0f7dafc..8bd9ed0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
@@ -16,33 +16,50 @@
 
 package com.android.systemui.qs.panels.ui.viewmodel
 
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
 import com.android.systemui.qs.panels.domain.interactor.PaginatedGridInteractor
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.stateIn
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
 
-@SysUISingleton
 class PaginatedGridViewModel
-@Inject
+@AssistedInject
 constructor(
     iconTilesViewModel: IconTilesViewModel,
-    gridSizeViewModel: QSColumnsViewModel,
+    private val gridSizeViewModel: QSColumnsViewModel,
     paginatedGridInteractor: PaginatedGridInteractor,
-    @Application applicationScope: CoroutineScope,
-) : IconTilesViewModel by iconTilesViewModel, QSColumnsViewModel by gridSizeViewModel {
-    val rows =
-        paginatedGridInteractor.rows.stateIn(
-            applicationScope,
-            SharingStarted.WhileSubscribed(),
-            paginatedGridInteractor.defaultRows,
+    inFirstPageViewModel: InFirstPageViewModel,
+) : IconTilesViewModel by iconTilesViewModel, ExclusiveActivatable() {
+
+    private val hydrator = Hydrator("PaginatedGridViewModel")
+
+    val rows by
+        hydrator.hydratedStateOf(
+            traceName = "rows",
+            initialValue = paginatedGridInteractor.defaultRows,
+            source = paginatedGridInteractor.rows,
         )
 
-    /*
-     * Tracks whether the current HorizontalPager (using this viewmodel) is in the first page.
-     * This requires it to be a `@SysUISingleton` to be shared between viewmodels.
-     */
-    var inFirstPage = true
+    var inFirstPage by inFirstPageViewModel::inFirstPage
+
+    val columns: State<Int>
+        get() = gridSizeViewModel.columns
+
+    override suspend fun onActivated(): Nothing {
+        coroutineScope {
+            launch { hydrator.activate() }
+            launch { gridSizeViewModel.activate() }
+            awaitCancellation()
+        }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): PaginatedGridViewModel
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt
index 0f1c77e..8926d2f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt
@@ -16,17 +16,25 @@
 
 package com.android.systemui.qs.panels.ui.viewmodel
 
-import com.android.systemui.dagger.SysUISingleton
+import androidx.compose.runtime.State
+import com.android.systemui.lifecycle.Activatable
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
 import com.android.systemui.qs.panels.domain.interactor.QSColumnsInteractor
 import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
 
-interface QSColumnsViewModel {
-    val columns: StateFlow<Int>
+interface QSColumnsViewModel : Activatable {
+    val columns: State<Int>
 }
 
-@SysUISingleton
 class QSColumnsSizeViewModelImpl @Inject constructor(interactor: QSColumnsInteractor) :
-    QSColumnsViewModel {
-    override val columns: StateFlow<Int> = interactor.columns
+    QSColumnsViewModel, ExclusiveActivatable() {
+    private val hydrator = Hydrator("QSColumnsSizeViewModelImpl")
+
+    override val columns =
+        hydrator.hydratedStateOf(traceName = "columns", source = interactor.columns)
+
+    override suspend fun onActivated(): Nothing {
+        hydrator.activate()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
index 887a70f..0859c86 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
@@ -16,67 +16,69 @@
 
 package com.android.systemui.qs.panels.ui.viewmodel
 
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
 import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
 import com.android.systemui.qs.panels.domain.interactor.QuickQuickSettingsRowInteractor
-import com.android.systemui.qs.panels.shared.model.SizedTile
 import com.android.systemui.qs.panels.shared.model.SizedTileImpl
 import com.android.systemui.qs.panels.shared.model.splitInRowsSequence
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.stateIn
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
 
-@OptIn(ExperimentalCoroutinesApi::class)
-@SysUISingleton
 class QuickQuickSettingsViewModel
-@Inject
+@AssistedInject
 constructor(
     tilesInteractor: CurrentTilesInteractor,
-    qsColumnsViewModel: QSColumnsViewModel,
+    private val qsColumnsViewModel: QSColumnsViewModel,
     quickQuickSettingsRowInteractor: QuickQuickSettingsRowInteractor,
     val squishinessViewModel: TileSquishinessViewModel,
-    private val iconTilesViewModel: IconTilesViewModel,
-    @Application private val applicationScope: CoroutineScope,
+    iconTilesViewModel: IconTilesViewModel,
     val tileHapticsViewModelFactoryProvider: TileHapticsViewModelFactoryProvider,
-) {
+) : ExclusiveActivatable() {
 
-    val columns = qsColumnsViewModel.columns
+    private val hydrator = Hydrator("QuickQuickSettingsViewModel")
 
-    private val rows =
-        quickQuickSettingsRowInteractor.rows.stateIn(
-            applicationScope,
-            SharingStarted.WhileSubscribed(),
-            quickQuickSettingsRowInteractor.defaultRows,
+    val columns by qsColumnsViewModel.columns
+
+    private val largeTiles by
+        hydrator.hydratedStateOf(traceName = "largeTiles", source = iconTilesViewModel.largeTiles)
+
+    private val rows by
+        hydrator.hydratedStateOf(
+            traceName = "rows",
+            initialValue = quickQuickSettingsRowInteractor.defaultRows,
+            source = quickQuickSettingsRowInteractor.rows,
         )
 
-    val tileViewModels: StateFlow<List<SizedTile<TileViewModel>>> =
-        columns
-            .flatMapLatest { columns ->
-                tilesInteractor.currentTiles.combine(rows, ::Pair).mapLatest { (tiles, rows) ->
-                    tiles
-                        .map { SizedTileImpl(TileViewModel(it.tile, it.spec), it.spec.width) }
-                        .let { splitInRowsSequence(it, columns).take(rows).toList().flatten() }
-                }
-            }
-            .stateIn(
-                applicationScope,
-                SharingStarted.WhileSubscribed(),
-                tilesInteractor.currentTiles.value
-                    .map { SizedTileImpl(TileViewModel(it.tile, it.spec), it.spec.width) }
-                    .let {
-                        splitInRowsSequence(it, columns.value).take(rows.value).toList().flatten()
-                    },
-            )
+    private val currentTiles by
+        hydrator.hydratedStateOf(traceName = "currentTiles", source = tilesInteractor.currentTiles)
+
+    val tileViewModels by derivedStateOf {
+        currentTiles
+            .map { SizedTileImpl(TileViewModel(it.tile, it.spec), it.spec.width) }
+            .let { splitInRowsSequence(it, columns).take(rows).toList().flatten() }
+    }
 
     private val TileSpec.width: Int
-        get() = if (iconTilesViewModel.isIconTile(this)) 1 else 2
+        get() = if (largeTiles.contains(this)) 2 else 1
+
+    override suspend fun onActivated(): Nothing {
+        coroutineScope {
+            launch { hydrator.activate() }
+            launch { qsColumnsViewModel.activate() }
+            awaitCancellation()
+        }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): QuickQuickSettingsViewModel
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
index b1eb3bb3..da175c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
@@ -24,22 +24,31 @@
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
 
 class QuickSettingsContainerViewModel
 @AssistedInject
 constructor(
     brightnessSliderViewModelFactory: BrightnessSliderViewModel.Factory,
+    quickQuickSettingsViewModelFactory: QuickQuickSettingsViewModel.Factory,
     @Assisted supportsBrightnessMirroring: Boolean,
     val tileGridViewModel: TileGridViewModel,
     val editModeViewModel: EditModeViewModel,
-    val quickQuickSettingsViewModel: QuickQuickSettingsViewModel,
 ) : ExclusiveActivatable() {
 
     val brightnessSliderViewModel =
         brightnessSliderViewModelFactory.create(supportsBrightnessMirroring)
 
+    val quickQuickSettingsViewModel = quickQuickSettingsViewModelFactory.create()
+
     override suspend fun onActivated(): Nothing {
-        brightnessSliderViewModel.activate()
+        coroutineScope {
+            launch { brightnessSliderViewModel.activate() }
+            launch { quickQuickSettingsViewModel.activate() }
+            awaitCancellation()
+        }
     }
 
     @AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index aa2a08f..08d177f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1987,6 +1987,7 @@
         mColorUpdateLogger = colorUpdateLogger;
         mDismissibilityProvider = dismissibilityProvider;
         mFeatureFlags = featureFlags;
+        setHapticFeedbackEnabled(!com.android.systemui.Flags.msdlFeedback());
     }
 
     private void initDimens() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 23a2fac..baad616 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -64,6 +64,9 @@
 import com.android.systemui.util.time.SystemClock;
 import com.android.systemui.wmshell.BubblesManager;
 
+import com.google.android.msdl.data.model.MSDLToken;
+import com.google.android.msdl.domain.MSDLPlayer;
+
 import java.util.List;
 import java.util.Optional;
 
@@ -114,6 +117,7 @@
     private final NotificationDismissibilityProvider mDismissibilityProvider;
     private final IStatusBarService mStatusBarService;
     private final UiEventLogger mUiEventLogger;
+    private final MSDLPlayer mMSDLPlayer;
 
     private final NotificationSettingsController mSettingsController;
 
@@ -273,7 +277,8 @@
             ExpandableNotificationRowDragController dragController,
             NotificationDismissibilityProvider dismissibilityProvider,
             IStatusBarService statusBarService,
-            UiEventLogger uiEventLogger) {
+            UiEventLogger uiEventLogger,
+            MSDLPlayer msdlPlayer) {
         mView = view;
         mListContainer = listContainer;
         mRemoteInputViewSubcomponentFactory = rivSubcomponentFactory;
@@ -309,6 +314,7 @@
         mDismissibilityProvider = dismissibilityProvider;
         mStatusBarService = statusBarService;
         mUiEventLogger = uiEventLogger;
+        mMSDLPlayer = msdlPlayer;
     }
 
     /**
@@ -352,6 +358,9 @@
             }
 
             mView.setLongPressListener((v, x, y, item) -> {
+                if (com.android.systemui.Flags.msdlFeedback()) {
+                    mMSDLPlayer.playToken(MSDLToken.LONG_PRESS, null);
+                }
                 if (mView.isSummaryWithChildren()) {
                     mView.expandNotification();
                     return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index c1d72e4..f02f7f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -678,7 +678,9 @@
         mGroupMembershipManager = Dependency.get(GroupMembershipManager.class);
         mGroupExpansionManager = Dependency.get(GroupExpansionManager.class);
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
-        setWindowInsetsAnimationCallback(mInsetsCallback);
+        if (!SceneContainerFlag.isEnabled()) {
+            setWindowInsetsAnimationCallback(mInsetsCallback);
+        }
     }
 
     /**
@@ -1694,7 +1696,7 @@
             } else if (mQsFullScreen) {
                 int stackStartPosition =
                         getContentHeight() - getTopPadding() + getIntrinsicPadding();
-                int stackEndPosition = mMaxTopPadding + mShelf.getIntrinsicHeight();
+                int stackEndPosition = getMaxTopPadding() + mShelf.getIntrinsicHeight();
                 if (stackStartPosition <= stackEndPosition) {
                     stackHeight = stackEndPosition;
                 } else {
@@ -2086,6 +2088,7 @@
     }
 
     private void updateImeInset(WindowInsets windowInsets) {
+        SceneContainerFlag.assertInLegacyMode();
         mImeInset = windowInsets.getInsets(WindowInsets.Type.ime()).bottom;
 
         if (mFooterView != null && mFooterView.getViewState() != null) {
@@ -2112,7 +2115,7 @@
         if (cutout != null) {
             mWaterfallTopInset = cutout.getWaterfallInsets().top;
         }
-        if (!mIsInsetAnimationRunning) {
+        if (!SceneContainerFlag.isEnabled() && !mIsInsetAnimationRunning) {
             // update bottom inset e.g. after rotation
             updateImeInset(insets);
         }
@@ -2513,6 +2516,7 @@
     }
 
     private int getImeInset() {
+        SceneContainerFlag.assertInLegacyMode();
         // The NotificationStackScrollLayout does not extend all the way to the bottom of the
         // display. Therefore, subtract that space from the mImeInset, in order to only include
         // the portion of the bottom inset that actually overlaps the NotificationStackScrollLayout.
@@ -2851,11 +2855,6 @@
         setExpandedHeight(mExpandedHeight);
     }
 
-    public void setMaxTopPadding(int maxTopPadding) {
-        SceneContainerFlag.assertInLegacyMode();
-        mMaxTopPadding = maxTopPadding;
-    }
-
     public int getLayoutMinHeight() {
         SceneContainerFlag.assertInLegacyMode();
         return getLayoutMinHeightInternal();
@@ -3639,7 +3638,11 @@
      * @return Whether a y coordinate is inside the content.
      */
     public boolean isInContentBounds(float y) {
-        return y < getHeight() - getEmptyBottomMarginInternal();
+        if (SceneContainerFlag.isEnabled()) {
+            return y < mAmbientState.getStackCutoff();
+        } else {
+            return y < getHeight() - getEmptyBottomMarginInternal();
+        }
     }
 
     private float getTouchSlop(MotionEvent event) {
@@ -5510,7 +5513,6 @@
             println(pw, "alpha", getAlpha());
             println(pw, "suppressChildrenMeasureLayout", mSuppressChildrenMeasureAndLayout);
             println(pw, "scrollY", mAmbientState.getScrollY());
-            println(pw, "maxTopPadding", mMaxTopPadding);
             println(pw, "showShelfOnly", mShouldShowShelfOnly);
             println(pw, "qsExpandFraction", mQsExpansionFraction);
             println(pw, "isCurrentUserSetup", mIsCurrentUserSetup);
@@ -5547,6 +5549,7 @@
                 println(pw, "intrinsicContentHeight", getIntrinsicContentHeight());
                 println(pw, "contentHeight", getContentHeight());
                 println(pw, "topPadding", getTopPadding());
+                println(pw, "maxTopPadding", getMaxTopPadding());
             }
         });
         pw.println();
@@ -6953,10 +6956,12 @@
 
     /** Use {@link ScrollViewFields#intrinsicStackHeight}, when SceneContainerFlag is enabled. */
     private int getContentHeight() {
+        SceneContainerFlag.assertInLegacyMode();
         return mContentHeight;
     }
 
     private void setContentHeight(int contentHeight) {
+        SceneContainerFlag.assertInLegacyMode();
         mContentHeight = contentHeight;
     }
 
@@ -6965,10 +6970,23 @@
      * @return the height of the content ignoring the footer.
      */
     public float getIntrinsicContentHeight() {
+        SceneContainerFlag.assertInLegacyMode();
         return mIntrinsicContentHeight;
     }
 
     private void setIntrinsicContentHeight(float intrinsicContentHeight) {
+        SceneContainerFlag.assertInLegacyMode();
         mIntrinsicContentHeight = intrinsicContentHeight;
     }
+
+    private int getMaxTopPadding() {
+        SceneContainerFlag.assertInLegacyMode();
+        return mMaxTopPadding;
+    }
+
+    /** Not used with SceneContainerFlag, because we rely on the placeholder for placement. */
+    public void setMaxTopPadding(int maxTopPadding) {
+        SceneContainerFlag.assertInLegacyMode();
+        mMaxTopPadding = maxTopPadding;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index a658115..d2c2003 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -37,6 +37,7 @@
 import com.android.systemui.ScreenDecorations;
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
@@ -78,6 +79,7 @@
     private View mNotificationShadeWindowView;
     private View mNotificationPanelView;
     private boolean mForceCollapsedUntilLayout = false;
+    private Boolean mCommunalVisible = false;
 
     private Region mTouchableRegion = new Region();
     private int mDisplayCutoutTouchableRegionSize;
@@ -98,7 +100,8 @@
             JavaAdapter javaAdapter,
             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
             PrimaryBouncerInteractor primaryBouncerInteractor,
-            AlternateBouncerInteractor alternateBouncerInteractor
+            AlternateBouncerInteractor alternateBouncerInteractor,
+            CommunalSceneInteractor communalSceneInteractor
     ) {
         mContext = context;
         initResources();
@@ -145,6 +148,9 @@
             javaAdapter.alwaysCollectFlow(
                     shadeInteractor.isAnyExpanded(),
                     this::onShadeOrQsExpanded);
+            javaAdapter.alwaysCollectFlow(
+                    communalSceneInteractor.isCommunalVisible(),
+                    this::onCommunalVisible);
         }
 
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
@@ -196,6 +202,10 @@
         }
     }
 
+    private void onCommunalVisible(Boolean visible) {
+        mCommunalVisible = visible;
+    }
+
     /**
      * Calculates the touch region needed for heads up notifications, taking into consideration
      * any existing display cutouts (notch)
@@ -304,6 +314,9 @@
                 && (!mIsSceneContainerUiEmpty || mIsRemoteUserInteractionOngoing))
                 || mPrimaryBouncerInteractor.isShowing().getValue()
                 || mAlternateBouncerInteractor.isVisibleState()
+                // The glanceable hub is a full-screen UI within the notification shade window. When
+                // it's visible, the touchable region should be the full screen.
+                || mCommunalVisible
                 || mUnlockedScreenOffAnimationController.isAnimationPlaying();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 06e8b1c..57fad83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -1207,6 +1207,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testWindowInsetAnimationProgress_updatesBottomInset() {
         int imeInset = 100;
         WindowInsets windowInsets = new WindowInsets.Builder()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index 4976cc2..19e077c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -53,6 +53,15 @@
     private val initInLockscreen: Boolean = true,
 
     /**
+     * Initial value for [FakeKeyguardTransitionRepository.sendTransitionStepsOnStartTransition].
+     * This needs to be configurable in the constructor since some transitions are triggered on
+     * init, before a test has the chance to set sendTransitionStepsOnStartTransition to false.
+     */
+    private val initiallySendTransitionStepsOnStartTransition: Boolean = true,
+    private val testScope: TestScope,
+) : KeyguardTransitionRepository {
+
+    /**
      * If true, calls to [startTransition] will automatically emit STARTED, RUNNING, and FINISHED
      * transition steps from/to the given states.
      *
@@ -64,11 +73,9 @@
      *
      * If your test needs to make assertions at specific points between STARTED/FINISHED, or if it's
      * difficult to set up all of the conditions to make the transition interactors actually call
-     * startTransition, then construct a FakeKeyguardTransitionRepository with this value false.
+     * startTransition, set this value to false.
      */
-    private val sendTransitionStepsOnStartTransition: Boolean = true,
-    private val testScope: TestScope,
-) : KeyguardTransitionRepository {
+    var sendTransitionStepsOnStartTransition = initiallySendTransitionStepsOnStartTransition
 
     private val _transitions =
         MutableSharedFlow<TransitionStep>(replay = 3, onBufferOverflow = BufferOverflow.DROP_OLDEST)
@@ -77,7 +84,11 @@
     @Inject
     constructor(
         testScope: TestScope
-    ) : this(initInLockscreen = true, sendTransitionStepsOnStartTransition = true, testScope)
+    ) : this(
+        initInLockscreen = true,
+        initiallySendTransitionStepsOnStartTransition = true,
+        testScope
+    )
 
     private val _currentTransitionInfo: MutableStateFlow<TransitionInfo> =
         MutableStateFlow(
@@ -176,6 +187,20 @@
         testScheduler: TestCoroutineScheduler,
         throughTransitionState: TransitionState = TransitionState.FINISHED,
     ) {
+        val lastStep = _transitions.replayCache.lastOrNull()
+        if (lastStep != null && lastStep.transitionState != TransitionState.FINISHED) {
+            sendTransitionStep(
+                step =
+                TransitionStep(
+                    transitionState = TransitionState.CANCELED,
+                    from = lastStep.from,
+                    to = lastStep.to,
+                    value = 0f,
+                )
+            )
+            testScheduler.runCurrent()
+        }
+
         sendTransitionStep(
             step =
                 TransitionStep(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplKosmos.kt
new file mode 100644
index 0000000..c337298
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+/** Empty mock */
+val Kosmos.legacyMediaDataManagerImpl by Kosmos.Fixture { mock<LegacyMediaDataManagerImpl>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManagerKosmos.kt
new file mode 100644
index 0000000..f733da1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManagerKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.mediaDataManager: MediaDataManager by Kosmos.Fixture()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerKosmos.kt
new file mode 100644
index 0000000..3c35ce9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.ui.controller
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+/** Empty mock */
+val Kosmos.mediaCarouselController by Kosmos.Fixture { mock<MediaCarouselController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLoggerKosmos.kt
new file mode 100644
index 0000000..9f5a832
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLoggerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.ui.controller
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.core.FakeLogBuffer
+
+val Kosmos.mediaCarouselControllerLogger by
+    Kosmos.Fixture { MediaCarouselControllerLogger(FakeLogBuffer.Factory.create()) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerKosmos.kt
index 7c24b4c..624e174 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerKosmos.kt
@@ -16,8 +16,15 @@
 
 package com.android.systemui.media.controls.ui.controller
 
+import android.content.testableContext
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.animation.UniqueObjectHostView
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
 
-var Kosmos.mediaHierarchyManager by Fixture { mock<MediaHierarchyManager>() }
+var Kosmos.mediaHierarchyManager by Fixture {
+    mock<MediaHierarchyManager> {
+        on { register(any()) }.thenAnswer { UniqueObjectHostView(this@Fixture.testableContext) }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaHostStatesManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaHostStatesManagerKosmos.kt
new file mode 100644
index 0000000..8b02c57
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaHostStatesManagerKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.ui.controller
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.mediaHostStatesManager by Kosmos.Fixture { MediaHostStatesManager() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/view/MediaHostKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/view/MediaHostKosmos.kt
new file mode 100644
index 0000000..9638e11
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/view/MediaHostKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.ui.view
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.media.controls.domain.pipeline.mediaDataManager
+import com.android.systemui.media.controls.ui.controller.mediaCarouselController
+import com.android.systemui.media.controls.ui.controller.mediaCarouselControllerLogger
+import com.android.systemui.media.controls.ui.controller.mediaHierarchyManager
+import com.android.systemui.media.controls.ui.controller.mediaHostStatesManager
+import java.util.function.Supplier
+
+private val Kosmos.mediaHostProvider by
+    Kosmos.Fixture {
+        Supplier<MediaHost> {
+            MediaHost(
+                MediaHost.MediaHostStateHolder(),
+                mediaHierarchyManager,
+                mediaDataManager,
+                mediaHostStatesManager,
+                mediaCarouselController,
+                mediaCarouselControllerLogger,
+            )
+        }
+    }
+
+val Kosmos.qqsMediaHost by Kosmos.Fixture { mediaHostProvider.get() }
+val Kosmos.qsMediaHost by Kosmos.Fixture { mediaHostProvider.get() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModuleKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModuleKosmos.kt
new file mode 100644
index 0000000..fb8ffb0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModuleKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.composefragment.dagger
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.usingMediaInComposeFragment by Kosmos.Fixture<Boolean>()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
index 462b408..bda3192 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
@@ -22,10 +22,13 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.media.controls.ui.view.qqsMediaHost
+import com.android.systemui.media.controls.ui.view.qsMediaHost
+import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment
 import com.android.systemui.qs.footerActionsController
 import com.android.systemui.qs.footerActionsViewModelFactory
 import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor
-import com.android.systemui.qs.panels.ui.viewmodel.paginatedGridViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.inFirstPageViewModel
 import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModelFactory
 import com.android.systemui.shade.largeScreenHeaderHelper
 import com.android.systemui.shade.transition.largeScreenShadeInterpolator
@@ -53,7 +56,10 @@
                     configurationInteractor,
                     largeScreenHeaderHelper,
                     tileSquishinessInteractor,
-                    paginatedGridViewModel,
+                    inFirstPageViewModel,
+                    qqsMediaHost,
+                    qsMediaHost,
+                    usingMediaInComposeFragment,
                     lifecycleScope,
                 )
             }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InFirstPageViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InFirstPageViewModelKosmos.kt
new file mode 100644
index 0000000..1fa74ca
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InFirstPageViewModelKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.inFirstPageViewModel by Kosmos.Fixture { InFirstPageViewModel() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
index 48ef57e..5c8ca83 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.qs.panels.ui.viewmodel
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.qs.panels.domain.interactor.paginatedGridInteractor
 
 val Kosmos.paginatedGridViewModel by
@@ -26,6 +25,6 @@
             iconTilesViewModel,
             qsColumnsViewModel,
             paginatedGridInteractor,
-            applicationCoroutineScope,
+            inFirstPageViewModel,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
index 41ee260..20be5c6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
@@ -18,19 +18,21 @@
 
 import com.android.systemui.haptics.msdl.tileHapticsViewModelFactoryProvider
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.qs.panels.domain.interactor.quickQuickSettingsRowInteractor
 import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
 
-val Kosmos.quickQuickSettingsViewModel by
+val Kosmos.quickQuickSettingsViewModelFactory by
     Kosmos.Fixture {
-        QuickQuickSettingsViewModel(
-            currentTilesInteractor,
-            qsColumnsViewModel,
-            quickQuickSettingsRowInteractor,
-            tileSquishinessViewModel,
-            iconTilesViewModel,
-            applicationCoroutineScope,
-            tileHapticsViewModelFactoryProvider,
-        )
+        object : QuickQuickSettingsViewModel.Factory {
+            override fun create(): QuickQuickSettingsViewModel {
+                return QuickQuickSettingsViewModel(
+                    currentTilesInteractor,
+                    qsColumnsViewModel,
+                    quickQuickSettingsRowInteractor,
+                    tileSquishinessViewModel,
+                    iconTilesViewModel,
+                    tileHapticsViewModelFactoryProvider,
+                )
+            }
+        }
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
index 6ded751..ce103ec 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.brightness.ui.viewmodel.brightnessSliderViewModelFactory
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.quickQuickSettingsViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.quickQuickSettingsViewModelFactory
 import com.android.systemui.qs.panels.ui.viewmodel.tileGridViewModel
 
 val Kosmos.quickSettingsContainerViewModelFactory by
@@ -30,10 +30,10 @@
             ): QuickSettingsContainerViewModel {
                 return QuickSettingsContainerViewModel(
                     brightnessSliderViewModelFactory,
+                    quickQuickSettingsViewModelFactory,
                     supportsBrightnessMirroring,
                     tileGridViewModel,
                     editModeViewModel,
-                    quickQuickSettingsViewModel,
                 )
             }
         }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerKosmos.kt
index 8785256..87ea147 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerKosmos.kt
@@ -19,6 +19,7 @@
 import android.content.applicationContext
 import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -43,5 +44,6 @@
             mock<UnlockedScreenOffAnimationController>(),
             primaryBouncerInteractor,
             alternateBouncerInteractor,
+            communalSceneInteractor,
         )
     }
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index a26fe66..70c1d78 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -2,6 +2,9 @@
 
 com.android.internal.ravenwood.*
 
+com.android.server.FgThread
+com.android.server.ServiceThread
+
 com.android.internal.display.BrightnessSynchronizer
 com.android.internal.util.ArrayUtils
 com.android.internal.logging.MetricsLogger
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index a960015..51034d2 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -31,13 +31,16 @@
 
 import static com.android.internal.util.CollectionUtils.any;
 import static com.android.internal.util.Preconditions.checkState;
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
+import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
 import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
 import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
 import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOr;
 import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
 
 import static java.util.Objects.requireNonNull;
+import static java.util.concurrent.TimeUnit.MINUTES;
 
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
@@ -66,22 +69,31 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.net.MacAddress;
+import android.net.NetworkPolicyManager;
 import android.os.Binder;
+import android.os.Environment;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.PowerExemptionManager;
 import android.os.PowerManagerInternal;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.permission.flags.Flags;
+import android.util.ArraySet;
 import android.util.ExceptionUtils;
 import android.util.Slog;
 
+import com.android.internal.app.IAppOpsService;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.notification.NotificationAccessConfirmationActivityContract;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.server.FgThread;
@@ -102,27 +114,35 @@
 import com.android.server.companion.devicepresence.ObservableUuid;
 import com.android.server.companion.devicepresence.ObservableUuidStore;
 import com.android.server.companion.transport.CompanionTransportManager;
+import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
+import java.io.File;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Collection;
 import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
+import java.util.Set;
 
 @SuppressLint("LongLogTag")
 public class CompanionDeviceManagerService extends SystemService {
     private static final String TAG = "CDM_CompanionDeviceManagerService";
 
     private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min
+
+    private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
+    private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done";
     private static final int MAX_CN_LENGTH = 500;
 
+    private final ActivityTaskManagerInternal mAtmInternal;
+    private final ActivityManagerInternal mAmInternal;
+    private final IAppOpsService mAppOpsManager;
+    private final PowerExemptionManager mPowerExemptionManager;
+    private final PackageManagerInternal mPackageManagerInternal;
+
     private final AssociationStore mAssociationStore;
     private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
     private final ObservableUuidStore mObservableUuidStore;
-
-    private final CompanionExemptionProcessor mCompanionExemptionProcessor;
     private final AssociationRequestsProcessor mAssociationRequestsProcessor;
     private final SystemDataTransferProcessor mSystemDataTransferProcessor;
     private final BackupRestoreProcessor mBackupRestoreProcessor;
@@ -136,15 +156,12 @@
         super(context);
 
         final ActivityManager activityManager = context.getSystemService(ActivityManager.class);
-        final PowerExemptionManager powerExemptionManager = context.getSystemService(
-                PowerExemptionManager.class);
-        final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
-        final ActivityTaskManagerInternal atmInternal = LocalServices.getService(
-                ActivityTaskManagerInternal.class);
-        final ActivityManagerInternal amInternal = LocalServices.getService(
-                ActivityManagerInternal.class);
-        final PackageManagerInternal packageManagerInternal = LocalServices.getService(
-                PackageManagerInternal.class);
+        mPowerExemptionManager = context.getSystemService(PowerExemptionManager.class);
+        mAppOpsManager = IAppOpsService.Stub.asInterface(
+                ServiceManager.getService(Context.APP_OPS_SERVICE));
+        mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+        mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
+        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         final UserManager userManager = context.getSystemService(UserManager.class);
         final PowerManagerInternal powerManagerInternal = LocalServices.getService(
                 PowerManagerInternal.class);
@@ -156,29 +173,25 @@
 
         // Init processors
         mAssociationRequestsProcessor = new AssociationRequestsProcessor(context,
-                packageManagerInternal, mAssociationStore);
-        mBackupRestoreProcessor = new BackupRestoreProcessor(context, packageManagerInternal,
+                mPackageManagerInternal, mAssociationStore);
+        mBackupRestoreProcessor = new BackupRestoreProcessor(context, mPackageManagerInternal,
                 mAssociationStore, associationDiskStore, mSystemDataTransferRequestStore,
                 mAssociationRequestsProcessor);
 
         mCompanionAppBinder = new CompanionAppBinder(context);
 
-        mCompanionExemptionProcessor = new CompanionExemptionProcessor(context,
-                powerExemptionManager, appOpsManager, packageManagerInternal, atmInternal,
-                amInternal, mAssociationStore);
-
         mDevicePresenceProcessor = new DevicePresenceProcessor(context,
                 mCompanionAppBinder, userManager, mAssociationStore, mObservableUuidStore,
-                powerManagerInternal, mCompanionExemptionProcessor);
+                powerManagerInternal);
 
         mTransportManager = new CompanionTransportManager(context, mAssociationStore);
 
         mDisassociationProcessor = new DisassociationProcessor(context, activityManager,
-                mAssociationStore, packageManagerInternal, mDevicePresenceProcessor,
+                mAssociationStore, mPackageManagerInternal, mDevicePresenceProcessor,
                 mCompanionAppBinder, mSystemDataTransferRequestStore, mTransportManager);
 
         mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
-                packageManagerInternal, mAssociationStore,
+                mPackageManagerInternal, mAssociationStore,
                 mSystemDataTransferRequestStore, mTransportManager);
 
         // TODO(b/279663946): move context sync to a dedicated system service
@@ -189,6 +202,7 @@
     public void onStart() {
         // Init association stores
         mAssociationStore.refreshCache();
+        mAssociationStore.registerLocalListener(mAssociationStoreChangeListener);
 
         // Init UUID store
         mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId());
@@ -226,11 +240,11 @@
 
         if (associations.isEmpty()) return;
 
-        mCompanionExemptionProcessor.updateAtm(userId, associations);
+        updateAtm(userId, associations);
 
-        try (ExecutorService executor = Executors.newSingleThreadExecutor()) {
-            executor.execute(mCompanionExemptionProcessor::updateAutoRevokeExemptions);
-        }
+        BackgroundThread.getHandler().sendMessageDelayed(
+                obtainMessage(CompanionDeviceManagerService::maybeGrantAutoRevokeExemptions, this),
+                MINUTES.toMillis(10));
     }
 
     @Override
@@ -248,12 +262,9 @@
         if (!associationsForPackage.isEmpty()) {
             Slog.i(TAG, "Package removed or data cleared for user=[" + userId + "], package=["
                     + packageName + "]. Cleaning up CDM data...");
-
-            for (AssociationInfo association : associationsForPackage) {
-                mDisassociationProcessor.disassociate(association.getId());
-            }
-
-            mCompanionAppBinder.onPackagesChanged(userId, packageName);
+        }
+        for (AssociationInfo association : associationsForPackage) {
+            mDisassociationProcessor.disassociate(association.getId());
         }
 
         // Clear observable UUIDs for the package.
@@ -262,16 +273,19 @@
         for (ObservableUuid uuid : uuidsTobeObserved) {
             mObservableUuidStore.removeObservableUuid(userId, uuid.getUuid(), packageName);
         }
+
+        mCompanionAppBinder.onPackagesChanged(userId);
     }
 
     private void onPackageModifiedInternal(@UserIdInt int userId, @NonNull String packageName) {
-        final List<AssociationInfo> associations =
+        final List<AssociationInfo> associationsForPackage =
                 mAssociationStore.getAssociationsByPackage(userId, packageName);
-        if (!associations.isEmpty()) {
-            mCompanionExemptionProcessor.exemptPackage(userId, packageName, false);
-
-            mCompanionAppBinder.onPackagesChanged(userId, packageName);
+        for (AssociationInfo association : associationsForPackage) {
+            updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
+                    association.getPackageName());
         }
+
+        mCompanionAppBinder.onPackagesChanged(userId);
     }
 
     private void onPackageAddedInternal(@UserIdInt int userId, @NonNull String packageName) {
@@ -751,6 +765,130 @@
         }
     }
 
+    /**
+     * Update special access for the association's package
+     */
+    public void updateSpecialAccessPermissionForAssociatedPackage(int userId, String packageName) {
+        final PackageInfo packageInfo =
+                getPackageInfo(getContext(), userId, packageName);
+
+        Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo));
+    }
+
+    private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo) {
+        if (packageInfo == null) {
+            return;
+        }
+
+        if (containsEither(packageInfo.requestedPermissions,
+                android.Manifest.permission.RUN_IN_BACKGROUND,
+                android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) {
+            mPowerExemptionManager.addToPermanentAllowList(packageInfo.packageName);
+        } else {
+            try {
+                mPowerExemptionManager.removeFromPermanentAllowList(packageInfo.packageName);
+            } catch (UnsupportedOperationException e) {
+                Slog.w(TAG, packageInfo.packageName + " can't be removed from power save"
+                        + " whitelist. It might due to the package is whitelisted by the system.");
+            }
+        }
+
+        NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(getContext());
+        try {
+            if (containsEither(packageInfo.requestedPermissions,
+                    android.Manifest.permission.USE_DATA_IN_BACKGROUND,
+                    android.Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND)) {
+                networkPolicyManager.addUidPolicy(
+                        packageInfo.applicationInfo.uid,
+                        NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
+            } else {
+                networkPolicyManager.removeUidPolicy(
+                        packageInfo.applicationInfo.uid,
+                        NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
+            }
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, e.getMessage());
+        }
+
+        exemptFromAutoRevoke(packageInfo.packageName, packageInfo.applicationInfo.uid);
+    }
+
+    private void exemptFromAutoRevoke(String packageName, int uid) {
+        try {
+            mAppOpsManager.setMode(
+                    AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
+                    uid,
+                    packageName,
+                    AppOpsManager.MODE_IGNORED);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Error while granting auto revoke exemption for " + packageName, e);
+        }
+    }
+
+    private void updateAtm(int userId, List<AssociationInfo> associations) {
+        final Set<Integer> companionAppUids = new ArraySet<>();
+        for (AssociationInfo association : associations) {
+            final int uid = mPackageManagerInternal.getPackageUid(association.getPackageName(),
+                    0, userId);
+            if (uid >= 0) {
+                companionAppUids.add(uid);
+            }
+        }
+        if (mAtmInternal != null) {
+            mAtmInternal.setCompanionAppUids(userId, companionAppUids);
+        }
+        if (mAmInternal != null) {
+            // Make a copy of the set and send it to ActivityManager.
+            mAmInternal.setCompanionAppUids(userId, new ArraySet<>(companionAppUids));
+        }
+    }
+
+    private void maybeGrantAutoRevokeExemptions() {
+        Slog.d(TAG, "maybeGrantAutoRevokeExemptions()");
+
+        PackageManager pm = getContext().getPackageManager();
+        for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) {
+            SharedPreferences pref = getContext().getSharedPreferences(
+                    new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME),
+                    Context.MODE_PRIVATE);
+            if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) {
+                continue;
+            }
+
+            try {
+                final List<AssociationInfo> associations =
+                        mAssociationStore.getActiveAssociationsByUser(userId);
+                for (AssociationInfo a : associations) {
+                    try {
+                        int uid = pm.getPackageUidAsUser(a.getPackageName(), userId);
+                        exemptFromAutoRevoke(a.getPackageName(), uid);
+                    } catch (PackageManager.NameNotFoundException e) {
+                        Slog.w(TAG, "Unknown companion package: " + a.getPackageName(), e);
+                    }
+                }
+            } finally {
+                pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply();
+            }
+        }
+    }
+
+    private final AssociationStore.OnChangeListener mAssociationStoreChangeListener =
+            new AssociationStore.OnChangeListener() {
+                @Override
+                public void onAssociationChanged(int changeType, AssociationInfo association) {
+                    Slog.d(TAG, "onAssociationChanged changeType=[" + changeType
+                            + "], association=[" + association);
+
+                    final int userId = association.getUserId();
+                    final List<AssociationInfo> updatedAssociations =
+                            mAssociationStore.getActiveAssociationsByUser(userId);
+
+                    updateAtm(userId, updatedAssociations);
+                    updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
+                            association.getPackageName());
+                }
+            };
+
     private final PackageMonitor mPackageMonitor = new PackageMonitor() {
         @Override
         public void onPackageRemoved(String packageName, int uid) {
@@ -773,6 +911,10 @@
         }
     };
 
+    private static <T> boolean containsEither(T[] array, T a, T b) {
+        return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b);
+    }
+
     private class LocalService implements CompanionDeviceManagerServiceInternal {
 
         @Override
diff --git a/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java b/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java
deleted file mode 100644
index 4969ffbf..0000000
--- a/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * 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.server.companion;
-
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.MODE_IGNORED;
-
-import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
-
-import android.annotation.SuppressLint;
-import android.app.ActivityManagerInternal;
-import android.app.AppOpsManager;
-import android.companion.AssociationInfo;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.net.NetworkPolicyManager;
-import android.os.Binder;
-import android.os.Environment;
-import android.os.PowerExemptionManager;
-import android.util.ArraySet;
-import android.util.Slog;
-
-import com.android.internal.util.ArrayUtils;
-import com.android.server.LocalServices;
-import com.android.server.companion.association.AssociationStore;
-import com.android.server.pm.UserManagerInternal;
-import com.android.server.wm.ActivityTaskManagerInternal;
-
-import java.io.File;
-import java.util.List;
-import java.util.Set;
-
-@SuppressLint("LongLogTag")
-public class CompanionExemptionProcessor {
-
-    private static final String TAG = "CDM_CompanionExemptionProcessor";
-
-    private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
-    private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done";
-
-    private final Context mContext;
-    private final PowerExemptionManager mPowerExemptionManager;
-    private final AppOpsManager mAppOpsManager;
-    private final PackageManagerInternal mPackageManager;
-    private final ActivityTaskManagerInternal mAtmInternal;
-    private final ActivityManagerInternal mAmInternal;
-    private final AssociationStore mAssociationStore;
-
-    public CompanionExemptionProcessor(Context context, PowerExemptionManager powerExemptionManager,
-            AppOpsManager appOpsManager, PackageManagerInternal packageManager,
-            ActivityTaskManagerInternal atmInternal, ActivityManagerInternal amInternal,
-            AssociationStore associationStore) {
-        mContext = context;
-        mPowerExemptionManager = powerExemptionManager;
-        mAppOpsManager = appOpsManager;
-        mPackageManager = packageManager;
-        mAtmInternal = atmInternal;
-        mAmInternal = amInternal;
-        mAssociationStore = associationStore;
-
-        mAssociationStore.registerLocalListener(new AssociationStore.OnChangeListener() {
-            @Override
-            public void onAssociationChanged(int changeType, AssociationInfo association) {
-                final int userId = association.getUserId();
-                final List<AssociationInfo> updatedAssociations =
-                        mAssociationStore.getActiveAssociationsByUser(userId);
-
-                updateAtm(userId, updatedAssociations);
-            }
-        });
-    }
-
-    /**
-     * Update ActivityManager and ActivityTaskManager exemptions
-     */
-    public void updateAtm(int userId, List<AssociationInfo> associations) {
-        final Set<Integer> companionAppUids = new ArraySet<>();
-        for (AssociationInfo association : associations) {
-            int uid = mPackageManager.getPackageUid(association.getPackageName(), 0, userId);
-            if (uid >= 0) {
-                companionAppUids.add(uid);
-            }
-        }
-        if (mAtmInternal != null) {
-            mAtmInternal.setCompanionAppUids(userId, companionAppUids);
-        }
-        if (mAmInternal != null) {
-            // Make a copy of the set and send it to ActivityManager.
-            mAmInternal.setCompanionAppUids(userId, new ArraySet<>(companionAppUids));
-        }
-    }
-
-    /**
-     * Update special access for the association's package
-     */
-    public void exemptPackage(int userId, String packageName, boolean hasPresentDevices) {
-        Slog.i(TAG, "Exempting package [" + packageName + "]...");
-
-        final PackageInfo packageInfo = getPackageInfo(mContext, userId, packageName);
-
-        Binder.withCleanCallingIdentity(
-                () -> exemptPackageAsSystem(userId, packageInfo, hasPresentDevices));
-    }
-
-    @SuppressLint("MissingPermission")
-    private void exemptPackageAsSystem(int userId, PackageInfo packageInfo,
-            boolean hasPresentDevices) {
-        if (packageInfo == null) {
-            return;
-        }
-
-        // If the app has run-in-bg permission and present devices, add it to power saver allowlist.
-        if (containsEither(packageInfo.requestedPermissions,
-                android.Manifest.permission.RUN_IN_BACKGROUND,
-                android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)
-                && hasPresentDevices) {
-            mPowerExemptionManager.addToPermanentAllowList(packageInfo.packageName);
-        } else {
-            try {
-                mPowerExemptionManager.removeFromPermanentAllowList(packageInfo.packageName);
-            } catch (UnsupportedOperationException e) {
-                Slog.w(TAG, packageInfo.packageName + " can't be removed from power save"
-                        + " allowlist. It might be due to the package being allowlisted by the"
-                        + " system.");
-            }
-        }
-
-        // If the app has run-in-bg permission and present device, allow metered network use.
-        NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(mContext);
-        try {
-            if (containsEither(packageInfo.requestedPermissions,
-                    android.Manifest.permission.USE_DATA_IN_BACKGROUND,
-                    android.Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND)
-                    && hasPresentDevices) {
-                networkPolicyManager.addUidPolicy(
-                        packageInfo.applicationInfo.uid,
-                        NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
-            } else {
-                networkPolicyManager.removeUidPolicy(
-                        packageInfo.applicationInfo.uid,
-                        NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
-            }
-        } catch (IllegalArgumentException e) {
-            Slog.e(TAG, e.getMessage());
-        }
-
-        updateAutoRevokeExemption(packageInfo.packageName, packageInfo.applicationInfo.uid,
-                !mAssociationStore.getActiveAssociationsByPackage(userId,
-                        packageInfo.packageName).isEmpty());
-    }
-
-    /**
-     * Update auto revoke exemptions.
-     * If the app has any association, exempt it from permission auto revoke.
-     */
-    public void updateAutoRevokeExemptions() {
-        Slog.d(TAG, "maybeGrantAutoRevokeExemptions()");
-
-        PackageManager pm = mContext.getPackageManager();
-        for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) {
-            SharedPreferences pref = mContext.getSharedPreferences(
-                    new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME),
-                    Context.MODE_PRIVATE);
-            if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) {
-                continue;
-            }
-
-            try {
-                final List<AssociationInfo> associations =
-                        mAssociationStore.getActiveAssociationsByUser(userId);
-                for (AssociationInfo a : associations) {
-                    try {
-                        int uid = pm.getPackageUidAsUser(a.getPackageName(), userId);
-                        updateAutoRevokeExemption(a.getPackageName(), uid, true);
-                    } catch (PackageManager.NameNotFoundException e) {
-                        Slog.w(TAG, "Unknown companion package: " + a.getPackageName(), e);
-                    }
-                }
-            } finally {
-                pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply();
-            }
-        }
-    }
-
-    @SuppressLint("MissingPermission")
-    private void updateAutoRevokeExemption(String packageName, int uid, boolean hasAssociations) {
-        try {
-            mAppOpsManager.setMode(
-                    AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
-                    uid,
-                    packageName,
-                    hasAssociations ? MODE_IGNORED : MODE_ALLOWED);
-        } catch (Exception e) {
-            Slog.e(TAG, "Error while granting auto revoke exemption for " + packageName, e);
-        }
-    }
-
-    private <T> boolean containsEither(T[] array, T a, T b) {
-        return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b);
-    }
-
-}
diff --git a/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java b/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java
index 1eb6d13f..60f4688 100644
--- a/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java
@@ -95,10 +95,7 @@
     /**
      * On package changed.
      */
-    public void onPackagesChanged(@UserIdInt int userId, String packageName) {
-        // TODO: We shouldn't need to clean up the whole user registry. We only need to remove the
-        //       package. I will do it in a separate change since it's not appropriate to use
-        //       PerUser anymore.
+    public void onPackagesChanged(@UserIdInt int userId) {
         mCompanionServicesRegister.invalidate(userId);
     }
 
diff --git a/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
index 7b4dd7d..a374d27 100644
--- a/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
@@ -57,7 +57,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.CollectionUtils;
-import com.android.server.companion.CompanionExemptionProcessor;
 import com.android.server.companion.association.AssociationStore;
 
 import java.io.PrintWriter;
@@ -102,8 +101,6 @@
     private final PowerManagerInternal mPowerManagerInternal;
     @NonNull
     private final UserManager mUserManager;
-    @NonNull
-    private final CompanionExemptionProcessor mCompanionExemptionProcessor;
 
     // NOTE: Same association may appear in more than one of the following sets at the same time.
     // (E.g. self-managed devices that have MAC addresses, could be reported as present by their
@@ -114,7 +111,7 @@
     @NonNull
     private final Set<Integer> mNearbyBleDevices = new HashSet<>();
     @NonNull
-    private final Set<Integer> mConnectedSelfManagedDevices = new HashSet<>();
+    private final Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
     @NonNull
     private final Set<ParcelUuid> mConnectedUuidDevices = new HashSet<>();
     @NonNull
@@ -149,8 +146,7 @@
             @NonNull UserManager userManager,
             @NonNull AssociationStore associationStore,
             @NonNull ObservableUuidStore observableUuidStore,
-            @NonNull PowerManagerInternal powerManagerInternal,
-            @NonNull CompanionExemptionProcessor companionExemptionProcessor) {
+            @NonNull PowerManagerInternal powerManagerInternal) {
         mContext = context;
         mCompanionAppBinder = companionAppBinder;
         mAssociationStore = associationStore;
@@ -160,7 +156,6 @@
                 mObservableUuidStore, this);
         mBleDeviceProcessor = new BleDeviceProcessor(associationStore, this);
         mPowerManagerInternal = powerManagerInternal;
-        mCompanionExemptionProcessor = companionExemptionProcessor;
     }
 
     /** Initialize {@link DevicePresenceProcessor} */
@@ -409,7 +404,7 @@
      * nearby (for "self-managed" associations).
      */
     public boolean isDevicePresent(int associationId) {
-        return mConnectedSelfManagedDevices.contains(associationId)
+        return mReportedSelfManagedDevices.contains(associationId)
                 || mConnectedBtDevices.contains(associationId)
                 || mNearbyBleDevices.contains(associationId)
                 || mSimulated.contains(associationId);
@@ -456,7 +451,7 @@
      * notifyDeviceAppeared()}
      */
     public void onSelfManagedDeviceConnected(int associationId) {
-        onDevicePresenceEvent(mConnectedSelfManagedDevices,
+        onDevicePresenceEvent(mReportedSelfManagedDevices,
                 associationId, EVENT_SELF_MANAGED_APPEARED);
     }
 
@@ -472,7 +467,7 @@
      * notifyDeviceDisappeared()}
      */
     public void onSelfManagedDeviceDisconnected(int associationId) {
-        onDevicePresenceEvent(mConnectedSelfManagedDevices,
+        onDevicePresenceEvent(mReportedSelfManagedDevices,
                 associationId, EVENT_SELF_MANAGED_DISAPPEARED);
     }
 
@@ -480,7 +475,7 @@
      * Marks a "self-managed" device as disconnected when binderDied.
      */
     public void onSelfManagedDeviceReporterBinderDied(int associationId) {
-        onDevicePresenceEvent(mConnectedSelfManagedDevices,
+        onDevicePresenceEvent(mReportedSelfManagedDevices,
                 associationId, EVENT_SELF_MANAGED_DISAPPEARED);
     }
 
@@ -688,7 +683,6 @@
 
                 if (association.shouldBindWhenPresent()) {
                     bindApplicationIfNeeded(userId, packageName, association.isSelfManaged());
-                    mCompanionExemptionProcessor.exemptPackage(userId, packageName, true);
                 } else {
                     return;
                 }
@@ -721,7 +715,6 @@
                 // Check if there are other devices associated to the app that are present.
                 if (!shouldBindPackage(userId, packageName)) {
                     mCompanionAppBinder.unbindCompanionApp(userId, packageName);
-                    mCompanionExemptionProcessor.exemptPackage(userId, packageName, false);
                 }
                 break;
             default:
@@ -947,7 +940,7 @@
 
         mConnectedBtDevices.remove(id);
         mNearbyBleDevices.remove(id);
-        mConnectedSelfManagedDevices.remove(id);
+        mReportedSelfManagedDevices.remove(id);
         mSimulated.remove(id);
         synchronized (mBtDisconnectedDevices) {
             mBtDisconnectedDevices.remove(id);
@@ -1107,7 +1100,7 @@
         out.append("Companion Device Present: ");
         if (mConnectedBtDevices.isEmpty()
                 && mNearbyBleDevices.isEmpty()
-                && mConnectedSelfManagedDevices.isEmpty()) {
+                && mReportedSelfManagedDevices.isEmpty()) {
             out.append("<empty>\n");
             return;
         } else {
@@ -1137,11 +1130,11 @@
         }
 
         out.append("  Self-Reported Devices: ");
-        if (mConnectedSelfManagedDevices.isEmpty()) {
+        if (mReportedSelfManagedDevices.isEmpty()) {
             out.append("<empty>\n");
         } else {
             out.append("\n");
-            for (int associationId : mConnectedSelfManagedDevices) {
+            for (int associationId : mReportedSelfManagedDevices) {
                 AssociationInfo a = mAssociationStore.getAssociationById(associationId);
                 out.append("    ").append(a.toShortString()).append('\n');
             }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2a9cb43..1c3569d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -60,7 +60,6 @@
 import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_INSTRUMENTATION;
 import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_PERSISTENT;
 import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_SYSTEM;
-import static android.content.Intent.isPreventIntentRedirectEnabled;
 import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT;
 import static android.content.pm.PackageManager.GET_SHARED_LIBRARY_FILES;
 import static android.content.pm.PackageManager.MATCH_ALL;
@@ -131,6 +130,7 @@
 import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
 import static android.provider.Settings.Global.DEBUG_APP;
 import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
+import static android.security.Flags.preventIntentRedirect;
 import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
 import static android.view.Display.INVALID_DISPLAY;
 
@@ -14301,6 +14301,10 @@
         mBroadcastController.unregisterReceiver(receiver);
     }
 
+    public List<IntentFilter> getRegisteredIntentFilters(IIntentReceiver receiver) {
+        return mBroadcastController.getRegisteredIntentFilters(receiver);
+    }
+
     @GuardedBy("this")
     final int broadcastIntentLocked(ProcessRecord callerApp,
             String callerPackage, String callerFeatureId, Intent intent, String resolvedType,
@@ -19281,7 +19285,7 @@
      * @hide
      */
     public void addCreatorToken(@Nullable Intent intent, String creatorPackage) {
-        if (!isPreventIntentRedirectEnabled()) return;
+        if (!preventIntentRedirect()) return;
 
         if (intent == null || intent.getExtraIntentKeys() == null) return;
         for (String key : intent.getExtraIntentKeys()) {
diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java
index 71ddf05..b0f88071 100644
--- a/services/core/java/com/android/server/am/BroadcastController.java
+++ b/services/core/java/com/android/server/am/BroadcastController.java
@@ -679,6 +679,21 @@
         }
     }
 
+    List<IntentFilter> getRegisteredIntentFilters(IIntentReceiver receiver) {
+        synchronized (mService) {
+            final ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
+            if (rl == null) {
+                return null;
+            }
+            final ArrayList<IntentFilter> filters = new ArrayList<>();
+            final int count = rl.size();
+            for (int i = 0; i < count; ++i) {
+                filters.add(rl.get(i));
+            }
+            return filters;
+        }
+    }
+
     int broadcastIntentWithFeature(IApplicationThread caller, String callingFeatureId,
             Intent intent, String resolvedType, IIntentReceiver resultTo,
             int resultCode, String resultData, Bundle resultExtras,
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 7cfe829..bcde1ff 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -209,6 +209,7 @@
         "pixel_perf",
         "pixel_sensors",
         "pixel_system_sw_video",
+        "pixel_video_sw",
         "pixel_watch",
         "platform_compat",
         "platform_security",
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 906e584..a3b20b9 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -693,6 +693,8 @@
                 elapsed = System.currentTimeMillis() - start;
                 if (elapsed >= SET_COMMUNICATION_DEVICE_TIMEOUT_MS) {
                     Log.e(TAG, "Timeout waiting for communication device update.");
+                    // reset counter to avoid sticky out of sync condition
+                    mCommunicationDeviceUpdateCount = 0;
                     break;
                 }
             }
@@ -1342,8 +1344,8 @@
     }
 
     /*package*/ void postSetModeOwner(int mode, int pid, int uid, boolean signal) {
-        sendILMsgNoDelay(MSG_IL_SET_MODE_OWNER, SENDMSG_REPLACE,
-                signal ? 1 : 0, new AudioModeInfo(mode, pid, uid));
+        sendLMsgNoDelay(signal ? MSG_L_SET_MODE_OWNER_SIGNAL : MSG_L_SET_MODE_OWNER,
+                SENDMSG_REPLACE, new AudioModeInfo(mode, pid, uid));
     }
 
     /*package*/ void postBluetoothDeviceConfigChange(@NonNull BtDeviceInfo info) {
@@ -2026,7 +2028,8 @@
                         mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
                     }
                     break;
-                case MSG_IL_SET_MODE_OWNER:
+                case MSG_L_SET_MODE_OWNER:
+                case MSG_L_SET_MODE_OWNER_SIGNAL:
                     synchronized (mSetModeLock) {
                         synchronized (mDeviceStateLock) {
                             mAudioModeOwner = (AudioModeInfo) msg.obj;
@@ -2037,7 +2040,7 @@
                             }
                         }
                     }
-                    if (msg.arg1 == 1 /*signal*/) {
+                    if (msg.what == MSG_L_SET_MODE_OWNER_SIGNAL) {
                         mAudioService.decrementAudioModeResetCount();
                     }
                     break;
@@ -2199,7 +2202,8 @@
     private static final int MSG_REPORT_NEW_ROUTES = 13;
     private static final int MSG_II_SET_HEARING_AID_VOLUME = 14;
     private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15;
-    private static final int MSG_IL_SET_MODE_OWNER = 16;
+    private static final int MSG_L_SET_MODE_OWNER = 16;
+    private static final int MSG_L_SET_MODE_OWNER_SIGNAL = 17;
 
     private static final int MSG_I_BT_SERVICE_DISCONNECTED_PROFILE = 22;
     private static final int MSG_IL_BT_SERVICE_CONNECTED_PROFILE = 23;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 3dbe48a..985155d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -457,7 +457,7 @@
     private static final int MSG_UPDATE_AUDIO_MODE = 36;
     private static final int MSG_RECORDING_CONFIG_CHANGE = 37;
     private static final int MSG_BT_DEV_CHANGED = 38;
-
+    private static final int MSG_UPDATE_AUDIO_MODE_SIGNAL = 39;
     private static final int MSG_DISPATCH_AUDIO_MODE = 40;
     private static final int MSG_ROUTING_UPDATED = 41;
     private static final int MSG_INIT_HEADTRACKING_SENSORS = 42;
@@ -4804,16 +4804,14 @@
     }
 
     static class UpdateAudioModeInfo {
-        UpdateAudioModeInfo(int mode, int pid, String packageName, boolean signal) {
+        UpdateAudioModeInfo(int mode, int pid, String packageName) {
             mMode = mode;
             mPid = pid;
             mPackageName = packageName;
-            mSignal = signal;
         }
         private final int mMode;
         private final int mPid;
         private final String mPackageName;
-        private final boolean mSignal;
 
         int getMode() {
             return mMode;
@@ -4824,9 +4822,6 @@
         String getPackageName() {
             return mPackageName;
         }
-        boolean getSignal() {
-            return mSignal;
-        }
     }
 
     void postUpdateAudioMode(int msgPolicy, int mode, int pid, String packageName,
@@ -4835,8 +4830,8 @@
             if (signal) {
                 mAudioModeResetCount++;
             }
-            sendMsg(mAudioHandler, MSG_UPDATE_AUDIO_MODE, msgPolicy, 0, 0,
-                new UpdateAudioModeInfo(mode, pid, packageName, signal), delay);
+            sendMsg(mAudioHandler, signal ? MSG_UPDATE_AUDIO_MODE_SIGNAL : MSG_UPDATE_AUDIO_MODE,
+                    msgPolicy, 0, 0, new UpdateAudioModeInfo(mode, pid, packageName), delay);
         }
     }
 
@@ -6654,6 +6649,9 @@
                 // connections not started by the application changing the mode when pid changes
                 mDeviceBroker.postSetModeOwner(mode, pid, uid, signal);
             } else {
+                // reset here to avoid sticky out of sync condition (would have been reset
+                // by AudioDeviceBroker processing MSG_L_SET_MODE_OWNER_SIGNAL message)
+                resetAudioModeResetCount();
                 Log.w(TAG, "onUpdateAudioMode: failed to set audio mode to: " + mode);
             }
         }
@@ -10420,10 +10418,11 @@
                     break;
 
                 case MSG_UPDATE_AUDIO_MODE:
+                case MSG_UPDATE_AUDIO_MODE_SIGNAL:
                     synchronized (mDeviceBroker.mSetModeLock) {
                         UpdateAudioModeInfo info = (UpdateAudioModeInfo) msg.obj;
                         onUpdateAudioMode(info.getMode(), info.getPid(), info.getPackageName(),
-                                false /*force*/, info.getSignal());
+                                false /*force*/, msg.what == MSG_UPDATE_AUDIO_MODE_SIGNAL);
                     }
                     break;
 
@@ -11164,6 +11163,8 @@
                     elapsed = java.lang.System.currentTimeMillis() - start;
                     if (elapsed >= AUDIO_MODE_RESET_TIMEOUT_MS) {
                         Log.e(TAG, "Timeout waiting for audio mode reset");
+                        // reset count to avoid sticky out of sync state.
+                        resetAudioModeResetCount();
                         break;
                     }
                 }
@@ -11175,7 +11176,7 @@
         return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa, callingPackageName);
     }
 
-    /** synchronization between setMode(NORMAL) and abandonAudioFocus() frmo Telecom */
+    /** synchronization between setMode(NORMAL) and abandonAudioFocus() from Telecom */
     private static final long AUDIO_MODE_RESET_TIMEOUT_MS = 3000;
 
     private final Object mAudioModeResetLock = new Object();
@@ -11194,6 +11195,13 @@
         }
     }
 
+    private void resetAudioModeResetCount() {
+        synchronized (mAudioModeResetLock) {
+            mAudioModeResetCount = 0;
+            mAudioModeResetLock.notify();
+        }
+    }
+
     /** see {@link AudioManager#abandonAudioFocusForTest(AudioFocusRequest, String)} */
     public int abandonAudioFocusForTest(IAudioFocusDispatcher fd, String clientId,
             AudioAttributes aa, String callingPackageName) {
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 1604e94..1da62d7 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -1068,7 +1068,6 @@
         switch (attr.getUsage()) {
             case AudioAttributes.USAGE_MEDIA:
             case AudioAttributes.USAGE_GAME:
-            case AudioAttributes.USAGE_SPEAKER_CLEANUP:
                 return 1000;
             case AudioAttributes.USAGE_ALARM:
             case AudioAttributes.USAGE_NOTIFICATION_RINGTONE:
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index 68eaf72..e665f96 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -25,6 +25,7 @@
 import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiPressGestures;
 import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
 import static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut;
+import static com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts;
 
 import android.annotation.BinderThread;
 import android.annotation.MainThread;
@@ -734,6 +735,54 @@
                     }
                 }
                 break;
+            case KeyEvent.KEYCODE_LEFT_BRACKET:
+                if (enableTaskResizingKeyboardShortcuts()) {
+                    if (firstDown && event.isAltPressed()) {
+                        return handleKeyGesture(deviceId, new int[]{keyCode},
+                                KeyEvent.META_ALT_ON,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+                                displayId,
+                                focusedToken, /* flags = */0);
+                    }
+                }
+                break;
+            case KeyEvent.KEYCODE_RIGHT_BRACKET:
+                if (enableTaskResizingKeyboardShortcuts()) {
+                    if (firstDown && event.isAltPressed()) {
+                        return handleKeyGesture(deviceId, new int[]{keyCode},
+                                KeyEvent.META_ALT_ON,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+                                displayId,
+                                focusedToken, /* flags = */0);
+                    }
+                }
+                break;
+            case KeyEvent.KEYCODE_EQUALS:
+                if (enableTaskResizingKeyboardShortcuts()) {
+                    if (firstDown && event.isAltPressed()) {
+                        return handleKeyGesture(deviceId, new int[]{keyCode},
+                                KeyEvent.META_ALT_ON,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+                                displayId,
+                                focusedToken, /* flags = */0);
+                    }
+                }
+                break;
+            case KeyEvent.KEYCODE_MINUS:
+                if (enableTaskResizingKeyboardShortcuts()) {
+                    if (firstDown && event.isAltPressed()) {
+                        return handleKeyGesture(deviceId, new int[]{keyCode},
+                                KeyEvent.META_ALT_ON,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+                                displayId,
+                                focusedToken, /* flags = */0);
+                    }
+                }
+                break;
             case KeyEvent.KEYCODE_SLASH:
                 if (firstDown && event.isMetaPressed()) {
                     return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 8798a64..45885f0 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -253,10 +253,10 @@
 
     private static final String MIGRATED_FRP2 = "migrated_frp2";
     private static final String MIGRATED_KEYSTORE_NS = "migrated_keystore_namespace";
-    private static final String MIGRATED_SP_CE_ONLY = "migrated_all_users_to_sp_and_bound_ce";
     private static final String MIGRATED_SP_FULL = "migrated_all_users_to_sp_and_bound_keys";
     private static final String MIGRATED_WEAVER_DISABLED_ON_UNSECURED_USERS =
             "migrated_weaver_disabled_on_unsecured_users";
+    // Note: some other migrated_* strings used to be used and may exist in the database already.
 
     // Duration that LockSettingsService will store the gatekeeper password for. This allows
     // multiple biometric enrollments without prompting the user to enter their password via
@@ -1219,21 +1219,16 @@
                 Slog.i(TAG, "Synthetic password is already not protected by Weaver");
             }
         } else if (sp == null) {
-            Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
-            return;
+            throw new IllegalStateException(
+                    "Failed to unwrap synthetic password for unsecured user " + userId);
         }
 
         // Call setCeStorageProtection(), to re-encrypt the CE key with the SP if it's currently
-        // encrypted by an empty secret.  Skip this if it was definitely already done as part of the
-        // upgrade to Android 14, since while setCeStorageProtection() is idempotent it does log
-        // some error messages when called again.  Do not skip this if
-        // config_disableWeaverOnUnsecuredUsers=true, since in that case we'd like to recover from
-        // the case where an earlier upgrade to Android 14 incorrectly skipped this step.
-        if (getString(MIGRATED_SP_CE_ONLY, null, 0) == null
-                || isWeaverDisabledOnUnsecuredUsers()) {
-            Slogf.i(TAG, "Encrypting CE key of user %d with synthetic password", userId);
-            setCeStorageProtection(userId, sp);
-        }
+        // encrypted by an empty secret.  If the CE key is already encrypted by the SP, then this is
+        // a no-op except for some log messages.
+        Slogf.i(TAG, "Encrypting CE key of user %d with synthetic password", userId);
+        setCeStorageProtection(userId, sp);
+
         Slogf.i(TAG, "Initializing Keystore super keys for user %d", userId);
         initKeystoreSuperKeys(userId, sp, /* allowExisting= */ true);
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 62df825..849f236 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -105,6 +105,7 @@
 import static android.service.notification.Adjustment.KEY_TYPE;
 import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
 import static android.service.notification.Adjustment.TYPE_PROMOTION;
+import static android.service.notification.Flags.FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT;
 import static android.service.notification.Flags.callstyleCallbackApi;
 import static android.service.notification.Flags.notificationClassification;
 import static android.service.notification.Flags.notificationForceGrouping;
@@ -6681,6 +6682,33 @@
         }
 
         @Override
+        @FlaggedApi(FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT)
+        public NotificationChannel createConversationNotificationChannelForPackageFromPrivilegedListener(
+                INotificationListener token, String pkg, UserHandle user,
+                String parentId, String conversationId) throws RemoteException {
+            Objects.requireNonNull(pkg);
+            Objects.requireNonNull(user);
+            Objects.requireNonNull(parentId);
+            Objects.requireNonNull(conversationId);
+
+            verifyPrivilegedListener(token, user, true);
+
+            int uid = getUidForPackageAndUser(pkg, user);
+            NotificationChannel conversationChannel =
+                    mPreferencesHelper.getNotificationChannel(pkg, uid, parentId, false).copy();
+            String conversationChannelId = String.format(
+                    CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId);
+            conversationChannel.setId(conversationChannelId);
+            conversationChannel.setConversationId(parentId, conversationId);
+            createNotificationChannelsImpl(
+                    pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)));
+            handleSavePolicyFile();
+
+            return mPreferencesHelper.getConversationNotificationChannel(
+                    pkg, uid, parentId, conversationId, false, false).copy();
+        }
+
+        @Override
         public void updateNotificationChannelGroupFromPrivilegedListener(
                 INotificationListener token, String pkg, UserHandle user,
                 NotificationChannelGroup group) throws RemoteException {
@@ -6698,7 +6726,7 @@
             Objects.requireNonNull(pkg);
             Objects.requireNonNull(user);
 
-            verifyPrivilegedListener(token, user, false);
+            verifyPrivilegedListener(token, user, true);
 
             final NotificationChannel originalChannel = mPreferencesHelper.getNotificationChannel(
                     pkg, getUidForPackageAndUser(pkg, user), channel.getId(), true);
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 84a5f2b..9f4b9f1 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -64,6 +64,9 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedService;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.pm.pkg.AndroidPackage;
@@ -80,6 +83,8 @@
  */
 public final class BroadcastHelper {
     private static final boolean DEBUG_BROADCASTS = false;
+    private static final String PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED =
+            "android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED";
 
     private final UserManagerInternal mUmInternal;
     private final ActivityManagerInternal mAmInternal;
@@ -291,6 +296,57 @@
         return bOptions;
     }
 
+    private ArrayList<String> getAllNotExportedComponents(@NonNull AndroidPackage pkg,
+            @NonNull ArrayList<String> inputComponentNames) {
+        final ArrayList<String> outputNotExportedComponentNames = new ArrayList<>();
+        int remainingComponentCount = inputComponentNames.size();
+        for (ParsedActivity component : pkg.getReceivers()) {
+            if (inputComponentNames.contains(component.getClassName())) {
+                if (!component.isExported()) {
+                    outputNotExportedComponentNames.add(component.getClassName());
+                }
+                remainingComponentCount--;
+                if (remainingComponentCount <= 0) {
+                    return outputNotExportedComponentNames;
+                }
+            }
+        }
+        for (ParsedProvider component : pkg.getProviders()) {
+            if (inputComponentNames.contains(component.getClassName())) {
+                if (!component.isExported()) {
+                    outputNotExportedComponentNames.add(component.getClassName());
+                }
+                remainingComponentCount--;
+                if (remainingComponentCount <= 0) {
+                    return outputNotExportedComponentNames;
+                }
+            }
+        }
+        for (ParsedService component : pkg.getServices()) {
+            if (inputComponentNames.contains(component.getClassName())) {
+                if (!component.isExported()) {
+                    outputNotExportedComponentNames.add(component.getClassName());
+                }
+                remainingComponentCount--;
+                if (remainingComponentCount <= 0) {
+                    return outputNotExportedComponentNames;
+                }
+            }
+        }
+        for (ParsedActivity component : pkg.getActivities()) {
+            if (inputComponentNames.contains(component.getClassName())) {
+                if (!component.isExported()) {
+                    outputNotExportedComponentNames.add(component.getClassName());
+                }
+                remainingComponentCount--;
+                if (remainingComponentCount <= 0) {
+                    return outputNotExportedComponentNames;
+                }
+            }
+        }
+        return outputNotExportedComponentNames;
+    }
+
     private void sendPackageChangedBroadcastInternal(@NonNull String packageName,
             boolean dontKillApp,
             @NonNull ArrayList<String> componentNames,
@@ -298,10 +354,48 @@
             @Nullable String reason,
             @Nullable int[] userIds,
             @Nullable int[] instantUserIds,
-            @Nullable SparseArray<int[]> broadcastAllowList) {
-        sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp, componentNames,
-                packageUid, reason, userIds, instantUserIds, broadcastAllowList,
-                null /* targetPackageName */, null /* requiredPermissions */);
+            @Nullable SparseArray<int[]> broadcastAllowList,
+            @NonNull AndroidPackage pkg) {
+        final boolean isForWholeApp = componentNames.contains(packageName);
+        if (isForWholeApp || !android.content.pm.Flags.reduceBroadcastsForComponentStateChanges()) {
+            sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp, componentNames,
+                    packageUid, reason, userIds, instantUserIds, broadcastAllowList,
+                    null /* targetPackageName */, null /* requiredPermissions */);
+            return;
+        }
+        // Currently only these four components of activity, receiver, provider and service are
+        // considered to send only the broadcast to the system and the application itself when the
+        // component is not exported. In order to avoid losing to send the broadcast for other
+        // components, it gets the not exported components for these four components of activity,
+        // receiver, provider and service and the others are considered the exported components.
+        final ArrayList<String> notExportedComponentNames = getAllNotExportedComponents(pkg,
+                componentNames);
+        final ArrayList<String> exportedComponentNames = (ArrayList<String>) componentNames.clone();
+        exportedComponentNames.removeAll(notExportedComponentNames);
+
+        if (!notExportedComponentNames.isEmpty()) {
+            // Limit sending of the PACKAGE_CHANGED broadcast to only the system and the
+            // application itself when the component is not exported.
+
+            // First, send the PACKAGE_CHANGED broadcast to the system.
+            sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
+                    notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
+                    broadcastAllowList, "android" /* targetPackageName */,
+                    new String[]{PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED});
+
+            // Second, send the PACKAGE_CHANGED broadcast to the application itself.
+            sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
+                    notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
+                    broadcastAllowList, packageName /* targetPackageName */,
+                    null /* requiredPermissions */);
+        }
+
+        if (!exportedComponentNames.isEmpty()) {
+            sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
+                    exportedComponentNames, packageUid, reason, userIds, instantUserIds,
+                    broadcastAllowList, null /* targetPackageName */,
+                    null /* requiredPermissions */);
+        }
     }
 
     private void sendPackageChangedBroadcastWithPermissions(@NonNull String packageName,
@@ -830,7 +924,7 @@
                                      @NonNull String reason) {
         PackageStateInternal setting = snapshot.getPackageStateInternal(packageName,
                 Process.SYSTEM_UID);
-        if (setting == null) {
+        if (setting == null || setting.getPkg() == null) {
             return;
         }
         final int userId = UserHandle.getUserId(packageUid);
@@ -842,7 +936,7 @@
                 isInstantApp ? null : snapshot.getVisibilityAllowLists(packageName, userIds);
         mHandler.post(() -> sendPackageChangedBroadcastInternal(
                 packageName, dontKillApp, componentNames, packageUid, reason, userIds,
-                instantUserIds, broadcastAllowList));
+                instantUserIds, broadcastAllowList, setting.getPkg()));
         mPackageMonitorCallbackHelper.notifyPackageChanged(packageName, dontKillApp, componentNames,
                 packageUid, reason, userIds, instantUserIds, broadcastAllowList, mHandler);
     }
diff --git a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
index c33ed55..1260eee 100644
--- a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
+++ b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
@@ -33,6 +33,7 @@
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.provider.Settings;
+import android.security.advancedprotection.AdvancedProtectionFeature;
 import android.security.advancedprotection.IAdvancedProtectionCallback;
 import android.security.advancedprotection.IAdvancedProtectionService;
 import android.util.ArrayMap;
@@ -44,9 +45,11 @@
 import com.android.server.SystemService;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.security.advancedprotection.features.AdvancedProtectionHook;
+import com.android.server.security.advancedprotection.features.AdvancedProtectionProvider;
 
 import java.io.FileDescriptor;
 import java.util.ArrayList;
+import java.util.List;
 
 /** @hide */
 public class AdvancedProtectionService extends IAdvancedProtectionService.Stub  {
@@ -58,10 +61,12 @@
     private final Handler mHandler;
     private final AdvancedProtectionStore mStore;
 
-    // Features owned by the service - their code will be executed when state changes
+    // Features living with the service - their code will be executed when state changes
     private final ArrayList<AdvancedProtectionHook> mHooks = new ArrayList<>();
     // External features - they will be called on state change
     private final ArrayMap<IBinder, IAdvancedProtectionCallback> mCallbacks = new ArrayMap<>();
+    // For tracking only - not called on state change
+    private final ArrayList<AdvancedProtectionProvider> mProviders = new ArrayList<>();
 
     private AdvancedProtectionService(@NonNull Context context) {
         super(PermissionEnforcer.fromContext(context));
@@ -71,13 +76,17 @@
     }
 
     private void initFeatures(boolean enabled) {
+        // Empty until features are added.
+        // Examples:
+        // mHooks.add(new SideloadingAdvancedProtectionHook(mContext, enabled));
+        // mProviders.add(new WifiAdvancedProtectionProvider());
     }
 
     // Only for tests
     @VisibleForTesting
     AdvancedProtectionService(@NonNull Context context, @NonNull AdvancedProtectionStore store,
             @NonNull Looper looper, @NonNull PermissionEnforcer permissionEnforcer,
-            @Nullable AdvancedProtectionHook hook) {
+            @Nullable AdvancedProtectionHook hook, @Nullable AdvancedProtectionProvider provider) {
         super(permissionEnforcer);
         mContext = context;
         mStore = store;
@@ -85,6 +94,10 @@
         if (hook != null) {
             mHooks.add(hook);
         }
+
+        if (provider != null) {
+            mProviders.add(provider);
+        }
     }
 
     @Override
@@ -146,6 +159,25 @@
     }
 
     @Override
+    @EnforcePermission(Manifest.permission.SET_ADVANCED_PROTECTION_MODE)
+    public List<AdvancedProtectionFeature> getAdvancedProtectionFeatures() {
+        getAdvancedProtectionFeatures_enforcePermission();
+        List<AdvancedProtectionFeature> features = new ArrayList<>();
+        for (int i = 0; i < mProviders.size(); i++) {
+            features.addAll(mProviders.get(i).getFeatures());
+        }
+
+        for (int i = 0; i < mHooks.size(); i++) {
+            AdvancedProtectionHook hook = mHooks.get(i);
+            if (hook.isAvailable()) {
+                features.add(hook.getFeature());
+            }
+        }
+
+        return features;
+    }
+
+    @Override
     public void onShellCommand(FileDescriptor in, FileDescriptor out,
             FileDescriptor err, @NonNull String[] args, ShellCallback callback,
             @NonNull ResultReceiver resultReceiver) {
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionHook.java b/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionHook.java
index b2acc51..f82db96 100644
--- a/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionHook.java
+++ b/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionHook.java
@@ -18,11 +18,15 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.security.advancedprotection.AdvancedProtectionFeature;
 
 /** @hide */
 public abstract class AdvancedProtectionHook {
     /** Called on boot phase PHASE_SYSTEM_SERVICES_READY */
     public AdvancedProtectionHook(@NonNull Context context, boolean enabled) {}
+    /** The feature this hook provides */
+    @NonNull
+    public abstract AdvancedProtectionFeature getFeature();
     /** Whether this feature is relevant on this device. If false, onAdvancedProtectionChanged will
      * not be called, and the feature will not be displayed in the onboarding UX. */
     public abstract boolean isAvailable();
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java b/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java
new file mode 100644
index 0000000..ed451f1
--- /dev/null
+++ b/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.advancedprotection.features;
+
+import android.security.advancedprotection.AdvancedProtectionFeature;
+
+import java.util.List;
+
+/** @hide */
+public abstract class AdvancedProtectionProvider {
+    /** The list of features provided */
+    public abstract List<AdvancedProtectionFeature> getFeatures();
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
index f069dcd..d0d3d43 100644
--- a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
@@ -149,7 +149,7 @@
             @NonNull Configuration newParentConfig) {
         mSizeCompatScale = mActivityRecord.mAppCompatController.getTransparentPolicy()
                 .findOpaqueNotFinishingActivityBelow()
-                .map(activityRecord -> mSizeCompatScale)
+                .map(ar -> Math.min(1.0f, ar.getCompatScale()))
                 .orElseGet(() -> calculateSizeCompatScale(
                         resolvedAppBounds, containerAppBounds, newParentConfig));
     }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b14dd3f..8aa0530 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3464,7 +3464,8 @@
             return null;
         }
         final Rect windowFrame = mainWindow.getFrame();
-        if (top.getBounds().equals(windowFrame)) {
+        final Rect parentFrame = mainWindow.getParentFrame();
+        if (parentFrame.equals(windowFrame)) {
             return null;
         }
         return windowFrame;
diff --git a/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java b/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java
index e97b48c..24bf6ca 100644
--- a/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.security.advancedprotection;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
@@ -28,15 +30,20 @@
 import android.os.test.FakePermissionEnforcer;
 import android.os.test.TestLooper;
 import android.provider.Settings;
+import android.security.advancedprotection.AdvancedProtectionFeature;
 import android.security.advancedprotection.IAdvancedProtectionCallback;
 
+import androidx.annotation.NonNull;
+
 import com.android.server.security.advancedprotection.features.AdvancedProtectionHook;
+import com.android.server.security.advancedprotection.features.AdvancedProtectionProvider;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 @SuppressLint("VisibleForTests")
@@ -47,6 +54,7 @@
     private Context mContext;
     private AdvancedProtectionService.AdvancedProtectionStore mStore;
     private TestLooper mLooper;
+    AdvancedProtectionFeature mFeature = new AdvancedProtectionFeature("test-id");
 
     @Before
     public void setup() throws Settings.SettingNotFoundException {
@@ -70,8 +78,9 @@
         };
 
         mLooper = new TestLooper();
+
         mService = new AdvancedProtectionService(mContext, mStore, mLooper.getLooper(),
-                mPermissionEnforcer, null);
+                mPermissionEnforcer, null, null);
     }
 
     @Test
@@ -93,6 +102,12 @@
         AtomicBoolean callbackCaptor = new AtomicBoolean(false);
         AdvancedProtectionHook hook =
                 new AdvancedProtectionHook(mContext, true) {
+                    @NonNull
+                    @Override
+                    public AdvancedProtectionFeature getFeature() {
+                        return mFeature;
+                    }
+
                     @Override
                     public boolean isAvailable() {
                         return true;
@@ -105,7 +120,7 @@
                 };
 
         mService = new AdvancedProtectionService(mContext, mStore, mLooper.getLooper(),
-                mPermissionEnforcer, hook);
+                mPermissionEnforcer, hook, null);
         mService.setAdvancedProtectionEnabled(true);
         mLooper.dispatchNext();
 
@@ -117,6 +132,12 @@
         AtomicBoolean callbackCalledCaptor = new AtomicBoolean(false);
         AdvancedProtectionHook hook =
                 new AdvancedProtectionHook(mContext, true) {
+                    @NonNull
+                    @Override
+                    public AdvancedProtectionFeature getFeature() {
+                        return mFeature;
+                    }
+
                     @Override
                     public boolean isAvailable() {
                         return false;
@@ -129,7 +150,8 @@
                 };
 
         mService = new AdvancedProtectionService(mContext, mStore, mLooper.getLooper(),
-                mPermissionEnforcer, hook);
+                mPermissionEnforcer, hook, null);
+
         mService.setAdvancedProtectionEnabled(true);
         mLooper.dispatchNext();
         assertFalse(callbackCalledCaptor.get());
@@ -140,6 +162,12 @@
         AtomicBoolean callbackCalledCaptor = new AtomicBoolean(false);
         AdvancedProtectionHook hook =
                 new AdvancedProtectionHook(mContext, true) {
+                    @NonNull
+                    @Override
+                    public AdvancedProtectionFeature getFeature() {
+                        return mFeature;
+                    }
+
                     @Override
                     public boolean isAvailable() {
                         return true;
@@ -152,7 +180,7 @@
                 };
 
         mService = new AdvancedProtectionService(mContext, mStore, mLooper.getLooper(),
-                mPermissionEnforcer, hook);
+                mPermissionEnforcer, hook, null);
         mService.setAdvancedProtectionEnabled(true);
         mLooper.dispatchNext();
         assertTrue(callbackCalledCaptor.get());
@@ -208,6 +236,66 @@
         assertFalse(callbackCalledCaptor.get());
     }
 
+    @Test
+    public void testGetFeatures() {
+        AdvancedProtectionFeature feature1 = new AdvancedProtectionFeature("id-1");
+        AdvancedProtectionFeature feature2 = new AdvancedProtectionFeature("id-2");
+        AdvancedProtectionHook hook = new AdvancedProtectionHook(mContext, true) {
+            @NonNull
+            @Override
+            public AdvancedProtectionFeature getFeature() {
+                return feature1;
+            }
+
+            @Override
+            public boolean isAvailable() {
+                return true;
+            }
+        };
+
+        AdvancedProtectionProvider provider = new AdvancedProtectionProvider() {
+            @Override
+            public List<AdvancedProtectionFeature> getFeatures() {
+                return List.of(feature2);
+            }
+        };
+
+        mService = new AdvancedProtectionService(mContext, mStore, mLooper.getLooper(),
+                mPermissionEnforcer, hook, provider);
+        List<AdvancedProtectionFeature> features = mService.getAdvancedProtectionFeatures();
+        assertThat(features, containsInAnyOrder(feature1, feature2));
+    }
+
+    @Test
+    public void testGetFeatures_featureNotAvailable() {
+        AdvancedProtectionFeature feature1 = new AdvancedProtectionFeature("id-1");
+        AdvancedProtectionFeature feature2 = new AdvancedProtectionFeature("id-2");
+        AdvancedProtectionHook hook = new AdvancedProtectionHook(mContext, true) {
+            @NonNull
+            @Override
+            public AdvancedProtectionFeature getFeature() {
+                return feature1;
+            }
+
+            @Override
+            public boolean isAvailable() {
+                return false;
+            }
+        };
+
+        AdvancedProtectionProvider provider = new AdvancedProtectionProvider() {
+            @Override
+            public List<AdvancedProtectionFeature> getFeatures() {
+                return List.of(feature2);
+            }
+        };
+
+        mService = new AdvancedProtectionService(mContext, mStore, mLooper.getLooper(),
+                mPermissionEnforcer, hook, provider);
+        List<AdvancedProtectionFeature> features = mService.getAdvancedProtectionFeatures();
+        assertThat(features, containsInAnyOrder(feature2));
+    }
+
 
     @Test
     public void testSetProtection_withoutPermission() {
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 b99ab05..ac021e1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -112,6 +112,7 @@
 import static android.service.notification.Condition.SOURCE_USER_ACTION;
 import static android.service.notification.Condition.STATE_TRUE;
 import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION;
+import static android.service.notification.Flags.FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT;
 import static android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING;
 import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
@@ -265,6 +266,7 @@
 import android.permission.PermissionManager;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.FlagsParameterization;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.platform.test.rule.LimitDevicesRule;
@@ -485,6 +487,15 @@
 
     NotificationChannel mMinChannel = new NotificationChannel("min", "min", IMPORTANCE_MIN);
 
+    private final NotificationChannel mParentChannel =
+            new NotificationChannel(PARENT_CHANNEL_ID, "parentName", IMPORTANCE_DEFAULT);
+    private final NotificationChannel mConversationChannel =
+            new NotificationChannel(CONVERSATION_CHANNEL_ID, "conversationName", IMPORTANCE_DEFAULT);
+
+    private static final String PARENT_CHANNEL_ID = "parentChannelId";
+    private static final String CONVERSATION_CHANNEL_ID = "conversationChannelId";
+    private static final String CONVERSATION_ID = "conversationId";
+
     private static final int NOTIFICATION_LOCATION_UNKNOWN = 0;
 
     private static final String VALID_CONVO_SHORTCUT_ID = "shortcut";
@@ -4672,8 +4683,161 @@
         verify(mAmi).hasForegroundServiceNotification(anyString(), anyInt(), anyString());
     }
 
+    private void setUpChannelsForConversationChannelTest() throws RemoteException {
+        when(mPreferencesHelper.getNotificationChannel(
+                eq(mPkg), eq(mUid), eq(PARENT_CHANNEL_ID), eq(false)))
+                .thenReturn(mParentChannel);
+        when(mPreferencesHelper.getConversationNotificationChannel(
+                eq(mPkg), eq(mUid), eq(PARENT_CHANNEL_ID), eq(CONVERSATION_ID), eq(false), eq(false)))
+                .thenReturn(mConversationChannel);
+        when(mPackageManager.getPackageUid(mPkg, 0, mUserId)).thenReturn(mUid);
+    }
+
     @Test
-    public void testUpdateNotificationChannelFromPrivilegedListener_success() throws Exception {
+    @RequiresFlagsEnabled(FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT)
+    public void createConversationChannelForPkgFromPrivilegedListener_cdm_success() throws Exception {
+        // Set up cdm
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mCompanionMgr.getAssociations(mPkg, mUserId))
+                .thenReturn(singletonList(mock(AssociationInfo.class)));
+
+        // Set up parent channel
+        setUpChannelsForConversationChannelTest();
+        final NotificationChannel parentChannelCopy = mParentChannel.copy();
+
+        NotificationChannel createdChannel =
+                mBinderService.createConversationNotificationChannelForPackageFromPrivilegedListener(
+                    null, mPkg, mUser, PARENT_CHANNEL_ID, CONVERSATION_ID);
+
+        // Verify that a channel is created and a copied channel is returned.
+        verify(mPreferencesHelper, times(1)).createNotificationChannel(
+                eq(mPkg), eq(mUid), any(), anyBoolean(), anyBoolean(),
+                eq(mUid), anyBoolean());
+        assertThat(createdChannel).isNotSameInstanceAs(mConversationChannel);
+        assertThat(createdChannel).isEqualTo(mConversationChannel);
+
+        // Verify that the channel creation is not directly use the parent channel.
+        verify(mPreferencesHelper, never()).createNotificationChannel(
+                anyString(), anyInt(), eq(mParentChannel), anyBoolean(), anyBoolean(),
+                anyInt(), anyBoolean());
+
+        // Verify that the content of parent channel is not changed.
+        assertThat(parentChannelCopy).isEqualTo(mParentChannel);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT)
+    public void createConversationChannelForPkgFromPrivilegedListener_cdm_noAccess() throws Exception {
+        // Set up cdm without access
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mCompanionMgr.getAssociations(mPkg, mUserId))
+                .thenReturn(emptyList());
+
+        // Set up parent channel
+        setUpChannelsForConversationChannelTest();
+
+        try {
+            mBinderService.createConversationNotificationChannelForPackageFromPrivilegedListener(
+                null, mPkg, mUser, "parentId", "conversationId");
+            fail("listeners that don't have a companion device shouldn't be able to call this");
+        } catch (SecurityException e) {
+            // pass
+        }
+
+        verify(mPreferencesHelper, never()).createNotificationChannel(
+                anyString(), anyInt(), any(), anyBoolean(), anyBoolean(),
+                anyInt(), anyBoolean());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT)
+    public void createConversationChannelForPkgFromPrivilegedListener_assistant_success() throws Exception {
+        // Set up assistant
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mCompanionMgr.getAssociations(mPkg, mUserId))
+                .thenReturn(emptyList());
+        when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
+
+        // Set up parent channel
+        setUpChannelsForConversationChannelTest();
+        final NotificationChannel parentChannelCopy = mParentChannel.copy();
+
+        NotificationChannel createdChannel =
+                mBinderService.createConversationNotificationChannelForPackageFromPrivilegedListener(
+                    null, mPkg, mUser, PARENT_CHANNEL_ID, CONVERSATION_ID);
+
+        // Verify that a channel is created and a copied channel is returned.
+        verify(mPreferencesHelper, times(1)).createNotificationChannel(
+                eq(mPkg), eq(mUid), any(), anyBoolean(), anyBoolean(),
+                eq(mUid), anyBoolean());
+        assertThat(createdChannel).isNotSameInstanceAs(mConversationChannel);
+        assertThat(createdChannel).isEqualTo(mConversationChannel);
+
+        // Verify that the channel creation is not directly use the parent channel.
+        verify(mPreferencesHelper, never()).createNotificationChannel(
+                anyString(), anyInt(), eq(mParentChannel), anyBoolean(), anyBoolean(),
+                anyInt(), anyBoolean());
+
+        // Verify that the content of parent channel is not changed.
+        assertThat(parentChannelCopy).isEqualTo(mParentChannel);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT)
+    public void createConversationChannelForPkgFromPrivilegedListener_assistant_noAccess() throws Exception {
+        // Set up assistant without access
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mCompanionMgr.getAssociations(mPkg, mUserId))
+                .thenReturn(emptyList());
+        when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(false);
+
+        // Set up parent channel
+        setUpChannelsForConversationChannelTest();
+
+        try {
+            mBinderService.createConversationNotificationChannelForPackageFromPrivilegedListener(
+                null, mPkg, mUser, "parentId", "conversationId");
+            fail("listeners that don't have a companion device shouldn't be able to call this");
+        } catch (SecurityException e) {
+            // pass
+        }
+
+        verify(mPreferencesHelper, never()).createNotificationChannel(
+                anyString(), anyInt(), any(), anyBoolean(), anyBoolean(),
+                anyInt(), anyBoolean());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT)
+    public void createConversationChannelForPkgFromPrivilegedListener_badUser() throws Exception {
+        // Set up bad user
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mCompanionMgr.getAssociations(mPkg, mUserId))
+                .thenReturn(singletonList(mock(AssociationInfo.class)));
+        mListener = mock(ManagedServices.ManagedServiceInfo.class);
+        mListener.component = new ComponentName(mPkg, mPkg);
+        when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
+        when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+        // Set up parent channel
+        setUpChannelsForConversationChannelTest();
+
+        try {
+            mBinderService.createConversationNotificationChannelForPackageFromPrivilegedListener(
+                null, mPkg, mUser, "parentId", "conversationId");
+            fail("listener getting channels from a user they cannot see");
+        } catch (SecurityException e) {
+            // pass
+        }
+
+        verify(mPreferencesHelper, never()).createNotificationChannel(
+                anyString(), anyInt(), any(), anyBoolean(), anyBoolean(),
+                anyInt(), anyBoolean());
+    }
+
+    @Test
+    public void updateNotificationChannelFromPrivilegedListener_cdm_success() throws Exception {
+
         mService.setPreferencesHelper(mPreferencesHelper);
         when(mCompanionMgr.getAssociations(mPkg, mUserId))
                 .thenReturn(singletonList(mock(AssociationInfo.class)));
@@ -4693,7 +4857,7 @@
     }
 
     @Test
-    public void testUpdateNotificationChannelFromPrivilegedListener_noAccess() throws Exception {
+    public void updateNotificationChannelFromPrivilegedListener_cdm_noAccess() throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
         when(mCompanionMgr.getAssociations(mPkg, mUserId))
                 .thenReturn(emptyList());
@@ -4715,7 +4879,51 @@
     }
 
     @Test
-    public void testUpdateNotificationChannelFromPrivilegedListener_badUser() throws Exception {
+    public void updateNotificationChannelFromPrivilegedListener_assistant_success() throws Exception {
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mCompanionMgr.getAssociations(mPkg, mUserId))
+                .thenReturn(emptyList());
+        when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
+        when(mPreferencesHelper.getNotificationChannel(eq(mPkg), anyInt(),
+                eq(mTestNotificationChannel.getId()), anyBoolean()))
+                .thenReturn(mTestNotificationChannel);
+
+        mBinderService.updateNotificationChannelFromPrivilegedListener(
+                null, mPkg, Process.myUserHandle(), mTestNotificationChannel);
+
+        verify(mPreferencesHelper, times(1)).updateNotificationChannel(
+                anyString(), anyInt(), any(), anyBoolean(),  anyInt(), anyBoolean());
+
+        verify(mListeners, never()).notifyNotificationChannelChanged(eq(mPkg),
+                eq(Process.myUserHandle()), eq(mTestNotificationChannel),
+                eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED));
+    }
+
+    @Test
+    public void updateNotificationChannelFromPrivilegedListener_assistant_noAccess() throws Exception {
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mCompanionMgr.getAssociations(mPkg, mUserId))
+                .thenReturn(emptyList());
+        when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(false);
+
+        try {
+            mBinderService.updateNotificationChannelFromPrivilegedListener(
+                    null, mPkg, Process.myUserHandle(), mTestNotificationChannel);
+            fail("listeners that don't have a companion device shouldn't be able to call this");
+        } catch (SecurityException e) {
+            // pass
+        }
+
+        verify(mPreferencesHelper, never()).updateNotificationChannel(
+                anyString(), anyInt(), any(), anyBoolean(),  anyInt(), anyBoolean());
+
+        verify(mListeners, never()).notifyNotificationChannelChanged(eq(mPkg),
+                eq(Process.myUserHandle()), eq(mTestNotificationChannel),
+                eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED));
+    }
+
+    @Test
+    public void updateNotificationChannelFromPrivilegedListener_badUser() throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
         when(mCompanionMgr.getAssociations(mPkg, mUserId))
                 .thenReturn(singletonList(mock(AssociationInfo.class)));
@@ -4741,7 +4949,7 @@
     }
 
     @Test
-    public void testUpdateNotificationChannelFromPrivilegedListener_noSoundUriPermission()
+    public void updateNotificationChannelFromPrivilegedListener_noSoundUriPermission()
             throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
         when(mCompanionMgr.getAssociations(mPkg, mUserId))
@@ -4773,7 +4981,7 @@
     }
 
     @Test
-    public void testUpdateNotificationChannelFromPrivilegedListener_noSoundUriPermission_sameSound()
+    public void updateNotificationChannelFromPrivilegedListener_noSoundUriPermission_sameSound()
             throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
         when(mCompanionMgr.getAssociations(mPkg, mUserId))
@@ -4805,7 +5013,7 @@
 
     @Test
     public void
-        testUpdateNotificationChannelFromPrivilegedListener_oldSoundNoUriPerm_newSoundHasUriPerm()
+        updateNotificationChannelFromPrivilegedListener_oldSoundNoUriPerm_newSoundHasUriPerm()
             throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
         when(mCompanionMgr.getAssociations(mPkg, mUserId))
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index bdf146f..3236f95 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -528,8 +528,8 @@
         String xml = "<zen version=\"12\">\n"
                 + "<allow calls=\"true\" repeatCallers=\"true\" messages=\"true\""
                 + " reminders=\"false\" events=\"false\" callsFrom=\"2\" messagesFrom=\"2\""
-                + " alarms=\"true\" media=\"true\" system=\"false\" convos=\"true\" convosFrom=\"2\""
-                + " priorityChannelsAllowed=\"true\" />\n"
+                + " alarms=\"true\" media=\"true\" system=\"false\" convos=\"true\""
+                + " convosFrom=\"2\" priorityChannelsAllowed=\"true\" />\n"
                 + "<disallow visualEffects=\"157\" />\n"
                 + "<manual enabled=\"true\" zen=\"1\" creationTime=\"0\" modified=\"false\" />\n"
                 + "<state areChannelsBypassingDnd=\"true\" />\n"
diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java
index 6bf7ff5..3247d1f 100644
--- a/test-mock/src/android/test/mock/MockContext.java
+++ b/test-mock/src/android/test/mock/MockContext.java
@@ -53,6 +53,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
@@ -607,6 +608,12 @@
         throw new UnsupportedOperationException();
     }
 
+    /** @hide */
+    @Override
+    public List<IntentFilter> getRegisteredIntentFilters(BroadcastReceiver receiver) {
+        throw new UnsupportedOperationException();
+    }
+
     @Override
     public ComponentName startService(Intent service) {
         throw new UnsupportedOperationException();
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 19dea0c..a7e2214 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -884,6 +884,86 @@
     }
 
     @Test
+    @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
+    fun testSnapLeftFreeformTask() {
+        val keyGestureController = KeyGestureController(context, testLooper.looper)
+        testKeyGestureInternal(
+            keyGestureController,
+            TestData(
+                "ALT + [ -> Resizes a task to fit the left half of the screen",
+                intArrayOf(
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_LEFT_BRACKET
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
+                intArrayOf(KeyEvent.KEYCODE_LEFT_BRACKET),
+                KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            )
+        )
+    }
+
+    @Test
+    @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
+    fun testSnapRightFreeformTask() {
+        val keyGestureController = KeyGestureController(context, testLooper.looper)
+        testKeyGestureInternal(
+            keyGestureController,
+            TestData(
+                "ALT + ] -> Resizes a task to fit the right half of the screen",
+                intArrayOf(
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_RIGHT_BRACKET
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
+                intArrayOf(KeyEvent.KEYCODE_RIGHT_BRACKET),
+                KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            )
+        )
+    }
+
+    @Test
+    @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
+    fun testMaximizeFreeformTask() {
+        val keyGestureController = KeyGestureController(context, testLooper.looper)
+        testKeyGestureInternal(
+            keyGestureController,
+            TestData(
+                "ALT + '=' -> Maximizes a task to fit the screen",
+                intArrayOf(
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_EQUALS
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
+                intArrayOf(KeyEvent.KEYCODE_EQUALS),
+                KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            )
+        )
+    }
+
+    @Test
+    @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
+    fun testRestoreFreeformTask() {
+        val keyGestureController = KeyGestureController(context, testLooper.looper)
+        testKeyGestureInternal(
+            keyGestureController,
+            TestData(
+                "ALT + '-' -> Restores a task size to its previous bounds",
+                intArrayOf(
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_MINUS
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE,
+                intArrayOf(KeyEvent.KEYCODE_MINUS),
+                KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            )
+        )
+    }
+
+    @Test
     fun testCapsLockPressNotified() {
         val keyGestureController = KeyGestureController(context, testLooper.looper)
         val listener = KeyGestureEventListener()
diff --git a/tests/graphics/HwAccelerationTest/AndroidManifest.xml b/tests/graphics/HwAccelerationTest/AndroidManifest.xml
index db3a992..05b2f4c 100644
--- a/tests/graphics/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/graphics/HwAccelerationTest/AndroidManifest.xml
@@ -24,7 +24,7 @@
     <uses-feature android:name="android.hardware.camera"/>
     <uses-feature android:name="android.hardware.camera.autofocus"/>
 
-    <uses-sdk android:minSdkVersion="21"/>
+    <uses-sdk android:minSdkVersion="21" />
 
     <application android:label="HwUi"
          android:theme="@android:style/Theme.Material.Light">
@@ -409,6 +409,24 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="ScrollingZAboveSurfaceView"
+            android:label="SurfaceView/Z-Above scrolling"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="com.android.test.hwui.TEST"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="ScrollingZAboveScaledSurfaceView"
+            android:label="SurfaceView/Z-Above scrolling, scaled surface"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="com.android.test.hwui.TEST"/>
+            </intent-filter>
+        </activity>
+
         <activity android:name="StretchySurfaceViewActivity"
                   android:label="SurfaceView/Stretchy Movement"
                   android:exported="true">
diff --git a/tests/graphics/HwAccelerationTest/res/layout/scrolling_zabove_surfaceview.xml b/tests/graphics/HwAccelerationTest/res/layout/scrolling_zabove_surfaceview.xml
new file mode 100644
index 0000000..31e5774
--- /dev/null
+++ b/tests/graphics/HwAccelerationTest/res/layout/scrolling_zabove_surfaceview.xml
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context=".MainActivity">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="Above the ScrollView"
+        android:textColor="#FFFFFFFF"
+        android:background="#FF444444"
+        android:padding="32dp" />
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Scrolling Header"
+                android:background="#FFCCCCCC"
+                android:padding="32dp" />
+
+            <SurfaceView
+                android:layout_width="match_parent"
+                android:layout_height="500dp"
+                android:id="@+id/surfaceview" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Scrolling Item"
+                android:background="#FFCCCCCC"
+                android:padding="32dp" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Scrolling Item"
+                android:background="#FFCCCCCC"
+                android:padding="32dp" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Scrolling Item"
+                android:background="#FFCCCCCC"
+                android:padding="32dp" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Scrolling Item"
+                android:background="#FFCCCCCC"
+                android:padding="32dp" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Scrolling Item"
+                android:background="#FFCCCCCC"
+                android:padding="32dp" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Scrolling Item"
+                android:background="#FFCCCCCC"
+                android:padding="32dp" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Scrolling Item"
+                android:background="#FFCCCCCC"
+                android:padding="32dp" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Scrolling Item"
+                android:background="#FFCCCCCC"
+                android:padding="32dp" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Scrolling Item"
+                android:background="#FFCCCCCC"
+                android:padding="32dp" />
+
+        </LinearLayout>
+
+    </ScrollView>
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="Below the ScrollView"
+        android:textColor="#FFFFFFFF"
+        android:background="#FF444444"
+        android:padding="32dp" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveScaledSurfaceView.kt b/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveScaledSurfaceView.kt
new file mode 100644
index 0000000..59ae885
--- /dev/null
+++ b/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveScaledSurfaceView.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.test.hwui
+
+import android.app.Activity
+import android.graphics.Color
+import android.graphics.Paint
+import android.os.Bundle
+import android.view.SurfaceHolder
+import android.view.SurfaceView
+
+class ScrollingZAboveScaledSurfaceView : Activity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.scrolling_zabove_surfaceview)
+
+        findViewById<SurfaceView>(R.id.surfaceview).apply {
+            setZOrderOnTop(true)
+            holder.setFixedSize(1000, 2000)
+            holder.addCallback(object : SurfaceHolder.Callback {
+                override fun surfaceCreated(p0: SurfaceHolder) {
+
+                }
+
+                override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
+                    holder.unlockCanvasAndPost(holder.lockCanvas().apply {
+                        drawColor(Color.BLUE)
+                        val paint = Paint()
+                        paint.textSize = 16 * resources.displayMetrics.density
+                        paint.textAlign = Paint.Align.CENTER
+                        paint.color = Color.WHITE
+                        drawText("I'm a setZOrderOnTop(true) SurfaceView!",
+                            (width / 2).toFloat(), (height / 2).toFloat(), paint)
+                    })
+                }
+
+                override fun surfaceDestroyed(p0: SurfaceHolder) {
+
+                }
+
+            })
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveSurfaceView.kt b/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveSurfaceView.kt
new file mode 100644
index 0000000..ccb71ec
--- /dev/null
+++ b/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveSurfaceView.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.test.hwui
+
+import android.app.Activity
+import android.graphics.Color
+import android.graphics.Paint
+import android.os.Bundle
+import android.view.SurfaceHolder
+import android.view.SurfaceView
+
+class ScrollingZAboveSurfaceView : Activity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.scrolling_zabove_surfaceview)
+
+        findViewById<SurfaceView>(R.id.surfaceview).apply {
+            setZOrderOnTop(true)
+            holder.addCallback(object : SurfaceHolder.Callback {
+                override fun surfaceCreated(p0: SurfaceHolder) {
+
+                }
+
+                override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
+                    holder.unlockCanvasAndPost(holder.lockCanvas().apply {
+                        drawColor(Color.BLUE)
+                        val paint = Paint()
+                        paint.textSize = 16 * resources.displayMetrics.density
+                        paint.textAlign = Paint.Align.CENTER
+                        paint.color = Color.WHITE
+                        drawText("I'm a setZOrderOnTop(true) SurfaceView!",
+                            (width / 2).toFloat(), (height / 2).toFloat(), paint)
+                    })
+                }
+
+                override fun surfaceDestroyed(p0: SurfaceHolder) {
+
+                }
+
+            })
+        }
+    }
+}
\ No newline at end of file