Merge "Cancel Ask-Every-Time support for SMS by default." into main
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 810be8f..fe95a59 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -63,7 +63,7 @@
    name: "remove_user_during_user_switch"
    namespace: "backstage_power"
    description: "Remove started user if user will be stopped due to user switch"
-   bug: "321598070"
+   bug: "337077643"
 }
 
 flag {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a593970..ce62ccf 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -550,6 +550,7 @@
     field public static final int config_systemTextIntelligence = 17039414; // 0x1040036
     field public static final int config_systemUi = 17039418; // 0x104003a
     field public static final int config_systemUiIntelligence = 17039410; // 0x1040032
+    field @FlaggedApi("android.permission.flags.system_vendor_intelligence_role_enabled") public static final int config_systemVendorIntelligence;
     field public static final int config_systemVisualIntelligence = 17039415; // 0x1040037
     field public static final int config_systemWearHealthService = 17039428; // 0x1040044
     field public static final int config_systemWellbeing = 17039408; // 0x1040030
@@ -12615,6 +12616,7 @@
     field public static final String ACTION_REQUEST_ENABLE_CONTENT_CAPTURE = "android.settings.REQUEST_ENABLE_CONTENT_CAPTURE";
     field public static final String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
     field public static final String ACTION_SHOW_RESTRICTED_SETTING_DIALOG = "android.settings.SHOW_RESTRICTED_SETTING_DIALOG";
+    field @FlaggedApi("com.android.internal.telephony.flags.action_sim_preference_settings") public static final String ACTION_SIM_PREFERENCE_SETTINGS = "android.settings.SIM_PREFERENCE_SETTINGS";
     field public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI";
     field public static final String ACTION_TETHER_SETTINGS = "android.settings.TETHER_SETTINGS";
     field public static final String ACTION_TETHER_UNSUPPORTED_CARRIER_UI = "android.settings.TETHER_UNSUPPORTED_CARRIER_UI";
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 1e971a5..9bb0ba4 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -1294,13 +1294,6 @@
     public static record Args(@NonNull String mModule, @Nullable String mApi,
             int mMaxEntries, boolean mIsolateUids, boolean mTestMode, boolean mCacheNulls) {
 
-        /**
-         * Default values for fields.
-         */
-        public static final int DEFAULT_MAX_ENTRIES = 32;
-        public static final boolean DEFAULT_ISOLATE_UIDS = true;
-        public static final boolean DEFAULT_CACHE_NULLS = false;
-
         // Validation: the module must be one of the known module strings and the maxEntries must
         // be positive.
         public Args {
@@ -1315,10 +1308,10 @@
         public Args(@NonNull String module) {
             this(module,
                     null,       // api
-                    DEFAULT_MAX_ENTRIES,
-                    DEFAULT_ISOLATE_UIDS,
+                    32,         // maxEntries
+                    true,       // isolateUids
                     false,      // testMode
-                    DEFAULT_CACHE_NULLS
+                    true        // allowNulls
                  );
         }
 
@@ -1368,7 +1361,7 @@
      * Burst a property name into module and api.  Throw if the key is invalid.  This method is
      * used in to transition legacy cache constructors to the args constructor.
      */
-    private static Args argsFromProperty(@NonNull String name) {
+    private static Args parseProperty(@NonNull String name) {
         throwIfInvalidCacheKey(name);
         // Strip off the leading well-known prefix.
         String base = name.substring(CACHE_KEY_PREFIX.length() + 1);
@@ -1391,9 +1384,8 @@
      *
      * @hide
      */
-    @Deprecated
     public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) {
-        this(argsFromProperty(propertyName).maxEntries(maxEntries), propertyName, null);
+        this(parseProperty(propertyName).maxEntries(maxEntries), propertyName, null);
     }
 
     /**
@@ -1407,10 +1399,9 @@
      * @param cacheName Name of this cache in debug and dumpsys
      * @hide
      */
-    @Deprecated
     public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName,
             @NonNull String cacheName) {
-        this(argsFromProperty(propertyName).maxEntries(maxEntries), cacheName, null);
+        this(parseProperty(propertyName).maxEntries(maxEntries), cacheName, null);
     }
 
     /**
@@ -1866,14 +1857,6 @@
     }
 
     /**
-     * Invalidate caches in all processes that have the module and api specified in the args.
-     * @hide
-     */
-    public static void invalidateCache(@NonNull Args args) {
-        invalidateCache(createPropertyName(args.mModule, args.mApi));
-    }
-
-    /**
      * Invalidate PropertyInvalidatedCache caches in all processes that are keyed on
      * {@var name}. This function is synchronous: caches are invalidated upon return.
      *
diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java
index a2e9314..8db1567 100644
--- a/core/java/android/os/IpcDataCache.java
+++ b/core/java/android/os/IpcDataCache.java
@@ -400,11 +400,10 @@
     }
 
     /**
-     * This is a convenience class that encapsulates configuration information for a cache.  It
-     * may be supplied to the cache constructors in lieu of the other parameters.  The class
-     * captures maximum entry count, the module, the key, and the api.  The key is used to
-     * invalidate the cache and may be shared by different caches.  The api is a user-visible (in
-     * debug) name for the cache.
+     * This is a convenience class that encapsulates configuration information for a
+     * cache.  It may be supplied to the cache constructors in lieu of the other
+     * parameters.  The class captures maximum entry count, the module, the key, and the
+     * api.
      *
      * There are three specific use cases supported by this class.
      *
@@ -431,8 +430,11 @@
      * @hide
      */
     public static class Config {
-        final Args mArgs;
-        final String mName;
+        private final int mMaxEntries;
+        @IpcDataCacheModule
+        private final String mModule;
+        private final String mApi;
+        private final String mName;
 
         /**
          * The list of cache names that were created extending this Config.  If
@@ -450,20 +452,12 @@
          */
         private boolean mDisabled = false;
 
-        /**
-         * Fully construct a config.
-         */
-        private Config(@NonNull Args args, @NonNull String name) {
-            mArgs = args;
-            mName = name;
-        }
-
-        /**
-         *
-         */
         public Config(int maxEntries, @NonNull @IpcDataCacheModule String module,
                 @NonNull String api, @NonNull String name) {
-            this(new Args(module).api(api).maxEntries(maxEntries), name);
+            mMaxEntries = maxEntries;
+            mModule = module;
+            mApi = api;
+            mName = name;
         }
 
         /**
@@ -479,7 +473,7 @@
          * the parameter list.
          */
         public Config(@NonNull Config root, @NonNull String api, @NonNull String name) {
-            this(root.mArgs.api(api), name);
+            this(root.maxEntries(), root.module(), api, name);
         }
 
         /**
@@ -487,7 +481,7 @@
          * the parameter list.
          */
         public Config(@NonNull Config root, @NonNull String api) {
-            this(root.mArgs.api(api), api);
+            this(root.maxEntries(), root.module(), api, api);
         }
 
         /**
@@ -496,23 +490,26 @@
          * current process.
          */
         public Config child(@NonNull String name) {
-            final Config result = new Config(mArgs, name);
+            final Config result = new Config(this, api(), name);
             registerChild(name);
             return result;
         }
 
-        /**
-         * Set the cacheNull behavior.
-         */
-        public Config cacheNulls(boolean enable) {
-            return new Config(mArgs.cacheNulls(enable), mName);
+        public final int maxEntries() {
+            return mMaxEntries;
         }
 
-        /**
-         * Set the isolateUidss behavior.
-         */
-        public Config isolateUids(boolean enable) {
-            return new Config(mArgs.isolateUids(enable), mName);
+        @IpcDataCacheModule
+        public final @NonNull String module() {
+            return mModule;
+        }
+
+        public final @NonNull String api() {
+            return mApi;
+        }
+
+        public final @NonNull String name() {
+            return mName;
         }
 
         /**
@@ -535,7 +532,7 @@
          * Invalidate all caches that share this Config's module and api.
          */
         public void invalidateCache() {
-            IpcDataCache.invalidateCache(mArgs);
+            IpcDataCache.invalidateCache(mModule, mApi);
         }
 
         /**
@@ -567,7 +564,8 @@
      * @hide
      */
     public IpcDataCache(@NonNull Config config, @NonNull QueryHandler<Query, Result> computer) {
-        super(config.mArgs, config.mName, computer);
+      super(new Args(config.module()).maxEntries(config.maxEntries()).api(config.api()),
+          config.name(), computer);
     }
 
     /**
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index bfcc5cc..8d35338 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -118,9 +118,10 @@
 # Memory
 per-file OomKillRecord.java = file:/MEMORY_OWNERS
 
-# MessageQueue
+# MessageQueue and related classes
 per-file MessageQueue.java = mfasheh@google.com, shayba@google.com
 per-file Message.java = mfasheh@google.com, shayba@google.com
+per-file TestLooperManager.java = mfasheh@google.com, shayba@google.com
 
 # Stats
 per-file IStatsBootstrapAtomService.aidl = file:/services/core/java/com/android/server/stats/OWNERS
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 57e1b58..2c4883f 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -483,3 +483,12 @@
     description: "Rate limit async noteOp callbacks for batched noteOperation binder call"
     bug: "366013082"
 }
+
+flag {
+    name: "system_vendor_intelligence_role_enabled"
+    is_exported: true
+    is_fixed_read_only: true
+    namespace: "permissions"
+    description: "This flag is used to enable the role system_vendor_intelligence"
+    bug: "377553620"
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0ae9ffa..9935be2 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2469,6 +2469,25 @@
             = "android.settings.APP_NOTIFICATION_BUBBLE_SETTINGS";
 
     /**
+     * Activity Action: Show the settings for users to select their preferred SIM subscription
+     * when a new SIM subscription has become available.
+     * <p>
+     * This Activity will only launch successfully if the newly active subscription ID is set as the
+     * value of {@link EXTRA_SUB_ID} and the value corresponds with an active SIM subscription.
+     * <p>
+     * Input: {@link #EXTRA_SUB_ID}: the subscription ID of the newly active SIM subscription.
+     * <p>
+     * Output: Nothing.
+     *
+     * @hide
+     */
+    @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_ACTION_SIM_PREFERENCE_SETTINGS)
+    @SystemApi
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_SIM_PREFERENCE_SETTINGS =
+            "android.settings.SIM_PREFERENCE_SETTINGS";
+
+    /**
      * Intent Extra: The value of {@link android.app.settings.SettingsEnums#EntryPointType} for
      * settings metrics that logs the entry point about physical keyboard settings.
      * <p>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1ed81a5..13c125c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7276,4 +7276,9 @@
     <!-- List of protected packages that require biometric authentication for modification
          (Disable, force-stop or uninstalling updates). -->
     <string-array name="config_biometric_protected_package_names" translatable="false" />
+
+    <!-- Package name of the on-device intelligent processor for vendor specific
+         features. Examples include the search functionality or the app
+         predictor. -->
+    <string name="config_systemVendorIntelligence" translatable="false"></string>
 </resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 90f1b8a..7b9d213 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -154,6 +154,9 @@
     <!-- @FlaggedApi(android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_PLATFORM_API_ENABLED)
          @hide @SystemApi -->
     <public name="config_defaultReservedForTestingProfileGroupExclusivity" />
+    <!-- @FlaggedApi(android.permission.flags.Flags.FLAG_SYSTEM_VENDOR_INTELLIGENCE_ROLE_ENABLED)
+         @hide @SystemApi -->
+    <public name="config_systemVendorIntelligence" />
   </staging-public-group>
 
   <staging-public-group type="dimen" first-id="0x01b30000">
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index bd27337..177c7f0 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -740,20 +740,5 @@
         assertEquals(null, cache.query(30));
         // The recompute is 4 because nulls were not cached.
         assertEquals(4, cache.getRecomputeCount());
-
-        // Verify that the default is not to cache nulls.
-        cache = new TestCache(new Args(MODULE_TEST)
-                .maxEntries(4).api("testCachingNulls"),
-                new TestQuery());
-        cache.invalidateCache();
-        assertEquals("foo1", cache.query(1));
-        assertEquals("foo2", cache.query(2));
-        assertEquals(null, cache.query(30));
-        assertEquals(3, cache.getRecomputeCount());
-        assertEquals("foo1", cache.query(1));
-        assertEquals("foo2", cache.query(2));
-        assertEquals(null, cache.query(30));
-        // The recompute is 4 because nulls were not cached.
-        assertEquals(4, cache.getRecomputeCount());
     }
 }
diff --git a/core/tests/coretests/src/android/os/IpcDataCacheTest.java b/core/tests/coretests/src/android/os/IpcDataCacheTest.java
index 523b1b0..e14608a 100644
--- a/core/tests/coretests/src/android/os/IpcDataCacheTest.java
+++ b/core/tests/coretests/src/android/os/IpcDataCacheTest.java
@@ -16,19 +16,13 @@
 
 package android.os;
 
-import static android.app.Flags.FLAG_PIC_CACHE_NULLS;
-import static android.app.Flags.FLAG_PIC_ISOLATE_CACHE_BY_UID;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
-import android.app.PropertyInvalidatedCache;
-import android.app.PropertyInvalidatedCache.Args;
 import android.multiuser.Flags;
 import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.ravenwood.RavenwoodRule;
-import android.os.IpcDataCache;
 
 import androidx.test.filters.SmallTest;
 
@@ -293,12 +287,7 @@
         @Override
         public String apply(Integer qv) {
             mRecomputeCount += 1;
-            // Special case for testing caches of nulls.  Integers in the range 30-40 return null.
-            if (qv >= 30 && qv < 40) {
-                return null;
-            } else {
-                return "foo" + qv.toString();
-            }
+            return "foo" + qv.toString();
         }
 
         int getRecomputeCount() {
@@ -417,16 +406,31 @@
     }
 
     @Test
-    public void testConfigDisable() {
-        // Create a set of caches based on a set of chained configs.
+    public void testConfig() {
         IpcDataCache.Config a = new IpcDataCache.Config(8, MODULE, "apiA");
         TestCache ac = new TestCache(a);
+        assertEquals(8, a.maxEntries());
+        assertEquals(MODULE, a.module());
+        assertEquals("apiA", a.api());
+        assertEquals("apiA", a.name());
         IpcDataCache.Config b = new IpcDataCache.Config(a, "apiB");
         TestCache bc = new TestCache(b);
+        assertEquals(8, b.maxEntries());
+        assertEquals(MODULE, b.module());
+        assertEquals("apiB", b.api());
+        assertEquals("apiB", b.name());
         IpcDataCache.Config c = new IpcDataCache.Config(a, "apiC", "nameC");
         TestCache cc = new TestCache(c);
+        assertEquals(8, c.maxEntries());
+        assertEquals(MODULE, c.module());
+        assertEquals("apiC", c.api());
+        assertEquals("nameC", c.name());
         IpcDataCache.Config d = a.child("nameD");
         TestCache dc = new TestCache(d);
+        assertEquals(8, d.maxEntries());
+        assertEquals(MODULE, d.module());
+        assertEquals("apiA", d.api());
+        assertEquals("nameD", d.name());
 
         a.disableForCurrentProcess();
         assertEquals(ac.isDisabled(), true);
@@ -445,7 +449,6 @@
         assertEquals(ec.isDisabled(), true);
     }
 
-
     // Verify that invalidating the cache from an app process would fail due to lack of permissions.
     @Test
     @android.platform.test.annotations.DisabledOnRavenwood(
@@ -504,47 +507,4 @@
         // Re-enable test mode (so that the cleanup for the test does not throw).
         IpcDataCache.setTestMode(true);
     }
-
-    @RequiresFlagsEnabled(FLAG_PIC_CACHE_NULLS)
-    @Test
-    public void testCachingNulls() {
-        IpcDataCache.Config c =
-                new IpcDataCache.Config(4, IpcDataCache.MODULE_TEST, "testCachingNulls");
-        TestCache cache;
-        cache = new TestCache(c.cacheNulls(true));
-        cache.invalidateCache();
-        assertEquals("foo1", cache.query(1));
-        assertEquals("foo2", cache.query(2));
-        assertEquals(null, cache.query(30));
-        assertEquals(3, cache.getRecomputeCount());
-        assertEquals("foo1", cache.query(1));
-        assertEquals("foo2", cache.query(2));
-        assertEquals(null, cache.query(30));
-        assertEquals(3, cache.getRecomputeCount());
-
-        cache = new TestCache(c.cacheNulls(false));
-        cache.invalidateCache();
-        assertEquals("foo1", cache.query(1));
-        assertEquals("foo2", cache.query(2));
-        assertEquals(null, cache.query(30));
-        assertEquals(3, cache.getRecomputeCount());
-        assertEquals("foo1", cache.query(1));
-        assertEquals("foo2", cache.query(2));
-        assertEquals(null, cache.query(30));
-        // The recompute is 4 because nulls were not cached.
-        assertEquals(4, cache.getRecomputeCount());
-
-        // Verify that the default is not to cache nulls.
-        cache = new TestCache(c);
-        cache.invalidateCache();
-        assertEquals("foo1", cache.query(1));
-        assertEquals("foo2", cache.query(2));
-        assertEquals(null, cache.query(30));
-        assertEquals(3, cache.getRecomputeCount());
-        assertEquals("foo1", cache.query(1));
-        assertEquals("foo2", cache.query(2));
-        assertEquals(null, cache.query(30));
-        // The recompute is 4 because nulls were not cached.
-        assertEquals(4, cache.getRecomputeCount());
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index f46b955..86e0d08 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -152,6 +152,7 @@
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer;
 import com.android.wm.shell.windowdecor.common.viewhost.DefaultWindowDecorViewHostSupplier;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
 import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
 import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationPromoController;
 import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController;
@@ -301,6 +302,7 @@
             Transitions transitions,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             FocusTransitionObserver focusTransitionObserver,
+            WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
             Optional<DesktopModeWindowDecorViewModel> desktopModeWindowDecorViewModel) {
         if (desktopModeWindowDecorViewModel.isPresent()) {
             return desktopModeWindowDecorViewModel.get();
@@ -318,7 +320,8 @@
                 rootTaskDisplayAreaOrganizer,
                 syncQueue,
                 transitions,
-                focusTransitionObserver);
+                focusTransitionObserver,
+                windowDecorViewHostSupplier);
     }
 
     @WMSingleton
@@ -343,7 +346,7 @@
 
     @WMSingleton
     @Provides
-    static WindowDecorViewHostSupplier provideWindowDecorViewHostSupplier(
+    static WindowDecorViewHostSupplier<WindowDecorViewHost> provideWindowDecorViewHostSupplier(
             @ShellMainThread @NonNull CoroutineScope mainScope) {
         return new DefaultWindowDecorViewHostSupplier(mainScope);
     }
@@ -908,6 +911,7 @@
             InteractionJankMonitor interactionJankMonitor,
             AppToWebGenericLinksParser genericLinksParser,
             AssistContentRequester assistContentRequester,
+            WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
             MultiInstanceHelper multiInstanceHelper,
             Optional<DesktopTasksLimiter> desktopTasksLimiter,
             AppHandleEducationController appHandleEducationController,
@@ -927,8 +931,8 @@
                 displayInsetsController, syncQueue, transitions, desktopTasksController,
                 desktopImmersiveController.get(),
                 rootTaskDisplayAreaOrganizer, interactionJankMonitor, genericLinksParser,
-                assistContentRequester, multiInstanceHelper, desktopTasksLimiter,
-                appHandleEducationController, appToWebEducationController,
+                assistContentRequester, windowDecorViewHostSupplier, multiInstanceHelper,
+                desktopTasksLimiter, appHandleEducationController, appToWebEducationController,
                 windowDecorCaptionHandleRepository, activityOrientationChangeHandler,
                 focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger));
     }
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 e08f9b2..d5a2a40 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
@@ -1510,28 +1510,20 @@
 
     /** Open an existing instance of an app. */
     fun openInstance(callingTask: RunningTaskInfo, requestedTaskId: Int) {
-        val wct = WindowContainerTransaction()
-        val options = createNewWindowOptions(callingTask)
-        if (options.launchWindowingMode == WINDOWING_MODE_FREEFORM) {
-            wct.startTask(requestedTaskId, options.toBundle())
-            val taskIdToMinimize =
-                bringDesktopAppsToFrontBeforeShowingNewTask(
-                    callingTask.displayId,
-                    wct,
+        if (callingTask.isFreeform) {
+            val requestedTaskInfo = shellTaskOrganizer.getRunningTaskInfo(requestedTaskId)
+            if (requestedTaskInfo?.isFreeform == true) {
+                // If requested task is an already open freeform task, just move it to front.
+                moveTaskToFront(requestedTaskId)
+            } else {
+                moveBackgroundTaskToDesktop(
                     requestedTaskId,
+                    WindowContainerTransaction(),
+                    DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON,
                 )
-            val exitResult =
-                desktopImmersiveController.exitImmersiveIfApplicable(
-                    wct = wct,
-                    displayId = callingTask.displayId,
-                    excludeTaskId = requestedTaskId,
-                    reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
-                )
-            val transition = transitions.startTransition(TRANSIT_OPEN, wct, null)
-            taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
-            addPendingAppLaunchTransition(transition, requestedTaskId, taskIdToMinimize)
-            exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
+            }
         } else {
+            val options = createNewWindowOptions(callingTask)
             val splitPosition = splitScreenController.determineNewInstancePosition(callingTask)
             splitScreenController.startTask(
                 requestedTaskId,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 885f3db..0b91966 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -68,6 +68,8 @@
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.FocusTransitionObserver;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
 import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
 
 /**
@@ -90,6 +92,7 @@
     private final Transitions mTransitions;
     private final Region mExclusionRegion = Region.obtain();
     private final InputManager mInputManager;
+    private final WindowDecorViewHostSupplier<WindowDecorViewHost> mWindowDecorViewHostSupplier;
     private TaskOperations mTaskOperations;
     private FocusTransitionObserver mFocusTransitionObserver;
 
@@ -130,7 +133,8 @@
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             SyncTransactionQueue syncQueue,
             Transitions transitions,
-            FocusTransitionObserver focusTransitionObserver) {
+            FocusTransitionObserver focusTransitionObserver,
+            WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier) {
         mContext = context;
         mMainExecutor = shellExecutor;
         mMainHandler = mainHandler;
@@ -143,6 +147,7 @@
         mSyncQueue = syncQueue;
         mTransitions = transitions;
         mFocusTransitionObserver = focusTransitionObserver;
+        mWindowDecorViewHostSupplier = windowDecorViewHostSupplier;
         if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
             mTaskOperations = new TaskOperations(null, mContext, mSyncQueue);
         }
@@ -332,7 +337,8 @@
                         mMainHandler,
                         mBgExecutor,
                         mMainChoreographer,
-                        mSyncQueue);
+                        mSyncQueue,
+                        mWindowDecorViewHostSupplier);
         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
 
         final FluidResizeTaskPositioner taskPositioner =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 112e429..23bb2aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -58,6 +58,8 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
 import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
 
 /**
@@ -90,9 +92,10 @@
             Handler handler,
             @ShellBackgroundThread ShellExecutor bgExecutor,
             Choreographer choreographer,
-            SyncTransactionQueue syncQueue) {
-        super(context, userContext, displayController, taskOrganizer, handler, taskInfo,
-                taskSurface);
+            SyncTransactionQueue syncQueue,
+            @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier) {
+        super(context, userContext, displayController, taskOrganizer, taskInfo,
+                taskSurface, windowDecorViewHostSupplier);
         mHandler = handler;
         mBgExecutor = bgExecutor;
         mChoreographer = choreographer;
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 e8b02dc..08d047a 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
@@ -139,6 +139,8 @@
 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.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
 import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
 import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
 import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
@@ -217,6 +219,7 @@
     private boolean mInImmersiveMode;
     private final String mSysUIPackageName;
     private final AssistContentRequester mAssistContentRequester;
+    private final WindowDecorViewHostSupplier<WindowDecorViewHost> mWindowDecorViewHostSupplier;
 
     private final DisplayChangeController.OnDisplayChangingListener mOnDisplayChangingListener;
     private final ISystemGestureExclusionListener mGestureExclusionListener =
@@ -260,6 +263,7 @@
             InteractionJankMonitor interactionJankMonitor,
             AppToWebGenericLinksParser genericLinksParser,
             AssistContentRequester assistContentRequester,
+            @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
             MultiInstanceHelper multiInstanceHelper,
             Optional<DesktopTasksLimiter> desktopTasksLimiter,
             AppHandleEducationController appHandleEducationController,
@@ -289,6 +293,7 @@
                 desktopImmersiveController,
                 genericLinksParser,
                 assistContentRequester,
+                windowDecorViewHostSupplier,
                 multiInstanceHelper,
                 new DesktopModeWindowDecoration.Factory(),
                 new InputMonitorFactory(),
@@ -329,6 +334,7 @@
             DesktopImmersiveController desktopImmersiveController,
             AppToWebGenericLinksParser genericLinksParser,
             AssistContentRequester assistContentRequester,
+            @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
             MultiInstanceHelper multiInstanceHelper,
             DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
             InputMonitorFactory inputMonitorFactory,
@@ -381,6 +387,7 @@
         mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository;
         mActivityOrientationChangeHandler = activityOrientationChangeHandler;
         mAssistContentRequester = assistContentRequester;
+        mWindowDecorViewHostSupplier = windowDecorViewHostSupplier;
         mOnDisplayChangingListener = (displayId, fromRotation, toRotation, displayAreaInfo, t) -> {
             DesktopModeWindowDecoration decoration;
             RunningTaskInfo taskInfo;
@@ -1623,6 +1630,7 @@
                         mRootTaskDisplayAreaOrganizer,
                         mGenericLinksParser,
                         mAssistContentRequester,
+                        mWindowDecorViewHostSupplier,
                         mMultiInstanceHelper,
                         mWindowDecorCaptionHandleRepository,
                         mDesktopModeEventLogger);
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 3d0c8a7..6562f38 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
@@ -105,6 +105,8 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer;
 import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
 import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
 import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
 import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
@@ -127,7 +129,6 @@
  */
 public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
     private static final String TAG = "DesktopModeWindowDecoration";
-    private static final int CAPTURED_LINK_TIMEOUT_MS = 7000;
 
     @VisibleForTesting
     static final long CLOSE_MAXIMIZE_MENU_DELAY_MS = 150L;
@@ -199,7 +200,6 @@
     // being hovered. There's a small delay after stopping the hover, to allow a quick reentry
     // to cancel the close.
     private final Runnable mCloseMaximizeWindowRunnable = this::closeMaximizeMenu;
-    private final Runnable mCapturedLinkExpiredRunnable = this::onCapturedLinkExpired;
     private final MultiInstanceHelper mMultiInstanceHelper;
     private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
     private final DesktopUserRepositories mDesktopUserRepositories;
@@ -221,6 +221,7 @@
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             AppToWebGenericLinksParser genericLinksParser,
             AssistContentRequester assistContentRequester,
+            @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
             MultiInstanceHelper multiInstanceHelper,
             WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
             DesktopModeEventLogger desktopModeEventLogger) {
@@ -232,6 +233,7 @@
                 WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper(
                         context.getSystemService(WindowManager.class)),
                 new SurfaceControlViewHostFactory() {},
+                windowDecorViewHostSupplier,
                 DefaultMaximizeMenuFactory.INSTANCE,
                 DefaultHandleMenuFactory.INSTANCE, multiInstanceHelper,
                 windowDecorCaptionHandleRepository, desktopModeEventLogger);
@@ -260,15 +262,16 @@
             Supplier<SurfaceControl> surfaceControlSupplier,
             WindowManagerWrapper windowManagerWrapper,
             SurfaceControlViewHostFactory surfaceControlViewHostFactory,
+            @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
             MaximizeMenuFactory maximizeMenuFactory,
             HandleMenuFactory handleMenuFactory,
             MultiInstanceHelper multiInstanceHelper,
             WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
             DesktopModeEventLogger desktopModeEventLogger) {
-        super(context, userContext, displayController, taskOrganizer, handler, taskInfo,
+        super(context, userContext, displayController, taskOrganizer, taskInfo,
                 taskSurface, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
                 windowContainerTransactionSupplier, surfaceControlSupplier,
-                surfaceControlViewHostFactory, desktopModeEventLogger);
+                surfaceControlViewHostFactory, windowDecorViewHostSupplier, desktopModeEventLogger);
         mSplitScreenController = splitScreenController;
         mHandler = handler;
         mBgExecutor = bgExecutor;
@@ -548,22 +551,12 @@
             return;
         }
         mCapturedLink = new CapturedLink(capturedLink, timeStamp);
-        mHandler.postDelayed(mCapturedLinkExpiredRunnable, CAPTURED_LINK_TIMEOUT_MS);
-    }
-
-    private void onCapturedLinkExpired() {
-        mHandler.removeCallbacks(mCapturedLinkExpiredRunnable);
-        if (mCapturedLink != null) {
-            mCapturedLink.setExpired();
-        }
     }
 
     @Nullable
     private Intent getBrowserLink() {
         final Uri browserLink;
-        // If the captured link is available and has not expired, return the captured link.
-        // Otherwise, return the generic link which is set to null if a generic link is unavailable.
-        if (mCapturedLink != null && !mCapturedLink.mExpired) {
+        if (isCapturedLinkAvailable()) {
             browserLink = mCapturedLink.mUri;
         } else if (mWebUri != null) {
             browserLink = mWebUri;
@@ -675,7 +668,13 @@
     }
 
     private boolean isCapturedLinkAvailable() {
-        return mCapturedLink != null && !mCapturedLink.mExpired;
+        return mCapturedLink != null && !mCapturedLink.mUsed;
+    }
+
+    private void onCapturedLinkUsed() {
+        if (mCapturedLink != null) {
+            mCapturedLink.setUsed();
+        }
     }
 
     private void notifyNoCaptionHandle() {
@@ -1365,7 +1364,7 @@
                 /* onAspectRatioSettingsClickListener= */ mOnChangeAspectRatioClickListener,
                 /* openInBrowserClickListener= */ (intent) -> {
                     mOpenInBrowserClickListener.accept(intent);
-                    onCapturedLinkExpired();
+                    onCapturedLinkUsed();
                     if (Flags.enableDesktopWindowingAppToWebEducationIntegration()) {
                         mWindowDecorCaptionHandleRepository.onAppToWebUsage();
                     }
@@ -1766,6 +1765,8 @@
                 RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
                 AppToWebGenericLinksParser genericLinksParser,
                 AssistContentRequester assistContentRequester,
+                @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost>
+                        windowDecorViewHostSupplier,
                 MultiInstanceHelper multiInstanceHelper,
                 WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
                 DesktopModeEventLogger desktopModeEventLogger) {
@@ -1786,6 +1787,7 @@
                     rootTaskDisplayAreaOrganizer,
                     genericLinksParser,
                     assistContentRequester,
+                    windowDecorViewHostSupplier,
                     multiInstanceHelper,
                     windowDecorCaptionHandleRepository,
                     desktopModeEventLogger);
@@ -1796,16 +1798,15 @@
     static class CapturedLink {
         private final long mTimeStamp;
         private final Uri mUri;
-        private boolean mExpired;
+        private boolean mUsed;
 
         CapturedLink(@NonNull Uri uri, long timeStamp) {
             mUri = uri;
             mTimeStamp = timeStamp;
-            mExpired = false;
         }
 
-        void setExpired() {
-            mExpired = true;
+        private void setUsed() {
+            mUsed = true;
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 261d400..5d1bedb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -39,7 +39,6 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Binder;
-import android.os.Handler;
 import android.os.Trace;
 import android.view.Display;
 import android.view.InsetsSource;
@@ -59,10 +58,11 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
-import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement;
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
 import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
 
 import java.util.ArrayList;
@@ -90,10 +90,10 @@
         implements AutoCloseable {
 
     /**
-     * The Z-order of {@link #mCaptionContainerSurface}.
+     * The Z-order of the caption surface.
      * <p>
      * We use {@link #mDecorationContainerSurface} to define input window for task resizing; by
-     * layering it in front of {@link #mCaptionContainerSurface}, we can allow it to handle input
+     * layering it in front of the caption surface, we can allow it to handle input
      * prior to caption view itself, treating corner inputs as resize events rather than
      * repositioning.
      */
@@ -102,7 +102,7 @@
      * The Z-order of the task input sink in {@link DragPositioningCallback}.
      * <p>
      * This task input sink is used to prevent undesired dispatching of motion events out of task
-     * bounds; by layering it behind {@link #mCaptionContainerSurface}, we allow captions to handle
+     * bounds; by layering it behind the caption surface, we allow captions to handle
      * input events first.
      */
     static final int INPUT_SINK_Z_ORDER = -2;
@@ -123,12 +123,13 @@
     final @NonNull Context mUserContext;
     final @NonNull DisplayController mDisplayController;
     final @NonNull DesktopModeEventLogger mDesktopModeEventLogger;
-    private final @ShellMainThread Handler mMainHandler;
     final ShellTaskOrganizer mTaskOrganizer;
     final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
     final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
     final Supplier<WindowContainerTransaction> mWindowContainerTransactionSupplier;
     final SurfaceControlViewHostFactory mSurfaceControlViewHostFactory;
+    @NonNull private final WindowDecorViewHostSupplier<WindowDecorViewHost>
+            mWindowDecorViewHostSupplier;
     private final DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener =
             new DisplayController.OnDisplaysChangedListener() {
                 @Override
@@ -153,9 +154,7 @@
     Display mDisplay;
     SurfaceControl mDecorationContainerSurface;
 
-    SurfaceControl mCaptionContainerSurface;
-    private CaptionWindowlessWindowManager mCaptionWindowManager;
-    private SurfaceControlViewHost mViewHost;
+    private WindowDecorViewHost mViewHost;
     private Configuration mWindowDecorConfig;
     TaskDragResizer mTaskDragResizer;
     boolean mIsCaptionVisible;
@@ -170,20 +169,19 @@
     private final Binder mOwner = new Binder();
     private final float[] mTmpColor = new float[3];
 
-    private Runnable mCurrentUpdateViewHostRunnable;
-
     WindowDecoration(
             Context context,
             @NonNull Context userContext,
             DisplayController displayController,
             ShellTaskOrganizer taskOrganizer,
-            @ShellMainThread Handler mainHandler,
             RunningTaskInfo taskInfo,
-            SurfaceControl taskSurface) {
-        this(context, userContext, displayController, taskOrganizer, mainHandler, taskInfo,
+            SurfaceControl taskSurface,
+            @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier) {
+        this(context, userContext, displayController, taskOrganizer, taskInfo,
                 taskSurface, SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
                 WindowContainerTransaction::new, SurfaceControl::new,
-                new SurfaceControlViewHostFactory() {}, new DesktopModeEventLogger());
+                new SurfaceControlViewHostFactory() {}, windowDecorViewHostSupplier,
+                new DesktopModeEventLogger());
     }
 
     WindowDecoration(
@@ -191,7 +189,6 @@
             @NonNull Context userContext,
             @NonNull DisplayController displayController,
             ShellTaskOrganizer taskOrganizer,
-            @ShellMainThread Handler mainHandler,
             RunningTaskInfo taskInfo,
             @NonNull SurfaceControl taskSurface,
             Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
@@ -199,13 +196,13 @@
             Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
             Supplier<SurfaceControl> surfaceControlSupplier,
             SurfaceControlViewHostFactory surfaceControlViewHostFactory,
+            @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
             @NonNull DesktopModeEventLogger desktopModeEventLogger
     ) {
         mContext = context;
         mUserContext = userContext;
         mDisplayController = displayController;
         mTaskOrganizer = taskOrganizer;
-        mMainHandler = mainHandler;
         mTaskInfo = taskInfo;
         mTaskSurface = cloneSurfaceControl(taskSurface, surfaceControlSupplier);
         mDesktopModeEventLogger = desktopModeEventLogger;
@@ -213,6 +210,7 @@
         mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
         mWindowContainerTransactionSupplier = windowContainerTransactionSupplier;
         mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
+        mWindowDecorViewHostSupplier = windowDecorViewHostSupplier;
         mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
         final InsetsState insetsState = mDisplayController.getInsetsState(mTaskInfo.displayId);
         mIsStatusBarVisible = insetsState != null
@@ -292,43 +290,20 @@
         outResult.mCaptionY = 0;
         outResult.mCaptionTopPadding = params.mCaptionTopPadding;
 
+        Trace.beginSection("relayout-createViewHostIfNeeded");
+        createViewHostIfNeeded(mDecorWindowContext, mDisplay);
+        Trace.endSection();
+
         Trace.beginSection("WindowDecoration#relayout-updateSurfacesAndInsets");
+        final SurfaceControl captionSurface = mViewHost.getSurfaceControl();
         updateDecorationContainerSurface(startT, outResult);
-        final SurfaceControl captionSurface = getOrCreateCaptionSurface();
         updateCaptionContainerSurface(captionSurface, startT, outResult);
         updateCaptionInsets(params, wct, outResult, taskBounds);
         updateTaskSurface(params, startT, finishT, outResult);
         Trace.endSection();
 
         Trace.beginSection("WindowDecoration#relayout-updateViewHost");
-        clearCurrentViewHostRunnable();
-        if (params.mAsyncViewHost) {
-            mCurrentUpdateViewHostRunnable = () -> updateViewHost(params, startT, outResult);
-            mMainHandler.post(mCurrentUpdateViewHostRunnable);
-        } else {
-            updateViewHost(params, startT, outResult);
-        }
-        Trace.endSection();
-
-        Trace.endSection(); // WindowDecoration#relayout
-    }
-
-    private void clearCurrentViewHostRunnable() {
-        if (mCurrentUpdateViewHostRunnable != null) {
-            mMainHandler.removeCallbacks(mCurrentUpdateViewHostRunnable);
-            mCurrentUpdateViewHostRunnable = null;
-        }
-    }
-
-    private void updateViewHost(RelayoutParams params, SurfaceControl.Transaction startT,
-            RelayoutResult<T> outResult) {
-        createCaptionWindowManagerIfNeeded();
-        Trace.beginSection("updateViewHost-createViewHostIfNeeded");
-        final boolean firstLayout = createViewHostIfNeeded(mDecorWindowContext, mDisplay);
-        Trace.endSection();
-
         outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0);
-        mCaptionWindowManager.setConfiguration(mTaskInfo.getConfiguration());
         final Rect localCaptionBounds = new Rect(
                 outResult.mCaptionX,
                 outResult.mCaptionY,
@@ -337,50 +312,21 @@
         final Region touchableRegion = params.mLimitTouchRegionToSystemAreas
                 ? calculateLimitedTouchableRegion(params, localCaptionBounds)
                 : null;
-        if (params.mLimitTouchRegionToSystemAreas) {
-            mCaptionWindowManager.setTouchRegion(mViewHost, touchableRegion);
-        }
-        if (touchableRegion != null) {
-            touchableRegion.recycle();
-        }
-        updateViewHierarchy(params, outResult, startT, firstLayout);
+        updateViewHierarchy(params, outResult, startT, touchableRegion);
+        Trace.endSection();
+
+        Trace.endSection(); // WindowDecoration#relayout
     }
 
-    private SurfaceControl getOrCreateCaptionSurface() {
-        if (mCaptionContainerSurface == null) {
-            final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
-            mCaptionContainerSurface = builder
-                    .setName("Caption container of Task=" + mTaskInfo.taskId)
-                    .setContainerLayer()
-                    .setParent(mDecorationContainerSurface)
-                    .setCallsite("WindowDecoration.updateCaptionContainerSurface")
-                    .build();
-        }
-        return mCaptionContainerSurface;
-    }
-
-    private boolean createViewHostIfNeeded(@NonNull Context context, @NonNull Display display) {
+    private void createViewHostIfNeeded(@NonNull Context context, @NonNull Display display) {
         if (mViewHost == null) {
-            mViewHost = mSurfaceControlViewHostFactory.create(context, display,
-                    mCaptionWindowManager);
-            Trace.endSection();
-            return true;
-        }
-        return false;
-    }
-
-    private void createCaptionWindowManagerIfNeeded() {
-        if (mCaptionWindowManager == null) {
-            // Put caption under a container surface because ViewRootImpl sets the destination frame
-            // of windowless window layers and BLASTBufferQueue#update() doesn't support offset.
-            mCaptionWindowManager = new CaptionWindowlessWindowManager(
-                    mTaskInfo.getConfiguration(), mCaptionContainerSurface);
+            mViewHost = mWindowDecorViewHostSupplier.acquire(context, display);
         }
     }
 
     private void updateViewHierarchy(@NonNull RelayoutParams params,
             @NonNull RelayoutResult<T> outResult, @NonNull SurfaceControl.Transaction startT,
-            boolean firstLayout) {
+            @Nullable Region touchableRegion) {
         Trace.beginSection("WindowDecoration#updateViewHierarchy");
         final WindowManager.LayoutParams lp =
                 new WindowManager.LayoutParams(
@@ -392,16 +338,15 @@
         lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
         lp.setTrustedOverlay();
         lp.inputFeatures = params.mInputFeatures;
-        if (params.mApplyStartTransactionOnDraw) {
-            if (params.mAsyncViewHost) {
+        if (params.mAsyncViewHost) {
+            if (params.mApplyStartTransactionOnDraw) {
                 throw new IllegalArgumentException("Cannot use sync draw tx with async relayout");
             }
-            mViewHost.getRootSurfaceControl().applyTransactionOnDraw(startT);
-        }
-        if (firstLayout) {
-            mViewHost.setView(outResult.mRootView, lp);
+            mViewHost.updateViewAsync(outResult.mRootView, lp, mTaskInfo.configuration,
+                    touchableRegion);
         } else {
-            mViewHost.relayout(lp);
+            mViewHost.updateView(outResult.mRootView, lp, mTaskInfo.configuration,
+                    touchableRegion, params.mApplyStartTransactionOnDraw ? startT : null);
         }
         Trace.endSection();
     }
@@ -719,18 +664,11 @@
     }
 
     void releaseViews(WindowContainerTransaction wct) {
-        if (mViewHost != null) {
-            mViewHost.release();
-            mViewHost = null;
-        }
-
-        mCaptionWindowManager = null;
-
         final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
         boolean released = false;
-        if (mCaptionContainerSurface != null) {
-            t.remove(mCaptionContainerSurface);
-            mCaptionContainerSurface = null;
+        if (mViewHost != null) {
+            mWindowDecorViewHostSupplier.release(mViewHost, t);
+            mViewHost = null;
             released = true;
         }
 
@@ -753,7 +691,6 @@
     @Override
     public void close() {
         Trace.beginSection("WindowDecoration#close");
-        clearCurrentViewHostRunnable();
         mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
         final WindowContainerTransaction wct = mWindowContainerTransactionSupplier.get();
         releaseViews(wct);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt
index c470eef..50aa21e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt
@@ -17,6 +17,7 @@
 
 import android.content.Context
 import android.content.res.Configuration
+import android.graphics.Region
 import android.view.Display
 import android.view.SurfaceControl
 import android.view.SurfaceControlViewHost
@@ -59,7 +60,7 @@
             .setCallsite("DefaultWindowDecorViewHost#init")
             .build()
 
-    private var wwm: WindowlessWindowManager? = null
+    private var wwm: WindowDecorWindowlessWindowManager? = null
     @VisibleForTesting var viewHost: SurfaceControlViewHost? = null
     private var currentUpdateJob: Job? = null
 
@@ -70,11 +71,12 @@
         view: View,
         attrs: WindowManager.LayoutParams,
         configuration: Configuration,
+        touchableRegion: Region?,
         onDrawTransaction: SurfaceControl.Transaction?,
     ) {
         Trace.beginSection("DefaultWindowDecorViewHost#updateView")
         clearCurrentUpdateJob()
-        updateViewHost(view, attrs, configuration, onDrawTransaction)
+        updateViewHost(view, attrs, configuration, touchableRegion, onDrawTransaction)
         Trace.endSection()
     }
 
@@ -82,12 +84,19 @@
         view: View,
         attrs: WindowManager.LayoutParams,
         configuration: Configuration,
+        touchableRegion: Region?,
     ) {
         Trace.beginSection("DefaultWindowDecorViewHost#updateViewAsync")
         clearCurrentUpdateJob()
         currentUpdateJob =
             mainScope.launch {
-                updateViewHost(view, attrs, configuration, onDrawTransaction = null)
+                updateViewHost(
+                    view,
+                    attrs,
+                    configuration,
+                    touchableRegion,
+                    onDrawTransaction = null
+                )
             }
         Trace.endSection()
     }
@@ -102,13 +111,13 @@
         view: View,
         attrs: WindowManager.LayoutParams,
         configuration: Configuration,
+        touchableRegion: Region?,
         onDrawTransaction: SurfaceControl.Transaction?,
     ) {
         Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost")
         if (wwm == null) {
-            wwm = WindowlessWindowManager(configuration, rootSurface, null)
+            wwm = WindowDecorWindowlessWindowManager(configuration, rootSurface)
         }
-        requireWindowlessWindowManager().setConfiguration(configuration)
         if (viewHost == null) {
             viewHost =
                 surfaceControlViewHostFactory.invoke(
@@ -118,6 +127,10 @@
                     "DefaultWindowDecorViewHost#updateViewHost",
                 )
         }
+        requireWindowlessWindowManager().apply {
+            setConfiguration(configuration)
+            setTouchRegion(requireViewHost(), touchableRegion)
+        }
         onDrawTransaction?.let { requireViewHost().rootSurfaceControl.applyTransactionOnDraw(it) }
         if (requireViewHost().view == null) {
             Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost-setView")
@@ -137,7 +150,7 @@
         currentUpdateJob = null
     }
 
-    private fun requireWindowlessWindowManager(): WindowlessWindowManager {
+    private fun requireWindowlessWindowManager(): WindowDecorWindowlessWindowManager {
         return wwm ?: error("Expected non-null windowless window manager")
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt
index 27ffd6c..7821619 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt
@@ -24,14 +24,15 @@
 /**
  * A supplier of [DefaultWindowDecorViewHost]s. It creates a new one every time one is requested.
  */
-class DefaultWindowDecorViewHostSupplier(@ShellMainThread private val mainScope: CoroutineScope) :
-    WindowDecorViewHostSupplier<DefaultWindowDecorViewHost> {
+class DefaultWindowDecorViewHostSupplier(
+    @ShellMainThread private val mainScope: CoroutineScope
+) : WindowDecorViewHostSupplier<WindowDecorViewHost> {
 
-    override fun acquire(context: Context, display: Display): DefaultWindowDecorViewHost {
+    override fun acquire(context: Context, display: Display): WindowDecorViewHost {
         return DefaultWindowDecorViewHost(context, mainScope, display)
     }
 
-    override fun release(viewHost: DefaultWindowDecorViewHost, t: SurfaceControl.Transaction) {
+    override fun release(viewHost: WindowDecorViewHost, t: SurfaceControl.Transaction) {
         viewHost.release(t)
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt
index 7c1479e..2dcbbac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt
@@ -16,6 +16,7 @@
 package com.android.wm.shell.windowdecor.common.viewhost
 
 import android.content.res.Configuration
+import android.graphics.Region
 import android.view.SurfaceControl
 import android.view.View
 import android.view.WindowManager
@@ -34,11 +35,17 @@
         view: View,
         attrs: WindowManager.LayoutParams,
         configuration: Configuration,
-        onDrawTransaction: SurfaceControl.Transaction?,
+        touchableRegion: Region? = null,
+        onDrawTransaction: SurfaceControl.Transaction? = null,
     )
 
     /** Asynchronously update the view hierarchy of this view host. */
-    fun updateViewAsync(view: View, attrs: WindowManager.LayoutParams, configuration: Configuration)
+    fun updateViewAsync(
+        view: View,
+        attrs: WindowManager.LayoutParams,
+        configuration: Configuration,
+        touchableRegion: Region? = null,
+    )
 
     /** Releases the underlying [View] hierarchy and removes the backing [SurfaceControl]. */
     fun release(t: SurfaceControl.Transaction)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorWindowlessWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorWindowlessWindowManager.kt
new file mode 100644
index 0000000..fbe8c6c8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorWindowlessWindowManager.kt
@@ -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.
+ */
+package com.android.wm.shell.windowdecor.common.viewhost
+
+import android.content.res.Configuration
+import android.graphics.Region
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.WindowlessWindowManager
+
+/**
+ * A [WindowlessWindowManager] for the window decor caption that allows customizing the touchable
+ * region.
+ */
+class WindowDecorWindowlessWindowManager(
+    configuration: Configuration,
+    rootSurface: SurfaceControl,
+) : WindowlessWindowManager(configuration, rootSurface, /* hostInputTransferToken= */ null) {
+
+    /** Set the view host's touchable region. */
+    fun setTouchRegion(viewHost: SurfaceControlViewHost, region: Region?) {
+        setTouchRegion(viewHost.windowToken.asBinder(), region)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index c10434a..8e21071 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -3305,44 +3305,41 @@
     setUpLandscapeDisplay()
     val task = setUpFreeformTask()
     val taskToRequest = setUpFreeformTask()
-    val wctCaptor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
     runOpenInstance(task, taskToRequest.taskId)
-    verify(transitions).startTransition(anyInt(), wctCaptor.capture(), anyOrNull())
-    assertThat(ActivityOptions.fromBundle(wctCaptor.value.hierarchyOps[0].launchOptions)
-      .launchWindowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
+    verify(desktopMixedTransitionHandler).startLaunchTransition(anyInt(), any(), anyInt(),
+      anyOrNull(), anyOrNull())
+    val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT)
+    assertThat(wct.hierarchyOps).hasSize(1)
+    wct.assertReorderAt(index = 0, taskToRequest)
   }
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
   fun openInstance_fromFreeform_minimizesIfNeeded() {
     setUpLandscapeDisplay()
-    val homeTask = setUpHomeTask()
     val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() }
     val oldestTask = freeformTasks.first()
     val newestTask = freeformTasks.last()
 
+    val transition = Binder()
+    val wctCaptor = argumentCaptor<WindowContainerTransaction>()
+    whenever(desktopMixedTransitionHandler.startLaunchTransition(anyInt(), wctCaptor.capture(),
+      anyInt(), anyOrNull(), anyOrNull()
+    ))
+      .thenReturn(transition)
+
     runOpenInstance(newestTask, freeformTasks[1].taskId)
 
-    val wct = getLatestWct(type = TRANSIT_OPEN)
-    // Home is moved to front of everything.
-    assertThat(
-      wct.hierarchyOps.any { hop ->
-        hop.container == homeTask.token.asBinder() && hop.toTop
-      }
-    ).isTrue()
-    // And the oldest task isn't moved in front of home, effectively minimizing it.
-    assertThat(
-      wct.hierarchyOps.none { hop ->
-        hop.container == oldestTask.token.asBinder() && hop.toTop
-      }
-    ).isTrue()
+    val wct = wctCaptor.firstValue
+    assertThat(wct.hierarchyOps.size).isEqualTo(2) // move-to-front + minimize
+    wct.assertReorderAt(0, freeformTasks[1], toTop = true)
+    wct.assertReorderAt(1, oldestTask, toTop = false)
   }
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
   fun openInstance_fromFreeform_exitsImmersiveIfNeeded() {
     setUpLandscapeDisplay()
-    val homeTask = setUpHomeTask()
     val freeformTask = setUpFreeformTask()
     val immersiveTask = setUpFreeformTask()
     taskRepository.setTaskInFullImmersiveState(
@@ -3352,11 +3349,13 @@
     )
     val runOnStartTransit = RunOnStartTransitionCallback()
     val transition = Binder()
-    whenever(transitions.startTransition(eq(TRANSIT_OPEN), any(), anyOrNull()))
+    whenever(desktopMixedTransitionHandler.startLaunchTransition(anyInt(), any(), anyInt(),
+      anyOrNull(), anyOrNull()
+    ))
       .thenReturn(transition)
     whenever(mMockDesktopImmersiveController
       .exitImmersiveIfApplicable(
-        any(), eq(immersiveTask.displayId), eq(freeformTask.taskId), any()))
+        any(), eq(DEFAULT_DISPLAY), eq(freeformTask.taskId), any()))
       .thenReturn(
         ExitResult.Exit(
         exitingTask = immersiveTask.taskId,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index afd4607..dce44b7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -74,6 +74,8 @@
 import com.android.wm.shell.util.StubTransaction
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier
 import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder
 import org.junit.After
 import org.junit.Rule
@@ -131,6 +133,8 @@
     protected val mockAssistContentRequester = mock<AssistContentRequester>()
     protected val bgExecutor = TestShellExecutor()
     protected val mockMultiInstanceHelper = mock<MultiInstanceHelper>()
+    private val mockWindowDecorViewHostSupplier =
+        mock<WindowDecorViewHostSupplier<WindowDecorViewHost>>()
     protected val mockTasksLimiter = mock<DesktopTasksLimiter>()
     protected val mockFreeformTaskTransitionStarter = mock<FreeformTaskTransitionStarter>()
     protected val mockActivityOrientationChangeHandler =
@@ -193,6 +197,7 @@
             mockDesktopImmersiveController,
             mockGenericLinksParser,
             mockAssistContentRequester,
+            mockWindowDecorViewHostSupplier,
             mockMultiInstanceHelper,
             mockDesktopModeWindowDecorFactory,
             mockInputMonitorFactory,
@@ -289,7 +294,7 @@
         whenever(
             mockDesktopModeWindowDecorFactory.create(
                 any(), any(), any(), any(), any(), any(), eq(task), any(), any(), any(), any(),
-                any(), any(), any(), any(), any(), any(), any(), any())
+                any(), any(), any(), any(), any(), any(), any(), any(), any())
         ).thenReturn(decoration)
         decoration.mTaskInfo = task
         whenever(decoration.user).thenReturn(mockUserHandle)
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 f4cd8e0..5d5d1f2 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
@@ -39,7 +39,6 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
@@ -114,6 +113,8 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
 import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
 
 import kotlin.Unit;
@@ -186,6 +187,10 @@
     @Mock
     private AttachedSurfaceControl mMockRootSurfaceControl;
     @Mock
+    private WindowDecorViewHostSupplier<WindowDecorViewHost> mMockWindowDecorViewHostSupplier;
+    @Mock
+    private WindowDecorViewHost mMockWindowDecorViewHost;
+    @Mock
     private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory;
     @Mock
     private TypedArray mMockRoundedCornersRadiusArray;
@@ -275,6 +280,9 @@
                 any())).thenReturn(mMockAppHeaderViewHolder);
         when(mMockDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository);
         when(mMockDesktopUserRepositories.getProfile(anyInt())).thenReturn(mDesktopRepository);
+        when(mMockWindowDecorViewHostSupplier.acquire(any(), eq(defaultDisplay)))
+                .thenReturn(mMockWindowDecorViewHost);
+        when(mMockWindowDecorViewHost.getSurfaceControl()).thenReturn(mock(SurfaceControl.class));
     }
 
     @After
@@ -1327,68 +1335,41 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
-    public void capturedLink_postsOnCapturedLinkExpiredRunnable() {
-        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
-        final DesktopModeWindowDecoration decor = createWindowDecoration(
-                taskInfo, TEST_URI1 /* captured link */, null /* web uri */,
-                null /* generic link */);
-        final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
-
-        // Run runnable to set captured link to expired
-        verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
-        runnableArgument.getValue().run();
-
-        // Verify captured link is no longer valid by verifying link is not set as handle menu
-        // browser link.
-        createHandleMenu(decor);
-        verifyHandleMenuCreated(null /* uri */);
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
     public void capturedLink_capturedLinkNotResetToSameLink() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
         final DesktopModeWindowDecoration decor = createWindowDecoration(
                 taskInfo, TEST_URI1 /* captured link */, null /* web uri */,
                 null /* generic link */);
-        final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
+        final ArgumentCaptor<Function1<Intent, Unit>> openInBrowserCaptor =
+                ArgumentCaptor.forClass(Function1.class);
 
-        // Run runnable to set captured link to expired
-        verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
-        runnableArgument.getValue().run();
+        createHandleMenu(decor);
+        verify(mMockHandleMenu).show(any(),
+                any(),
+                any(),
+                any(),
+                any(),
+                any(),
+                openInBrowserCaptor.capture(),
+                any(),
+                any(),
+                any(),
+                anyBoolean()
+        );
+        // Run runnable to set captured link to used
+        openInBrowserCaptor.getValue().invoke(new Intent(Intent.ACTION_MAIN, TEST_URI1));
 
         // Relayout decor with same captured link
         decor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
 
-        // Verify handle menu's browser link not set to captured link since link is expired
+        // Verify handle menu's browser link not set to captured link since link is already used
         createHandleMenu(decor);
         verifyHandleMenuCreated(null /* uri */);
     }
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
-    public void capturedLink_capturedLinkStillUsedIfExpiredAfterHandleMenuCreation() {
-        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
-        final DesktopModeWindowDecoration decor = createWindowDecoration(
-                taskInfo, TEST_URI1 /* captured link */, null /* web uri */,
-                null /* generic link */);
-        final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
-
-        // Create handle menu before link expires
-        createHandleMenu(decor);
-
-        // Run runnable to set captured link to expired
-        verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
-        runnableArgument.getValue().run();
-
-        // Verify handle menu's browser link is set to captured link since menu was opened before
-        // captured link expired
-        verifyHandleMenuCreated(TEST_URI1);
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
-    public void capturedLink_capturedLinkExpiresAfterClick() {
+    public void capturedLink_capturedLinkSetToUsedAfterClick() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
         final DesktopModeWindowDecoration decor = createWindowDecoration(
                 taskInfo, TEST_URI1 /* captured link */, null /* web uri */,
@@ -1750,7 +1731,7 @@
                 mMockGenericLinksParser, mMockAssistContentRequester, SurfaceControl.Builder::new,
                 mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new,
                 new WindowManagerWrapper(mMockWindowManager), mMockSurfaceControlViewHostFactory,
-                maximizeMenuFactory, mMockHandleMenuFactory,
+                mMockWindowDecorViewHostSupplier, maximizeMenuFactory, mMockHandleMenuFactory,
                 mMockMultiInstanceHelper, mMockCaptionHandleRepository, mDesktopModeEventLogger);
         windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener,
                 mMockTouchEventListener, mMockTouchEventListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 3a82ff6..d969346 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -89,6 +89,8 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.tests.R;
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -131,6 +133,10 @@
     @Mock
     private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory;
     @Mock
+    private WindowDecorViewHostSupplier<WindowDecorViewHost> mMockWindowDecorViewHostSupplier;
+    @Mock
+    private WindowDecorViewHost mMockWindowDecorViewHost;
+    @Mock
     private SurfaceControlViewHost mMockSurfaceControlViewHost;
     @Mock
     private AttachedSurfaceControl mMockRootSurfaceControl;
@@ -180,6 +186,10 @@
         // Add status bar inset so that WindowDecoration does not think task is in immersive mode
         mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars()).setVisible(true);
         doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
+
+        when(mMockWindowDecorViewHostSupplier.acquire(any(), any()))
+                .thenReturn(mMockWindowDecorViewHost);
+        when(mMockWindowDecorViewHost.getSurfaceControl()).thenReturn(mock(SurfaceControl.class));
     }
 
     @Test
@@ -236,10 +246,6 @@
         final SurfaceControl.Builder decorContainerSurfaceBuilder =
                 createMockSurfaceControlBuilder(decorContainerSurface);
         mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
-        final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
-        final SurfaceControl.Builder captionContainerSurfaceBuilder =
-                createMockSurfaceControlBuilder(captionContainerSurface);
-        mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
 
         final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
                 .setDisplayId(Display.DEFAULT_DISPLAY)
@@ -260,18 +266,19 @@
         verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true);
         verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100);
 
-        verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
-        verify(captionContainerSurfaceBuilder).setContainerLayer();
+        final SurfaceControl captionContainerSurface = mMockWindowDecorViewHost.getSurfaceControl();
+        verify(mMockSurfaceControlStartT).reparent(captionContainerSurface, decorContainerSurface);
         verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
         verify(mMockSurfaceControlStartT).show(captionContainerSurface);
 
-        verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
-
-        verify(mMockSurfaceControlViewHost)
-                .setView(same(mMockView),
-                        argThat(lp -> lp.height == 64
-                                && lp.width == 300
-                                && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0));
+        verify(mMockWindowDecorViewHost).updateView(
+                same(mMockView),
+                argThat(lp -> lp.height == 64
+                        && lp.width == 300
+                        && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0),
+                eq(taskInfo.configuration),
+                any(),
+                eq(null) /* onDrawTransaction */);
         verify(mMockView).setTaskFocusState(true);
         verify(mMockWindowContainerTransaction).addInsetsSource(
                 eq(taskInfo.token),
@@ -300,10 +307,6 @@
         final SurfaceControl.Builder decorContainerSurfaceBuilder =
                 createMockSurfaceControlBuilder(decorContainerSurface);
         mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
-        final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
-        final SurfaceControl.Builder captionContainerSurfaceBuilder =
-                createMockSurfaceControlBuilder(captionContainerSurface);
-        mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
 
         final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
         mMockSurfaceControlTransactions.add(t);
@@ -326,7 +329,7 @@
 
         windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
 
-        verify(mMockSurfaceControlViewHost, never()).release();
+        verify(mMockWindowDecorViewHost, never()).release(any());
         verify(t, never()).apply();
         verify(mMockWindowContainerTransaction, never())
                 .removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt());
@@ -336,9 +339,8 @@
         taskInfo.isVisible = false;
         windowDecor.relayout(taskInfo, false /* hasGlobalFocus */);
 
-        final InOrder releaseOrder = inOrder(t2, mMockSurfaceControlViewHost);
-        releaseOrder.verify(mMockSurfaceControlViewHost).release();
-        releaseOrder.verify(t2).remove(captionContainerSurface);
+        final InOrder releaseOrder = inOrder(t2, mMockWindowDecorViewHostSupplier);
+        releaseOrder.verify(mMockWindowDecorViewHostSupplier).release(mMockWindowDecorViewHost, t2);
         releaseOrder.verify(t2).remove(decorContainerSurface);
         releaseOrder.verify(t2).apply();
         // Expect to remove two insets sources, the caption insets and the mandatory gesture insets.
@@ -386,8 +388,8 @@
         verify(mMockDisplayController).removeDisplayWindowListener(same(listener));
 
         assertThat(mRelayoutResult.mRootView).isSameInstanceAs(mMockView);
-        verify(mMockSurfaceControlViewHostFactory).create(any(), eq(mockDisplay), any());
-        verify(mMockSurfaceControlViewHost).setView(same(mMockView), any());
+        verify(mMockWindowDecorViewHostSupplier).acquire(any(), eq(mockDisplay));
+        verify(mMockWindowDecorViewHost).updateView(same(mMockView), any(), any(), any(), any());
     }
 
     @Test
@@ -400,10 +402,6 @@
         final SurfaceControl.Builder decorContainerSurfaceBuilder =
                 createMockSurfaceControlBuilder(decorContainerSurface);
         mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
-        final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
-        final SurfaceControl.Builder captionContainerSurfaceBuilder =
-                createMockSurfaceControlBuilder(captionContainerSurface);
-        mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
 
         final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
         mMockSurfaceControlTransactions.add(t);
@@ -439,8 +437,7 @@
                 windowDecor.mDecorWindowContext.getResources(), mRelayoutParams.mCaptionHeightId);
         verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, width, height);
         verify(mMockSurfaceControlAddWindowT).show(additionalWindowSurface);
-        verify(mMockSurfaceControlViewHostFactory, Mockito.times(2))
-                .create(any(), eq(defaultDisplay), any());
+        verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
     }
 
     @Test
@@ -453,10 +450,6 @@
         final SurfaceControl.Builder decorContainerSurfaceBuilder =
                 createMockSurfaceControlBuilder(decorContainerSurface);
         mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
-        final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
-        final SurfaceControl.Builder captionContainerSurfaceBuilder =
-                createMockSurfaceControlBuilder(captionContainerSurface);
-        mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
 
         final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
         mMockSurfaceControlTransactions.add(t);
@@ -475,8 +468,8 @@
 
         windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
 
-        verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
-        verify(captionContainerSurfaceBuilder).setContainerLayer();
+        final SurfaceControl captionContainerSurface = mMockWindowDecorViewHost.getSurfaceControl();
+        verify(mMockSurfaceControlStartT).reparent(captionContainerSurface, decorContainerSurface);
         // Width of the captionContainerSurface should match the width of TASK_BOUNDS
         verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
         verify(mMockSurfaceControlStartT).show(captionContainerSurface);
@@ -492,10 +485,6 @@
         final SurfaceControl.Builder decorContainerSurfaceBuilder =
                 createMockSurfaceControlBuilder(decorContainerSurface);
         mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
-        final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
-        final SurfaceControl.Builder captionContainerSurfaceBuilder =
-                createMockSurfaceControlBuilder(captionContainerSurface);
-        mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
 
         final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
         mMockSurfaceControlTransactions.add(t);
@@ -512,10 +501,11 @@
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
 
-        windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */,
-                true /* hasGlobalFocus */, Region.obtain());
+        mRelayoutParams.mApplyStartTransactionOnDraw = true;
+        windowDecor.relayout(taskInfo, true /* hasGlobalFocus */, Region.obtain());
 
-        verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT);
+        verify(mMockWindowDecorViewHost).updateView(any(), any(), any(), any(),
+                eq(mMockSurfaceControlStartT));
     }
 
     @Test
@@ -918,7 +908,13 @@
                 /* hasGlobalFocus= */ true,
                 Region.obtain());
 
-        verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT);
+        verify(mMockWindowDecorViewHost)
+                .updateView(
+                        eq(mRelayoutResult.mRootView),
+                        any(),
+                        eq(windowDecor.mTaskInfo.configuration),
+                        any(),
+                        eq(mMockSurfaceControlStartT));
         windowDecor.close();
     }
 
@@ -933,15 +929,11 @@
         mRelayoutParams.mAsyncViewHost = true;
         mRelayoutResult.mRootView = mMockView;
 
-        windowDecor.relayout(
-                windowDecor.mTaskInfo,
-                /* hasGlobalFocus= */ true,
-                Region.obtain());
-        final ArgumentCaptor<Runnable> updateViewHostCaptor =
-                ArgumentCaptor.forClass(Runnable.class);
-        verify(mMockHandler).post(updateViewHostCaptor.capture());
         assertThrows(IllegalArgumentException.class,
-                () -> updateViewHostCaptor.getValue().run());
+                () -> windowDecor.relayout(
+                        windowDecor.mTaskInfo,
+                        /* hasGlobalFocus= */ true,
+                        Region.obtain()));
         windowDecor.close();
     }
 
@@ -961,13 +953,9 @@
                 /* hasGlobalFocus= */ true,
                 Region.obtain());
 
-        final ArgumentCaptor<Runnable> updateViewHostCaptor =
-                ArgumentCaptor.forClass(Runnable.class);
-        verify(mMockHandler).post(updateViewHostCaptor.capture());
-
-        updateViewHostCaptor.getValue().run();
-
-        verify(mMockSurfaceControlViewHost).setView(eq(mMockView), any());
+        verify(mMockWindowDecorViewHost)
+                .updateViewAsync(eq(mRelayoutResult.mRootView), any(),
+                        eq(windowDecor.mTaskInfo.configuration), any());
         windowDecor.close();
     }
 
@@ -1046,13 +1034,14 @@
 
     private TestWindowDecoration createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo) {
         return new TestWindowDecoration(mContext, mContext, mMockDisplayController,
-                mMockShellTaskOrganizer, mMockHandler, taskInfo, mMockTaskSurface,
+                mMockShellTaskOrganizer, taskInfo, mMockTaskSurface,
                 new MockObjectSupplier<>(mMockSurfaceControlBuilders,
                         () -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))),
                 new MockObjectSupplier<>(mMockSurfaceControlTransactions,
                         () -> mock(SurfaceControl.Transaction.class)),
                 () -> mMockWindowContainerTransaction, () -> mMockTaskSurface,
-                mMockSurfaceControlViewHostFactory, mDesktopModeEventLogger);
+                mMockSurfaceControlViewHostFactory, mMockWindowDecorViewHostSupplier,
+                mDesktopModeEventLogger);
     }
 
     private class MockObjectSupplier<T> implements Supplier<T> {
@@ -1087,7 +1076,6 @@
         TestWindowDecoration(Context context, @NonNull Context userContext,
                 DisplayController displayController,
                 ShellTaskOrganizer taskOrganizer,
-                Handler handler,
                 ActivityManager.RunningTaskInfo taskInfo,
                 SurfaceControl taskSurface,
                 Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
@@ -1095,11 +1083,14 @@
                 Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
                 Supplier<SurfaceControl> surfaceControlSupplier,
                 SurfaceControlViewHostFactory surfaceControlViewHostFactory,
+                @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost>
+                        windowDecorViewHostSupplier,
                 DesktopModeEventLogger desktopModeEventLogger) {
-            super(context, userContext, displayController, taskOrganizer, handler, taskInfo,
+            super(context, userContext, displayController, taskOrganizer, taskInfo,
                     taskSurface, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
                     windowContainerTransactionSupplier, surfaceControlSupplier,
-                    surfaceControlViewHostFactory, desktopModeEventLogger);
+                    surfaceControlViewHostFactory, windowDecorViewHostSupplier,
+                    desktopModeEventLogger);
         }
 
         void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus) {
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index dd5067a..54f172c 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -189,6 +189,7 @@
      * the device.
      *
      * @see #getType
+     * @see AudioDeviceInfo#TYPE_BUILTIN_SPEAKER
      */
     public static final int TYPE_BUILTIN_SPEAKER = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
 
@@ -196,6 +197,7 @@
      * Indicates the route is a headset, which is the combination of a headphones and a microphone.
      *
      * @see #getType
+     * @see AudioDeviceInfo#TYPE_WIRED_HEADSET
      */
     public static final int TYPE_WIRED_HEADSET = AudioDeviceInfo.TYPE_WIRED_HEADSET;
 
@@ -203,6 +205,7 @@
      * Indicates the route is a pair of wired headphones.
      *
      * @see #getType
+     * @see AudioDeviceInfo#TYPE_WIRED_HEADPHONES
      */
     public static final int TYPE_WIRED_HEADPHONES = AudioDeviceInfo.TYPE_WIRED_HEADPHONES;
 
@@ -210,6 +213,7 @@
      * Indicates the route is a bluetooth device, such as a bluetooth speaker or headphones.
      *
      * @see #getType
+     * @see AudioDeviceInfo#TYPE_BLUETOOTH_A2DP
      */
     public static final int TYPE_BLUETOOTH_A2DP = AudioDeviceInfo.TYPE_BLUETOOTH_A2DP;
 
@@ -217,6 +221,7 @@
      * Indicates the route is an HDMI connection.
      *
      * @see #getType
+     * @see AudioDeviceInfo#TYPE_HDMI
      */
     public static final int TYPE_HDMI = AudioDeviceInfo.TYPE_HDMI;
 
@@ -224,6 +229,7 @@
      * Indicates the route is an Audio Return Channel of an HDMI connection.
      *
      * @see #getType
+     * @see AudioDeviceInfo#TYPE_HDMI_ARC
      */
     @FlaggedApi(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
     public static final int TYPE_HDMI_ARC = AudioDeviceInfo.TYPE_HDMI_ARC;
@@ -232,24 +238,34 @@
      * Indicates the route is an Enhanced Audio Return Channel of an HDMI connection.
      *
      * @see #getType
+     * @see AudioDeviceInfo#TYPE_HDMI_EARC
      */
     @FlaggedApi(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
     public static final int TYPE_HDMI_EARC = AudioDeviceInfo.TYPE_HDMI_EARC;
 
     /**
      * Indicates the route is a digital line connection (for example S/PDIF).
+     *
+     * @see #getType
+     * @see AudioDeviceInfo#TYPE_LINE_DIGITAL
      */
     @FlaggedApi(FLAG_ENABLE_NEW_WIRED_MEDIA_ROUTE_2_INFO_TYPES)
     public static final int TYPE_LINE_DIGITAL = AudioDeviceInfo.TYPE_LINE_DIGITAL;
 
     /**
      * Indicates the route is an analog line-level connection.
+     *
+     * @see #getType
+     * @see AudioDeviceInfo#TYPE_LINE_ANALOG
      */
     @FlaggedApi(FLAG_ENABLE_NEW_WIRED_MEDIA_ROUTE_2_INFO_TYPES)
     public static final int TYPE_LINE_ANALOG = AudioDeviceInfo.TYPE_LINE_ANALOG;
 
     /**
      * Indicates the route is using the auxiliary line-level connectors.
+     *
+     * @see #getType
+     * @see AudioDeviceInfo#TYPE_AUX_LINE
      */
     @FlaggedApi(FLAG_ENABLE_NEW_WIRED_MEDIA_ROUTE_2_INFO_TYPES)
     public static final int TYPE_AUX_LINE = AudioDeviceInfo.TYPE_AUX_LINE;
@@ -258,6 +274,7 @@
      * Indicates the route is a USB audio device.
      *
      * @see #getType
+     * @see AudioDeviceInfo#TYPE_USB_DEVICE
      */
     public static final int TYPE_USB_DEVICE = AudioDeviceInfo.TYPE_USB_DEVICE;
 
@@ -265,6 +282,7 @@
      * Indicates the route is a USB audio device in accessory mode.
      *
      * @see #getType
+     * @see AudioDeviceInfo#TYPE_USB_ACCESSORY
      */
     public static final int TYPE_USB_ACCESSORY = AudioDeviceInfo.TYPE_USB_ACCESSORY;
 
@@ -272,6 +290,7 @@
      * Indicates the route is the audio device associated with a dock.
      *
      * @see #getType
+     * @see AudioDeviceInfo#TYPE_DOCK
      */
     public static final int TYPE_DOCK = AudioDeviceInfo.TYPE_DOCK;
 
@@ -279,6 +298,7 @@
      * Indicates the route is a USB audio headset.
      *
      * @see #getType
+     * @see AudioDeviceInfo#TYPE_USB_HEADSET
      */
     public static final int TYPE_USB_HEADSET = AudioDeviceInfo.TYPE_USB_HEADSET;
 
@@ -286,6 +306,7 @@
      * Indicates the route is a hearing aid.
      *
      * @see #getType
+     * @see AudioDeviceInfo#TYPE_HEARING_AID
      */
     public static final int TYPE_HEARING_AID = AudioDeviceInfo.TYPE_HEARING_AID;
 
@@ -293,6 +314,7 @@
      * Indicates the route is a Bluetooth Low Energy (BLE) HEADSET.
      *
      * @see #getType
+     * @see AudioDeviceInfo#TYPE_BLE_HEADSET
      */
     public static final int TYPE_BLE_HEADSET = AudioDeviceInfo.TYPE_BLE_HEADSET;
 
@@ -304,6 +326,7 @@
      * to provide a better experience on multichannel contents.
      *
      * @see #getType
+     * @see AudioDeviceInfo#TYPE_MULTICHANNEL_GROUP
      */
     @FlaggedApi(FLAG_ENABLE_MULTICHANNEL_GROUP_DEVICE)
     public static final int TYPE_MULTICHANNEL_SPEAKER_GROUP =
diff --git a/native/android/Android.bp b/native/android/Android.bp
index cd6de5a..129d616 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -73,6 +73,7 @@
         "surface_control.cpp",
         "surface_texture.cpp",
         "system_fonts.cpp",
+        "system_health.cpp",
         "trace.cpp",
         "thermal.cpp",
     ],
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 78a7ddb..8dd8830 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -320,6 +320,23 @@
     ASystemFontIterator_open; # introduced=29
     ASystemFontIterator_close; # introduced=29
     ASystemFontIterator_next; # introduced=29
+    ASystemHealth_getCpuHeadroom; # introduced=36
+    ASystemHealth_getGpuHeadroom; # introduced=36
+    ASystemHealth_getCpuHeadroomMinIntervalMillis; # introduced=36
+    ASystemHealth_getGpuHeadroomMinIntervalMillis; # introduced=36
+    ACpuHeadroomParams_create; # introduced=36
+    ACpuHeadroomParams_destroy; # introduced=36
+    ACpuHeadroomParams_setCalculationType; # introduced=36
+    ACpuHeadroomParams_getCalculationType; # introduced=36
+    ACpuHeadroomParams_setCalculationWindowMillis; # introduced=36
+    ACpuHeadroomParams_getCalculationWindowMillis; # introduced=36
+    ACpuHeadroomParams_setTids; # introduced=36
+    AGpuHeadroomParams_create; # introduced=36
+    AGpuHeadroomParams_destroy; # introduced=36
+    AGpuHeadroomParams_setCalculationType; # introduced=36
+    AGpuHeadroomParams_getCalculationType; # introduced=36
+    AGpuHeadroomParams_setCalculationWindowMillis; # introduced=36
+    AGpuHeadroomParams_getCalculationWindowMillis; # introduced=36
     AFont_close; # introduced=29
     AFont_getFontFilePath; # introduced=29
     AFont_getWeight; # introduced=29
diff --git a/native/android/system_health.cpp b/native/android/system_health.cpp
new file mode 100644
index 0000000..f3fa9f6
--- /dev/null
+++ b/native/android/system_health.cpp
@@ -0,0 +1,278 @@
+/*
+ * 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 <aidl/android/hardware/power/CpuHeadroomParams.h>
+#include <aidl/android/hardware/power/GpuHeadroomParams.h>
+#include <aidl/android/os/CpuHeadroomParamsInternal.h>
+#include <aidl/android/os/GpuHeadroomParamsInternal.h>
+#include <aidl/android/os/IHintManager.h>
+#include <android/binder_manager.h>
+#include <android/system_health.h>
+#include <binder/IServiceManager.h>
+#include <binder/Status.h>
+
+using namespace android;
+using namespace aidl::android::os;
+namespace hal = aidl::android::hardware::power;
+
+struct ACpuHeadroomParams : public CpuHeadroomParamsInternal {};
+struct AGpuHeadroomParams : public GpuHeadroomParamsInternal {};
+
+const int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50;
+const int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000;
+const int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50;
+const int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000;
+const int CPU_HEADROOM_MAX_TID_COUNT = 5;
+
+struct ASystemHealthManager {
+public:
+    static ASystemHealthManager* getInstance();
+    ASystemHealthManager(std::shared_ptr<IHintManager>& hintManager);
+    ASystemHealthManager() = delete;
+    ~ASystemHealthManager();
+    int getCpuHeadroom(const ACpuHeadroomParams* params, float* outHeadroom);
+    int getGpuHeadroom(const AGpuHeadroomParams* params, float* outHeadroom);
+    int getCpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis);
+    int getGpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis);
+
+private:
+    static ASystemHealthManager* create(std::shared_ptr<IHintManager> hintManager);
+    std::shared_ptr<IHintManager> mHintManager;
+};
+
+ASystemHealthManager* ASystemHealthManager::getInstance() {
+    static std::once_flag creationFlag;
+    static ASystemHealthManager* instance = nullptr;
+    std::call_once(creationFlag, []() { instance = create(nullptr); });
+    return instance;
+}
+
+ASystemHealthManager::ASystemHealthManager(std::shared_ptr<IHintManager>& hintManager)
+      : mHintManager(std::move(hintManager)) {}
+
+ASystemHealthManager::~ASystemHealthManager() {}
+
+ASystemHealthManager* ASystemHealthManager::create(std::shared_ptr<IHintManager> hintManager) {
+    if (!hintManager) {
+        hintManager = IHintManager::fromBinder(
+                ndk::SpAIBinder(AServiceManager_waitForService("performance_hint")));
+    }
+    if (hintManager == nullptr) {
+        ALOGE("%s: PerformanceHint service is not ready ", __FUNCTION__);
+        return nullptr;
+    }
+    return new ASystemHealthManager(hintManager);
+}
+
+ASystemHealthManager* ASystemHealth_acquireManager() {
+    return ASystemHealthManager::getInstance();
+}
+
+int ASystemHealthManager::getCpuHeadroom(const ACpuHeadroomParams* params, float* outHeadroom) {
+    std::optional<hal::CpuHeadroomResult> res;
+    ::ndk::ScopedAStatus ret;
+    CpuHeadroomParamsInternal internalParams;
+    if (!params) {
+        ret = mHintManager->getCpuHeadroom(internalParams, &res);
+    } else {
+        ret = mHintManager->getCpuHeadroom(*params, &res);
+    }
+    if (!ret.isOk()) {
+        LOG_ALWAYS_FATAL_IF(ret.getExceptionCode() == EX_ILLEGAL_ARGUMENT,
+                            "Invalid ACpuHeadroomParams: %s", ret.getMessage());
+        ALOGE("ASystemHealth_getCpuHeadroom fails: %s", ret.getMessage());
+        if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+            return ENOTSUP;
+        } else if (ret.getExceptionCode() == EX_SECURITY) {
+            return EPERM;
+        }
+        return EPIPE;
+    }
+    *outHeadroom = res->get<hal::CpuHeadroomResult::Tag::globalHeadroom>();
+    return OK;
+}
+
+int ASystemHealthManager::getGpuHeadroom(const AGpuHeadroomParams* params, float* outHeadroom) {
+    std::optional<hal::GpuHeadroomResult> res;
+    ::ndk::ScopedAStatus ret;
+    GpuHeadroomParamsInternal internalParams;
+    if (!params) {
+        ret = mHintManager->getGpuHeadroom(internalParams, &res);
+    } else {
+        ret = mHintManager->getGpuHeadroom(*params, &res);
+    }
+    if (!ret.isOk()) {
+        LOG_ALWAYS_FATAL_IF(ret.getExceptionCode() == EX_ILLEGAL_ARGUMENT,
+                            "Invalid AGpuHeadroomParams: %s", ret.getMessage());
+        ALOGE("ASystemHealth_getGpuHeadroom fails: %s", ret.getMessage());
+        if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+            return ENOTSUP;
+        }
+        return EPIPE;
+    }
+    *outHeadroom = res->get<hal::GpuHeadroomResult::Tag::globalHeadroom>();
+    return OK;
+}
+
+int ASystemHealthManager::getCpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis) {
+    int64_t minIntervalMillis = 0;
+    ::ndk::ScopedAStatus ret = mHintManager->getCpuHeadroomMinIntervalMillis(&minIntervalMillis);
+    if (!ret.isOk()) {
+        ALOGE("ASystemHealth_getCpuHeadroomMinIntervalMillis fails: %s", ret.getMessage());
+        if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+            return ENOTSUP;
+        }
+        return EPIPE;
+    }
+    *outMinIntervalMillis = minIntervalMillis;
+    return OK;
+}
+
+int ASystemHealthManager::getGpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis) {
+    int64_t minIntervalMillis = 0;
+    ::ndk::ScopedAStatus ret = mHintManager->getGpuHeadroomMinIntervalMillis(&minIntervalMillis);
+    if (!ret.isOk()) {
+        ALOGE("ASystemHealth_getGpuHeadroomMinIntervalMillis fails: %s", ret.getMessage());
+        if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+            return ENOTSUP;
+        }
+        return EPIPE;
+    }
+    *outMinIntervalMillis = minIntervalMillis;
+    return OK;
+}
+
+int ASystemHealth_getCpuHeadroom(const ACpuHeadroomParams* _Nullable params,
+                                 float* _Nonnull outHeadroom) {
+    LOG_ALWAYS_FATAL_IF(outHeadroom == nullptr, "%s: outHeadroom should not be null", __FUNCTION__);
+    auto manager = ASystemHealthManager::getInstance();
+    if (manager == nullptr) return ENOTSUP;
+    return manager->getCpuHeadroom(params, outHeadroom);
+}
+
+int ASystemHealth_getGpuHeadroom(const AGpuHeadroomParams* _Nullable params,
+                                 float* _Nonnull outHeadroom) {
+    LOG_ALWAYS_FATAL_IF(outHeadroom == nullptr, "%s: outHeadroom should not be null", __FUNCTION__);
+    auto manager = ASystemHealthManager::getInstance();
+    if (manager == nullptr) return ENOTSUP;
+    return manager->getGpuHeadroom(params, outHeadroom);
+}
+
+int ASystemHealth_getCpuHeadroomMinIntervalMillis(int64_t* _Nonnull outMinIntervalMillis) {
+    LOG_ALWAYS_FATAL_IF(outMinIntervalMillis == nullptr,
+                        "%s: outMinIntervalMillis should not be null", __FUNCTION__);
+    auto manager = ASystemHealthManager::getInstance();
+    if (manager == nullptr) return ENOTSUP;
+    return manager->getCpuHeadroomMinIntervalMillis(outMinIntervalMillis);
+}
+
+int ASystemHealth_getGpuHeadroomMinIntervalMillis(int64_t* _Nonnull outMinIntervalMillis) {
+    LOG_ALWAYS_FATAL_IF(outMinIntervalMillis == nullptr,
+                        "%s: outMinIntervalMillis should not be null", __FUNCTION__);
+    auto manager = ASystemHealthManager::getInstance();
+    if (manager == nullptr) return ENOTSUP;
+    return manager->getGpuHeadroomMinIntervalMillis(outMinIntervalMillis);
+}
+
+void ACpuHeadroomParams_setCalculationWindowMillis(ACpuHeadroomParams* _Nonnull params,
+                                                   int windowMillis) {
+    LOG_ALWAYS_FATAL_IF(windowMillis < CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN ||
+                                windowMillis > CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX,
+                        "%s: windowMillis should be in range [50, 10000] but got %d", __FUNCTION__,
+                        windowMillis);
+    params->calculationWindowMillis = windowMillis;
+}
+
+void AGpuHeadroomParams_setCalculationWindowMillis(AGpuHeadroomParams* _Nonnull params,
+                                                   int windowMillis) {
+    LOG_ALWAYS_FATAL_IF(windowMillis < GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN ||
+                                windowMillis > GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX,
+                        "%s: windowMillis should be in range [50, 10000] but got %d", __FUNCTION__,
+                        windowMillis);
+    params->calculationWindowMillis = windowMillis;
+}
+
+int ACpuHeadroomParams_getCalculationWindowMillis(ACpuHeadroomParams* _Nonnull params) {
+    return params->calculationWindowMillis;
+}
+
+int AGpuHeadroomParams_getCalculationWindowMillis(AGpuHeadroomParams* _Nonnull params) {
+    return params->calculationWindowMillis;
+}
+
+void ACpuHeadroomParams_setTids(ACpuHeadroomParams* _Nonnull params, const int* _Nonnull tids,
+                                int tidsSize) {
+    LOG_ALWAYS_FATAL_IF(tids == nullptr, "%s: tids should not be null", __FUNCTION__);
+    LOG_ALWAYS_FATAL_IF(tidsSize > CPU_HEADROOM_MAX_TID_COUNT, "%s: tids size should not exceed 5",
+                        __FUNCTION__);
+    params->tids.resize(tidsSize);
+    params->tids.clear();
+    for (int i = 0; i < tidsSize; ++i) {
+        LOG_ALWAYS_FATAL_IF(tids[i] <= 0, "ACpuHeadroomParams_setTids: Invalid non-positive tid %d",
+                            tids[i]);
+        params->tids[i] = tids[i];
+    }
+}
+
+void ACpuHeadroomParams_setCalculationType(ACpuHeadroomParams* _Nonnull params,
+                                           ACpuHeadroomCalculationType calculationType) {
+    LOG_ALWAYS_FATAL_IF(calculationType < ACpuHeadroomCalculationType::
+                                                  ACPU_HEADROOM_CALCULATION_TYPE_MIN ||
+                                calculationType > ACpuHeadroomCalculationType::
+                                                          ACPU_HEADROOM_CALCULATION_TYPE_AVERAGE,
+                        "%s: calculationType should be one of ACpuHeadroomCalculationType values "
+                        "but got %d",
+                        __FUNCTION__, calculationType);
+    params->calculationType = static_cast<hal::CpuHeadroomParams::CalculationType>(calculationType);
+}
+
+ACpuHeadroomCalculationType ACpuHeadroomParams_getCalculationType(
+        ACpuHeadroomParams* _Nonnull params) {
+    return static_cast<ACpuHeadroomCalculationType>(params->calculationType);
+}
+
+void AGpuHeadroomParams_setCalculationType(AGpuHeadroomParams* _Nonnull params,
+                                           AGpuHeadroomCalculationType calculationType) {
+    LOG_ALWAYS_FATAL_IF(calculationType < AGpuHeadroomCalculationType::
+                                                  AGPU_HEADROOM_CALCULATION_TYPE_MIN ||
+                                calculationType > AGpuHeadroomCalculationType::
+                                                          AGPU_HEADROOM_CALCULATION_TYPE_AVERAGE,
+                        "%s: calculationType should be one of AGpuHeadroomCalculationType values "
+                        "but got %d",
+                        __FUNCTION__, calculationType);
+    params->calculationType = static_cast<hal::GpuHeadroomParams::CalculationType>(calculationType);
+}
+
+AGpuHeadroomCalculationType AGpuHeadroomParams_getCalculationType(
+        AGpuHeadroomParams* _Nonnull params) {
+    return static_cast<AGpuHeadroomCalculationType>(params->calculationType);
+}
+
+ACpuHeadroomParams* _Nonnull ACpuHeadroomParams_create() {
+    return new ACpuHeadroomParams();
+}
+
+AGpuHeadroomParams* _Nonnull AGpuHeadroomParams_create() {
+    return new AGpuHeadroomParams();
+}
+
+void ACpuHeadroomParams_destroy(ACpuHeadroomParams* _Nonnull params) {
+    delete params;
+}
+
+void AGpuHeadroomParams_destroy(AGpuHeadroomParams* _Nonnull params) {
+    delete params;
+}
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 cd03dd7..07b1c9e 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
@@ -22,6 +22,7 @@
 import com.google.errorprone.annotations.CanIgnoreReturnValue
 import java.util.WeakHashMap
 import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicInteger
 
 /**
  * Callback to be informed of changes in [KeyedObservable] object.
@@ -203,13 +204,71 @@
         }
     }
 
-    fun hasAnyObserver(): Boolean {
+    open fun hasAnyObserver(): Boolean {
         synchronized(observers) { if (observers.isNotEmpty()) return true }
         synchronized(keyedObservers) { if (keyedObservers.isNotEmpty()) return true }
         return false
     }
 }
 
+/** [KeyedDataObservable] that maintains a counter for the observers. */
+abstract class AbstractKeyedDataObservable<K> : KeyedDataObservable<K>() {
+    /**
+     * Counter of observers.
+     *
+     * The value is accurate only when [addObserver] and [removeObserver] are invoked in pairs.
+     */
+    private val counter = AtomicInteger()
+
+    override fun addObserver(observer: KeyedObserver<K?>, executor: Executor) =
+        if (super.addObserver(observer, executor)) {
+            onObserverAdded()
+            true
+        } else {
+            false
+        }
+
+    override fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor) =
+        if (super.addObserver(key, observer, executor)) {
+            onObserverAdded()
+            true
+        } else {
+            false
+        }
+
+    private fun onObserverAdded() {
+        if (counter.getAndIncrement() == 0) onFirstObserverAdded()
+    }
+
+    /** Callbacks when the first observer is just added. */
+    protected abstract fun onFirstObserverAdded()
+
+    override fun removeObserver(observer: KeyedObserver<K?>) =
+        if (super.removeObserver(observer)) {
+            onObserverRemoved()
+            true
+        } else {
+            false
+        }
+
+    override fun removeObserver(key: K, observer: KeyedObserver<K>) =
+        if (super.removeObserver(key, observer)) {
+            onObserverRemoved()
+            true
+        } else {
+            false
+        }
+
+    private fun onObserverRemoved() {
+        if (counter.decrementAndGet() == 0) onLastObserverRemoved()
+    }
+
+    /** Callbacks when the last observer is just removed. */
+    protected abstract fun onLastObserverRemoved()
+
+    override fun hasAnyObserver() = counter.get() > 0
+}
+
 /** [KeyedObservable] with no-op implementations for all interfaces. */
 open class NoOpKeyedObservable<K> : KeyedObservable<K> {
 
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt
index 04d4bfe..d6e7a89 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt
@@ -20,21 +20,10 @@
 import android.database.ContentObserver
 import android.net.Uri
 import android.util.Log
-import java.util.concurrent.Executor
-import java.util.concurrent.atomic.AtomicInteger
 
 /** Base class of the Settings provider data stores. */
 abstract class SettingsStore(protected val contentResolver: ContentResolver) :
-    KeyedDataObservable<String>(), KeyValueStore {
-
-    /**
-     * Counter of observers.
-     *
-     * The value is accurate only when [addObserver] and [removeObserver] are called correctly. When
-     * an observer is not removed (and its weak reference is garbage collected), the content
-     * observer is not unregistered but this is not a big deal.
-     */
-    private val counter = AtomicInteger()
+    AbstractKeyedDataObservable<String>(), KeyValueStore {
 
     private val contentObserver =
         object : ContentObserver(HandlerExecutor.main) {
@@ -48,49 +37,15 @@
             }
         }
 
-    override fun addObserver(observer: KeyedObserver<String?>, executor: Executor) =
-        if (super.addObserver(observer, executor)) {
-            onObserverAdded()
-            true
-        } else {
-            false
-        }
+    /** The URI to watch for any key change. */
+    protected abstract val uri: Uri
 
-    override fun addObserver(key: String, observer: KeyedObserver<String>, executor: Executor) =
-        if (super.addObserver(key, observer, executor)) {
-            onObserverAdded()
-            true
-        } else {
-            false
-        }
-
-    private fun onObserverAdded() {
-        if (counter.getAndIncrement() != 0) return
+    override fun onFirstObserverAdded() {
         Log.i(tag, "registerContentObserver")
         contentResolver.registerContentObserver(uri, true, contentObserver)
     }
 
-    /** The URI to watch for any key change. */
-    protected abstract val uri: Uri
-
-    override fun removeObserver(observer: KeyedObserver<String?>) =
-        if (super.removeObserver(observer)) {
-            onObserverRemoved()
-            true
-        } else {
-            false
-        }
-
-    override fun removeObserver(key: String, observer: KeyedObserver<String>) =
-        if (super.removeObserver(key, observer)) {
-            onObserverRemoved()
-            true
-        } else {
-            false
-        }
-
-    private fun onObserverRemoved() {
-        if (counter.decrementAndGet() != 0) return
+    override fun onLastObserverRemoved() {
         Log.i(tag, "unregisterContentObserver")
         contentResolver.unregisterContentObserver(contentObserver)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 2817f55..acc97a9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -43,6 +43,7 @@
 
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
+import android.app.KeyguardManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -182,6 +183,8 @@
     @Captor
     private ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mFaceAuthenticatorsRegisteredCaptor;
     @Captor
+    private ArgumentCaptor<KeyguardManager.KeyguardLockedStateListener> mKeyguardLockedStateCaptor;
+    @Captor
     private ArgumentCaptor<BiometricStateListener> mBiometricStateCaptor;
     @Captor
     private ArgumentCaptor<Integer> mModalityCaptor;
@@ -192,6 +195,8 @@
     @Mock
     private VibratorHelper mVibratorHelper;
     @Mock
+    private KeyguardManager mKeyguardManager;
+    @Mock
     private MSDLPlayer mMSDLPlayer;
 
     private TestableContext mContextSpy;
@@ -272,6 +277,9 @@
         mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(fpProps);
         mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(faceProps);
 
+        verify(mKeyguardManager).addKeyguardLockedStateListener(any(),
+                mKeyguardLockedStateCaptor.capture());
+
         // Ensures that the operations posted on the handler get executed.
         waitForIdleSync();
     }
@@ -977,6 +985,18 @@
     }
 
     @Test
+    public void testCloseDialog_whenDeviceLocks() throws Exception {
+        showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
+
+        mKeyguardLockedStateCaptor.getValue().onKeyguardLockedStateChanged(
+                true /* isKeyguardLocked */);
+
+        verify(mReceiver).onDialogDismissed(
+                eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL),
+                eq(null) /* credentialAttestation */);
+    }
+
+    @Test
     public void testShowDialog_whenOwnerNotInForeground() {
         PromptInfo promptInfo = createTestPromptInfo();
         promptInfo.setAllowBackgroundAuthentication(false);
@@ -1193,7 +1213,7 @@
                     mWakefulnessLifecycle, mUserManager, mLockPatternUtils, () -> mUdfpsLogger,
                     () -> mLogContextInteractor, () -> mPromptSelectionInteractor,
                     () -> mCredentialViewModel, () -> mPromptViewModel, mInteractionJankMonitor,
-                    mHandler, mBackgroundExecutor, mUdfpsUtils, mVibratorHelper,
+                    mHandler, mBackgroundExecutor, mUdfpsUtils, mVibratorHelper, mKeyguardManager,
                     mLazyViewCapture, mMSDLPlayer);
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt
index 0122028..9edd62a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt
@@ -99,14 +99,14 @@
             assertThat(playerModel).isNotNull()
             assertThat(playerModel?.titleName).isEqualTo(TITLE)
             assertThat(playerModel?.artistName).isEqualTo(ARTIST)
-            assertThat(underTest.isNewPlayer(playerModel!!)).isTrue()
+            assertThat(underTest.setPlayer(playerModel!!)).isTrue()
 
             mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)
 
             assertThat(playerModel).isNotNull()
             assertThat(playerModel?.titleName).isEqualTo(TITLE)
             assertThat(playerModel?.artistName).isEqualTo(ARTIST)
-            assertThat(underTest.isNewPlayer(playerModel!!)).isFalse()
+            assertThat(underTest.setPlayer(playerModel!!)).isFalse()
         }
 
     @Test
@@ -120,7 +120,7 @@
             assertThat(playerModel).isNotNull()
             assertThat(playerModel?.titleName).isEqualTo(TITLE)
             assertThat(playerModel?.artistName).isEqualTo(ARTIST)
-            assertThat(underTest.isNewPlayer(playerModel!!)).isTrue()
+            assertThat(underTest.setPlayer(playerModel!!)).isTrue()
 
             mediaData = initMediaData(ARTIST_2, TITLE_2)
 
@@ -129,7 +129,7 @@
             assertThat(playerModel).isNotNull()
             assertThat(playerModel?.titleName).isEqualTo(TITLE_2)
             assertThat(playerModel?.artistName).isEqualTo(ARTIST_2)
-            assertThat(underTest.isNewPlayer(playerModel!!)).isTrue()
+            assertThat(underTest.setPlayer(playerModel!!)).isTrue()
         }
 
     private fun initMediaData(artist: String, title: String): MediaData {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index f6b6655..b653711 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -27,7 +27,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AlertDialog;
-import android.app.KeyguardManager;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.PixelFormat;
@@ -320,16 +319,6 @@
         mBiometricCallback = new BiometricCallback();
         mMSDLPlayer = msdlPlayer;
 
-        // Listener for when device locks from adaptive auth, dismiss prompt
-        getContext().getSystemService(KeyguardManager.class).addKeyguardLockedStateListener(
-                getContext().getMainExecutor(),
-                isKeyguardLocked -> {
-                    if (isKeyguardLocked) {
-                        onStartedGoingToSleep();
-                    }
-                }
-        );
-
         final BiometricModalities biometricModalities = new BiometricModalities(
                 Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
                 Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds));
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 4faf6ff..316849d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -27,6 +27,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityTaskManager;
+import android.app.KeyguardManager;
 import android.app.TaskStackListener;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -737,6 +738,7 @@
             @Background DelayableExecutor bgExecutor,
             @NonNull UdfpsUtils udfpsUtils,
             @NonNull VibratorHelper vibratorHelper,
+            @NonNull KeyguardManager keyguardManager,
             Lazy<ViewCapture> daggerLazyViewCapture,
             @NonNull MSDLPlayer msdlPlayer) {
         mContext = context;
@@ -768,6 +770,15 @@
         mPromptViewModelProvider = promptViewModelProvider;
         mCredentialViewModelProvider = credentialViewModelProvider;
 
+        keyguardManager.addKeyguardLockedStateListener(
+                context.getMainExecutor(),
+                isKeyguardLocked -> {
+                    if (isKeyguardLocked) {
+                        closeDialog("Device lock");
+                    }
+                }
+        );
+
         mOrientationListener = new BiometricDisplayListener(
                 context,
                 mDisplayManager,
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
index 1b044de..0c1bc83 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
@@ -20,6 +20,7 @@
 import androidx.annotation.RawRes
 import androidx.compose.animation.core.animateFloatAsState
 import androidx.compose.foundation.background
+import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
@@ -33,8 +34,12 @@
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.platform.LocalConfiguration
@@ -125,6 +130,8 @@
     config: TutorialScreenConfig,
     modifier: Modifier = Modifier,
 ) {
+    val focusRequester = remember { FocusRequester() }
+    LaunchedEffect(Unit) { focusRequester.requestFocus() }
     val (titleTextId, bodyTextId) =
         if (actionState is Finished) {
             config.strings.titleSuccessResId to config.strings.bodySuccessResId
@@ -136,6 +143,7 @@
             text = stringResource(id = titleTextId),
             style = MaterialTheme.typography.displayLarge,
             color = config.colors.title,
+            modifier = Modifier.focusRequester(focusRequester).focusable(),
         )
         Spacer(modifier = Modifier.height(16.dp))
         Text(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
index 341b8d8..1b39d55 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
@@ -30,14 +30,10 @@
 import com.android.systemui.media.controls.util.MediaSmartspaceLogger.Companion.SMARTSPACE_CARD_DISMISS_EVENT
 import com.android.systemui.media.controls.util.MediaSmartspaceLogger.Companion.SMARTSPACE_CARD_SEEN_EVENT
 import com.android.systemui.media.controls.util.SmallHash
-import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.time.SystemClock
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import java.util.Locale
 import java.util.TreeMap
 import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -49,37 +45,9 @@
 constructor(
     @Application private val applicationContext: Context,
     private val systemClock: SystemClock,
-    private val configurationController: ConfigurationController,
     private val smartspaceLogger: MediaSmartspaceLogger,
 ) {
 
-    val onAnyMediaConfigurationChange: Flow<Unit> = conflatedCallbackFlow {
-        val callback =
-            object : ConfigurationController.ConfigurationListener {
-                override fun onDensityOrFontScaleChanged() {
-                    trySend(Unit)
-                }
-
-                override fun onThemeChanged() {
-                    trySend(Unit)
-                }
-
-                override fun onUiModeChanged() {
-                    trySend(Unit)
-                }
-
-                override fun onLocaleListChanged() {
-                    if (locale != applicationContext.resources.configuration.locales.get(0)) {
-                        locale = applicationContext.resources.configuration.locales.get(0)
-                        trySend(Unit)
-                    }
-                }
-            }
-        configurationController.addCallback(callback)
-        trySend(Unit)
-        awaitClose { configurationController.removeCallback(callback) }
-    }
-
     /** Instance id of media control that recommendations card reactivated. */
     private val _reactivatedId: MutableStateFlow<InstanceId?> = MutableStateFlow(null)
     val reactivatedId: StateFlow<InstanceId?> = _reactivatedId.asStateFlow()
@@ -190,7 +158,7 @@
 
     fun addMediaDataLoadingState(
         mediaDataLoadingModel: MediaDataLoadingModel,
-        isUpdate: Boolean = true
+        isUpdate: Boolean = true,
     ) {
         val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator)
         sortedMap.putAll(
@@ -395,7 +363,7 @@
                     logSmarspaceRecommendationCardUserEvent(
                         SMARTSPACE_CARD_SEEN_EVENT,
                         surface,
-                        visibleIndex
+                        visibleIndex,
                     )
                 }
             }
@@ -409,7 +377,7 @@
         interactedSubCardRank: Int = 0,
         interactedSubCardCardinality: Int = 0,
         instanceId: InstanceId? = null,
-        isRec: Boolean = false
+        isRec: Boolean = false,
     ) {
         _currentMedia.value.forEachIndexed { index, mediaCommonModel ->
             when (mediaCommonModel) {
@@ -423,7 +391,7 @@
                                 surface,
                                 mediaCommonModel.mediaLoadedModel.isSsReactivated,
                                 interactedSubCardRank,
-                                interactedSubCardCardinality
+                                interactedSubCardCardinality,
                             )
                         }
                         return
@@ -437,7 +405,7 @@
                                 surface,
                                 index,
                                 interactedSubCardRank,
-                                interactedSubCardCardinality
+                                interactedSubCardCardinality,
                             )
                         }
                         return
@@ -459,14 +427,14 @@
                             SMARTSPACE_CARD_DISMISS_EVENT,
                             surface,
                             mediaCommonModel.mediaLoadedModel.isSsReactivated,
-                            isSwipeToDismiss = true
+                            isSwipeToDismiss = true,
                         )
                     is MediaCommonModel.MediaRecommendations ->
                         logSmarspaceRecommendationCardUserEvent(
                             SMARTSPACE_CARD_DISMISS_EVENT,
                             surface,
                             index,
-                            isSwipeToDismiss = true
+                            isSwipeToDismiss = true,
                         )
                 }
             }
@@ -513,7 +481,7 @@
         isReactivated: Boolean,
         interactedSubCardRank: Int = 0,
         interactedSubCardCardinality: Int = 0,
-        isSwipeToDismiss: Boolean = false
+        isSwipeToDismiss: Boolean = false,
     ) {
         _selectedUserEntries.value[instanceId]?.let {
             smartspaceLogger.logSmartspaceCardUIEvent(
@@ -537,7 +505,7 @@
         index: Int,
         interactedSubCardRank: Int = 0,
         interactedSubCardCardinality: Int = 0,
-        isSwipeToDismiss: Boolean = false
+        isSwipeToDismiss: Boolean = false,
     ) {
         smartspaceLogger.logSmartspaceCardUIEvent(
             eventId,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
index 1f339dd..09aa85b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
@@ -68,8 +68,6 @@
             .map { entries -> entries[instanceId]?.let { toMediaControlModel(it) } }
             .distinctUntilChanged()
 
-    val onAnyMediaConfigurationChange: Flow<Unit> = repository.onAnyMediaConfigurationChange
-
     fun removeMediaControl(
         token: MediaSession.Token?,
         instanceId: InstanceId,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt
index c3a36b2..48ed391 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt
@@ -66,14 +66,12 @@
             .distinctUntilChanged()
             .stateIn(applicationScope, SharingStarted.WhileSubscribed(), false)
 
-    val onAnyMediaConfigurationChange: Flow<Unit> = repository.onAnyMediaConfigurationChange
-
     fun removeMediaRecommendations(
         key: String,
         dismissIntent: Intent?,
         delayMs: Long,
         eventId: Int,
-        location: Int
+        location: Int,
     ) {
         logSmartspaceCardUserEvent(eventId, location)
         mediaDataProcessor.dismissSmartspaceRecommendation(key, delayMs)
@@ -101,7 +99,7 @@
         eventId: Int,
         location: Int,
         interactedSubCardRank: Int,
-        interactedSubCardCardinality: Int
+        interactedSubCardCardinality: Int,
     ) {
         if (interactedSubCardRank == -1) {
             logSmartspaceCardUserEvent(eventId, MediaSmartspaceLogger.getSurface(location))
@@ -111,7 +109,7 @@
                 MediaSmartspaceLogger.getSurface(location),
                 interactedSubCardRank = interactedSubCardRank,
                 interactedSubCardCardinality = interactedSubCardCardinality,
-                isRec = true
+                isRec = true,
             )
         }
         if (shouldActivityOpenInForeground(intent)) {
@@ -121,7 +119,7 @@
                 0 /* delay */,
                 expandable.activityTransitionController(
                     InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER
-                )
+                ),
             )
         } else {
             // Otherwise, open the activity in background directly.
@@ -133,7 +131,7 @@
         repository.logSmartspaceCardUserEvent(
             eventId,
             MediaSmartspaceLogger.getSurface(location),
-            isRec = true
+            isRec = true,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
index a6e1582..910d3a8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
@@ -34,6 +34,7 @@
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.settingslib.widget.AdaptiveIcon
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
@@ -64,7 +65,6 @@
 import com.android.systemui.surfaceeffects.ripple.RippleShader
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.collectLatest
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.withContext
 
 private const val TAG = "MediaControlViewBinder"
@@ -85,7 +85,7 @@
                 launch {
                     viewModel.player.collectLatest { player ->
                         player?.let {
-                            if (viewModel.isNewPlayer(it)) {
+                            if (viewModel.setPlayer(it)) {
                                 bindMediaCard(
                                     viewHolder,
                                     viewController,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 43c2011..f0f8a95 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -37,6 +37,7 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import androidx.recyclerview.widget.DiffUtil
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.app.tracing.traceSection
 import com.android.internal.logging.InstanceId
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -115,7 +116,6 @@
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.withContext
 
 private const val TAG = "MediaCarouselController"
@@ -752,7 +752,11 @@
         }
     }
 
-    private fun onAdded(commonViewModel: MediaCommonViewModel, position: Int) {
+    private fun onAdded(
+        commonViewModel: MediaCommonViewModel,
+        position: Int,
+        configChanged: Boolean = false,
+    ) {
         val viewController = mediaViewControllerFactory.get()
         viewController.sizeChangedListener = this::updateCarouselDimensions
         val lp =
@@ -763,12 +767,13 @@
         when (commonViewModel) {
             is MediaCommonViewModel.MediaControl -> {
                 val viewHolder = MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
-                if (SceneContainerFlag.isEnabled) {
-                    viewController.widthInSceneContainerPx = widthInSceneContainerPx
-                    viewController.heightInSceneContainerPx = heightInSceneContainerPx
-                }
+                viewController.widthInSceneContainerPx = widthInSceneContainerPx
+                viewController.heightInSceneContainerPx = heightInSceneContainerPx
                 viewController.attachPlayer(viewHolder)
                 viewController.mediaViewHolder?.player?.layoutParams = lp
+                if (configChanged) {
+                    commonViewModel.controlViewModel.onMediaConfigChanged()
+                }
                 MediaControlViewBinder.bind(
                     viewHolder,
                     commonViewModel.controlViewModel,
@@ -1271,23 +1276,14 @@
             ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator))
         if (recreateMedia) {
             mediaContent.removeAllViews()
-            commonViewModels.forEach { viewModel ->
+            commonViewModels.forEachIndexed { index, viewModel ->
                 when (viewModel) {
-                    is MediaCommonViewModel.MediaControl -> {
-                        controllerById[viewModel.instanceId.toString()]?.let {
-                            it.widthInSceneContainerPx = widthInSceneContainerPx
-                            it.heightInSceneContainerPx = heightInSceneContainerPx
-                            mediaContent.addView(it.mediaViewHolder?.player)
-                        }
-                    }
-                    is MediaCommonViewModel.MediaRecommendations -> {
-                        controllerById[viewModel.key]?.let {
-                            it.widthInSceneContainerPx = widthInSceneContainerPx
-                            it.heightInSceneContainerPx = heightInSceneContainerPx
-                            mediaContent.addView(it.recommendationViewHolder?.recommendations)
-                        }
-                    }
+                    is MediaCommonViewModel.MediaControl ->
+                        controllerById[viewModel.instanceId.toString()]?.onDestroy()
+                    is MediaCommonViewModel.MediaRecommendations ->
+                        controllerById[viewModel.key]?.onDestroy()
                 }
+                onAdded(viewModel, index, configChanged = true)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
index 4173d2a..4e97f20 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
@@ -41,10 +41,8 @@
 import com.android.systemui.res.R
 import java.util.concurrent.Executor
 import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 
@@ -56,15 +54,9 @@
     private val interactor: MediaControlInteractor,
     private val logger: MediaUiEventLogger,
 ) {
-
-    @OptIn(ExperimentalCoroutinesApi::class)
     val player: Flow<MediaPlayerViewModel?> =
-        interactor.onAnyMediaConfigurationChange
-            .flatMapLatest {
-                interactor.mediaControl.map { mediaControl ->
-                    mediaControl?.let { toViewModel(it) }
-                }
-            }
+        interactor.mediaControl
+            .map { mediaControl -> mediaControl?.let { toViewModel(it) } }
             .distinctUntilChanged { old, new ->
                 (new == null && old == null) || new?.contentEquals(old) ?: false
             }
@@ -74,14 +66,21 @@
     private var isAnyButtonClicked = false
     @MediaLocation private var location = MediaHierarchyManager.LOCATION_UNKNOWN
     private var playerViewModel: MediaPlayerViewModel? = null
+    private var allowPlayerUpdate: Boolean = false
 
-    fun isNewPlayer(viewModel: MediaPlayerViewModel): Boolean {
-        val contentEquals = playerViewModel?.contentEquals(viewModel) ?: false
-        return (!contentEquals).also { playerViewModel = viewModel }
+    fun setPlayer(viewModel: MediaPlayerViewModel): Boolean {
+        val tempViewModel = playerViewModel
+        playerViewModel = viewModel
+        return allowPlayerUpdate || !(tempViewModel?.contentEquals(viewModel) ?: false)
+    }
+
+    fun onMediaConfigChanged() {
+        allowPlayerUpdate = true
     }
 
     fun onMediaControlIsBound(artistName: CharSequence, titleName: CharSequence) {
         interactor.logMediaControlIsBound(artistName, titleName)
+        allowPlayerUpdate = false
     }
 
     private fun onDismissMediaData(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
index 6bc6b10a..88cfbaf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
@@ -41,10 +41,8 @@
 import com.android.systemui.res.R
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 
@@ -59,12 +57,9 @@
     private val logger: MediaUiEventLogger,
 ) {
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     val mediaRecsCard: Flow<MediaRecsCardViewModel?> =
-        interactor.onAnyMediaConfigurationChange
-            .flatMapLatest {
-                interactor.recommendations.map { recsCard -> toRecsViewModel(recsCard) }
-            }
+        interactor.recommendations
+            .map { recsCard -> toRecsViewModel(recsCard) }
             .distinctUntilChanged()
             .flowOn(backgroundDispatcher)
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
index 7a04aa2..7964c11 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
@@ -19,7 +19,6 @@
 import android.content.applicationContext
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.media.controls.util.mediaSmartspaceLogger
-import com.android.systemui.statusbar.policy.configurationController
 import com.android.systemui.util.time.systemClock
 
 val Kosmos.mediaFilterRepository by
@@ -27,7 +26,6 @@
         MediaFilterRepository(
             applicationContext = applicationContext,
             systemClock = systemClock,
-            configurationController = configurationController,
             smartspaceLogger = mediaSmartspaceLogger,
         )
     }
diff --git a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
index f5360eb..6b28047 100644
--- a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
+++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
@@ -213,7 +213,8 @@
     }
 
     private int getShareEventType(IntentFilter intentFilter) {
-        String mimeType = intentFilter != null ? intentFilter.getDataType(0) : null;
+        String mimeType = (intentFilter != null && intentFilter.countDataTypes() > 0)
+                ? intentFilter.getDataType(0) : null;
         return getDataManager().mimeTypeToShareEventType(mimeType);
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
index d58e772..835705d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
@@ -188,8 +188,8 @@
     }
 
     @Test
-    public void testDensityBasedCoarsening_ifFeatureIsEnabledButNotDefault_cacheIsNotUsed() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+    public void testDensityBasedCoarsening_ifFeatureIsEnabledButNoDefaultValue_cacheIsNotUsed() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
         LocationFudgerCache cache = mock(LocationFudgerCache.class);
         doReturn(false).when(cache).hasDefaultValue();
 
diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
index 27486b7..71a05f3 100644
--- a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
@@ -286,6 +286,25 @@
     }
 
     @Test
+    public void testPredictTargets_emptyIntentFilter() {
+        Bundle bundle = new Bundle();
+        IntentFilter filter = new IntentFilter();
+        bundle.putObject(ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY, filter);
+        AppPredictionContext predictionContext = new AppPredictionContext.Builder(mContext)
+                .setUiSurface(UI_SURFACE_SHARE)
+                .setPredictedTargetCount(NUM_PREDICTED_TARGETS)
+                .setExtras(bundle)
+                .build();
+        mPredictor = new ShareTargetPredictor(
+                predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID, mContext);
+
+        mPredictor.predictTargets();
+
+        verify(mUpdatePredictionsMethod).accept(mAppTargetCaptor.capture());
+        assertThat(mAppTargetCaptor.getValue()).isEmpty();
+    }
+
+    @Test
     public void testPredictTargets_noSharingHistoryRankedByShortcutRank() {
         mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1", 3));
         mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2", 2));