Merge "base: ConsumerBase-based classes now create their own BufferQueues" into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 9c93c3a..e40cbc1 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3443,7 +3443,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.ActivityPolicyExemption> CREATOR;
   }
 
-  public static final class ActivityPolicyExemption.Builder {
+  @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public static final class ActivityPolicyExemption.Builder {
     ctor public ActivityPolicyExemption.Builder();
     method @NonNull public android.companion.virtual.ActivityPolicyExemption build();
     method @NonNull public android.companion.virtual.ActivityPolicyExemption.Builder setComponentName(@NonNull android.content.ComponentName);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index ce0d38f..8dd4adc 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3676,6 +3676,7 @@
 
   public static final class Display.Mode implements android.os.Parcelable {
     ctor public Display.Mode(int, int, float);
+    method public boolean isSynthetic();
     method public boolean matches(int, int, float);
   }
 
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index e57630b..68063c4 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -233,6 +233,10 @@
     private static final RateLimitingCache<List<RunningAppProcessInfo>> mRunningProcessesCache =
             new RateLimitingCache<>(10, 4);
 
+    /** Rate-Limiting Cache that allows no more than 200 calls to the service per second. */
+    private static final RateLimitingCache<List<ProcessErrorStateInfo>> mErrorProcessesCache =
+            new RateLimitingCache<>(10, 2);
+
     /**
      * Map of callbacks that have registered for {@link UidFrozenStateChanged} events.
      * Will be called when a Uid has become frozen or unfrozen.
@@ -3685,6 +3689,16 @@
      * specified.
      */
     public List<ProcessErrorStateInfo> getProcessesInErrorState() {
+        if (Flags.rateLimitGetProcessesInErrorState()) {
+            return mErrorProcessesCache.get(() -> {
+                return getProcessesInErrorStateInternal();
+            });
+        } else {
+            return getProcessesInErrorStateInternal();
+        }
+    }
+
+    private List<ProcessErrorStateInfo> getProcessesInErrorStateInternal() {
         try {
             return getService().getProcessesInErrorState();
         } catch (RemoteException e) {
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index d9594d3..32e6e80 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -93,3 +93,14 @@
      }
 }
 
+flag {
+     namespace: "backstage_power"
+     name: "rate_limit_get_processes_in_error_state"
+     description: "Rate limit calls to getProcessesInErrorState using a cache"
+     is_fixed_read_only: true
+     bug: "361146083"
+     metadata {
+         purpose: PURPOSE_BUGFIX
+     }
+}
+
diff --git a/core/java/android/companion/virtual/ActivityPolicyExemption.java b/core/java/android/companion/virtual/ActivityPolicyExemption.java
index c81bb43..dc285d4 100644
--- a/core/java/android/companion/virtual/ActivityPolicyExemption.java
+++ b/core/java/android/companion/virtual/ActivityPolicyExemption.java
@@ -118,6 +118,7 @@
     /**
      * Builder for {@link ActivityPolicyExemption}.
      */
+    @FlaggedApi(Flags.FLAG_ACTIVITY_CONTROL_API)
     public static final class Builder {
 
         private @Nullable ComponentName mComponentName;
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 17d2790..013ec5f 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -28,7 +28,9 @@
 import android.util.Log;
 import android.view.WindowManager;
 
+import java.lang.ref.WeakReference;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 
 /**
@@ -52,43 +54,51 @@
     // An {@link IDreamOverlayClient} implementation that identifies itself when forwarding
     // requests to the {@link DreamOverlayService}
     private static class OverlayClient extends IDreamOverlayClient.Stub {
-        private final DreamOverlayService mService;
+        private final WeakReference<DreamOverlayService> mService;
         private boolean mShowComplications;
         private ComponentName mDreamComponent;
         IDreamOverlayCallback mDreamOverlayCallback;
 
-        OverlayClient(DreamOverlayService service) {
+        OverlayClient(WeakReference<DreamOverlayService> service) {
             mService = service;
         }
 
+        private void applyToDream(Consumer<DreamOverlayService> consumer) {
+            final DreamOverlayService service = mService.get();
+
+            if (service != null) {
+                consumer.accept(service);
+            }
+        }
+
         @Override
         public void startDream(WindowManager.LayoutParams params, IDreamOverlayCallback callback,
                 String dreamComponent, boolean shouldShowComplications) throws RemoteException {
             mDreamComponent = ComponentName.unflattenFromString(dreamComponent);
             mShowComplications = shouldShowComplications;
             mDreamOverlayCallback = callback;
-            mService.startDream(this, params);
+            applyToDream(dreamOverlayService -> dreamOverlayService.startDream(this, params));
         }
 
         @Override
         public void wakeUp() {
-            mService.wakeUp(this);
+            applyToDream(dreamOverlayService -> dreamOverlayService.wakeUp(this));
         }
 
         @Override
         public void endDream() {
-            mService.endDream(this);
+            applyToDream(dreamOverlayService -> dreamOverlayService.endDream(this));
         }
 
         @Override
         public void comeToFront() {
-            mService.comeToFront(this);
+            applyToDream(dreamOverlayService -> dreamOverlayService.comeToFront(this));
         }
 
         @Override
         public void onWakeRequested() {
             if (Flags.dreamWakeRedirect()) {
-                mService.onWakeRequested();
+                applyToDream(DreamOverlayService::onWakeRequested);
             }
         }
 
@@ -161,17 +171,24 @@
         });
     }
 
-    private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
+    private static class DreamOverlay extends IDreamOverlay.Stub {
+        private final WeakReference<DreamOverlayService> mService;
+
+        DreamOverlay(DreamOverlayService service) {
+            mService = new WeakReference<>(service);
+        }
+
         @Override
         public void getClient(IDreamOverlayClientCallback callback) {
             try {
-                callback.onDreamOverlayClient(
-                        new OverlayClient(DreamOverlayService.this));
+                callback.onDreamOverlayClient(new OverlayClient(mService));
             } catch (RemoteException e) {
                 Log.e(TAG, "could not send client to callback", e);
             }
         }
-    };
+    }
+
+    private final IDreamOverlay mDreamOverlay = new DreamOverlay(this);
 
     public DreamOverlayService() {
     }
@@ -195,6 +212,12 @@
         }
     }
 
+    @Override
+    public void onDestroy() {
+        mCurrentClient = null;
+        super.onDestroy();
+    }
+
     @Nullable
     @Override
     public final IBinder onBind(@NonNull Intent intent) {
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 40070c7..d33c95e 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -297,3 +297,10 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "typeface_redesign"
+  namespace: "text"
+  description: "Decouple variation settings, weight and style information from Typeface class"
+  bug: "361260253"
+}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 15b0c13..1f7ed8b 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -2344,6 +2344,8 @@
          * SurfaceControl.DisplayMode
          * @hide
          */
+        @SuppressWarnings("UnflaggedApi") // For testing only
+        @TestApi
         public boolean isSynthetic() {
             return mIsSynthetic;
         }
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index ec79f94..1083f64 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -1141,7 +1141,6 @@
         // Customize activity transition animation
         private CustomActivityTransition mCustomActivityOpenTransition;
         private CustomActivityTransition mCustomActivityCloseTransition;
-        private int mUserId;
 
         private AnimationOptions(int type) {
             mType = type;
@@ -1160,7 +1159,6 @@
             mAnimations = in.readInt();
             mCustomActivityOpenTransition = in.readTypedObject(CustomActivityTransition.CREATOR);
             mCustomActivityCloseTransition = in.readTypedObject(CustomActivityTransition.CREATOR);
-            mUserId = in.readInt();
         }
 
         /** Make basic customized animation for a package */
@@ -1285,14 +1283,6 @@
             return options;
         }
 
-        public void setUserId(int userId) {
-            mUserId = userId;
-        }
-
-        public int getUserId() {
-            return mUserId;
-        }
-
         public int getType() {
             return mType;
         }
@@ -1359,7 +1349,6 @@
             dest.writeInt(mAnimations);
             dest.writeTypedObject(mCustomActivityOpenTransition, flags);
             dest.writeTypedObject(mCustomActivityCloseTransition, flags);
-            dest.writeInt(mUserId);
         }
 
         @NonNull
@@ -1417,7 +1406,6 @@
             if (mExitResId != DEFAULT_ANIMATION_RESOURCES_ID) {
                 sb.append(" exitResId=").append(mExitResId);
             }
-            sb.append(" mUserId=").append(mUserId);
             sb.append('}');
             return sb.toString();
         }
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index 201f267..238e6f5 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -49,7 +49,7 @@
 import android.media.Image;
 import android.media.ImageReader;
 import android.os.Handler;
-import android.os.UserHandle;
+import android.os.SystemProperties;
 import android.util.Slog;
 import android.view.InflateException;
 import android.view.SurfaceControl;
@@ -187,44 +187,23 @@
         return createHiddenByKeyguardExit(mContext, mInterpolator, onWallpaper, toShade, subtle);
     }
 
-    /** Load keyguard unocclude animation for user. */
-    @Nullable
-    public Animation loadKeyguardUnoccludeAnimation(int userId) {
-        return loadDefaultAnimationRes(com.android.internal.R.anim.wallpaper_open_exit, userId);
-    }
-
-    /** Same as {@code loadKeyguardUnoccludeAnimation} for current user. */
     @Nullable
     public Animation loadKeyguardUnoccludeAnimation() {
-        return loadKeyguardUnoccludeAnimation(UserHandle.USER_CURRENT);
+        return loadDefaultAnimationRes(com.android.internal.R.anim.wallpaper_open_exit);
     }
 
-    /** Load voice activity open animation for user. */
-    @Nullable
-    public Animation loadVoiceActivityOpenAnimation(boolean enter, int userId) {
-        return loadDefaultAnimationRes(enter
-                ? com.android.internal.R.anim.voice_activity_open_enter
-                : com.android.internal.R.anim.voice_activity_open_exit, userId);
-    }
-
-    /** Same as {@code loadVoiceActivityOpenAnimation} for current user. */
     @Nullable
     public Animation loadVoiceActivityOpenAnimation(boolean enter) {
-        return loadVoiceActivityOpenAnimation(enter, UserHandle.USER_CURRENT);
-    }
-
-    /** Load voice activity exit animation for user. */
-    @Nullable
-    public Animation loadVoiceActivityExitAnimation(boolean enter, int userId) {
         return loadDefaultAnimationRes(enter
-                ? com.android.internal.R.anim.voice_activity_close_enter
-                : com.android.internal.R.anim.voice_activity_close_exit, userId);
+                ? com.android.internal.R.anim.voice_activity_open_enter
+                : com.android.internal.R.anim.voice_activity_open_exit);
     }
 
-    /** Same as {@code loadVoiceActivityExitAnimation} for current user. */
     @Nullable
     public Animation loadVoiceActivityExitAnimation(boolean enter) {
-        return loadVoiceActivityExitAnimation(enter, UserHandle.USER_CURRENT);
+        return loadDefaultAnimationRes(enter
+                ? com.android.internal.R.anim.voice_activity_close_enter
+                : com.android.internal.R.anim.voice_activity_close_exit);
     }
 
     @Nullable
@@ -232,17 +211,10 @@
         return loadAnimationRes(packageName, resId);
     }
 
-    /** Load cross profile app enter animation for user. */
-    @Nullable
-    public Animation loadCrossProfileAppEnterAnimation(int userId) {
-        return loadAnimationRes(DEFAULT_PACKAGE,
-                com.android.internal.R.anim.task_open_enter_cross_profile_apps, userId);
-    }
-
-    /** Same as {@code loadCrossProfileAppEnterAnimation} for current user. */
     @Nullable
     public Animation loadCrossProfileAppEnterAnimation() {
-        return loadCrossProfileAppEnterAnimation(UserHandle.USER_CURRENT);
+        return loadAnimationRes(DEFAULT_PACKAGE,
+                com.android.internal.R.anim.task_open_enter_cross_profile_apps);
     }
 
     @Nullable
@@ -258,11 +230,11 @@
                 appRect.height(), 0, null);
     }
 
-    /** Load animation by resource Id from specific package for user. */
+    /** Load animation by resource Id from specific package. */
     @Nullable
-    public Animation loadAnimationRes(String packageName, int resId, int userId) {
+    public Animation loadAnimationRes(String packageName, int resId) {
         if (ResourceId.isValid(resId)) {
-            AttributeCache.Entry ent = getCachedAnimations(packageName, resId, userId);
+            AttributeCache.Entry ent = getCachedAnimations(packageName, resId);
             if (ent != null) {
                 return loadAnimationSafely(ent.context, resId, mTag);
             }
@@ -270,22 +242,10 @@
         return null;
     }
 
-    /** Same as {@code loadAnimationRes} for current user. */
-    @Nullable
-    public Animation loadAnimationRes(String packageName, int resId) {
-        return loadAnimationRes(packageName, resId, UserHandle.USER_CURRENT);
-    }
-
-    /** Load animation by resource Id from android package for user. */
-    @Nullable
-    public Animation loadDefaultAnimationRes(int resId, int userId) {
-        return loadAnimationRes(DEFAULT_PACKAGE, resId, userId);
-    }
-
-    /** Same as {@code loadDefaultAnimationRes} for current user. */
+    /** Load animation by resource Id from android package. */
     @Nullable
     public Animation loadDefaultAnimationRes(int resId) {
-        return loadAnimationRes(DEFAULT_PACKAGE, resId, UserHandle.USER_CURRENT);
+        return loadAnimationRes(DEFAULT_PACKAGE, resId);
     }
 
     /** Load animation by attribute Id from specific LayoutParams */
@@ -418,10 +378,10 @@
     }
 
     @Nullable
-    private AttributeCache.Entry getCachedAnimations(String packageName, int resId, int userId) {
+    private AttributeCache.Entry getCachedAnimations(String packageName, int resId) {
         if (mDebug) {
-            Slog.v(mTag, "Loading animations: package=" + packageName + " resId=0x"
-                    + Integer.toHexString(resId) + " for user=" + userId);
+            Slog.v(mTag, "Loading animations: package="
+                    + packageName + " resId=0x" + Integer.toHexString(resId));
         }
         if (packageName != null) {
             if ((resId & 0xFF000000) == 0x01000000) {
@@ -432,16 +392,11 @@
                         + packageName);
             }
             return AttributeCache.instance().get(packageName, resId,
-                    com.android.internal.R.styleable.WindowAnimation, userId);
+                    com.android.internal.R.styleable.WindowAnimation);
         }
         return null;
     }
 
-    @Nullable
-    private AttributeCache.Entry getCachedAnimations(String packageName, int resId) {
-        return getCachedAnimations(packageName, resId, UserHandle.USER_CURRENT);
-    }
-
     /** Returns window animation style ID from {@link LayoutParams} or from system in some cases */
     public int getAnimationStyleResId(@NonNull LayoutParams lp) {
         int resId = lp.windowAnimations;
diff --git a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
index b82c660..34e0418 100644
--- a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
@@ -40,6 +40,8 @@
  */
 @Deprecated
 public class LogcatOnlyProtoLogImpl implements IProtoLog {
+    private static final String LOG_TAG = LogcatOnlyProtoLogImpl.class.getName();
+
     @Override
     public void log(LogLevel logLevel, IProtoLogGroup group, long messageHash, int paramsMask,
             Object[] args) {
@@ -48,19 +50,21 @@
 
     @Override
     public void log(LogLevel logLevel, IProtoLogGroup group, String messageString, Object[] args) {
-        if (REQUIRE_PROTOLOGTOOL) {
-            throw new RuntimeException(
-                    "REQUIRE_PROTOLOGTOOL not set to false before the first log call.");
+        if (REQUIRE_PROTOLOGTOOL && group.isLogToProto()) {
+            Log.w(LOG_TAG, "ProtoLog message not processed. Failed to log it to proto. "
+                    + "Logging it below to logcat instead.");
         }
 
-        String formattedString = TextUtils.formatSimple(messageString, args);
-        switch (logLevel) {
-            case VERBOSE -> Log.v(group.getTag(), formattedString);
-            case INFO -> Log.i(group.getTag(), formattedString);
-            case DEBUG -> Log.d(group.getTag(), formattedString);
-            case WARN -> Log.w(group.getTag(), formattedString);
-            case ERROR -> Log.e(group.getTag(), formattedString);
-            case WTF -> Log.wtf(group.getTag(), formattedString);
+        if (group.isLogToLogcat() || group.isLogToProto()) {
+            String formattedString = TextUtils.formatSimple(messageString, args);
+            switch (logLevel) {
+                case VERBOSE -> Log.v(group.getTag(), formattedString);
+                case INFO -> Log.i(group.getTag(), formattedString);
+                case DEBUG -> Log.d(group.getTag(), formattedString);
+                case WARN -> Log.w(group.getTag(), formattedString);
+                case ERROR -> Log.e(group.getTag(), formattedString);
+                case WTF -> Log.wtf(group.getTag(), formattedString);
+            }
         }
     }
 
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index 660d3c9..bf77db7 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -63,6 +63,9 @@
      * @param groups The ProtoLog groups that will be used in the process.
      */
     public static void init(IProtoLogGroup... groups) {
+        // These tracing instances are only used when we cannot or do not preprocess the source
+        // files to extract out the log strings. Otherwise, the trace calls are replaced with calls
+        // directly to the generated tracing implementations.
         if (android.tracing.Flags.perfettoProtologTracing()) {
             synchronized (sInitLock) {
                 if (sProtoLogInstance != null) {
@@ -76,8 +79,6 @@
                 sProtoLogInstance = new PerfettoProtoLogImpl(groups);
             }
         } else {
-            // The first call to ProtoLog is likely to flip REQUIRE_PROTOLOGTOOL, which is when this
-            // static block will be executed before REQUIRE_PROTOLOGTOOL is actually set.
             sProtoLogInstance = new LogcatOnlyProtoLogImpl();
         }
     }
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 7bc0d2f..ce7e858 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -69,6 +69,7 @@
 import android.view.WindowManager.BadTokenException;
 import android.view.WindowManager.LayoutParams;
 import android.view.animation.LinearInterpolator;
+import android.view.inputmethod.Flags;
 import android.view.inputmethod.ImeTracker;
 import android.widget.TextView;
 
@@ -136,7 +137,7 @@
             mTestHandler = new TestHandler(null, mTestClock);
             mTestHost = spy(new TestHost(mViewRoot));
             mController = new InsetsController(mTestHost, (controller, id, type) -> {
-                if (type == ime()) {
+                if (!Flags.refactorInsetsController() && type == ime()) {
                     return new InsetsSourceConsumer(id, type, controller.getState(),
                             Transaction::new, controller) {
 
@@ -260,7 +261,11 @@
             mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener);
             mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
             // since there is no focused view, forcefully make IME visible.
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+            }
             // When using the animation thread, this must not invoke onReady()
             mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
         });
@@ -277,7 +282,12 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
             // since there is no focused view, forcefully make IME visible.
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                InsetsSourceControl ime = createControl(ID_IME, ime());
+                mController.onControlsChanged(new InsetsSourceControl[]{ime});
+            }
             mController.show(all());
             // quickly jump to final state by cancelling it.
             mController.cancelExistingAnimations();
@@ -299,7 +309,11 @@
         mController.onControlsChanged(new InsetsSourceControl[] { ime });
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+            }
             mController.cancelExistingAnimations();
             assertTrue(isRequestedVisible(mController, ime()));
             mController.hide(ime(), true /* fromIme */, ImeTracker.Token.empty());
@@ -469,7 +483,12 @@
             assertFalse(mController.getState().peekSource(ID_IME).isVisible());
 
             // Pretend IME is calling
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                InsetsSourceControl ime = createControl(ID_IME, ime());
+                mController.onControlsChanged(new InsetsSourceControl[]{ime});
+            }
 
             // Gaining control shortly after
             mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
@@ -493,7 +512,12 @@
             mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
 
             // Pretend IME is calling
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                InsetsSourceControl ime = createControl(ID_IME, ime());
+                mController.onControlsChanged(new InsetsSourceControl[]{ime});
+            }
 
             assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
             mController.cancelExistingAnimations();
@@ -558,7 +582,13 @@
 
     @Test
     public void testControlImeNotReady() {
-        prepareControls();
+        if (!Flags.refactorInsetsController()) {
+            prepareControls();
+        } else {
+            // With the flag on, the IME control should not contain a leash, otherwise the custom
+            // animation will start immediately.
+            prepareControls(false /* imeControlHasLeash */);
+        }
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             WindowInsetsAnimationControlListener listener =
                     mock(WindowInsetsAnimationControlListener.class);
@@ -571,7 +601,13 @@
             verify(listener, never()).onReady(any(), anyInt());
 
             // Pretend that IME is calling.
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                // Send the IME control with leash, so that the animation can start
+                InsetsSourceControl ime = createControl(ID_IME, ime(), true /* hasLeash */);
+                mController.onControlsChanged(new InsetsSourceControl[]{ime});
+            }
 
             // Ready gets deferred until next predraw
             mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
@@ -583,7 +619,13 @@
 
     @Test
     public void testControlImeNotReady_controlRevoked() {
-        prepareControls();
+        if (!Flags.refactorInsetsController()) {
+            prepareControls();
+        } else {
+            // With the flag on, the IME control should not contain a leash, otherwise the custom
+            // animation will start immediately.
+            prepareControls(false /* imeControlHasLeash */);
+        }
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             WindowInsetsAnimationControlListener listener =
                     mock(WindowInsetsAnimationControlListener.class);
@@ -604,7 +646,13 @@
 
     @Test
     public void testControlImeNotReady_timeout() {
-        prepareControls();
+        if (!Flags.refactorInsetsController()) {
+            prepareControls();
+        } else {
+            // With the flag on, the IME control should not contain a leash, otherwise the custom
+            // animation will start immediately.
+            prepareControls(false /* imeControlHasLeash */);
+        }
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             WindowInsetsAnimationControlListener listener =
                     mock(WindowInsetsAnimationControlListener.class);
@@ -655,7 +703,11 @@
             mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
 
             // Pretend IME is calling
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+            }
 
             InsetsState copy = new InsetsState(mController.getState(), true /* copySources */);
             copy.peekSource(ID_IME).setFrame(0, 1, 2, 3);
@@ -886,7 +938,11 @@
 
             // Showing invisible ime should only causes insets change once.
             clearInvocations(mTestHost);
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+            }
             verify(mTestHost, times(1)).notifyInsetsChanged();
 
             // Sending the same insets state should not cause insets change.
@@ -953,7 +1009,11 @@
             assertNull(imeInsetsConsumer.getControl());
 
             // Verify IME requested visibility should be updated to IME consumer from controller.
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+            }
             assertTrue(isRequestedVisible(mController, ime()));
 
             mController.hide(ime());
@@ -966,7 +1026,11 @@
         prepareControls();
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             // show ime as initial state
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+            }
             mController.cancelExistingAnimations(); // fast forward show animation
             assertTrue(mController.getState().peekSource(ID_IME).isVisible());
 
@@ -990,8 +1054,13 @@
     public void testImeShowRequestCancelsPredictiveBackPostCommitAnim() {
         prepareControls();
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            InsetsSourceControl ime = createControl(ID_IME, ime());
             // show ime as initial state
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+            }
             mController.cancelExistingAnimations(); // fast forward show animation
             mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
             assertTrue(mController.getState().peekSource(ID_IME).isVisible());
@@ -1008,12 +1077,20 @@
             assertEquals(ANIMATION_TYPE_USER, mController.getAnimationType(ime()));
 
             // verify show request is ignored during pre commit phase of predictive back anim
-            mController.show(ime(), true /* fromIme */, null /* statsToken */);
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, null /* statsToken */);
+            } else {
+                mController.onControlsChanged(new InsetsSourceControl[]{ime});
+            }
             assertEquals(ANIMATION_TYPE_USER, mController.getAnimationType(ime()));
 
             // verify show request is applied during post commit phase of predictive back anim
             mController.setPredictiveBackImeHideAnimInProgress(true);
-            mController.show(ime(), true /* fromIme */, null /* statsToken */);
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, null /* statsToken */);
+            } else {
+                mController.show(ime(), false /* fromIme */, null /* statsToken */);
+            }
             assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
 
             // additionally verify that IME ends up visible
@@ -1027,7 +1104,11 @@
         prepareControls();
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             // show ime as initial state
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+            }
             mController.cancelExistingAnimations(); // fast forward show animation
             mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
             assertTrue(mController.getState().peekSource(ID_IME).isVisible());
@@ -1058,11 +1139,15 @@
     }
 
     private InsetsSourceControl createControl(int id, @InsetsType int type) {
+        return createControl(id, type, true);
+    }
+
+    private InsetsSourceControl createControl(int id, @InsetsType int type, boolean hasLeash) {
 
         // Simulate binder behavior by copying SurfaceControl. Otherwise, InsetsController will
         // attempt to release mLeash directly.
         SurfaceControl copy = new SurfaceControl(mLeash, "InsetsControllerTest.createControl");
-        return new InsetsSourceControl(id, type, copy,
+        return new InsetsSourceControl(id, type, hasLeash ? copy : null,
                 (type & WindowInsets.Type.defaultVisible()) != 0, new Point(), Insets.NONE);
     }
 
@@ -1071,9 +1156,13 @@
     }
 
     private InsetsSourceControl[] prepareControls() {
+        return prepareControls(true);
+    }
+
+    private InsetsSourceControl[] prepareControls(boolean imeControlHasLeash) {
         final InsetsSourceControl navBar = createControl(ID_NAVIGATION_BAR, navigationBars());
         final InsetsSourceControl statusBar = createControl(ID_STATUS_BAR, statusBars());
-        final InsetsSourceControl ime = createControl(ID_IME, ime());
+        final InsetsSourceControl ime = createControl(ID_IME, ime(), imeControlHasLeash);
 
         InsetsSourceControl[] controls = new InsetsSourceControl[3];
         controls[0] = navBar;
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 1a3aa8e..b338a2a 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -46,9 +46,6 @@
     srcs: [
         "src/com/android/wm/shell/common/bubbles/*.kt",
         "src/com/android/wm/shell/common/bubbles/*.java",
-        "src/com/android/wm/shell/common/desktopmode/*.kt",
-        "src/com/android/wm/shell/pip/PipContentOverlay.java",
-        "src/com/android/wm/shell/util/**/*.java",
     ],
     path: "src",
 }
@@ -208,6 +205,7 @@
         // TODO(b/168581922) protologtool do not support kotlin(*.kt)
         ":wm_shell-sources-kt",
         ":wm_shell-aidls",
+        ":wm_shell-shared-aidls",
     ],
     resource_dirs: [
         "res",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
similarity index 86%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
copy to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
index c968e80..e21bf8f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.desktopmode;
+package com.android.wm.shell.shared;
 
-parcelable DesktopModeTransitionSource;
\ No newline at end of file
+parcelable GroupedRecentTaskInfo;
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java
index a2d2b9a..65e079e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.util;
+package com.android.wm.shell.shared;
 
 import android.annotation.IntDef;
 import android.app.ActivityManager;
@@ -25,6 +25,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.wm.shell.shared.split.SplitBounds;
+
 import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.aidl
similarity index 92%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.aidl
index c968e80..f7ddf71 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.desktopmode;
+package com.android.wm.shell.shared.desktopmode;
 
 parcelable DesktopModeTransitionSource;
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.kt
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.kt
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.kt
index dbbf1786..d15fbed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.desktopmode
+package com.android.wm.shell.shared.desktopmode
 
 import android.os.Parcel
 import android.os.Parcelable
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
index ff2d46e..cf39415 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.pip;
+package com.android.wm.shell.shared.pip;
 
 import static android.util.TypedValue.COMPLEX_UNIT_DIP;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitBounds.java
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitBounds.java
index 88b7528..7c1faa6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitBounds.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.wm.shell.util;
+package com.android.wm.shell.shared.split;
 
 import android.graphics.Rect;
 import android.os.Parcel;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 3dc33c2..b508c1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1225,7 +1225,7 @@
         mBubblePositioner.setBubbleBarLocation(location);
         mBubblePositioner.setBubbleBarTopOnScreen(topOnScreen);
         if (mBubbleData.getSelectedBubble() != null) {
-            mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true);
+            showExpandedViewForBubbleBar();
         }
     }
 
@@ -1243,7 +1243,7 @@
         }
         if (selectedBubbleKey != null && !selectedBubbleKey.equals(bubbleKey)) {
             // We did not remove the selected bubble. Expand it again
-            mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true);
+            showExpandedViewForBubbleBar();
         }
     }
 
@@ -1997,15 +1997,10 @@
 
         @Override
         public void expansionChanged(boolean isExpanded) {
-            if (mLayerView != null) {
-                if (!isExpanded) {
-                    mLayerView.collapse();
-                } else {
-                    BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
-                    if (selectedBubble != null) {
-                        mLayerView.showExpandedView(selectedBubble);
-                    }
-                }
+            // in bubble bar mode, let the request to show the expanded view come from launcher.
+            // only collapse here if we're collapsing.
+            if (mLayerView != null && !isExpanded) {
+                mLayerView.collapse();
             }
         }
 
@@ -2151,6 +2146,13 @@
         }
     };
 
+    private void showExpandedViewForBubbleBar() {
+        BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
+        if (selectedBubble != null && mLayerView != null) {
+            mLayerView.showExpandedView(selectedBubble);
+        }
+    }
+
     private void updateOverflowButtonDot() {
         BubbleOverflow overflow = mBubbleData.getOverflow();
         if (overflow == null) return;
@@ -2532,6 +2534,15 @@
                 if (mLayerView != null) mLayerView.updateExpandedView();
             });
         }
+
+        @Override
+        public void showExpandedView() {
+            mMainExecutor.execute(() -> {
+                if (mLayerView != null) {
+                    showExpandedViewForBubbleBar();
+                }
+            });
+        }
     }
 
     private class BubblesImpl implements Bubbles {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 5c78974..5779a8f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -53,4 +53,6 @@
     oneway void showShortcutBubble(in ShortcutInfo info) = 12;
 
     oneway void showAppBubble(in Intent intent) = 13;
+
+    oneway void showExpandedView() = 14;
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index 31c8f1e..cca7500 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -18,8 +18,8 @@
 
 import android.graphics.Region;
 
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.shared.annotations.ExternalThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
index eca3c1f..dba8c93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -16,7 +16,7 @@
 
 package com.android.wm.shell.desktopmode
 
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.UNKNOWN
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
 import com.android.wm.shell.sysui.ShellCommandHandler
 import java.io.PrintWriter
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt
index b24bd10..d6fccd1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt
@@ -17,7 +17,7 @@
 package com.android.wm.shell.desktopmode
 
 import android.view.WindowManager.TransitionType
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
 import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_TYPES
 
 /**
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 ffd534b..2852631 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
@@ -69,7 +69,6 @@
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SingleInstanceRemoteListener
 import com.android.wm.shell.common.SyncTransactionQueue
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
 import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
@@ -89,6 +88,7 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
 import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
 import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
 import com.android.wm.shell.splitscreen.SplitScreenController
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 04506c1..80e106f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -41,7 +41,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.jank.InteractionJankMonitor;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index 171378f..e87be52 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -44,7 +44,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.Cuj;
 import com.android.internal.jank.InteractionJankMonitor;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.transition.Transitions;
 
 import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index a7ec203..b036e40e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -18,8 +18,8 @@
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.window.RemoteTransition;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.desktopmode.IDesktopTaskListener;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 
 /**
  * Interface that is exposed to remote callers to manipulate desktop mode features.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index b0c896f..4df649c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -43,6 +43,7 @@
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.animation.Interpolators;
+import com.android.wm.shell.shared.pip.PipContentOverlay;
 import com.android.wm.shell.transition.Transitions;
 
 import java.lang.annotation.Retention;
@@ -418,7 +419,7 @@
         }
 
         SurfaceControl getContentOverlayLeash() {
-            return mContentOverlay == null ? null : mContentOverlay.mLeash;
+            return mContentOverlay == null ? null : mContentOverlay.getLeash();
         }
 
         void setColorContentOverlay(Context context) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 2de545a..e4cd10f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -91,6 +91,7 @@
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.animation.Interpolators;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.pip.PipContentOverlay;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.transition.Transitions;
 
@@ -361,8 +362,7 @@
     SurfaceControl mPipOverlay;
 
     /**
-     * The app bounds used for the buffer size of the
-     * {@link com.android.wm.shell.pip.PipContentOverlay.PipAppIconOverlay}.
+     * The app bounds used for the buffer size of the {@link PipContentOverlay.PipAppIconOverlay}.
      *
      * Note that this is empty if the overlay is removed or if it's some other type of overlay
      * defined in {@link PipContentOverlay}.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index b102e40..05d1984 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -75,6 +75,7 @@
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.TransitionUtil;
+import com.android.wm.shell.shared.pip.PipContentOverlay;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.CounterRotatorHelper;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index ed18712..c6b1a72 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -50,10 +50,10 @@
 import com.android.wm.shell.common.pip.PipBoundsState;
 import com.android.wm.shell.common.pip.PipMenuController;
 import com.android.wm.shell.common.pip.PipUtils;
-import com.android.wm.shell.pip.PipContentOverlay;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
 import com.android.wm.shell.pip2.animation.PipEnterExitAnimator;
+import com.android.wm.shell.shared.pip.PipContentOverlay;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
index 4048c5b..ebfd357 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
@@ -24,7 +24,7 @@
 import android.view.IRecentsAnimationRunner;
 
 import com.android.wm.shell.recents.IRecentTasksListener;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
 
 /**
  * Interface that is exposed to remote callers to fetch recent tasks.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index 77b8663..8c5d1e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -19,8 +19,8 @@
 import android.annotation.Nullable;
 import android.graphics.Color;
 
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
 import com.android.wm.shell.shared.annotations.ExternalThread;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
 
 import java.util.List;
 import java.util.concurrent.Executor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 2f0af855..39bea1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -51,16 +51,16 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
 import com.android.wm.shell.shared.annotations.ExternalThread;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.split.SplitBounds;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
-import com.android.wm.shell.util.SplitBounds;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 7a9eb1c..c90da05 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -30,7 +30,7 @@
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 
 import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION;
-import static com.android.wm.shell.util.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
+import static com.android.wm.shell.shared.split.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
 
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 0b5c751..95f864a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -133,13 +133,13 @@
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.shared.TransitionUtil;
+import com.android.wm.shell.shared.split.SplitBounds;
 import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
 import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.splitscreen.SplitScreen.StageType;
 import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
 import com.android.wm.shell.transition.DefaultMixedHandler;
 import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.SplitBounds;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import dalvik.annotation.optimization.NeverCompile;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 9b0fb20..4fc6c44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -18,8 +18,8 @@
 
 import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
 import static android.app.ActivityOptions.ANIM_CUSTOM;
-import static android.app.ActivityOptions.ANIM_FROM_STYLE;
 import static android.app.ActivityOptions.ANIM_NONE;
+import static android.app.ActivityOptions.ANIM_FROM_STYLE;
 import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
 import static android.app.ActivityOptions.ANIM_SCALE_UP;
 import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
@@ -473,7 +473,7 @@
                                 change.getLeash(),
                                 startTransaction);
                     } else if (isOnlyTranslucent && TransitionUtil.isOpeningType(info.getType())
-                            && TransitionUtil.isClosingType(mode)) {
+                                && TransitionUtil.isClosingType(mode)) {
                         // If there is a closing translucent task in an OPENING transition, we will
                         // actually select a CLOSING animation, so move the closing task into
                         // the animating part of the z-order.
@@ -767,12 +767,12 @@
             a = mTransitionAnimation.loadKeyguardExitAnimation(flags,
                     (changeFlags & FLAG_SHOW_WALLPAPER) != 0);
         } else if (type == TRANSIT_KEYGUARD_UNOCCLUDE) {
-            a = mTransitionAnimation.loadKeyguardUnoccludeAnimation(options.getUserId());
+            a = mTransitionAnimation.loadKeyguardUnoccludeAnimation();
         } else if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
             if (isOpeningType) {
-                a = mTransitionAnimation.loadVoiceActivityOpenAnimation(enter, options.getUserId());
+                a = mTransitionAnimation.loadVoiceActivityOpenAnimation(enter);
             } else {
-                a = mTransitionAnimation.loadVoiceActivityExitAnimation(enter, options.getUserId());
+                a = mTransitionAnimation.loadVoiceActivityExitAnimation(enter);
             }
         } else if (changeMode == TRANSIT_CHANGE) {
             // In the absence of a specific adapter, we just want to keep everything stationary.
@@ -783,9 +783,9 @@
         } else if (overrideType == ANIM_CUSTOM
                 && (!isTask || options.getOverrideTaskTransition())) {
             a = mTransitionAnimation.loadAnimationRes(options.getPackageName(), enter
-                    ? options.getEnterResId() : options.getExitResId(), options.getUserId());
+                    ? options.getEnterResId() : options.getExitResId());
         } else if (overrideType == ANIM_OPEN_CROSS_PROFILE_APPS && enter) {
-            a = mTransitionAnimation.loadCrossProfileAppEnterAnimation(options.getUserId());
+            a = mTransitionAnimation.loadCrossProfileAppEnterAnimation();
         } else if (overrideType == ANIM_CLIP_REVEAL) {
             a = mTransitionAnimation.createClipRevealAnimationLocked(type, wallpaperTransit, enter,
                     endBounds, endBounds, options.getTransitionBounds());
@@ -905,9 +905,9 @@
         final Rect bounds = change.getEndAbsBounds();
         // Show the right drawable depending on the user we're transitioning to.
         final Drawable thumbnailDrawable = change.hasFlags(FLAG_CROSS_PROFILE_OWNER_THUMBNAIL)
-                ? mContext.getDrawable(R.drawable.ic_account_circle)
-                : change.hasFlags(FLAG_CROSS_PROFILE_WORK_THUMBNAIL)
-                        ? mEnterpriseThumbnailDrawable : null;
+                        ? mContext.getDrawable(R.drawable.ic_account_circle)
+                        : change.hasFlags(FLAG_CROSS_PROFILE_WORK_THUMBNAIL)
+                                ? mEnterpriseThumbnailDrawable : null;
         if (thumbnailDrawable == null) {
             return;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.aidl
deleted file mode 100644
index 15797cd..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.util;
-
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/util/OWNERS
deleted file mode 100644
index 482aaab..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-per-file KtProtolog.kt = file:platform/development:/tools/winscope/OWNERS
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 20a406f..1f95667 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
@@ -97,7 +97,6 @@
 import com.android.wm.shell.common.MultiInstanceHelper;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
@@ -107,6 +106,7 @@
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.splitscreen.SplitScreen.StageType;
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 8e87d0f..2bec3fa 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
@@ -26,7 +26,7 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
-import static com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON;
+import static com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
@@ -79,10 +79,10 @@
 import com.android.wm.shell.common.MultiInstanceHelper;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
 import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 114c331..deef378 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -318,7 +318,7 @@
             rootView.setOnTouchListener { _, event ->
                 if (event.actionMasked == ACTION_OUTSIDE) {
                     onOutsideTouchListener?.invoke()
-                    false
+                    return@setOnTouchListener false
                 }
                 true
             }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt
index 518c00d..db4e93d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt
@@ -18,11 +18,6 @@
 
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.APP_FROM_OVERVIEW
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.KEYBOARD_SHORTCUT
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.TASK_DRAG
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.UNKNOWN
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
@@ -33,6 +28,11 @@
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.getEnterTransitionType
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.getExitTransitionType
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_FROM_OVERVIEW
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.KEYBOARD_SHORTCUT
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.TASK_DRAG
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
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 058a26a..d248720 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
@@ -83,7 +83,6 @@
 import com.android.wm.shell.common.MultiInstanceHelper
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SyncTransactionQueue
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.UNKNOWN
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
@@ -94,6 +93,7 @@
 import com.android.wm.shell.recents.RecentsTransitionHandler
 import com.android.wm.shell.recents.RecentsTransitionStateListener
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
 import com.android.wm.shell.shared.split.SplitScreenConstants
 import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.sysui.ShellCommandHandler
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
index e5157c9..e0463b4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
@@ -48,7 +48,7 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
index 6736593..0c3f98a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
@@ -24,13 +24,13 @@
 import android.window.WindowContainerToken
 import androidx.test.filters.SmallTest
 import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.shared.GroupedRecentTaskInfo
+import com.android.wm.shell.shared.GroupedRecentTaskInfo.CREATOR
+import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_FREEFORM
+import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SINGLE
+import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SPLIT
+import com.android.wm.shell.shared.split.SplitBounds
 import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50
-import com.android.wm.shell.util.GroupedRecentTaskInfo
-import com.android.wm.shell.util.GroupedRecentTaskInfo.CREATOR
-import com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM
-import com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_SINGLE
-import com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_SPLIT
-import com.android.wm.shell.util.SplitBounds
 import com.google.common.truth.Correspondence
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertThrows
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index e1fe4e9..a8d40db 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -68,13 +68,13 @@
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
 import com.android.wm.shell.shared.ShellSharedConstants;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.split.SplitBounds;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
-import com.android.wm.shell.util.SplitBounds;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
index bfb760b..248393c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
@@ -12,7 +12,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.util.SplitBounds;
+import com.android.wm.shell.shared.split.SplitBounds;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index f7ac3e4..da0aca7b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -79,12 +79,12 @@
 import com.android.wm.shell.common.MultiInstanceHelper
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SyncTransactionQueue
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
 import com.android.wm.shell.desktopmode.DesktopTasksController
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
 import com.android.wm.shell.desktopmode.DesktopTasksLimiter
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
 import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index ac75c07..c1c30f5 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -49,6 +49,15 @@
 #endif  // __ANDROID__
 }
 
+inline bool typeface_redesign() {
+#ifdef __ANDROID__
+    static bool flag = com_android_text_flags_typeface_redesign();
+    return flag;
+#else
+    return true;
+#endif  // __ANDROID__
+}
+
 }  // namespace text_feature
 
 }  // namespace android
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index f8574ee..1510ce1 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -27,6 +27,8 @@
 #include <cutils/compiler.h>
 #include <log/log.h>
 #include <minikin/Layout.h>
+
+#include "FeatureFlags.h"
 #include "MinikinSkia.h"
 #include "Paint.h"
 #include "Typeface.h"
@@ -71,27 +73,42 @@
     static void forFontRun(const minikin::Layout& layout, Paint* paint, F& f) {
         float saveSkewX = paint->getSkFont().getSkewX();
         bool savefakeBold = paint->getSkFont().isEmbolden();
-        const minikin::MinikinFont* curFont = nullptr;
-        size_t start = 0;
-        size_t nGlyphs = layout.nGlyphs();
-        for (size_t i = 0; i < nGlyphs; i++) {
-            const minikin::MinikinFont* nextFont = layout.typeface(i).get();
-            if (i > 0 && nextFont != curFont) {
+        if (text_feature::typeface_redesign()) {
+            for (uint32_t runIdx = 0; runIdx < layout.getFontRunCount(); ++runIdx) {
+                uint32_t start = layout.getFontRunStart(runIdx);
+                uint32_t end = layout.getFontRunEnd(runIdx);
+                const minikin::FakedFont& fakedFont = layout.getFontRunFont(runIdx);
+
+                std::shared_ptr<minikin::MinikinFont> font = fakedFont.typeface();
                 SkFont* skfont = &paint->getSkFont();
-                MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
-                f(start, i);
+                MinikinFontSkia::populateSkFont(skfont, font.get(), fakedFont.fakery);
+                f(start, end);
                 skfont->setSkewX(saveSkewX);
                 skfont->setEmbolden(savefakeBold);
-                start = i;
             }
-            curFont = nextFont;
-        }
-        if (nGlyphs > start) {
-            SkFont* skfont = &paint->getSkFont();
-            MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
-            f(start, nGlyphs);
-            skfont->setSkewX(saveSkewX);
-            skfont->setEmbolden(savefakeBold);
+        } else {
+            const minikin::MinikinFont* curFont = nullptr;
+            size_t start = 0;
+            size_t nGlyphs = layout.nGlyphs();
+            for (size_t i = 0; i < nGlyphs; i++) {
+                const minikin::MinikinFont* nextFont = layout.typeface(i).get();
+                if (i > 0 && nextFont != curFont) {
+                    SkFont* skfont = &paint->getSkFont();
+                    MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
+                    f(start, i);
+                    skfont->setSkewX(saveSkewX);
+                    skfont->setEmbolden(savefakeBold);
+                    start = i;
+                }
+                curFont = nextFont;
+            }
+            if (nGlyphs > start) {
+                SkFont* skfont = &paint->getSkFont();
+                MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
+                f(start, nGlyphs);
+                skfont->setSkewX(saveSkewX);
+                skfont->setEmbolden(savefakeBold);
+            }
         }
     }
 };
diff --git a/packages/EasterEgg/src/com/android/egg/paint/PaintActivity.java b/packages/EasterEgg/src/com/android/egg/paint/PaintActivity.java
index ac47fbd..391b16d 100644
--- a/packages/EasterEgg/src/com/android/egg/paint/PaintActivity.java
+++ b/packages/EasterEgg/src/com/android/egg/paint/PaintActivity.java
@@ -23,7 +23,6 @@
 
 import android.app.Activity;
 import android.content.res.Configuration;
-import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.os.Bundle;
 import android.view.MotionEvent;
@@ -38,9 +37,7 @@
 
 import com.android.egg.R;
 
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.stream.IntStream;
 
 public class PaintActivity extends Activity {
@@ -60,31 +57,28 @@
     private View.OnClickListener buttonHandler = new View.OnClickListener() {
         @Override
         public void onClick(View view) {
-            switch (view.getId()) {
-                case R.id.btnBrush:
-                    view.setSelected(true);
-                    hideToolbar(colors);
-                    toggleToolbar(brushes);
-                    break;
-                case R.id.btnColor:
-                    view.setSelected(true);
-                    hideToolbar(brushes);
-                    toggleToolbar(colors);
-                    break;
-                case R.id.btnClear:
-                    painting.clear();
-                    break;
-                case R.id.btnSample:
-                    sampling = true;
-                    view.setSelected(true);
-                    break;
-                case R.id.btnZen:
-                    painting.setZenMode(!painting.getZenMode());
-                    view.animate()
-                            .setStartDelay(200)
-                            .setInterpolator(new OvershootInterpolator())
-                            .rotation(painting.getZenMode() ? 0f : 90f);
-                    break;
+            // With non final fields in the R class we can't switch on the
+            // id since the case values are no longer constants.
+            int viewId = view.getId();
+            if (viewId == R.id.btnBrush) {
+                view.setSelected(true);
+                hideToolbar(colors);
+                toggleToolbar(brushes);
+            } else if (viewId == R.id.btnColor) {
+                view.setSelected(true);
+                hideToolbar(brushes);
+                toggleToolbar(colors);
+            } else if (viewId == R.id.btnClear) {
+                painting.clear();
+            } else if (viewId == R.id.btnSample) {
+                sampling = true;
+                view.setSelected(true);
+            } else if (viewId == R.id.btnZen) {
+                painting.setZenMode(!painting.getZenMode());
+                view.animate()
+                        .setStartDelay(200)
+                        .setInterpolator(new OvershootInterpolator())
+                        .rotation(painting.getZenMode() ? 0f : 90f);
             }
         }
     };
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index d0fb279..a129ac1 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1384,3 +1384,13 @@
     }
 }
 
+flag {
+    name: "media_load_metadata_via_media_data_loader"
+    namespace: "systemui"
+    description: "Use MediaDataLoader for loading media metadata with better threading"
+    bug: "358350077"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 91a88bc..6f415ea 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -1154,7 +1154,7 @@
                 .then(selectableModifier)
                 .thenIf(!viewModel.isEditMode && !model.inQuietMode) {
                     Modifier.pointerInput(Unit) {
-                        observeTaps { viewModel.onTapWidget(model.componentName, model.priority) }
+                        observeTaps { viewModel.onTapWidget(model.componentName, model.rank) }
                     }
                 }
                 .thenIf(!viewModel.isEditMode && model.inQuietMode) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
index 38a3474..1137357 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -34,11 +34,11 @@
     return remember(communalContent) {
         ContentListState(
             communalContent,
-            { componentName, user, priority ->
+            { componentName, user, rank ->
                 viewModel.onAddWidget(
                     componentName,
                     user,
-                    priority,
+                    rank,
                     widgetConfigurator,
                 )
             },
@@ -56,10 +56,9 @@
 class ContentListState
 internal constructor(
     communalContent: List<CommunalContentModel>,
-    private val onAddWidget:
-        (componentName: ComponentName, user: UserHandle, priority: Int) -> Unit,
-    private val onDeleteWidget: (id: Int, componentName: ComponentName, priority: Int) -> Unit,
-    private val onReorderWidgets: (widgetIdToPriorityMap: Map<Int, Int>) -> Unit,
+    private val onAddWidget: (componentName: ComponentName, user: UserHandle, rank: Int) -> Unit,
+    private val onDeleteWidget: (id: Int, componentName: ComponentName, rank: Int) -> Unit,
+    private val onReorderWidgets: (widgetIdToRankMap: Map<Int, Int>) -> Unit,
 ) {
     var list = communalContent.toMutableStateList()
         private set
@@ -74,7 +73,7 @@
         if (list[indexToRemove].isWidgetContent()) {
             val widget = list[indexToRemove] as CommunalContentModel.WidgetContent
             list.apply { removeAt(indexToRemove) }
-            onDeleteWidget(widget.appWidgetId, widget.componentName, widget.priority)
+            onDeleteWidget(widget.appWidgetId, widget.componentName, widget.rank)
         }
     }
 
@@ -94,24 +93,24 @@
         newItemUser: UserHandle? = null,
         newItemIndex: Int? = null
     ) {
-        // filters placeholder, but, maintains the indices of the widgets as if the placeholder was
-        // in the list. When persisted in DB, this leaves space for the new item (to be added) at
-        // the correct priority.
-        val widgetIdToPriorityMap: Map<Int, Int> =
+        // New widget added to the grid. Other widgets are shifted as needed at the database level.
+        if (newItemComponentName != null && newItemUser != null && newItemIndex != null) {
+            onAddWidget(newItemComponentName, newItemUser, /* rank= */ newItemIndex)
+            return
+        }
+
+        // No new widget, only reorder existing widgets.
+        val widgetIdToRankMap: Map<Int, Int> =
             list
                 .mapIndexedNotNull { index, item ->
                     if (item is CommunalContentModel.WidgetContent) {
-                        item.appWidgetId to list.size - index
+                        item.appWidgetId to index
                     } else {
                         null
                     }
                 }
                 .toMap()
-        // reorder and then add the new widget
-        onReorderWidgets(widgetIdToPriorityMap)
-        if (newItemComponentName != null && newItemUser != null && newItemIndex != null) {
-            onAddWidget(newItemComponentName, newItemUser, /* priority= */ list.size - newItemIndex)
-        }
+        onReorderWidgets(widgetIdToRankMap)
     }
 
     /** Returns true if the item at given index is editable. */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
index 0c29394..f2f7c87 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
@@ -193,7 +193,7 @@
             val widgetExtra = event.maybeWidgetExtra() ?: return false
             val (componentName, user) = widgetExtra
             if (componentName != null && user != null) {
-                // Placeholder isn't removed yet to allow the setting the right priority for items
+                // Placeholder isn't removed yet to allow the setting the right rank for items
                 // before adding in the new item.
                 contentListState.onSaveList(
                     newItemComponentName = componentName,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
index 2bc8c87..b166737 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
@@ -26,15 +26,15 @@
 import kotlinx.coroutines.launch
 
 internal fun CoroutineScope.animateContent(
+    layoutState: MutableSceneTransitionLayoutStateImpl,
     transition: TransitionState.Transition,
     oneOffAnimation: OneOffAnimation,
     targetProgress: Float,
-    startTransition: () -> Unit,
-    finishTransition: () -> Unit,
+    chain: Boolean = true,
 ) {
     // Start the transition. This will compute the TransformationSpec associated to [transition],
     // which we need to initialize the Animatable that will actually animate it.
-    startTransition()
+    layoutState.startTransition(transition, chain)
 
     // The transition now contains the transformation spec that we should use to instantiate the
     // Animatable.
@@ -59,7 +59,7 @@
             try {
                 animatable.animateTo(targetProgress, animationSpec, initialVelocity)
             } finally {
-                finishTransition()
+                layoutState.finishTransition(transition)
             }
         }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateOverlay.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateOverlay.kt
new file mode 100644
index 0000000..e020f14
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateOverlay.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.compose.animation.scene
+
+import com.android.compose.animation.scene.content.state.TransitionState
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+
+/** Trigger a one-off transition to show or hide an overlay. */
+internal fun CoroutineScope.showOrHideOverlay(
+    layoutState: MutableSceneTransitionLayoutStateImpl,
+    overlay: OverlayKey,
+    fromOrToScene: SceneKey,
+    isShowing: Boolean,
+    transitionKey: TransitionKey?,
+    replacedTransition: TransitionState.Transition.ShowOrHideOverlay?,
+    reversed: Boolean,
+): TransitionState.Transition.ShowOrHideOverlay {
+    val targetProgress = if (reversed) 0f else 1f
+    val (fromContent, toContent) =
+        if (isShowing xor reversed) {
+            fromOrToScene to overlay
+        } else {
+            overlay to fromOrToScene
+        }
+
+    val oneOffAnimation = OneOffAnimation()
+    val transition =
+        OneOffShowOrHideOverlayTransition(
+            overlay = overlay,
+            fromOrToScene = fromOrToScene,
+            fromContent = fromContent,
+            toContent = toContent,
+            isEffectivelyShown = isShowing,
+            key = transitionKey,
+            replacedTransition = replacedTransition,
+            oneOffAnimation = oneOffAnimation,
+        )
+
+    animateContent(
+        layoutState = layoutState,
+        transition = transition,
+        oneOffAnimation = oneOffAnimation,
+        targetProgress = targetProgress,
+    )
+
+    return transition
+}
+
+/** Trigger a one-off transition to replace an overlay by another one. */
+internal fun CoroutineScope.replaceOverlay(
+    layoutState: MutableSceneTransitionLayoutStateImpl,
+    fromOverlay: OverlayKey,
+    toOverlay: OverlayKey,
+    transitionKey: TransitionKey?,
+    replacedTransition: TransitionState.Transition.ReplaceOverlay?,
+    reversed: Boolean,
+): TransitionState.Transition.ReplaceOverlay {
+    val targetProgress = if (reversed) 0f else 1f
+    val effectivelyShownOverlay = if (reversed) fromOverlay else toOverlay
+
+    val oneOffAnimation = OneOffAnimation()
+    val transition =
+        OneOffOverlayReplacingTransition(
+            fromOverlay = fromOverlay,
+            toOverlay = toOverlay,
+            effectivelyShownOverlay = effectivelyShownOverlay,
+            key = transitionKey,
+            replacedTransition = replacedTransition,
+            oneOffAnimation = oneOffAnimation,
+        )
+
+    animateContent(
+        layoutState = layoutState,
+        transition = transition,
+        oneOffAnimation = oneOffAnimation,
+        targetProgress = targetProgress,
+    )
+
+    return transition
+}
+
+private class OneOffShowOrHideOverlayTransition(
+    overlay: OverlayKey,
+    fromOrToScene: SceneKey,
+    fromContent: ContentKey,
+    toContent: ContentKey,
+    override val isEffectivelyShown: Boolean,
+    override val key: TransitionKey?,
+    replacedTransition: TransitionState.Transition?,
+    private val oneOffAnimation: OneOffAnimation,
+) :
+    TransitionState.Transition.ShowOrHideOverlay(
+        overlay,
+        fromOrToScene,
+        fromContent,
+        toContent,
+        replacedTransition,
+    ) {
+    override val progress: Float
+        get() = oneOffAnimation.progress
+
+    override val progressVelocity: Float
+        get() = oneOffAnimation.progressVelocity
+
+    override val isInitiatedByUserInput: Boolean = false
+    override val isUserInputOngoing: Boolean = false
+
+    override fun finish(): Job = oneOffAnimation.finish()
+}
+
+private class OneOffOverlayReplacingTransition(
+    fromOverlay: OverlayKey,
+    toOverlay: OverlayKey,
+    override val effectivelyShownOverlay: OverlayKey,
+    override val key: TransitionKey?,
+    replacedTransition: TransitionState.Transition?,
+    private val oneOffAnimation: OneOffAnimation,
+) : TransitionState.Transition.ReplaceOverlay(fromOverlay, toOverlay, replacedTransition) {
+    override val progress: Float
+        get() = oneOffAnimation.progress
+
+    override val progressVelocity: Float
+        get() = oneOffAnimation.progressVelocity
+
+    override val isInitiatedByUserInput: Boolean = false
+    override val isUserInputOngoing: Boolean = false
+
+    override fun finish(): Job = oneOffAnimation.finish()
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index 7eef5d6..4aa50b5 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -412,7 +412,7 @@
                             if (canOverflow) transition.progress
                             else transition.progress.fastCoerceIn(0f, 1f)
                         }
-                        overscrollSpec.scene == transition.toContent -> 1f
+                        overscrollSpec.content == transition.toContent -> 1f
                         else -> 0f
                     }
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index 8aa0690..abe079a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -166,11 +166,11 @@
         }
 
     animateContent(
+        layoutState = layoutState,
         transition = transition,
         oneOffAnimation = oneOffAnimation,
         targetProgress = targetProgress,
-        startTransition = { layoutState.startTransition(transition, chain) },
-        finishTransition = { layoutState.finishTransition(transition) },
+        chain = chain,
     )
 
     return transition
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 6ea0285..9b1740d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -310,9 +310,10 @@
 
         val transition = elementState as? TransitionState.Transition
 
-        // If this element is not supposed to be laid out now because the other content of its
-        // transition is overscrolling, then lay out the element normally and don't place it.
-        val overscrollScene = transition?.currentOverscrollSpec?.scene
+        // If this element is not supposed to be laid out now, either because it is not part of any
+        // ongoing transition or the other content of its transition is overscrolling, then lay out
+        // the element normally and don't place it.
+        val overscrollScene = transition?.currentOverscrollSpec?.content
         val isOtherSceneOverscrolling = overscrollScene != null && overscrollScene != content.key
         if (isOtherSceneOverscrolling) {
             return doNotPlace(measurable, constraints)
@@ -845,7 +846,7 @@
     transition: TransitionState.Transition,
 ): Boolean {
     // If we are overscrolling, only place/compose the element in the overscrolling scene.
-    val overscrollScene = transition.currentOverscrollSpec?.scene
+    val overscrollScene = transition.currentOverscrollSpec?.content
     if (overscrollScene != null) {
         return content == overscrollScene
     }
@@ -1184,7 +1185,7 @@
     val currentContent = currentContentState.content
     if (transition is TransitionState.HasOverscrollProperties) {
         val overscroll = transition.currentOverscrollSpec
-        if (overscroll?.scene == currentContent) {
+        if (overscroll?.content == currentContent) {
             val elementSpec =
                 overscroll.transformationSpec.transformations(element.key, currentContent)
             val propertySpec = transformation(elementSpec) ?: return currentValue()
@@ -1210,7 +1211,7 @@
             // TODO(b/290184746): Make sure that we don't overflow transformations associated to a
             // range.
             val directionSign = if (transition.isUpOrLeft) -1 else 1
-            val isToContent = overscroll.scene == transition.toContent
+            val isToContent = overscroll.content == transition.toContent
             val linearProgress = transition.progress.let { if (isToContent) it - 1f else it }
             val progressConverter =
                 overscroll.progressConverter
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 21f11e4..5f5141e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -432,7 +432,7 @@
                 val progress =
                     when {
                         overscrollSpec == null -> transition.progress
-                        overscrollSpec.scene == transition.toScene -> 1f
+                        overscrollSpec.content == transition.toScene -> 1f
                         else -> 0f
                     }
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 74cd136..0ac6912 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -81,12 +81,15 @@
 
     /**
      * Whether we are transitioning. If [from] or [to] is empty, we will also check that they match
-     * the scenes we are animating from and/or to.
+     * the contents we are animating from and/or to.
      */
-    fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean
+    fun isTransitioning(from: ContentKey? = null, to: ContentKey? = null): Boolean
 
-    /** Whether we are transitioning from [scene] to [other], or from [other] to [scene]. */
-    fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean
+    /** Whether we are transitioning from [content] to [other], or from [other] to [content]. */
+    fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean
+
+    /** Whether we are transitioning from or to [content]. */
+    fun isTransitioningFromOrTo(content: ContentKey): Boolean
 }
 
 /** A [SceneTransitionLayoutState] whose target scene can be imperatively set. */
@@ -260,14 +263,19 @@
         }
     }
 
-    override fun isTransitioning(from: SceneKey?, to: SceneKey?): Boolean {
+    override fun isTransitioning(from: ContentKey?, to: ContentKey?): Boolean {
         val transition = currentTransition ?: return false
         return transition.isTransitioning(from, to)
     }
 
-    override fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean {
+    override fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean {
         val transition = currentTransition ?: return false
-        return transition.isTransitioningBetween(scene, other)
+        return transition.isTransitioningBetween(content, other)
+    }
+
+    override fun isTransitioningFromOrTo(content: ContentKey): Boolean {
+        val transition = currentTransition ?: return false
+        return transition.isTransitioningFromOrTo(content)
     }
 
     override fun setTargetScene(
@@ -293,10 +301,7 @@
      *
      * Important: you *must* call [finishTransition] once the transition is finished.
      */
-    internal fun startTransition(
-        transition: TransitionState.Transition.ChangeCurrentScene,
-        chain: Boolean = true,
-    ) {
+    internal fun startTransition(transition: TransitionState.Transition, chain: Boolean = true) {
         checkThread()
 
         // Set the current scene and overlays on the transition.
@@ -305,23 +310,23 @@
         transition.currentOverlaysWhenTransitionStarted = currentState.currentOverlays
 
         // Compute the [TransformationSpec] when the transition starts.
-        val fromScene = transition.fromScene
-        val toScene = transition.toScene
+        val fromContent = transition.fromContent
+        val toContent = transition.toContent
         val orientation = (transition as? TransitionState.HasOverscrollProperties)?.orientation
 
         // Update the transition specs.
         transition.transformationSpec =
             transitions
-                .transitionSpec(fromScene, toScene, key = transition.key)
+                .transitionSpec(fromContent, toContent, key = transition.key)
                 .transformationSpec()
         transition.previewTransformationSpec =
             transitions
-                .transitionSpec(fromScene, toScene, key = transition.key)
+                .transitionSpec(fromContent, toContent, key = transition.key)
                 .previewTransformationSpec()
         if (orientation != null) {
             transition.updateOverscrollSpecs(
-                fromSpec = transitions.overscrollSpec(fromScene, orientation),
-                toSpec = transitions.overscrollSpec(toScene, orientation),
+                fromSpec = transitions.overscrollSpec(fromContent, orientation),
+                toSpec = transitions.overscrollSpec(toContent, orientation),
             )
         } else {
             transition.updateOverscrollSpecs(fromSpec = null, toSpec = null)
@@ -402,32 +407,27 @@
     }
 
     private fun setupTransitionLinks(transition: TransitionState.Transition) {
-        when (transition) {
-            is TransitionState.Transition.ChangeCurrentScene -> {
-                stateLinks.fastForEach { stateLink ->
-                    val matchingLinks =
-                        stateLink.transitionLinks.fastFilter { it.isMatchingLink(transition) }
-                    if (matchingLinks.isEmpty()) return@fastForEach
-                    if (matchingLinks.size > 1) error("More than one link matched.")
+        stateLinks.fastForEach { stateLink ->
+            val matchingLinks =
+                stateLink.transitionLinks.fastFilter { it.isMatchingLink(transition) }
+            if (matchingLinks.isEmpty()) return@fastForEach
+            if (matchingLinks.size > 1) error("More than one link matched.")
 
-                    val targetCurrentScene = stateLink.target.transitionState.currentScene
-                    val matchingLink = matchingLinks[0]
+            val targetCurrentScene = stateLink.target.transitionState.currentScene
+            val matchingLink = matchingLinks[0]
 
-                    if (!matchingLink.targetIsInValidState(targetCurrentScene)) return@fastForEach
+            if (!matchingLink.targetIsInValidState(targetCurrentScene)) return@fastForEach
 
-                    val linkedTransition =
-                        LinkedTransition(
-                            originalTransition = transition,
-                            fromScene = targetCurrentScene,
-                            toScene = matchingLink.targetTo,
-                            key = matchingLink.targetTransitionKey,
-                        )
+            val linkedTransition =
+                LinkedTransition(
+                    originalTransition = transition,
+                    fromScene = targetCurrentScene,
+                    toScene = matchingLink.targetTo,
+                    key = matchingLink.targetTransitionKey,
+                )
 
-                    stateLink.target.startTransition(linkedTransition)
-                    transition.activeTransitionLinks[stateLink] = linkedTransition
-                }
-            }
-            else -> error("transition links are not supported with overlays yet")
+            stateLink.target.startTransition(linkedTransition)
+            transition.activeTransitionLinks[stateLink] = linkedTransition
         }
     }
 
@@ -552,12 +552,39 @@
         checkThread()
 
         // Overlay is already shown, do nothing.
-        if (overlay in transitionState.currentOverlays) {
+        val currentState = transitionState
+        if (overlay in currentState.currentOverlays) {
             return
         }
 
-        // TODO(b/353679003): Animate the overlay instead of instantly snapping to an Idle state.
-        snapToScene(transitionState.currentScene, transitionState.currentOverlays + overlay)
+        val fromScene = currentState.currentScene
+        fun animate(
+            replacedTransition: TransitionState.Transition.ShowOrHideOverlay? = null,
+            reversed: Boolean = false,
+        ) {
+            animationScope.showOrHideOverlay(
+                layoutState = this@MutableSceneTransitionLayoutStateImpl,
+                overlay = overlay,
+                fromOrToScene = fromScene,
+                isShowing = true,
+                transitionKey = transitionKey,
+                replacedTransition = replacedTransition,
+                reversed = reversed,
+            )
+        }
+
+        if (
+            currentState is TransitionState.Transition.ShowOrHideOverlay &&
+                currentState.overlay == overlay &&
+                currentState.fromOrToScene == fromScene
+        ) {
+            animate(
+                replacedTransition = currentState,
+                reversed = overlay == currentState.fromContent
+            )
+        } else {
+            animate()
+        }
     }
 
     override fun hideOverlay(
@@ -568,12 +595,36 @@
         checkThread()
 
         // Overlay is not shown, do nothing.
-        if (!transitionState.currentOverlays.contains(overlay)) {
+        val currentState = transitionState
+        if (!currentState.currentOverlays.contains(overlay)) {
             return
         }
 
-        // TODO(b/353679003): Animate the overlay instead of instantly snapping to an Idle state.
-        snapToScene(transitionState.currentScene, transitionState.currentOverlays - overlay)
+        val toScene = currentState.currentScene
+        fun animate(
+            replacedTransition: TransitionState.Transition.ShowOrHideOverlay? = null,
+            reversed: Boolean = false,
+        ) {
+            animationScope.showOrHideOverlay(
+                layoutState = this@MutableSceneTransitionLayoutStateImpl,
+                overlay = overlay,
+                fromOrToScene = toScene,
+                isShowing = false,
+                transitionKey = transitionKey,
+                replacedTransition = replacedTransition,
+                reversed = reversed,
+            )
+        }
+
+        if (
+            currentState is TransitionState.Transition.ShowOrHideOverlay &&
+                currentState.overlay == overlay &&
+                currentState.fromOrToScene == toScene
+        ) {
+            animate(replacedTransition = currentState, reversed = overlay == currentState.toContent)
+        } else {
+            animate()
+        }
     }
 
     override fun replaceOverlay(
@@ -583,16 +634,45 @@
         transitionKey: TransitionKey?
     ) {
         checkThread()
-        require(from in currentOverlays) {
+
+        val currentState = transitionState
+        require(from != to) {
+            "replaceOverlay must be called with different overlays (from = to = ${from.debugName})"
+        }
+        require(from in currentState.currentOverlays) {
             "Overlay ${from.debugName} is not shown so it can't be replaced by ${to.debugName}"
         }
-        require(to !in currentOverlays) {
+        require(to !in currentState.currentOverlays) {
             "Overlay ${to.debugName} is already shown so it can't replace ${from.debugName}"
         }
 
-        // TODO(b/353679003): Animate from into to instead of hiding/showing the overlays
-        // separately.
-        snapToScene(transitionState.currentScene, transitionState.currentOverlays - from + to)
+        fun animate(
+            replacedTransition: TransitionState.Transition.ReplaceOverlay? = null,
+            reversed: Boolean = false,
+        ) {
+            animationScope.replaceOverlay(
+                layoutState = this@MutableSceneTransitionLayoutStateImpl,
+                fromOverlay = if (reversed) to else from,
+                toOverlay = if (reversed) from else to,
+                transitionKey = transitionKey,
+                replacedTransition = replacedTransition,
+                reversed = reversed,
+            )
+        }
+
+        if (currentState is TransitionState.Transition.ReplaceOverlay) {
+            if (currentState.fromOverlay == from && currentState.toOverlay == to) {
+                animate(replacedTransition = currentState, reversed = false)
+                return
+            }
+
+            if (currentState.fromOverlay == to && currentState.toOverlay == from) {
+                animate(replacedTransition = currentState, reversed = true)
+                return
+            }
+        }
+
+        animate()
     }
 }
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index d35d956..cefcff7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -49,16 +49,16 @@
 ) {
     private val transitionCache =
         mutableMapOf<
-            SceneKey,
-            MutableMap<SceneKey, MutableMap<TransitionKey?, TransitionSpecImpl>>
+            ContentKey,
+            MutableMap<ContentKey, MutableMap<TransitionKey?, TransitionSpecImpl>>
         >()
 
     private val overscrollCache =
-        mutableMapOf<SceneKey, MutableMap<Orientation, OverscrollSpecImpl?>>()
+        mutableMapOf<ContentKey, MutableMap<Orientation, OverscrollSpecImpl?>>()
 
     internal fun transitionSpec(
-        from: SceneKey,
-        to: SceneKey,
+        from: ContentKey,
+        to: ContentKey,
         key: TransitionKey?,
     ): TransitionSpecImpl {
         return transitionCache
@@ -67,7 +67,11 @@
             .getOrPut(key) { findSpec(from, to, key) }
     }
 
-    private fun findSpec(from: SceneKey, to: SceneKey, key: TransitionKey?): TransitionSpecImpl {
+    private fun findSpec(
+        from: ContentKey,
+        to: ContentKey,
+        key: TransitionKey?
+    ): TransitionSpecImpl {
         val spec = transition(from, to, key) { it.from == from && it.to == to }
         if (spec != null) {
             return spec
@@ -93,8 +97,8 @@
     }
 
     private fun transition(
-        from: SceneKey,
-        to: SceneKey,
+        from: ContentKey,
+        to: ContentKey,
         key: TransitionKey?,
         filter: (TransitionSpecImpl) -> Boolean,
     ): TransitionSpecImpl? {
@@ -110,16 +114,16 @@
         return match
     }
 
-    private fun defaultTransition(from: SceneKey, to: SceneKey) =
+    private fun defaultTransition(from: ContentKey, to: ContentKey) =
         TransitionSpecImpl(key = null, from, to, null, null, TransformationSpec.EmptyProvider)
 
-    internal fun overscrollSpec(scene: SceneKey, orientation: Orientation): OverscrollSpecImpl? =
+    internal fun overscrollSpec(scene: ContentKey, orientation: Orientation): OverscrollSpecImpl? =
         overscrollCache
             .getOrPut(scene) { mutableMapOf() }
-            .getOrPut(orientation) { overscroll(scene, orientation) { it.scene == scene } }
+            .getOrPut(orientation) { overscroll(scene, orientation) { it.content == scene } }
 
     private fun overscroll(
-        scene: SceneKey,
+        scene: ContentKey,
         orientation: Orientation,
         filter: (OverscrollSpecImpl) -> Boolean,
     ): OverscrollSpecImpl? {
@@ -264,10 +268,10 @@
         previewTransformationSpec?.invoke()
 }
 
-/** The definition of the overscroll behavior of the [scene]. */
+/** The definition of the overscroll behavior of the [content]. */
 interface OverscrollSpec {
     /** The scene we are over scrolling. */
-    val scene: SceneKey
+    val content: ContentKey
 
     /** The orientation of this [OverscrollSpec]. */
     val orientation: Orientation
@@ -288,7 +292,7 @@
 }
 
 internal class OverscrollSpecImpl(
-    override val scene: SceneKey,
+    override val content: ContentKey,
     override val orientation: Orientation,
     override val transformationSpec: TransformationSpecImpl,
     override val progressConverter: ProgressConverter?,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 523e5bdd7..18e356f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -107,7 +107,7 @@
     ): OverscrollSpec {
         val spec =
             OverscrollSpecImpl(
-                scene = scene,
+                content = scene,
                 orientation = orientation,
                 transformationSpec =
                     TransformationSpecImpl(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index 6f608cb..6bc1754 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -96,6 +96,8 @@
                 .approachLayout(
                     isMeasurementApproachInProgress = { layoutImpl.state.isTransitioning() }
                 ) { measurable, constraints ->
+                    // TODO(b/353679003): Use the ModifierNode API to set this *before* the approach
+                    // pass is started.
                     targetSize = lookaheadSize
                     val placeable = measurable.measure(constraints)
                     layout(placeable.width, placeable.height) { placeable.place(0, 0) }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
index 89b0040..59ddb13 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
@@ -23,7 +23,7 @@
 
 /** A linked transition which is driven by a [originalTransition]. */
 internal class LinkedTransition(
-    private val originalTransition: TransitionState.Transition.ChangeCurrentScene,
+    private val originalTransition: TransitionState.Transition,
     fromScene: SceneKey,
     toScene: SceneKey,
     override val key: TransitionKey? = null,
@@ -32,8 +32,8 @@
     override val currentScene: SceneKey
         get() {
             return when (originalTransition.currentScene) {
-                originalTransition.fromScene -> fromScene
-                originalTransition.toScene -> toScene
+                originalTransition.fromContent -> fromScene
+                originalTransition.toContent -> toScene
                 else -> error("Original currentScene is neither FromScene nor ToScene")
             }
         }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
index c29bf21..c830ca4 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
@@ -16,6 +16,7 @@
 
 package com.android.compose.animation.scene.transition.link
 
+import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateImpl
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneTransitionLayoutState
@@ -35,8 +36,8 @@
      * target to `SceneA` from any current scene.
      */
     class TransitionLink(
-        val sourceFrom: SceneKey?,
-        val sourceTo: SceneKey?,
+        val sourceFrom: ContentKey?,
+        val sourceTo: ContentKey?,
         val targetFrom: SceneKey?,
         val targetTo: SceneKey,
         val targetTransitionKey: TransitionKey? = null,
@@ -50,15 +51,15 @@
         }
 
         internal fun isMatchingLink(
-            transition: TransitionState.Transition.ChangeCurrentScene,
+            transition: TransitionState.Transition,
         ): Boolean {
-            return (sourceFrom == null || sourceFrom == transition.fromScene) &&
-                (sourceTo == null || sourceTo == transition.toScene)
+            return (sourceFrom == null || sourceFrom == transition.fromContent) &&
+                (sourceTo == null || sourceTo == transition.toContent)
         }
 
-        internal fun targetIsInValidState(targetCurrentScene: SceneKey): Boolean {
-            return (targetFrom == null || targetFrom == targetCurrentScene) &&
-                targetTo != targetCurrentScene
+        internal fun targetIsInValidState(targetCurrentContent: ContentKey): Boolean {
+            return (targetFrom == null || targetFrom == targetCurrentContent) &&
+                targetTo != targetCurrentContent
         }
     }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index 25be3f9..7d8e898 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -1020,7 +1020,7 @@
 
         // We scrolled down, under scene C there is nothing, so we can use the overscroll spec
         assertThat(layoutState.currentTransition?.currentOverscrollSpec).isNotNull()
-        assertThat(layoutState.currentTransition?.currentOverscrollSpec?.scene).isEqualTo(SceneC)
+        assertThat(layoutState.currentTransition?.currentOverscrollSpec?.content).isEqualTo(SceneC)
         val transition = layoutState.currentTransition
         assertThat(transition).isNotNull()
         assertThat(transition!!.progress).isEqualTo(-0.1f)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
index d4391e0..85db418 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.compose.animation.scene
 
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.size
@@ -33,6 +35,7 @@
 import androidx.compose.ui.test.hasTestTag
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.compose.animation.scene.TestOverlays.OverlayA
@@ -49,8 +52,8 @@
     @get:Rule val rule = createComposeRule()
 
     @Composable
-    private fun ContentScope.Foo() {
-        Box(Modifier.element(TestElements.Foo).size(100.dp))
+    private fun ContentScope.Foo(width: Dp = 100.dp, height: Dp = 100.dp) {
+        Box(Modifier.element(TestElements.Foo).size(width, height))
     }
 
     @Test
@@ -316,4 +319,209 @@
         // of the layout.
         rule.onNodeWithTag(contentTag).assertSizeIsEqualTo(100.dp)
     }
+
+    @Test
+    fun showAnimation() {
+        rule.testShowOverlayTransition(
+            fromSceneContent = {
+                Box(Modifier.size(width = 180.dp, height = 120.dp)) {
+                    Foo(width = 60.dp, height = 40.dp)
+                }
+            },
+            overlayContent = { Foo(width = 100.dp, height = 80.dp) },
+            transition = {
+                // 4 frames of animation
+                spec = tween(4 * 16, easing = LinearEasing)
+            },
+        ) {
+            // Foo moves from (0,0) with a size of 60x40dp to centered (in a 180x120dp Box) with a
+            // size of 100x80dp, so at (40,20).
+            before {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertSizeIsEqualTo(60.dp, 40.dp)
+                    .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+                rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+            }
+
+            at(16) {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertSizeIsEqualTo(70.dp, 50.dp)
+                    .assertPositionInRootIsEqualTo(10.dp, 5.dp)
+            }
+
+            at(32) {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertSizeIsEqualTo(80.dp, 60.dp)
+                    .assertPositionInRootIsEqualTo(20.dp, 10.dp)
+            }
+
+            at(48) {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertSizeIsEqualTo(90.dp, 70.dp)
+                    .assertPositionInRootIsEqualTo(30.dp, 15.dp)
+            }
+
+            after {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertSizeIsEqualTo(100.dp, 80.dp)
+                    .assertPositionInRootIsEqualTo(40.dp, 20.dp)
+            }
+        }
+    }
+
+    @Test
+    fun hideAnimation() {
+        rule.testHideOverlayTransition(
+            toSceneContent = {
+                Box(Modifier.size(width = 180.dp, height = 120.dp)) {
+                    Foo(width = 60.dp, height = 40.dp)
+                }
+            },
+            overlayContent = { Foo(width = 100.dp, height = 80.dp) },
+            transition = {
+                // 4 frames of animation
+                spec = tween(4 * 16, easing = LinearEasing)
+            },
+        ) {
+            // Foo moves from centered (in a 180x120dp Box) with a size of 100x80dp, so at (40,20),
+            // to (0,0) with a size of 60x40dp.
+            before {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertSizeIsEqualTo(100.dp, 80.dp)
+                    .assertPositionInRootIsEqualTo(40.dp, 20.dp)
+            }
+
+            at(16) {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertSizeIsEqualTo(90.dp, 70.dp)
+                    .assertPositionInRootIsEqualTo(30.dp, 15.dp)
+            }
+
+            at(32) {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertSizeIsEqualTo(80.dp, 60.dp)
+                    .assertPositionInRootIsEqualTo(20.dp, 10.dp)
+            }
+
+            at(48) {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertSizeIsEqualTo(70.dp, 50.dp)
+                    .assertPositionInRootIsEqualTo(10.dp, 5.dp)
+            }
+
+            after {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertSizeIsEqualTo(60.dp, 40.dp)
+                    .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+                rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+            }
+        }
+    }
+
+    @Test
+    fun replaceAnimation() {
+        rule.testReplaceOverlayTransition(
+            currentSceneContent = { Box(Modifier.size(width = 180.dp, height = 120.dp)) },
+            fromContent = { Foo(width = 60.dp, height = 40.dp) },
+            fromAlignment = Alignment.TopStart,
+            toContent = { Foo(width = 100.dp, height = 80.dp) },
+            transition = {
+                // 4 frames of animation
+                spec = tween(4 * 16, easing = LinearEasing)
+            },
+        ) {
+            // Foo moves from (0,0) with a size of 60x40dp to centered (in a 180x120dp Box) with a
+            // size of 100x80dp, so at (40,20).
+            before {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertSizeIsEqualTo(60.dp, 40.dp)
+                    .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+                rule.onNode(isElement(TestElements.Foo, content = OverlayB)).assertDoesNotExist()
+            }
+
+            at(16) {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayB))
+                    .assertSizeIsEqualTo(70.dp, 50.dp)
+                    .assertPositionInRootIsEqualTo(10.dp, 5.dp)
+            }
+
+            at(32) {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayB))
+                    .assertSizeIsEqualTo(80.dp, 60.dp)
+                    .assertPositionInRootIsEqualTo(20.dp, 10.dp)
+            }
+
+            at(48) {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayB))
+                    .assertSizeIsEqualTo(90.dp, 70.dp)
+                    .assertPositionInRootIsEqualTo(30.dp, 15.dp)
+            }
+
+            after {
+                rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayB))
+                    .assertSizeIsEqualTo(100.dp, 80.dp)
+                    .assertPositionInRootIsEqualTo(40.dp, 20.dp)
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 3422a8e..69f2cba 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -495,7 +495,7 @@
         // overscroll for SceneB is defined
         progress.value = 1.1f
         val overscrollSpec = assertThat(transition).hasOverscrollSpec()
-        assertThat(overscrollSpec.scene).isEqualTo(SceneB)
+        assertThat(overscrollSpec.content).isEqualTo(SceneB)
     }
 
     @Test
@@ -516,7 +516,7 @@
         // overscroll for SceneA is defined
         progress.value = -0.1f
         val overscrollSpec = assertThat(transition).hasOverscrollSpec()
-        assertThat(overscrollSpec.scene).isEqualTo(SceneA)
+        assertThat(overscrollSpec.content).isEqualTo(SceneA)
 
         // scroll from SceneA to SceneB
         progress.value = 0.5f
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index 7f26b98..1ebd3d9 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -16,9 +16,12 @@
 
 package com.android.compose.animation.scene
 
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.semantics.SemanticsNode
 import androidx.compose.ui.test.SemanticsNodeInteraction
@@ -115,6 +118,97 @@
     )
 }
 
+/** Test the transition when showing [overlay] from [fromScene]. */
+fun ComposeContentTestRule.testShowOverlayTransition(
+    fromSceneContent: @Composable ContentScope.() -> Unit,
+    overlayContent: @Composable ContentScope.() -> Unit,
+    transition: TransitionBuilder.() -> Unit,
+    fromScene: SceneKey = TestScenes.SceneA,
+    overlay: OverlayKey = TestOverlays.OverlayA,
+    builder: TransitionTestBuilder.() -> Unit,
+) {
+    testTransition(
+        state =
+            runOnUiThread {
+                MutableSceneTransitionLayoutState(
+                    fromScene,
+                    transitions = transitions { from(fromScene, overlay, builder = transition) },
+                )
+            },
+        transitionLayout = { state ->
+            SceneTransitionLayout(state) {
+                scene(fromScene) { fromSceneContent() }
+                overlay(overlay) { overlayContent() }
+            }
+        },
+        changeState = { state -> state.showOverlay(overlay, animationScope = this) },
+        builder = builder,
+    )
+}
+
+/** Test the transition when hiding [overlay] to [toScene]. */
+fun ComposeContentTestRule.testHideOverlayTransition(
+    toSceneContent: @Composable ContentScope.() -> Unit,
+    overlayContent: @Composable ContentScope.() -> Unit,
+    transition: TransitionBuilder.() -> Unit,
+    toScene: SceneKey = TestScenes.SceneA,
+    overlay: OverlayKey = TestOverlays.OverlayA,
+    builder: TransitionTestBuilder.() -> Unit,
+) {
+    testTransition(
+        state =
+            runOnUiThread {
+                MutableSceneTransitionLayoutState(
+                    toScene,
+                    initialOverlays = setOf(overlay),
+                    transitions = transitions { from(overlay, toScene, builder = transition) },
+                )
+            },
+        transitionLayout = { state ->
+            SceneTransitionLayout(state) {
+                scene(toScene) { toSceneContent() }
+                overlay(overlay) { overlayContent() }
+            }
+        },
+        changeState = { state -> state.hideOverlay(overlay, animationScope = this) },
+        builder = builder,
+    )
+}
+
+/** Test the transition when replace [from] to [to]. */
+fun ComposeContentTestRule.testReplaceOverlayTransition(
+    fromContent: @Composable ContentScope.() -> Unit,
+    toContent: @Composable ContentScope.() -> Unit,
+    transition: TransitionBuilder.() -> Unit,
+    currentSceneContent: @Composable ContentScope.() -> Unit = { Box(Modifier.fillMaxSize()) },
+    fromAlignment: Alignment = Alignment.Center,
+    toAlignment: Alignment = Alignment.Center,
+    from: OverlayKey = TestOverlays.OverlayA,
+    to: OverlayKey = TestOverlays.OverlayB,
+    currentScene: SceneKey = TestScenes.SceneA,
+    builder: TransitionTestBuilder.() -> Unit,
+) {
+    testTransition(
+        state =
+            runOnUiThread {
+                MutableSceneTransitionLayoutState(
+                    currentScene,
+                    initialOverlays = setOf(from),
+                    transitions = transitions { from(from, to, builder = transition) },
+                )
+            },
+        transitionLayout = { state ->
+            SceneTransitionLayout(state) {
+                scene(currentScene) { currentSceneContent() }
+                overlay(from, fromAlignment) { fromContent() }
+                overlay(to, toAlignment) { toContent() }
+            }
+        },
+        changeState = { state -> state.replaceOverlay(from, to, animationScope = this) },
+        builder = builder,
+    )
+}
+
 data class TransitionRecordingSpec(
     val recordBefore: Boolean = true,
     val recordAfter: Boolean = true,
@@ -188,6 +282,21 @@
             "(${currentScene.debugName})"
     }
 
+    testTransition(
+        state = state,
+        changeState = { state -> state.setTargetScene(to, coroutineScope = this) },
+        transitionLayout = transitionLayout,
+        builder = builder,
+    )
+}
+
+/** Test the transition from [state] to [to]. */
+fun ComposeContentTestRule.testTransition(
+    state: MutableSceneTransitionLayoutState,
+    changeState: CoroutineScope.(MutableSceneTransitionLayoutState) -> Unit,
+    transitionLayout: @Composable (state: MutableSceneTransitionLayoutState) -> Unit,
+    builder: TransitionTestBuilder.() -> Unit,
+) {
     val test = transitionTest(builder)
     val assertionScope =
         object : TransitionTestAssertionScope {
@@ -213,7 +322,7 @@
     mainClock.autoAdvance = false
 
     // Change the current scene.
-    runOnUiThread { state.setTargetScene(to, coroutineScope) }
+    runOnUiThread { coroutineScope.changeState(state) }
     waitForIdle()
     mainClock.advanceTimeByFrame()
     waitForIdle()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java
index 201ed00..43db5a7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java
@@ -148,6 +148,7 @@
                 mKosmos.getWifiInteractor(),
                 mKosmos.getCommunalSceneInteractor(),
                 mLogBuffer);
+        mController.onInit();
     }
 
     @Test
@@ -517,6 +518,15 @@
         verify(mDreamOverlayStateController).setDreamOverlayStatusBarVisible(false);
     }
 
+    @Test
+    public void testStatusBarWindowStateControllerListenerLifecycle() {
+        ArgumentCaptor<StatusBarWindowStateListener> listenerCaptor =
+                ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
+        verify(mStatusBarWindowStateController).addListener(listenerCaptor.capture());
+        mController.destroy();
+        verify(mStatusBarWindowStateController).removeListener(eq(listenerCaptor.getValue()));
+    }
+
     private StatusBarWindowStateListener updateStatusBarWindowState(boolean show) {
         when(mStatusBarWindowStateController.windowIsShowing()).thenReturn(show);
         final ArgumentCaptor<StatusBarWindowStateListener>
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
index ad2c42f..eba395b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
@@ -115,21 +115,21 @@
                 .addWidget(
                     widgetId = 0,
                     componentName = defaultWidgets[0],
-                    priority = 3,
+                    rank = 0,
                     userSerialNumber = 0,
                 )
             verify(communalWidgetDao)
                 .addWidget(
                     widgetId = 1,
                     componentName = defaultWidgets[1],
-                    priority = 2,
+                    rank = 1,
                     userSerialNumber = 0,
                 )
             verify(communalWidgetDao)
                 .addWidget(
                     widgetId = 2,
                     componentName = defaultWidgets[2],
-                    priority = 1,
+                    rank = 2,
                     userSerialNumber = 0,
                 )
         }
@@ -150,7 +150,7 @@
                 .addWidget(
                     widgetId = anyInt(),
                     componentName = any(),
-                    priority = anyInt(),
+                    rank = anyInt(),
                     userSerialNumber = anyInt(),
                 )
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index ca81838..980a5ec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -154,7 +154,7 @@
                     CommunalWidgetContentModel.Available(
                         appWidgetId = communalWidgetItemEntry.widgetId,
                         providerInfo = providerInfoA,
-                        priority = communalItemRankEntry.rank,
+                        rank = communalItemRankEntry.rank,
                     )
                 )
 
@@ -190,12 +190,12 @@
                     CommunalWidgetContentModel.Available(
                         appWidgetId = 1,
                         providerInfo = providerInfoA,
-                        priority = 1,
+                        rank = 1,
                     ),
                     CommunalWidgetContentModel.Available(
                         appWidgetId = 2,
                         providerInfo = providerInfoB,
-                        priority = 2,
+                        rank = 2,
                     ),
                 )
         }
@@ -225,12 +225,12 @@
                     CommunalWidgetContentModel.Available(
                         appWidgetId = 1,
                         providerInfo = providerInfoA,
-                        priority = 1,
+                        rank = 1,
                     ),
                     CommunalWidgetContentModel.Available(
                         appWidgetId = 2,
                         providerInfo = providerInfoB,
-                        priority = 2,
+                        rank = 2,
                     ),
                 )
 
@@ -248,12 +248,12 @@
                         appWidgetId = 1,
                         // Verify that provider info updated
                         providerInfo = providerInfoC,
-                        priority = 1,
+                        rank = 1,
                     ),
                     CommunalWidgetContentModel.Available(
                         appWidgetId = 2,
                         providerInfo = providerInfoB,
-                        priority = 2,
+                        rank = 2,
                     ),
                 )
         }
@@ -263,7 +263,7 @@
         testScope.runTest {
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
-            val priority = 1
+            val rank = 1
             whenever(communalWidgetHost.getAppWidgetInfo(id))
                 .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
             whenever(
@@ -273,12 +273,11 @@
                     )
                 )
                 .thenReturn(id)
-            underTest.addWidget(provider, mainUser, priority, kosmos.widgetConfiguratorSuccess)
+            underTest.addWidget(provider, mainUser, rank, kosmos.widgetConfiguratorSuccess)
             runCurrent()
 
             verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
-            verify(communalWidgetDao)
-                .addWidget(id, provider, priority, testUserSerialNumber(mainUser))
+            verify(communalWidgetDao).addWidget(id, provider, rank, testUserSerialNumber(mainUser))
 
             // Verify backup requested
             verify(backupManager).dataChanged()
@@ -289,7 +288,7 @@
         testScope.runTest {
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
-            val priority = 1
+            val rank = 1
             whenever(communalWidgetHost.getAppWidgetInfo(id))
                 .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
             whenever(
@@ -299,7 +298,7 @@
                     )
                 )
                 .thenReturn(id)
-            underTest.addWidget(provider, mainUser, priority, kosmos.widgetConfiguratorFail)
+            underTest.addWidget(provider, mainUser, rank, kosmos.widgetConfiguratorFail)
             runCurrent()
 
             verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
@@ -316,7 +315,7 @@
         testScope.runTest {
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
-            val priority = 1
+            val rank = 1
             whenever(communalWidgetHost.getAppWidgetInfo(id))
                 .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
             whenever(
@@ -326,7 +325,7 @@
                     )
                 )
                 .thenReturn(id)
-            underTest.addWidget(provider, mainUser, priority) {
+            underTest.addWidget(provider, mainUser, rank) {
                 throw IllegalStateException("some error")
             }
             runCurrent()
@@ -345,7 +344,7 @@
         testScope.runTest {
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
-            val priority = 1
+            val rank = 1
             whenever(communalWidgetHost.getAppWidgetInfo(id))
                 .thenReturn(PROVIDER_INFO_CONFIGURATION_OPTIONAL)
             whenever(
@@ -355,12 +354,11 @@
                     )
                 )
                 .thenReturn(id)
-            underTest.addWidget(provider, mainUser, priority, kosmos.widgetConfiguratorFail)
+            underTest.addWidget(provider, mainUser, rank, kosmos.widgetConfiguratorFail)
             runCurrent()
 
             verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
-            verify(communalWidgetDao)
-                .addWidget(id, provider, priority, testUserSerialNumber(mainUser))
+            verify(communalWidgetDao).addWidget(id, provider, rank, testUserSerialNumber(mainUser))
 
             // Verify backup requested
             verify(backupManager).dataChanged()
@@ -399,11 +397,11 @@
     @Test
     fun reorderWidgets_queryDb() =
         testScope.runTest {
-            val widgetIdToPriorityMap = mapOf(104 to 1, 103 to 2, 101 to 3)
-            underTest.updateWidgetOrder(widgetIdToPriorityMap)
+            val widgetIdToRankMap = mapOf(104 to 1, 103 to 2, 101 to 3)
+            underTest.updateWidgetOrder(widgetIdToRankMap)
             runCurrent()
 
-            verify(communalWidgetDao).updateWidgetOrder(widgetIdToPriorityMap)
+            verify(communalWidgetDao).updateWidgetOrder(widgetIdToRankMap)
 
             // Verify backup requested
             verify(backupManager).dataChanged()
@@ -691,11 +689,11 @@
                     CommunalWidgetContentModel.Available(
                         appWidgetId = 1,
                         providerInfo = providerInfoA,
-                        priority = 1,
+                        rank = 1,
                     ),
                     CommunalWidgetContentModel.Pending(
                         appWidgetId = 2,
-                        priority = 2,
+                        rank = 2,
                         componentName = ComponentName("pk_2", "cls_2"),
                         icon = fakeIcon,
                         user = mainUser,
@@ -730,7 +728,7 @@
                 .containsExactly(
                     CommunalWidgetContentModel.Pending(
                         appWidgetId = 1,
-                        priority = 1,
+                        rank = 1,
                         componentName = ComponentName("pk_1", "cls_1"),
                         icon = fakeIcon,
                         user = mainUser,
@@ -750,7 +748,7 @@
                     CommunalWidgetContentModel.Available(
                         appWidgetId = 1,
                         providerInfo = providerInfoA,
-                        priority = 1,
+                        rank = 1,
                     ),
                 )
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 57ce9de..8218178 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -150,8 +150,8 @@
             tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
 
             // Widgets available.
-            widgetRepository.addWidget(appWidgetId = 0, priority = 30)
-            widgetRepository.addWidget(appWidgetId = 1, priority = 20)
+            widgetRepository.addWidget(appWidgetId = 0, rank = 30)
+            widgetRepository.addWidget(appWidgetId = 1, rank = 20)
 
             // Smartspace available.
             smartspaceRepository.setTimers(
@@ -212,8 +212,8 @@
             tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
 
             // Widgets available.
-            widgetRepository.addWidget(appWidgetId = 0, priority = 30)
-            widgetRepository.addWidget(appWidgetId = 1, priority = 20)
+            widgetRepository.addWidget(appWidgetId = 0, rank = 30)
+            widgetRepository.addWidget(appWidgetId = 1, rank = 20)
 
             val communalContent by collectLastValue(underTest.communalContent)
 
@@ -227,7 +227,7 @@
             underTest.onDeleteWidget(
                 id = 0,
                 componentName = ComponentName("test_package", "test_class"),
-                priority = 30,
+                rank = 30,
             )
 
             // Only one widget and CTA tile remain.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index cc945d6..fb151a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -101,7 +101,6 @@
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
 import org.mockito.kotlin.spy
-import org.mockito.kotlin.times
 import org.mockito.kotlin.whenever
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
 import platform.test.runner.parameterized.Parameters
@@ -213,8 +212,8 @@
             tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
 
             // Widgets available.
-            widgetRepository.addWidget(appWidgetId = 0, priority = 30)
-            widgetRepository.addWidget(appWidgetId = 1, priority = 20)
+            widgetRepository.addWidget(appWidgetId = 0, rank = 30)
+            widgetRepository.addWidget(appWidgetId = 1, rank = 20)
 
             // Smartspace available.
             smartspaceRepository.setTimers(
@@ -303,7 +302,7 @@
         testScope.runTest {
             tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
 
-            widgetRepository.addWidget(appWidgetId = 1, priority = 1)
+            widgetRepository.addWidget(appWidgetId = 1, rank = 1)
             mediaRepository.mediaInactive()
             smartspaceRepository.setTimers(emptyList())
 
@@ -660,8 +659,8 @@
             )
 
             // Widgets available
-            widgetRepository.addWidget(appWidgetId = 0, priority = 30)
-            widgetRepository.addWidget(appWidgetId = 1, priority = 20)
+            widgetRepository.addWidget(appWidgetId = 0, rank = 30)
+            widgetRepository.addWidget(appWidgetId = 1, rank = 20)
 
             // Then hub shows widgets and the CTA tile
             assertThat(communalContent).hasSize(3)
@@ -716,8 +715,8 @@
             )
 
             // And widgets available
-            widgetRepository.addWidget(appWidgetId = 0, priority = 30)
-            widgetRepository.addWidget(appWidgetId = 1, priority = 20)
+            widgetRepository.addWidget(appWidgetId = 0, rank = 30)
+            widgetRepository.addWidget(appWidgetId = 1, rank = 20)
 
             // Then emits widgets and the CTA tile
             assertThat(communalContent).hasSize(3)
@@ -770,7 +769,7 @@
 
     @Test
     fun onTapWidget_logEvent() {
-        underTest.onTapWidget(ComponentName("test_pkg", "test_cls"), priority = 10)
+        underTest.onTapWidget(ComponentName("test_pkg", "test_cls"), rank = 10)
         verify(metricsLogger).logTapWidget("test_pkg/test_cls", rank = 10)
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 91259a6..3e75ceb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -34,6 +34,7 @@
 import android.os.CancellationSignal
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.internal.logging.InstanceId.fakeInstanceId
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.Flags as AConfigFlags
@@ -55,6 +56,7 @@
 import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
 import com.android.systemui.display.data.repository.displayRepository
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.keyguard.data.repository.BiometricType
 import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
@@ -77,6 +79,9 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.model.SelectionStatus
@@ -90,6 +95,7 @@
 import java.io.PrintWriter
 import java.io.StringWriter
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
@@ -136,12 +142,12 @@
 
     @Captor
     private lateinit var faceLockoutResetCallback: ArgumentCaptor<FaceManager.LockoutResetCallback>
-    private val testDispatcher = kosmos.testDispatcher
+    private val testDispatcher by lazy { kosmos.testDispatcher }
 
-    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
-    private val testScope = kosmos.testScope
-    private val fakeUserRepository = kosmos.fakeUserRepository
-    private val fakeExecutor = kosmos.fakeExecutor
+    private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+    private val testScope by lazy { kosmos.testScope }
+    private val fakeUserRepository by lazy { kosmos.fakeUserRepository }
+    private val fakeExecutor by lazy { kosmos.fakeExecutor }
     private lateinit var authStatus: FlowValue<FaceAuthenticationStatus?>
     private lateinit var detectStatus: FlowValue<FaceDetectionStatus?>
     private lateinit var authRunning: FlowValue<Boolean?>
@@ -149,18 +155,19 @@
     private lateinit var lockedOut: FlowValue<Boolean?>
     private lateinit var canFaceAuthRun: FlowValue<Boolean?>
     private lateinit var authenticated: FlowValue<Boolean?>
-    private val biometricSettingsRepository = kosmos.fakeBiometricSettingsRepository
-    private val deviceEntryFingerprintAuthRepository =
+    private val biometricSettingsRepository by lazy { kosmos.fakeBiometricSettingsRepository }
+    private val deviceEntryFingerprintAuthRepository by lazy {
         kosmos.fakeDeviceEntryFingerprintAuthRepository
-    private val trustRepository = kosmos.fakeTrustRepository
-    private val keyguardRepository = kosmos.fakeKeyguardRepository
-    private val powerInteractor = kosmos.powerInteractor
-    private val keyguardInteractor = kosmos.keyguardInteractor
-    private val alternateBouncerInteractor = kosmos.alternateBouncerInteractor
-    private val displayStateInteractor = kosmos.displayStateInteractor
-    private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
-    private val displayRepository = kosmos.displayRepository
-    private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
+    }
+    private val trustRepository by lazy { kosmos.fakeTrustRepository }
+    private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+    private val powerInteractor by lazy { kosmos.powerInteractor }
+    private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
+    private val alternateBouncerInteractor by lazy { kosmos.alternateBouncerInteractor }
+    private val displayStateInteractor by lazy { kosmos.displayStateInteractor }
+    private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
+    private val displayRepository by lazy { kosmos.displayRepository }
+    private val keyguardTransitionInteractor by lazy { kosmos.keyguardTransitionInteractor }
     private lateinit var featureFlags: FakeFeatureFlags
 
     private var wasAuthCancelled = false
@@ -180,9 +187,11 @@
         whenever(bypassController.bypassEnabled).thenReturn(true)
         underTest = createDeviceEntryFaceAuthRepositoryImpl(faceManager, bypassController)
 
-        mSetFlagsRule.disableFlags(
-            AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
-        )
+        if (!SceneContainerFlag.isEnabled) {
+            mSetFlagsRule.disableFlags(
+                AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+            )
+        }
     }
 
     private fun createDeviceEntryFaceAuthRepositoryImpl(
@@ -227,6 +236,7 @@
             powerInteractor,
             keyguardInteractor,
             alternateBouncerInteractor,
+            { kosmos.sceneInteractor },
             faceDetectBuffer,
             faceAuthBuffer,
             keyguardTransitionInteractor,
@@ -547,6 +557,24 @@
         }
 
     @Test
+    @EnableSceneContainer
+    fun withSceneContainerEnabled_authenticateDoesNotRunWhenKeyguardIsGoingAway() =
+        testScope.runTest {
+            testGatingCheckForFaceAuth(sceneContainerEnabled = true) {
+                keyguardTransitionRepository.sendTransitionStep(
+                    TransitionStep(
+                        KeyguardState.LOCKSCREEN,
+                        KeyguardState.UNDEFINED,
+                        value = 0.5f,
+                        transitionState = TransitionState.RUNNING
+                    ),
+                    validateStep = false
+                )
+                runCurrent()
+            }
+        }
+
+    @Test
     fun authenticateDoesNotRunWhenDeviceIsGoingToSleep() =
         testScope.runTest {
             testGatingCheckForFaceAuth {
@@ -595,6 +623,31 @@
         }
 
     @Test
+    @EnableSceneContainer
+    fun withSceneContainer_authenticateRunsWhenSecureCameraIsActiveIfBouncerIsShowing() =
+        testScope.runTest {
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled = true)
+            bouncerRepository.setAlternateVisible(false)
+
+            // launch secure camera
+            keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+            keyguardRepository.setKeyguardOccluded(true)
+            kosmos.sceneInteractor.snapToScene(Scenes.Lockscreen, "for-test")
+            runCurrent()
+            assertThat(canFaceAuthRun()).isFalse()
+
+            // but bouncer is shown after that.
+            kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "for-test")
+            kosmos.sceneInteractor.setTransitionState(
+                MutableStateFlow(ObservableTransitionState.Idle(Scenes.Bouncer))
+            )
+            runCurrent()
+
+            assertThat(canFaceAuthRun()).isTrue()
+        }
+
+    @Test
     fun authenticateDoesNotRunOnUnsupportedPosture() =
         testScope.runTest {
             testGatingCheckForFaceAuth {
@@ -841,6 +894,24 @@
         }
 
     @Test
+    @EnableSceneContainer
+    fun withSceneContainer_faceDetectDoesNotRunWhenKeyguardGoingAway() =
+        testScope.runTest {
+            testGatingCheckForDetect(sceneContainerEnabled = true) {
+                keyguardTransitionRepository.sendTransitionStep(
+                    TransitionStep(
+                        KeyguardState.LOCKSCREEN,
+                        KeyguardState.UNDEFINED,
+                        value = 0.5f,
+                        transitionState = TransitionState.RUNNING
+                    ),
+                    validateStep = false
+                )
+                runCurrent()
+            }
+        }
+
+    @Test
     fun detectDoesNotRunWhenDeviceSleepingStartingToSleep() =
         testScope.runTest {
             testGatingCheckForDetect {
@@ -1052,10 +1123,11 @@
         }
 
     private suspend fun TestScope.testGatingCheckForFaceAuth(
+        sceneContainerEnabled: Boolean = false,
         gatingCheckModifier: suspend () -> Unit
     ) {
         initCollectors()
-        allPreconditionsToRunFaceAuthAreTrue()
+        allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled)
 
         gatingCheckModifier()
         runCurrent()
@@ -1069,7 +1141,7 @@
         faceAuthenticateIsNotCalled()
 
         // flip the gating check back on.
-        allPreconditionsToRunFaceAuthAreTrue()
+        allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled)
         assertThat(underTest.canRunFaceAuth.value).isTrue()
 
         faceAuthenticateIsCalled()
@@ -1094,10 +1166,11 @@
     }
 
     private suspend fun TestScope.testGatingCheckForDetect(
+        sceneContainerEnabled: Boolean = false,
         gatingCheckModifier: suspend () -> Unit
     ) {
         initCollectors()
-        allPreconditionsToRunFaceAuthAreTrue()
+        allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled)
 
         // This will stop face auth from running but is required to be false for detect.
         biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false)
@@ -1145,12 +1218,22 @@
         cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true }
     }
 
-    private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue() {
+    private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue(
+        sceneContainerEnabled: Boolean = false
+    ) {
         fakeExecutor.runAllReady()
         verify(faceManager, atLeastOnce())
             .addLockoutResetCallback(faceLockoutResetCallback.capture())
         trustRepository.setCurrentUserTrusted(false)
-        keyguardRepository.setKeyguardGoingAway(false)
+        if (sceneContainerEnabled) {
+            // Keyguard is not going away
+            kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(KeyguardState.OFF, KeyguardState.LOCKSCREEN, value = 1.0f),
+                validateStep = false
+            )
+        } else {
+            keyguardRepository.setKeyguardGoingAway(false)
+        }
         powerInteractor.setAwakeForTest()
         biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
         biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 6412276..3895595 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -62,6 +62,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -324,4 +325,13 @@
         // enabled.
         mController.onViewAttached();
     }
+
+    @Test
+    public void destroy_cleansUpState() {
+        mController.destroy();
+        verify(mStateController).removeCallback(any());
+        verify(mAmbientStatusBarViewController).destroy();
+        verify(mComplicationHostViewController).destroy();
+        verify(mLowLightTransitionCoordinator).setLowLightEnterListener(ArgumentMatchers.isNull());
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index 89ec3cf..29aa89c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -68,7 +68,6 @@
 import com.android.systemui.testKosmos
 import com.android.systemui.touch.TouchInsetManager
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -89,7 +88,9 @@
 import org.mockito.kotlin.any
 import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
 import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -115,8 +116,6 @@
 
     @Mock lateinit var mComplicationComponentFactory: ComplicationComponent.Factory
 
-    @Mock lateinit var mComplicationComponent: ComplicationComponent
-
     @Mock lateinit var mComplicationHostViewController: ComplicationHostViewController
 
     @Mock lateinit var mComplicationVisibilityController: ComplicationLayoutEngine
@@ -125,20 +124,12 @@
     lateinit var mDreamComplicationComponentFactory:
         com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
 
-    @Mock
-    lateinit var mDreamComplicationComponent:
-        com.android.systemui.dreams.complication.dagger.ComplicationComponent
-
     @Mock lateinit var mHideComplicationTouchHandler: HideComplicationTouchHandler
 
     @Mock lateinit var mDreamOverlayComponentFactory: DreamOverlayComponent.Factory
 
-    @Mock lateinit var mDreamOverlayComponent: DreamOverlayComponent
-
     @Mock lateinit var mAmbientTouchComponentFactory: AmbientTouchComponent.Factory
 
-    @Mock lateinit var mAmbientTouchComponent: AmbientTouchComponent
-
     @Mock lateinit var mDreamOverlayContainerView: DreamOverlayContainerView
 
     @Mock lateinit var mDreamOverlayContainerViewController: DreamOverlayContainerViewController
@@ -170,10 +161,83 @@
     private lateinit var communalRepository: FakeCommunalSceneRepository
     private var viewCaptureSpy = spy(ViewCaptureFactory.getInstance(context))
     private lateinit var gestureInteractor: GestureInteractor
+    private lateinit var environmentComponents: EnvironmentComponents
 
     @Captor var mViewCaptor: ArgumentCaptor<View>? = null
     private lateinit var mService: DreamOverlayService
 
+    private class EnvironmentComponents(
+        val dreamsComplicationComponent:
+            com.android.systemui.dreams.complication.dagger.ComplicationComponent,
+        val dreamOverlayComponent: DreamOverlayComponent,
+        val complicationComponent: ComplicationComponent,
+        val ambientTouchComponent: AmbientTouchComponent,
+    ) {
+        fun clearInvocations() {
+            clearInvocations(
+                dreamsComplicationComponent,
+                dreamOverlayComponent,
+                complicationComponent,
+                ambientTouchComponent
+            )
+        }
+
+        fun verifyNoMoreInteractions() {
+            Mockito.verifyNoMoreInteractions(
+                dreamsComplicationComponent,
+                dreamOverlayComponent,
+                complicationComponent,
+                ambientTouchComponent
+            )
+        }
+    }
+
+    private fun setupComponentFactories(
+        dreamComplicationComponentFactory:
+            com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory,
+        dreamOverlayComponentFactory: DreamOverlayComponent.Factory,
+        complicationComponentFactory: ComplicationComponent.Factory,
+        ambientTouchComponentFactory: AmbientTouchComponent.Factory
+    ): EnvironmentComponents {
+        val dreamOverlayComponent = mock<DreamOverlayComponent>()
+        whenever(dreamOverlayComponent.getDreamOverlayContainerViewController())
+            .thenReturn(mDreamOverlayContainerViewController)
+
+        val complicationComponent = mock<ComplicationComponent>()
+        whenever(complicationComponent.getComplicationHostViewController())
+            .thenReturn(mComplicationHostViewController)
+        whenever(mLifecycleOwner.registry).thenReturn(lifecycleRegistry)
+
+        mCommunalInteractor = Mockito.spy(kosmos.communalInteractor)
+
+        whenever(complicationComponentFactory.create(any(), any(), any(), any()))
+            .thenReturn(complicationComponent)
+        whenever(complicationComponent.getVisibilityController())
+            .thenReturn(mComplicationVisibilityController)
+
+        val dreamComplicationComponent =
+            mock<com.android.systemui.dreams.complication.dagger.ComplicationComponent>()
+        whenever(dreamComplicationComponent.getHideComplicationTouchHandler())
+            .thenReturn(mHideComplicationTouchHandler)
+        whenever(dreamComplicationComponentFactory.create(any(), any()))
+            .thenReturn(dreamComplicationComponent)
+
+        whenever(dreamOverlayComponentFactory.create(any(), any(), any()))
+            .thenReturn(dreamOverlayComponent)
+
+        val ambientTouchComponent = mock<AmbientTouchComponent>()
+        whenever(ambientTouchComponentFactory.create(any(), any()))
+            .thenReturn(ambientTouchComponent)
+        whenever(ambientTouchComponent.getTouchMonitor()).thenReturn(mTouchMonitor)
+
+        return EnvironmentComponents(
+            dreamComplicationComponent,
+            dreamOverlayComponent,
+            complicationComponent,
+            ambientTouchComponent
+        )
+    }
+
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
@@ -183,27 +247,14 @@
         communalRepository = kosmos.fakeCommunalSceneRepository
         gestureInteractor = spy(kosmos.gestureInteractor)
 
-        whenever(mDreamOverlayComponent.getDreamOverlayContainerViewController())
-            .thenReturn(mDreamOverlayContainerViewController)
-        whenever(mComplicationComponent.getComplicationHostViewController())
-            .thenReturn(mComplicationHostViewController)
-        whenever(mLifecycleOwner.registry).thenReturn(lifecycleRegistry)
+        environmentComponents =
+            setupComponentFactories(
+                mDreamComplicationComponentFactory,
+                mDreamOverlayComponentFactory,
+                mComplicationComponentFactory,
+                mAmbientTouchComponentFactory
+            )
 
-        mCommunalInteractor = Mockito.spy(kosmos.communalInteractor)
-
-        whenever(mComplicationComponentFactory.create(any(), any(), any(), any()))
-            .thenReturn(mComplicationComponent)
-        whenever(mComplicationComponent.getVisibilityController())
-            .thenReturn(mComplicationVisibilityController)
-        whenever(mDreamComplicationComponent.getHideComplicationTouchHandler())
-            .thenReturn(mHideComplicationTouchHandler)
-        whenever(mDreamComplicationComponentFactory.create(any(), any()))
-            .thenReturn(mDreamComplicationComponent)
-        whenever(mDreamOverlayComponentFactory.create(any(), any(), any()))
-            .thenReturn(mDreamOverlayComponent)
-        whenever(mAmbientTouchComponentFactory.create(any(), any()))
-            .thenReturn(mAmbientTouchComponent)
-        whenever(mAmbientTouchComponent.getTouchMonitor()).thenReturn(mTouchMonitor)
         whenever(mDreamOverlayContainerViewController.containerView)
             .thenReturn(mDreamOverlayContainerView)
         whenever(mScrimManager.getCurrentController()).thenReturn(mScrimController)
@@ -570,9 +621,8 @@
 
         // Assert that the overlay is not showing complications.
         assertThat(mService.shouldShowComplications()).isFalse()
-        Mockito.clearInvocations(mDreamOverlayComponent)
-        Mockito.clearInvocations(mAmbientTouchComponent)
-        Mockito.clearInvocations(mWindowManager)
+        environmentComponents.clearInvocations()
+        clearInvocations(mWindowManager)
 
         // New dream starting with dream complications showing. Note that when a new dream is
         // binding to the dream overlay service, it receives the same instance of IBinder as the
@@ -594,8 +644,11 @@
 
         // Verify that new instances of overlay container view controller and overlay touch monitor
         // are created.
-        verify(mDreamOverlayComponent).getDreamOverlayContainerViewController()
-        verify(mAmbientTouchComponent).getTouchMonitor()
+        verify(environmentComponents.dreamOverlayComponent).getDreamOverlayContainerViewController()
+        verify(environmentComponents.ambientTouchComponent).getTouchMonitor()
+
+        // Verify DreamOverlayContainerViewController is destroyed.
+        verify(mDreamOverlayContainerViewController).destroy()
     }
 
     @Test
@@ -1002,6 +1055,34 @@
             .isEqualTo(ComponentName.unflattenFromString(DREAM_COMPONENT)?.packageName)
     }
 
+    @Test
+    fun testComponentsRecreatedBetweenDreams() {
+        clearInvocations(
+            mDreamComplicationComponentFactory,
+            mDreamOverlayComponentFactory,
+            mComplicationComponentFactory,
+            mAmbientTouchComponentFactory
+        )
+
+        mService.onEndDream()
+
+        setupComponentFactories(
+            mDreamComplicationComponentFactory,
+            mDreamOverlayComponentFactory,
+            mComplicationComponentFactory,
+            mAmbientTouchComponentFactory
+        )
+
+        client.startDream(
+            mWindowParams,
+            mDreamOverlayCallback,
+            DREAM_COMPONENT,
+            false /*shouldShowComplication*/
+        )
+        mMainExecutor.runAllReady()
+        environmentComponents.verifyNoMoreInteractions()
+    }
+
     internal class FakeLifecycleRegistry(provider: LifecycleOwner) : LifecycleRegistry(provider) {
         val mLifecycles: MutableList<State> = ArrayList()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
index 2b2c121..aee72de2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
@@ -25,8 +25,11 @@
 import com.android.internal.logging.uiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -260,6 +263,23 @@
         }
 
     @Test
+    fun triggersFaceAuthWhenLockscreenIsClicked() =
+        testScope.runTest {
+            collectLastValue(underTest.isMenuVisible)
+            runCurrent()
+            kosmos.fakeDeviceEntryFaceAuthRepository.canRunFaceAuth.value = true
+
+            underTest.onClick(100.0f, 100.0f)
+            runCurrent()
+
+            val runningAuthRequest =
+                kosmos.fakeDeviceEntryFaceAuthRepository.runningAuthRequest.value
+            assertThat(runningAuthRequest?.first)
+                .isEqualTo(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED)
+            assertThat(runningAuthRequest?.second).isEqualTo(true)
+        }
+
+    @Test
     fun showMenu_leaveLockscreen_returnToLockscreen_menuNotVisible() =
         testScope.runTest {
             val isMenuVisible by collectLastValue(underTest.isMenuVisible)
@@ -302,6 +322,7 @@
                 broadcastDispatcher = fakeBroadcastDispatcher,
                 accessibilityManager = kosmos.accessibilityManagerWrapper,
                 pulsingGestureListener = kosmos.pulsingGestureListener,
+                faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor,
             )
         setUpState()
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
new file mode 100644
index 0000000..22e5896
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
@@ -0,0 +1,399 @@
+/**
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.systemui.media.controls.domain.pipeline
+
+import android.app.Notification
+import android.app.Notification.MediaStyle
+import android.app.PendingIntent
+import android.app.statusBarManager
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.media.AudioAttributes
+import android.media.MediaDescription
+import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
+import android.os.Bundle
+import android.service.notification.StatusBarNotification
+import androidx.media.utils.MediaConstants
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.Flags.MEDIA_RESUME_PROGRESS
+import com.android.systemui.flags.Flags.MEDIA_SESSION_ACTIONS
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.graphics.imageLoader
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
+import com.android.systemui.media.controls.util.mediaFlags
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.SbnBuilder
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+private const val KEY = "KEY"
+private const val PACKAGE_NAME = "com.example.app"
+private const val SYSTEM_PACKAGE_NAME = "com.android.systemui"
+private const val APP_NAME = "SystemUI"
+private const val SESSION_ARTIST = "artist"
+private const val SESSION_TITLE = "title"
+private const val SESSION_EMPTY_TITLE = ""
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MediaDataLoaderTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val testDispatcher = kosmos.testDispatcher
+    private val statusBarManager = kosmos.statusBarManager
+    private val mediaController = mock<MediaController>()
+    private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic
+    private val mediaFlags = kosmos.mediaFlags
+    private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
+    private val session = MediaSession(context, "MediaDataLoaderTestSession")
+    private val metadataBuilder =
+        MediaMetadata.Builder().apply {
+            putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
+            putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
+        }
+
+    private val underTest: MediaDataLoader =
+        MediaDataLoader(
+            context,
+            testDispatcher,
+            testScope,
+            kosmos.activityStarter,
+            mediaControllerFactory,
+            mediaFlags,
+            kosmos.imageLoader,
+            statusBarManager
+        )
+
+    @Before
+    fun setUp() {
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
+        mediaControllerFactory.setControllerForToken(session.sessionToken, mediaController)
+    }
+
+    @Test
+    fun loadMediaData_returnsMediaData() =
+        testScope.runTest {
+            val song = "THIS_IS_A_SONG"
+            val artist = "THIS_IS_AN_ARTIST"
+            val albumArt = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
+
+            whenever(mediaController.playbackState)
+                .thenReturn(
+                    PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 12, 1.0f).build()
+                )
+            whenever(mediaController.playbackInfo)
+                .thenReturn(
+                    MediaController.PlaybackInfo(
+                        MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
+                        0,
+                        0,
+                        0,
+                        AudioAttributes.Builder().build(),
+                        null
+                    )
+                )
+            whenever(mediaController.metadata)
+                .thenReturn(
+                    metadataBuilder
+                        .putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, song)
+                        .putString(MediaMetadata.METADATA_KEY_ARTIST, artist)
+                        .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, albumArt)
+                        .putLong(
+                            MediaConstants.METADATA_KEY_IS_EXPLICIT,
+                            MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+                        )
+                        .build()
+                )
+
+            val result = underTest.loadMediaData(KEY, createMediaNotification())
+            assertThat(result).isNotNull()
+            assertThat(result?.appIcon).isNotNull()
+            assertThat(result?.appIcon?.resId).isEqualTo(android.R.drawable.ic_media_pause)
+            assertThat(result?.artist).isEqualTo(artist)
+            assertThat(result?.song).isEqualTo(song)
+            assertThat(result?.artworkIcon).isNotNull()
+            assertThat(result?.artworkIcon?.bitmap?.width).isEqualTo(albumArt.width)
+            assertThat(result?.artworkIcon?.bitmap?.height).isEqualTo(albumArt.height)
+            assertThat(result?.token).isEqualTo(session.sessionToken)
+            assertThat(result?.device).isNull()
+            assertThat(result?.playbackLocation).isEqualTo(MediaData.PLAYBACK_LOCAL)
+            assertThat(result?.isPlaying).isTrue()
+            assertThat(result?.isExplicit).isTrue()
+            assertThat(result?.resumeAction).isNull()
+            assertThat(result?.resumeProgress).isNull()
+        }
+
+    @Test
+    fun loadMediaDataForResumption_returnsMediaData() =
+        testScope.runTest {
+            fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
+
+            val song = "THIS_IS_A_SONG"
+            val artist = "THIS_IS_AN_ARTIST"
+            val albumArt = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
+
+            val extras = Bundle()
+            extras.putInt(
+                MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+                MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
+            )
+            extras.putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.3)
+            extras.putLong(
+                MediaConstants.METADATA_KEY_IS_EXPLICIT,
+                MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+            )
+
+            val description =
+                MediaDescription.Builder()
+                    .setTitle(song)
+                    .setSubtitle(artist)
+                    .setIconBitmap(albumArt)
+                    .setExtras(extras)
+                    .build()
+
+            val intent =
+                PendingIntent.getActivity(context, 0, Intent(), PendingIntent.FLAG_IMMUTABLE)
+
+            val result =
+                underTest.loadMediaDataForResumption(
+                    0,
+                    description,
+                    Runnable {},
+                    null,
+                    session.sessionToken,
+                    APP_NAME,
+                    intent,
+                    PACKAGE_NAME
+                )
+            assertThat(result).isNotNull()
+            assertThat(result?.appName).isEqualTo(APP_NAME)
+            assertThat(result?.song).isEqualTo(song)
+            assertThat(result?.artist).isEqualTo(artist)
+            assertThat(result?.artworkIcon).isNotNull()
+            assertThat(result?.artworkIcon?.bitmap?.width).isEqualTo(100)
+            assertThat(result?.artworkIcon?.bitmap?.height).isEqualTo(100)
+            assertThat(result?.token).isEqualTo(session.sessionToken)
+            assertThat(result?.clickIntent).isEqualTo(intent)
+            assertThat(result?.isExplicit).isTrue()
+            assertThat(result?.resumeProgress).isEqualTo(0.3)
+        }
+
+    @Test
+    fun loadMediaData_songNameFallbacks() =
+        testScope.runTest {
+            // Check ordering of Song resolution:
+            // DISPLAY_TITLE > TITLE > notification TITLE > notification TITLE_BIG
+
+            // DISPLAY_TITLE
+            whenever(mediaController.metadata)
+                .thenReturn(
+                    MediaMetadata.Builder()
+                        .putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, "title1")
+                        .putString(MediaMetadata.METADATA_KEY_TITLE, "title2")
+                        .build()
+                )
+            val result1 = underTest.loadMediaData(KEY, createMediaNotification())
+            assertThat(result1?.song).isEqualTo("title1")
+
+            // TITLE
+            whenever(mediaController.metadata)
+                .thenReturn(
+                    MediaMetadata.Builder()
+                        .putString(MediaMetadata.METADATA_KEY_TITLE, "title2")
+                        .build()
+                )
+            val result2 = underTest.loadMediaData(KEY, createMediaNotification())
+            assertThat(result2?.song).isEqualTo("title2")
+
+            // notification TITLE
+            val notif =
+                SbnBuilder().run {
+                    setPkg(PACKAGE_NAME)
+                    modifyNotification(context).also {
+                        it.setSmallIcon(android.R.drawable.ic_media_pause)
+                        it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+                        it.setContentTitle("notiftitle")
+                    }
+                    build()
+                }
+            whenever(mediaController.metadata).thenReturn(MediaMetadata.Builder().build())
+            val result3 = underTest.loadMediaData(KEY, notif)
+            assertThat(result3?.song).isEqualTo("notiftitle")
+
+            // Final fallback
+            whenever(mediaController.metadata).thenReturn(MediaMetadata.Builder().build())
+            val result4 = underTest.loadMediaData(KEY, createMediaNotification())
+            assertThat(result4?.song)
+                .isEqualTo(context.getString(R.string.controls_media_empty_title, result4?.appName))
+        }
+
+    @Test
+    fun loadMediaData_emptyTitle_hasPlaceholder() =
+        testScope.runTest {
+            val packageManager = mock<PackageManager>()
+            context.setMockPackageManager(packageManager)
+            whenever(packageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
+            whenever(mediaController.metadata)
+                .thenReturn(
+                    metadataBuilder
+                        .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
+                        .build()
+                )
+
+            val result = underTest.loadMediaData(KEY, createMediaNotification())
+
+            val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME)
+            assertThat(result).isNotNull()
+            assertThat(result?.song).isEqualTo(placeholderTitle)
+        }
+
+    @Test
+    fun loadMediaData_emptyMetadata_usesNotificationTitle() =
+        testScope.runTest {
+            val packageManager = mock<PackageManager>()
+            context.setMockPackageManager(packageManager)
+            whenever(packageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
+            whenever(mediaController.metadata)
+                .thenReturn(
+                    metadataBuilder
+                        .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
+                        .putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, SESSION_EMPTY_TITLE)
+                        .build()
+                )
+            val mediaNotification =
+                SbnBuilder().run {
+                    setPkg(PACKAGE_NAME)
+                    modifyNotification(context).also {
+                        it.setSmallIcon(android.R.drawable.ic_media_pause)
+                        it.setContentTitle(SESSION_TITLE)
+                        it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+                    }
+                    build()
+                }
+
+            val result = underTest.loadMediaData(KEY, mediaNotification)
+
+            assertThat(result).isNotNull()
+            assertThat(result?.song).isEqualTo(SESSION_TITLE)
+        }
+
+    @Test
+    fun loadMediaData_badArtwork_isNotUsed() =
+        testScope.runTest {
+            val artwork = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+            val mediaNotification =
+                SbnBuilder().run {
+                    setPkg(PACKAGE_NAME)
+                    modifyNotification(context).also {
+                        it.setSmallIcon(android.R.drawable.ic_media_pause)
+                        it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+                        it.setLargeIcon(artwork)
+                    }
+                    build()
+                }
+
+            val result = underTest.loadMediaData(KEY, mediaNotification)
+
+            assertThat(result).isNotNull()
+        }
+
+    @Test
+    fun loadMediaData_invalidTokenNoCrash() =
+        testScope.runTest {
+            val bundle = Bundle()
+            // wrong data type
+            bundle.putParcelable(Notification.EXTRA_MEDIA_SESSION, Bundle())
+            val rcn =
+                SbnBuilder().run {
+                    setPkg(SYSTEM_PACKAGE_NAME)
+                    modifyNotification(context).also {
+                        it.setSmallIcon(android.R.drawable.ic_media_pause)
+                        it.addExtras(bundle)
+                        it.setStyle(
+                            MediaStyle().apply { setRemotePlaybackInfo("Remote device", 0, null) }
+                        )
+                    }
+                    build()
+                }
+
+            val result = underTest.loadMediaData(KEY, rcn)
+            assertThat(result).isNull()
+        }
+
+    @Test
+    fun testLoadMediaDataInBg_invalidMediaRemoteIntentNoCrash() =
+        testScope.runTest {
+            val bundle = Bundle()
+            // wrong data type
+            bundle.putParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT, Bundle())
+            val rcn =
+                SbnBuilder().run {
+                    setPkg(SYSTEM_PACKAGE_NAME)
+                    modifyNotification(context).also {
+                        it.setSmallIcon(android.R.drawable.ic_media_pause)
+                        it.addExtras(bundle)
+                        it.setStyle(
+                            MediaStyle().apply {
+                                setMediaSession(session.sessionToken)
+                                setRemotePlaybackInfo("Remote device", 0, null)
+                            }
+                        )
+                    }
+                    build()
+                }
+
+            val result = underTest.loadMediaData(KEY, rcn)
+            assertThat(result).isNotNull()
+        }
+
+    private fun createMediaNotification(
+        mediaSession: MediaSession? = session,
+        applicationInfo: ApplicationInfo? = null
+    ): StatusBarNotification =
+        SbnBuilder().run {
+            setPkg(PACKAGE_NAME)
+            modifyNotification(context).also {
+                it.setSmallIcon(android.R.drawable.ic_media_pause)
+                it.setStyle(MediaStyle().apply { setMediaSession(mediaSession?.sessionToken) })
+                if (applicationInfo != null) {
+                    val bundle = Bundle()
+                    bundle.putParcelable(
+                        Notification.EXTRA_BUILDER_APPLICATION_INFO,
+                        applicationInfo
+                    )
+                    it.addExtras(bundle)
+                }
+            }
+            build()
+        }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt
index 789a473..f920b18 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt
@@ -4,6 +4,7 @@
 import android.util.Log
 import android.view.View
 import androidx.annotation.VisibleForTesting
+import androidx.core.text.util.LocalePreferences
 
 typealias WeatherTouchAction = (View) -> Unit
 
@@ -54,12 +55,35 @@
             }
         }
 
-        private fun readIntFromBundle(extras: Bundle, key: String): Int? =
+        private fun readIntFromBundle(extras: Bundle, key: String): Int? {
             try {
-                extras.getString(key)?.toInt()
+                return extras.getString(key)?.toInt()
             } catch (e: Exception) {
-                null
+                return null
             }
+        }
+
+        fun getPlaceholderWeatherData(): WeatherData {
+            return getPlaceholderWeatherData(
+                LocalePreferences.getTemperatureUnit() == LocalePreferences.TemperatureUnit.CELSIUS
+            )
+        }
+
+        private const val DESCRIPTION_PLACEHODLER = ""
+        private const val TEMPERATURE_FAHRENHEIT_PLACEHOLDER = 58
+        private const val TEMPERATURE_CELSIUS_PLACEHOLDER = 21
+        private val WEATHERICON_PLACEHOLDER = WeatherData.WeatherStateIcon.MOSTLY_SUNNY
+
+        fun getPlaceholderWeatherData(useCelsius: Boolean): WeatherData {
+            return WeatherData(
+                description = DESCRIPTION_PLACEHODLER,
+                state = WEATHERICON_PLACEHOLDER,
+                temperature =
+                    if (useCelsius) TEMPERATURE_CELSIUS_PLACEHOLDER
+                    else TEMPERATURE_FAHRENHEIT_PLACEHOLDER,
+                useCelsius = useCelsius,
+            )
+        }
     }
 
     // Values for WeatherStateIcon must stay in sync with go/g3-WeatherStateIcon
diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index fc6d20e..0c11d2f 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -27,6 +27,8 @@
     <!-- Whether to use the split 2-column notification shade -->
     <bool name="config_use_split_notification_shade">true</bool>
 
+    <bool name="config_use_large_screen_shade_header">true</bool>
+
     <!-- The number of columns in the QuickSettings -->
     <integer name="quick_settings_num_columns">2</integer>
 
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index b438315..c594f1c 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -35,8 +35,6 @@
     <!-- How many lines to show in the security footer -->
     <integer name="qs_security_footer_maxLines">1</integer>
 
-    <bool name="config_use_large_screen_shade_header">true</bool>
-
     <!-- Whether to show bottom sheets edge to edge -->
     <bool name="config_edgeToEdgeBottomSheetDialog">false</bool>
 
diff --git a/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/3.json b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/3.json
new file mode 100644
index 0000000..fe996b7
--- /dev/null
+++ b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/3.json
@@ -0,0 +1,81 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 3,
+    "identityHash": "02e2da2d36e6955200edd5fb49e63c72",
+    "entities": [
+      {
+        "tableName": "communal_widget_table",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `widget_id` INTEGER NOT NULL, `component_name` TEXT NOT NULL, `item_id` INTEGER NOT NULL, `user_serial_number` INTEGER NOT NULL DEFAULT -1)",
+        "fields": [
+          {
+            "fieldPath": "uid",
+            "columnName": "uid",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "widgetId",
+            "columnName": "widget_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "componentName",
+            "columnName": "component_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "itemId",
+            "columnName": "item_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "userSerialNumber",
+            "columnName": "user_serial_number",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "-1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "uid"
+          ]
+        }
+      },
+      {
+        "tableName": "communal_item_rank_table",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `rank` INTEGER NOT NULL DEFAULT 0)",
+        "fields": [
+          {
+            "fieldPath": "uid",
+            "columnName": "uid",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "rank",
+            "columnName": "rank",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "uid"
+          ]
+        }
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '02e2da2d36e6955200edd5fb49e63c72')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index fbe1399..02d39a4 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -48,6 +48,7 @@
         "src/**/*.kt",
         "src/**/*.aidl",
         ":wm_shell-aidls",
+        ":wm_shell-shared-aidls",
         ":wm_shell_util-sources",
     ],
     static_libs: [
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 5dcf161..c1eae2e 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -477,6 +477,12 @@
         smallClockFrame?.viewTreeObserver?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
     }
 
+    fun setFallbackWeatherData(data: WeatherData) {
+        if (weatherData != null) return
+        weatherData = data
+        clock?.run { events.onWeatherDataChanged(data) }
+    }
+
     /**
      * Sets this clock as showing in a secondary display.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java
index abdc333..04595a2 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java
@@ -48,6 +48,7 @@
 import com.android.systemui.statusbar.policy.NextAlarmController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.time.DateFormatUtil;
 
@@ -127,6 +128,9 @@
     private final DreamOverlayStatusBarItemsProvider.Callback mStatusBarItemsProviderCallback =
             this::onStatusBarItemsChanged;
 
+    private final StatusBarWindowStateListener mStatusBarWindowStateListener =
+            this::onSystemStatusBarStateChanged;
+
     @Inject
     public AmbientStatusBarViewController(
             AmbientStatusBarView view,
@@ -161,10 +165,22 @@
         mWifiInteractor = wifiInteractor;
         mCommunalSceneInteractor = communalSceneInteractor;
         mLogger = new DreamLogger(logBuffer, TAG);
+    }
+
+    @Override
+    protected void onInit() {
+        super.onInit();
 
         // Register to receive show/hide updates for the system status bar. Our custom status bar
         // needs to hide when the system status bar is showing to ovoid overlapping status bars.
-        statusBarWindowStateController.addListener(this::onSystemStatusBarStateChanged);
+        mStatusBarWindowStateController.addListener(mStatusBarWindowStateListener);
+    }
+
+    @Override
+    public void destroy() {
+        mStatusBarWindowStateController.removeListener(mStatusBarWindowStateListener);
+
+        super.destroy();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
index 190bc15..d27e72a 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
@@ -122,4 +122,9 @@
      * @param session
      */
     void onSessionStart(TouchSession session);
+
+    /**
+     * Called when the handler is being torn down.
+     */
+    default void onDestroy() {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
index efa55e9..1be6f9e 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
@@ -581,6 +581,10 @@
             mBoundsFlow.cancel(new CancellationException());
         }
 
+        for (TouchHandler handler : mHandlers) {
+            handler.onDestroy();
+        }
+
         mInitialized = false;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
index dff6352..8f1854f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
@@ -26,7 +26,7 @@
 import androidx.sqlite.db.SupportSQLiteDatabase
 import com.android.systemui.res.R
 
-@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 2)
+@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 3)
 abstract class CommunalDatabase : RoomDatabase() {
     abstract fun communalWidgetDao(): CommunalWidgetDao
 
@@ -55,7 +55,7 @@
                             context.resources.getString(R.string.config_communalDatabase)
                         )
                         .also { builder ->
-                            builder.addMigrations(MIGRATION_1_2)
+                            builder.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
                             builder.fallbackToDestructiveMigration(dropAllTables = true)
                             callback?.let { callback -> builder.addCallback(callback) }
                         }
@@ -87,5 +87,21 @@
                     )
                 }
             }
+
+        /**
+         * This migration reverses the ranks. For example, if the ranks are 2, 1, 0, then after the
+         * migration they will be 0, 1, 2.
+         */
+        @VisibleForTesting
+        val MIGRATION_2_3 =
+            object : Migration(2, 3) {
+                override fun migrate(db: SupportSQLiteDatabase) {
+                    Log.i(TAG, "Migrating from version 2 to 3")
+                    db.execSQL(
+                        "UPDATE communal_item_rank_table " +
+                            "SET rank = (SELECT MAX(rank) FROM communal_item_rank_table) - rank"
+                    )
+                }
+            }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index 933a25a..93b86bd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -97,7 +97,7 @@
                             .addWidget(
                                 widgetId = id,
                                 componentName = name,
-                                priority = defaultWidgets.size - index,
+                                rank = index,
                                 userSerialNumber = userSerialNumber,
                             )
                     }
@@ -132,10 +132,17 @@
     @Query(
         "SELECT * FROM communal_widget_table JOIN communal_item_rank_table " +
             "ON communal_item_rank_table.uid = communal_widget_table.item_id " +
-            "ORDER BY communal_item_rank_table.rank DESC"
+            "ORDER BY communal_item_rank_table.rank ASC"
     )
     fun getWidgets(): Flow<Map<CommunalItemRank, CommunalWidgetItem>>
 
+    @Query(
+        "SELECT * FROM communal_widget_table JOIN communal_item_rank_table " +
+            "ON communal_item_rank_table.uid = communal_widget_table.item_id " +
+            "ORDER BY communal_item_rank_table.rank ASC"
+    )
+    fun getWidgetsNow(): Map<CommunalItemRank, CommunalWidgetItem>
+
     @Query("SELECT * FROM communal_widget_table WHERE widget_id = :id")
     fun getWidgetByIdNow(id: Int): CommunalWidgetItem?
 
@@ -167,11 +174,11 @@
     @Query("DELETE FROM communal_item_rank_table") fun clearCommunalItemRankTable()
 
     @Transaction
-    fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
-        widgetIdToPriorityMap.forEach { (id, priority) ->
+    fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) {
+        widgetIdToRankMap.forEach { (id, rank) ->
             val widget = getWidgetByIdNow(id)
             if (widget != null) {
-                updateItemRank(widget.itemId, priority)
+                updateItemRank(widget.itemId, rank)
             }
         }
     }
@@ -180,13 +187,13 @@
     fun addWidget(
         widgetId: Int,
         provider: ComponentName,
-        priority: Int,
+        rank: Int? = null,
         userSerialNumber: Int,
     ): Long {
         return addWidget(
             widgetId = widgetId,
             componentName = provider.flattenToString(),
-            priority = priority,
+            rank = rank,
             userSerialNumber = userSerialNumber,
         )
     }
@@ -195,13 +202,27 @@
     fun addWidget(
         widgetId: Int,
         componentName: String,
-        priority: Int,
+        rank: Int? = null,
         userSerialNumber: Int,
     ): Long {
+        val widgets = getWidgetsNow()
+
+        // If rank is not specified, rank it last by finding the current maximum rank and increment
+        // by 1. If the new widget is the first widget, set the rank to 0.
+        val newRank = rank ?: widgets.keys.maxOfOrNull { it.rank + 1 } ?: 0
+
+        // Shift widgets after [rank], unless widget is added at the end.
+        if (rank != null) {
+            widgets.forEach { (rankEntry, widgetEntry) ->
+                if (rankEntry.rank < newRank) return@forEach
+                updateItemRank(widgetEntry.itemId, rankEntry.rank + 1)
+            }
+        }
+
         return insertWidget(
             widgetId = widgetId,
             componentName = componentName,
-            itemId = insertItemRank(priority),
+            itemId = insertItemRank(newRank),
             userSerialNumber = userSerialNumber,
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index ad0bfc7..6cdd9ff 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -57,12 +57,17 @@
     /** A flow of information about active communal widgets stored in database. */
     val communalWidgets: Flow<List<CommunalWidgetContentModel>>
 
-    /** Add a widget at the specified position in the app widget service and the database. */
+    /**
+     * Add a widget in the app widget service and the database.
+     *
+     * @param rank The rank of the widget determines its position in the grid. 0 is first place, 1
+     *   is second, etc. If rank is not specified, widget is added at the end.
+     */
     fun addWidget(
         provider: ComponentName,
         user: UserHandle,
-        priority: Int,
-        configurator: WidgetConfigurator? = null
+        rank: Int?,
+        configurator: WidgetConfigurator? = null,
     ) {}
 
     /**
@@ -75,9 +80,9 @@
     /**
      * Update the order of widgets in the database.
      *
-     * @param widgetIdToPriorityMap mapping of the widget ids to the priority of the widget.
+     * @param widgetIdToRankMap mapping of the widget ids to the rank of the widget.
      */
-    fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {}
+    fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) {}
 
     /**
      * Restores the database by reading a state file from disk and updating the widget ids according
@@ -121,7 +126,7 @@
                 CommunalWidgetEntry(
                     appWidgetId = widget.widgetId,
                     componentName = widget.componentName,
-                    priority = rank.rank,
+                    rank = rank.rank,
                     providerInfo = providers[widget.widgetId]
                 )
             }
@@ -151,8 +156,8 @@
     override fun addWidget(
         provider: ComponentName,
         user: UserHandle,
-        priority: Int,
-        configurator: WidgetConfigurator?
+        rank: Int?,
+        configurator: WidgetConfigurator?,
     ) {
         bgScope.launch {
             val id = communalWidgetHost.allocateIdAndBindWidget(provider, user)
@@ -190,14 +195,14 @@
                 communalWidgetDao.addWidget(
                     widgetId = id,
                     provider = provider,
-                    priority = priority,
+                    rank = rank,
                     userSerialNumber = userManager.getUserSerialNumber(user.identifier),
                 )
                 backupManager.dataChanged()
             } else {
                 appWidgetHost.deleteAppWidgetId(id)
             }
-            logger.i("Added widget ${provider.flattenToString()} at position $priority.")
+            logger.i("Added widget ${provider.flattenToString()} at position $rank.")
         }
     }
 
@@ -211,11 +216,11 @@
         }
     }
 
-    override fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
+    override fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) {
         bgScope.launch {
-            communalWidgetDao.updateWidgetOrder(widgetIdToPriorityMap)
+            communalWidgetDao.updateWidgetOrder(widgetIdToRankMap)
             logger.i({ "Updated the order of widget list with ids: $str1." }) {
-                str1 = widgetIdToPriorityMap.toString()
+                str1 = widgetIdToRankMap.toString()
             }
             backupManager.dataChanged()
         }
@@ -342,7 +347,7 @@
                 addWidget(
                     provider = ComponentName.unflattenFromString(widget.componentName)!!,
                     user = newUser,
-                    priority = widget.rank,
+                    rank = widget.rank,
                 )
             }
 
@@ -377,7 +382,7 @@
         return CommunalWidgetContentModel.Available(
             appWidgetId = entry.appWidgetId,
             providerInfo = entry.providerInfo!!,
-            priority = entry.priority,
+            rank = entry.rank,
         )
     }
 
@@ -394,7 +399,7 @@
             return CommunalWidgetContentModel.Available(
                 appWidgetId = entry.appWidgetId,
                 providerInfo = entry.providerInfo!!,
-                priority = entry.priority,
+                rank = entry.rank,
             )
         }
 
@@ -403,7 +408,7 @@
         return if (componentName != null && session != null) {
             CommunalWidgetContentModel.Pending(
                 appWidgetId = entry.appWidgetId,
-                priority = entry.priority,
+                rank = entry.rank,
                 componentName = componentName,
                 icon = session.icon,
                 user = session.user,
@@ -416,7 +421,7 @@
     private data class CommunalWidgetEntry(
         val appWidgetId: Int,
         val componentName: String,
-        val priority: Int,
+        val rank: Int,
         var providerInfo: AppWidgetProviderInfo? = null,
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 7181b15..2aa6c19 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -367,13 +367,16 @@
     /** Dismiss the CTA tile from the hub in view mode. */
     suspend fun dismissCtaTile() = communalPrefsInteractor.setCtaDismissed()
 
-    /** Add a widget at the specified position. */
+    /**
+     * Add a widget at the specified rank. If rank is not provided, the widget will be added at the
+     * end.
+     */
     fun addWidget(
         componentName: ComponentName,
         user: UserHandle,
-        priority: Int,
+        rank: Int? = null,
         configurator: WidgetConfigurator?,
-    ) = widgetRepository.addWidget(componentName, user, priority, configurator)
+    ) = widgetRepository.addWidget(componentName, user, rank, configurator)
 
     /**
      * Delete a widget by id. Called when user deletes a widget from the hub or a widget is
@@ -384,10 +387,10 @@
     /**
      * Reorder the widgets.
      *
-     * @param widgetIdToPriorityMap mapping of the widget ids to their new priorities.
+     * @param widgetIdToRankMap mapping of the widget ids to their new priorities.
      */
-    fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) =
-        widgetRepository.updateWidgetOrder(widgetIdToPriorityMap)
+    fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) =
+        widgetRepository.updateWidgetOrder(widgetIdToRankMap)
 
     /** Request to unpause work profile that is currently in quiet mode. */
     fun unpauseWorkProfile() {
@@ -440,7 +443,7 @@
                     is CommunalWidgetContentModel.Available -> {
                         WidgetContent.Widget(
                             appWidgetId = widget.appWidgetId,
-                            priority = widget.priority,
+                            rank = widget.rank,
                             providerInfo = widget.providerInfo,
                             appWidgetHost = appWidgetHost,
                             inQuietMode = isQuietModeEnabled(widget.providerInfo.profile)
@@ -449,7 +452,7 @@
                     is CommunalWidgetContentModel.Pending -> {
                         WidgetContent.PendingWidget(
                             appWidgetId = widget.appWidgetId,
-                            priority = widget.priority,
+                            rank = widget.rank,
                             componentName = widget.componentName,
                             icon = widget.icon,
                         )
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 73c6ce3..4c821d4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -47,12 +47,12 @@
 
     sealed interface WidgetContent : CommunalContentModel {
         val appWidgetId: Int
-        val priority: Int
+        val rank: Int
         val componentName: ComponentName
 
         data class Widget(
             override val appWidgetId: Int,
-            override val priority: Int,
+            override val rank: Int,
             val providerInfo: AppWidgetProviderInfo,
             val appWidgetHost: CommunalAppWidgetHost,
             val inQuietMode: Boolean,
@@ -71,7 +71,7 @@
 
         data class DisabledWidget(
             override val appWidgetId: Int,
-            override val priority: Int,
+            override val rank: Int,
             val providerInfo: AppWidgetProviderInfo
         ) : WidgetContent {
             override val key = KEY.disabledWidget(appWidgetId)
@@ -85,7 +85,7 @@
 
         data class PendingWidget(
             override val appWidgetId: Int,
-            override val priority: Int,
+            override val rank: Int,
             override val componentName: ComponentName,
             val icon: Bitmap? = null,
         ) : WidgetContent {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
index 9ce8cf7..7cfad60 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
@@ -31,7 +31,7 @@
     private val statsLogProxy: StatsLogProxy,
 ) {
     /** Logs an add widget event for metrics. No-op if widget is not loggable. */
-    fun logAddWidget(componentName: String, rank: Int) {
+    fun logAddWidget(componentName: String, rank: Int?) {
         if (!componentName.isLoggable()) {
             return
         }
@@ -39,7 +39,7 @@
         statsLogProxy.writeCommunalHubWidgetEventReported(
             SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__ADD,
             componentName,
-            rank,
+            rank ?: -1,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
index 7cddb72..63b1a14 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
@@ -24,19 +24,19 @@
 /** Encapsulates data for a communal widget. */
 sealed interface CommunalWidgetContentModel {
     val appWidgetId: Int
-    val priority: Int
+    val rank: Int
 
     /** Widget is ready to display */
     data class Available(
         override val appWidgetId: Int,
         val providerInfo: AppWidgetProviderInfo,
-        override val priority: Int,
+        override val rank: Int,
     ) : CommunalWidgetContentModel
 
     /** Widget is pending installation */
     data class Pending(
         override val appWidgetId: Int,
-        override val priority: Int,
+        override val rank: Int,
         val componentName: ComponentName,
         val icon: Bitmap?,
         val user: UserHandle,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index b822133..6be94a7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -153,7 +153,7 @@
     open fun onAddWidget(
         componentName: ComponentName,
         user: UserHandle,
-        priority: Int,
+        rank: Int? = null,
         configurator: WidgetConfigurator? = null,
     ) {}
 
@@ -161,23 +161,23 @@
     open fun onDeleteWidget(
         id: Int,
         componentName: ComponentName,
-        priority: Int,
+        rank: Int,
     ) {}
 
     /** Called as the UI detects a tap event on the widget. */
     open fun onTapWidget(
         componentName: ComponentName,
-        priority: Int,
+        rank: Int,
     ) {}
 
     /**
      * Called as the UI requests reordering widgets.
      *
-     * @param widgetIdToPriorityMap mapping of the widget ids to its priority. When re-ordering to
-     *   add a new item in the middle, provide the priorities of existing widgets as if the new item
-     *   existed, and then, call [onAddWidget] to add the new item at intended order.
+     * @param widgetIdToRankMap mapping of the widget ids to its rank. When re-ordering to add a new
+     *   item in the middle, provide the priorities of existing widgets as if the new item existed,
+     *   and then, call [onAddWidget] to add the new item at intended order.
      */
-    open fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) {}
+    open fun onReorderWidgets(widgetIdToRankMap: Map<Int, Int>) {}
 
     /** Called as the UI requests opening the widget editor with an optional preselected widget. */
     open fun onOpenWidgetEditor(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 1a86c71..16788d1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -125,24 +125,24 @@
     override fun onAddWidget(
         componentName: ComponentName,
         user: UserHandle,
-        priority: Int,
+        rank: Int?,
         configurator: WidgetConfigurator?
     ) {
-        communalInteractor.addWidget(componentName, user, priority, configurator)
-        metricsLogger.logAddWidget(componentName.flattenToString(), priority)
+        communalInteractor.addWidget(componentName, user, rank, configurator)
+        metricsLogger.logAddWidget(componentName.flattenToString(), rank)
     }
 
     override fun onDeleteWidget(
         id: Int,
         componentName: ComponentName,
-        priority: Int,
+        rank: Int,
     ) {
         communalInteractor.deleteWidget(id)
-        metricsLogger.logRemoveWidget(componentName.flattenToString(), priority)
+        metricsLogger.logRemoveWidget(componentName.flattenToString(), rank)
     }
 
-    override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) =
-        communalInteractor.updateWidgetOrder(widgetIdToPriorityMap)
+    override fun onReorderWidgets(widgetIdToRankMap: Map<Int, Int>) =
+        communalInteractor.updateWidgetOrder(widgetIdToRankMap)
 
     override fun onReorderWidgetStart() {
         // Clear selection status
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index c0a18f2..4c762dc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -272,8 +272,8 @@
         }
     }
 
-    override fun onTapWidget(componentName: ComponentName, priority: Int) {
-        metricsLogger.logTapWidget(componentName.flattenToString(), priority)
+    override fun onTapWidget(componentName: ComponentName, rank: Int) {
+        metricsLogger.logTapWidget(componentName.flattenToString(), rank)
     }
 
     fun onClick() {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 7b9868b..93c3a63 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -187,11 +187,11 @@
                         if (!isPendingWidgetDrag) {
                             val (componentName, user) = getWidgetExtraFromIntent(intent)
                             if (componentName != null && user != null) {
+                                // Add widget at the end.
                                 communalViewModel.onAddWidget(
                                     componentName,
                                     user,
-                                    0,
-                                    widgetConfigurator
+                                    configurator = widgetConfigurator,
                                 )
                             } else {
                                 run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index 9460eaf..d288cce 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -57,7 +57,10 @@
 import com.android.systemui.log.SessionTracker
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.Scenes.Bouncer
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.UserRepository
@@ -159,6 +162,7 @@
     private val powerInteractor: PowerInteractor,
     private val keyguardInteractor: KeyguardInteractor,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
+    private val sceneInteractor: dagger.Lazy<SceneInteractor>,
     @FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
     @FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
@@ -385,7 +389,16 @@
                 biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
                 "isFaceAuthEnrolledAndEnabled"
             ),
-            Pair(keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway"),
+            Pair(
+                if (SceneContainerFlag.isEnabled) {
+                    keyguardTransitionInteractor
+                        .isInTransitionWhere(toStatePredicate = { it == KeyguardState.UNDEFINED })
+                        .isFalse()
+                } else {
+                    keyguardRepository.isKeyguardGoingAway.isFalse()
+                },
+                "keyguardNotGoingAway"
+            ),
             Pair(
                 keyguardTransitionInteractor
                     .isInTransitionWhere(toStatePredicate = KeyguardState::deviceIsAsleepInState)
@@ -397,7 +410,11 @@
                     .isFalse()
                     .or(
                         alternateBouncerInteractor.isVisible.or(
-                            keyguardInteractor.primaryBouncerShowing
+                            if (SceneContainerFlag.isEnabled) {
+                                sceneInteractor.get().transitionState.map { it.isIdle(Bouncer) }
+                            } else {
+                                keyguardInteractor.primaryBouncerShowing
+                            }
                         )
                     ),
                 "secureCameraNotActiveOrAnyBouncerIsShowing"
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index c536d6b..183e0e9 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -46,6 +46,9 @@
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.kotlin.pairwise
@@ -90,6 +93,7 @@
     private val powerInteractor: PowerInteractor,
     private val biometricSettingsRepository: BiometricSettingsRepository,
     private val trustManager: TrustManager,
+    private val sceneInteractor: Lazy<SceneInteractor>,
     deviceEntryFaceAuthStatusInteractor: DeviceEntryFaceAuthStatusInteractor,
 ) : DeviceEntryFaceAuthInteractor {
 
@@ -103,9 +107,7 @@
         keyguardUpdateMonitor.setFaceAuthInteractor(this)
         observeFaceAuthStateUpdates()
         faceAuthenticationLogger.interactorStarted()
-        primaryBouncerInteractor
-            .get()
-            .isShowing
+        isBouncerVisible
             .whenItFlipsToTrue()
             .onEach {
                 faceAuthenticationLogger.bouncerVisibilityChanged()
@@ -181,19 +183,23 @@
         // auth so that the switched user can unlock the device with face auth.
         userRepository.selectedUser
             .pairwise()
-            .onEach { (previous, curr) ->
+            .filter { (previous, curr) ->
                 val wasSwitching = previous.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
                 val isSwitching = curr.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
-                if (wasSwitching && !isSwitching) {
-                    resetLockedOutState(curr.userInfo.id)
-                    yield()
-                    runFaceAuth(
-                        FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING,
-                        // Fallback to detection if bouncer is not showing so that we can detect a
-                        // face and then show the bouncer to the user if face auth can't run
-                        fallbackToDetect = !primaryBouncerInteractor.get().isBouncerShowing()
-                    )
-                }
+                // User switching was in progress and is complete now.
+                wasSwitching && !isSwitching
+            }
+            .map { (_, curr) -> curr.userInfo.id }
+            .sample(isBouncerVisible, ::Pair)
+            .onEach { (userId, isBouncerCurrentlyVisible) ->
+                resetLockedOutState(userId)
+                yield()
+                runFaceAuth(
+                    FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING,
+                    // Fallback to detection if bouncer is not showing so that we can detect a
+                    // face and then show the bouncer to the user if face auth can't run
+                    fallbackToDetect = !isBouncerCurrentlyVisible
+                )
             }
             .launchIn(applicationScope)
 
@@ -210,6 +216,14 @@
             .launchIn(applicationScope)
     }
 
+    private val isBouncerVisible: Flow<Boolean> by lazy {
+        if (SceneContainerFlag.isEnabled) {
+            sceneInteractor.get().transitionState.map { it.isIdle(Scenes.Bouncer) }
+        } else {
+            primaryBouncerInteractor.get().isShowing
+        }
+    }
+
     private suspend fun resetLockedOutState(currentUserId: Int) {
         val lockoutMode = facePropertyRepository.getLockoutMode(currentUserId)
         repository.setLockedOut(
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index b45ebd8..24ac542 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -44,6 +44,7 @@
 import com.android.systemui.statusbar.CrossFadeHelper
 import javax.inject.Inject
 import javax.inject.Named
+import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.launch
 
 /** Controller for dream overlay animations. */
@@ -84,51 +85,62 @@
 
     private var mCurrentBlurRadius: Float = 0f
 
+    private var mLifecycleFlowHandle: DisposableHandle? = null
+
     fun init(view: View) {
         this.view = view
 
-        view.repeatWhenAttached {
-            repeatOnLifecycle(Lifecycle.State.CREATED) {
-                launch {
-                    dreamViewModel.dreamOverlayTranslationY.collect { px ->
-                        ComplicationLayoutParams.iteratePositions(
-                            { position: Int -> setElementsTranslationYAtPosition(px, position) },
-                            POSITION_TOP or POSITION_BOTTOM
-                        )
+        mLifecycleFlowHandle =
+            view.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    launch {
+                        dreamViewModel.dreamOverlayTranslationY.collect { px ->
+                            ComplicationLayoutParams.iteratePositions(
+                                { position: Int ->
+                                    setElementsTranslationYAtPosition(px, position)
+                                },
+                                POSITION_TOP or POSITION_BOTTOM
+                            )
+                        }
                     }
-                }
 
-                launch {
-                    dreamViewModel.dreamOverlayTranslationX.collect { px ->
-                        ComplicationLayoutParams.iteratePositions(
-                            { position: Int -> setElementsTranslationXAtPosition(px, position) },
-                            POSITION_TOP or POSITION_BOTTOM
-                        )
+                    launch {
+                        dreamViewModel.dreamOverlayTranslationX.collect { px ->
+                            ComplicationLayoutParams.iteratePositions(
+                                { position: Int ->
+                                    setElementsTranslationXAtPosition(px, position)
+                                },
+                                POSITION_TOP or POSITION_BOTTOM
+                            )
+                        }
                     }
-                }
 
-                launch {
-                    dreamViewModel.dreamOverlayAlpha.collect { alpha ->
-                        ComplicationLayoutParams.iteratePositions(
-                            { position: Int ->
-                                setElementsAlphaAtPosition(
-                                    alpha = alpha,
-                                    position = position,
-                                    fadingOut = true,
-                                )
-                            },
-                            POSITION_TOP or POSITION_BOTTOM
-                        )
+                    launch {
+                        dreamViewModel.dreamOverlayAlpha.collect { alpha ->
+                            ComplicationLayoutParams.iteratePositions(
+                                { position: Int ->
+                                    setElementsAlphaAtPosition(
+                                        alpha = alpha,
+                                        position = position,
+                                        fadingOut = true,
+                                    )
+                                },
+                                POSITION_TOP or POSITION_BOTTOM
+                            )
+                        }
                     }
-                }
 
-                launch {
-                    dreamViewModel.transitionEnded.collect { _ ->
-                        mOverlayStateController.setExitAnimationsRunning(false)
+                    launch {
+                        dreamViewModel.transitionEnded.collect { _ ->
+                            mOverlayStateController.setExitAnimationsRunning(false)
+                        }
                     }
                 }
             }
-        }
+    }
+
+    fun destroy() {
+        mLifecycleFlowHandle?.dispose()
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 76c7d23..bf6d266 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -59,6 +59,7 @@
 import com.android.systemui.util.ViewController;
 
 import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.DisposableHandle;
 import kotlinx.coroutines.flow.FlowKt;
 
 import java.util.Arrays;
@@ -185,6 +186,8 @@
                 }
             };
 
+    private DisposableHandle mFlowHandle;
+
     @Inject
     public DreamOverlayContainerViewController(
             DreamOverlayContainerView containerView,
@@ -252,6 +255,17 @@
     }
 
     @Override
+    public void destroy() {
+        mStateController.removeCallback(mDreamOverlayStateCallback);
+        mStatusBarViewController.destroy();
+        mComplicationHostViewController.destroy();
+        mDreamOverlayAnimationsController.destroy();
+        mLowLightTransitionCoordinator.setLowLightEnterListener(null);
+
+        super.destroy();
+    }
+
+    @Override
     protected void onViewAttached() {
         mWakingUpFromSwipe = false;
         mJitterStartTimeMillis = System.currentTimeMillis();
@@ -263,7 +277,7 @@
         emptyRegion.recycle();
 
         if (dreamHandlesBeingObscured()) {
-            collectFlow(
+            mFlowHandle = collectFlow(
                     mView,
                     FlowKt.distinctUntilChanged(combineFlows(
                             mKeyguardTransitionInteractor.isFinishedIn(
@@ -295,6 +309,10 @@
 
     @Override
     protected void onViewDetached() {
+        if (mFlowHandle != null) {
+            mFlowHandle.dispose();
+            mFlowHandle = null;
+        }
         mHandler.removeCallbacksAndMessages(null);
         mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
         mBouncerlessScrimController.removeCallback(mBouncerlessExpansionCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 7a9537b..4c22763 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -60,7 +60,6 @@
 import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.communal.shared.log.CommunalUiEvent;
 import com.android.systemui.communal.shared.model.CommunalScenes;
-import com.android.systemui.complication.Complication;
 import com.android.systemui.complication.dagger.ComplicationComponent;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
@@ -70,8 +69,12 @@
 import com.android.systemui.touch.TouchInsetManager;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
+import kotlinx.coroutines.Job;
+
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.concurrent.CancellationException;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -129,17 +132,21 @@
      */
     private boolean mBouncerShowing = false;
 
-    private final ComplicationComponent mComplicationComponent;
+    private final com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
+            mDreamComplicationComponentFactory;
+    private final ComplicationComponent.Factory mComplicationComponentFactory;
+    private final DreamOverlayComponent.Factory mDreamOverlayComponentFactory;
+    private final AmbientTouchComponent.Factory mAmbientTouchComponentFactory;
 
-    private final AmbientTouchComponent mAmbientTouchComponent;
+    private final TouchInsetManager mTouchInsetManager;
+    private final LifecycleOwner mLifecycleOwner;
 
-    private final com.android.systemui.dreams.complication.dagger.ComplicationComponent
-            mDreamComplicationComponent;
 
-    private final DreamOverlayComponent mDreamOverlayComponent;
 
     private ComponentName mCurrentBlockedGestureDreamActivityComponent;
 
+    private final ArrayList<Job> mFlows = new ArrayList<>();
+
     /**
      * This {@link LifecycleRegistry} controls when dream overlay functionality, like touch
      * handling, should be active. It will automatically be paused when the dream overlay is hidden
@@ -285,36 +292,27 @@
         mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
         mStateController = stateController;
         mUiEventLogger = uiEventLogger;
+        mComplicationComponentFactory = complicationComponentFactory;
+        mDreamComplicationComponentFactory = dreamComplicationComponentFactory;
         mDreamOverlayCallbackController = dreamOverlayCallbackController;
         mWindowTitle = windowTitle;
         mCommunalInteractor = communalInteractor;
         mSystemDialogsCloser = systemDialogsCloser;
         mGestureInteractor = gestureInteractor;
-
-        final ViewModelStore viewModelStore = new ViewModelStore();
-        final Complication.Host host =
-                () -> mExecutor.execute(DreamOverlayService.this::requestExit);
-
-        mComplicationComponent = complicationComponentFactory.create(lifecycleOwner, host,
-                viewModelStore, touchInsetManager);
-        mDreamComplicationComponent = dreamComplicationComponentFactory.create(
-                mComplicationComponent.getVisibilityController(), touchInsetManager);
-        mDreamOverlayComponent = dreamOverlayComponentFactory.create(lifecycleOwner,
-                mComplicationComponent.getComplicationHostViewController(), touchInsetManager);
-        mAmbientTouchComponent = ambientTouchComponentFactory.create(lifecycleOwner,
-                new HashSet<>(Arrays.asList(
-                        mDreamComplicationComponent.getHideComplicationTouchHandler(),
-                        mDreamOverlayComponent.getCommunalTouchHandler())));
+        mDreamOverlayComponentFactory = dreamOverlayComponentFactory;
+        mAmbientTouchComponentFactory = ambientTouchComponentFactory;
+        mTouchInsetManager = touchInsetManager;
+        mLifecycleOwner = lifecycleOwner;
         mLifecycleRegistry = lifecycleOwner.getRegistry();
 
         mExecutor.execute(() -> setLifecycleStateLocked(Lifecycle.State.CREATED));
 
-        collectFlow(getLifecycle(), mCommunalInteractor.isCommunalAvailable(),
-                mIsCommunalAvailableCallback);
-        collectFlow(getLifecycle(), communalInteractor.isCommunalVisible(),
-                mCommunalVisibleConsumer);
-        collectFlow(getLifecycle(), keyguardInteractor.primaryBouncerShowing,
-                mBouncerShowingConsumer);
+        mFlows.add(collectFlow(getLifecycle(), mCommunalInteractor.isCommunalAvailable(),
+                mIsCommunalAvailableCallback));
+        mFlows.add(collectFlow(getLifecycle(), communalInteractor.isCommunalVisible(),
+                mCommunalVisibleConsumer));
+        mFlows.add(collectFlow(getLifecycle(), keyguardInteractor.primaryBouncerShowing,
+                mBouncerShowingConsumer));
     }
 
     @NonNull
@@ -339,6 +337,11 @@
     public void onDestroy() {
         mKeyguardUpdateMonitor.removeCallback(mKeyguardCallback);
 
+        for (Job job : mFlows) {
+            job.cancel(new CancellationException());
+        }
+        mFlows.clear();
+
         mExecutor.execute(() -> {
             setLifecycleStateLocked(Lifecycle.State.DESTROYED);
 
@@ -353,6 +356,23 @@
 
     @Override
     public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
+        final ComplicationComponent complicationComponent = mComplicationComponentFactory.create(
+                mLifecycleOwner,
+                () -> mExecutor.execute(DreamOverlayService.this::requestExit),
+                new ViewModelStore(), mTouchInsetManager);
+        final com.android.systemui.dreams.complication.dagger.ComplicationComponent
+                dreamComplicationComponent = mDreamComplicationComponentFactory.create(
+                complicationComponent.getVisibilityController(), mTouchInsetManager);
+
+        final DreamOverlayComponent dreamOverlayComponent = mDreamOverlayComponentFactory.create(
+                mLifecycleOwner, complicationComponent.getComplicationHostViewController(),
+                mTouchInsetManager);
+        final AmbientTouchComponent ambientTouchComponent = mAmbientTouchComponentFactory.create(
+                mLifecycleOwner,
+                new HashSet<>(Arrays.asList(
+                        dreamComplicationComponent.getHideComplicationTouchHandler(),
+                        dreamOverlayComponent.getCommunalTouchHandler())));
+
         setLifecycleStateLocked(Lifecycle.State.STARTED);
 
         mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
@@ -371,8 +391,8 @@
         }
 
         mDreamOverlayContainerViewController =
-                mDreamOverlayComponent.getDreamOverlayContainerViewController();
-        mTouchMonitor = mAmbientTouchComponent.getTouchMonitor();
+                dreamOverlayComponent.getDreamOverlayContainerViewController();
+        mTouchMonitor = ambientTouchComponent.getTouchMonitor();
         mTouchMonitor.init();
 
         mStateController.setShouldShowComplications(shouldShowComplications());
@@ -541,6 +561,10 @@
     }
 
     private void removeContainerViewFromParentLocked() {
+        if (mDreamOverlayContainerViewController == null) {
+            return;
+        }
+
         View containerView = mDreamOverlayContainerViewController.getContainerView();
         if (containerView == null) {
             return;
@@ -559,8 +583,13 @@
             return;
         }
 
+        // This ensures the container view of the current dream is removed before
+        // the controller is potentially reset.
+        removeContainerViewFromParentLocked();
+
         if (mStarted && mWindow != null) {
             try {
+                mWindow.clearContentView();
                 mWindowManager.removeView(mWindow.getDecorView());
             } catch (IllegalArgumentException e) {
                 Log.e(TAG, "Error removing decor view when resetting overlay", e);
@@ -571,7 +600,10 @@
         mStateController.setLowLightActive(false);
         mStateController.setEntryAnimationsFinished(false);
 
-        mDreamOverlayContainerViewController = null;
+        if (mDreamOverlayContainerViewController != null) {
+            mDreamOverlayContainerViewController.destroy();
+            mDreamOverlayContainerViewController = null;
+        }
 
         if (mTouchMonitor != null) {
             mTouchMonitor.destroy();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index ee7b6f5..5ba780f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -33,7 +33,11 @@
 import com.android.systemui.dreams.touch.dagger.CommunalTouchModule;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 
+import kotlinx.coroutines.Job;
+
+import java.util.ArrayList;
 import java.util.Optional;
+import java.util.concurrent.CancellationException;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -49,6 +53,8 @@
     private final ConfigurationInteractor mConfigurationInteractor;
     private Boolean mIsEnabled = false;
 
+    private ArrayList<Job> mFlows = new ArrayList<>();
+
     private int mLayoutDirection = LayoutDirection.LTR;
 
     @VisibleForTesting
@@ -70,17 +76,17 @@
         mCommunalInteractor = communalInteractor;
         mConfigurationInteractor = configurationInteractor;
 
-        collectFlow(
+        mFlows.add(collectFlow(
                 mLifecycle,
                 mCommunalInteractor.isCommunalAvailable(),
                 mIsCommunalAvailableCallback
-        );
+        ));
 
-        collectFlow(
+        mFlows.add(collectFlow(
                 mLifecycle,
                 mConfigurationInteractor.getLayoutDirection(),
                 mLayoutDirectionCallback
-        );
+        ));
     }
 
     @Override
@@ -140,4 +146,13 @@
             }
         });
     }
+
+    @Override
+    public void onDestroy() {
+        for (Job job : mFlows) {
+            job.cancel(new CancellationException());
+        }
+        mFlows.clear();
+        TouchHandler.super.onDestroy();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
index 567bf70..ca43871 100644
--- a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
@@ -35,6 +35,7 @@
 import android.util.Log
 import android.util.Size
 import androidx.core.content.res.ResourcesCompat
+import com.android.app.tracing.traceSection
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -162,20 +163,21 @@
         @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
-    ): Bitmap? {
-        return try {
-            ImageDecoder.decodeBitmap(source) { decoder, info, _ ->
-                configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
-                decoder.allocator = allocator
+    ): Bitmap? =
+        traceSection("ImageLoader#loadBitmap") {
+            return try {
+                ImageDecoder.decodeBitmap(source) { decoder, info, _ ->
+                    configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
+                    decoder.allocator = allocator
+                }
+            } catch (e: IOException) {
+                Log.w(TAG, "Failed to load source $source", e)
+                return null
+            } catch (e: DecodeException) {
+                Log.w(TAG, "Failed to decode source $source", e)
+                return null
             }
-        } catch (e: IOException) {
-            Log.w(TAG, "Failed to load source $source", e)
-            return null
-        } catch (e: DecodeException) {
-            Log.w(TAG, "Failed to decode source $source", e)
-            return null
         }
-    }
 
     /**
      * Loads passed [Source] on a background thread and returns the [Drawable].
@@ -253,28 +255,31 @@
         @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
-    ): Drawable? {
-        return try {
-            loadDrawableSync(
-                toImageDecoderSource(source, defaultContext),
-                maxWidth,
-                maxHeight,
-                allocator
-            )
-                ?:
-                // If we have a resource, retry fallback using the "normal" Resource loading system.
-                // This will come into effect in cases like trying to load AnimatedVectorDrawable.
-                if (source is Res) {
-                    val context = source.context ?: defaultContext
-                    ResourcesCompat.getDrawable(context.resources, source.resId, context.theme)
-                } else {
-                    null
-                }
-        } catch (e: NotFoundException) {
-            Log.w(TAG, "Couldn't load resource $source", e)
-            null
+    ): Drawable? =
+        traceSection("ImageLoader#loadDrawable") {
+            return try {
+                loadDrawableSync(
+                    toImageDecoderSource(source, defaultContext),
+                    maxWidth,
+                    maxHeight,
+                    allocator
+                )
+                    ?:
+                    // If we have a resource, retry fallback using the "normal" Resource loading
+                    // system.
+                    // This will come into effect in cases like trying to load
+                    // AnimatedVectorDrawable.
+                    if (source is Res) {
+                        val context = source.context ?: defaultContext
+                        ResourcesCompat.getDrawable(context.resources, source.resId, context.theme)
+                    } else {
+                        null
+                    }
+            } catch (e: NotFoundException) {
+                Log.w(TAG, "Couldn't load resource $source", e)
+                null
+            }
         }
-    }
 
     /**
      * Loads passed [ImageDecoder.Source] synchronously and returns the drawable.
@@ -297,20 +302,21 @@
         @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
-    ): Drawable? {
-        return try {
-            ImageDecoder.decodeDrawable(source) { decoder, info, _ ->
-                configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
-                decoder.allocator = allocator
+    ): Drawable? =
+        traceSection("ImageLoader#loadDrawable") {
+            return try {
+                ImageDecoder.decodeDrawable(source) { decoder, info, _ ->
+                    configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
+                    decoder.allocator = allocator
+                }
+            } catch (e: IOException) {
+                Log.w(TAG, "Failed to load source $source", e)
+                return null
+            } catch (e: DecodeException) {
+                Log.w(TAG, "Failed to decode source $source", e)
+                return null
             }
-        } catch (e: IOException) {
-            Log.w(TAG, "Failed to load source $source", e)
-            return null
-        } catch (e: DecodeException) {
-            Log.w(TAG, "Failed to decode source $source", e)
-            return null
         }
-    }
 
     /** Loads icon drawable while attempting to size restrict the drawable. */
     @WorkerThread
@@ -320,55 +326,59 @@
         @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
-    ): Drawable? {
-        return when (icon.type) {
-            Icon.TYPE_URI,
-            Icon.TYPE_URI_ADAPTIVE_BITMAP -> {
-                val source = ImageDecoder.createSource(context.contentResolver, icon.uri)
-                loadDrawableSync(source, maxWidth, maxHeight, allocator)
-            }
-            Icon.TYPE_RESOURCE -> {
-                val resources = resolveResourcesForIcon(context, icon)
-                resources?.let {
+    ): Drawable? =
+        traceSection("ImageLoader#loadDrawable") {
+            return when (icon.type) {
+                Icon.TYPE_URI,
+                Icon.TYPE_URI_ADAPTIVE_BITMAP -> {
+                    val source = ImageDecoder.createSource(context.contentResolver, icon.uri)
+                    loadDrawableSync(source, maxWidth, maxHeight, allocator)
+                }
+                Icon.TYPE_RESOURCE -> {
+                    val resources = resolveResourcesForIcon(context, icon)
+                    resources?.let {
+                        loadDrawableSync(
+                            ImageDecoder.createSource(it, icon.resId),
+                            maxWidth,
+                            maxHeight,
+                            allocator
+                        )
+                    }
+                        // Fallback to non-ImageDecoder load if the attempt failed (e.g. the
+                        // resource
+                        // is a Vector drawable which ImageDecoder doesn't support.)
+                        ?: loadIconDrawable(icon, context)
+                }
+                Icon.TYPE_BITMAP -> {
+                    BitmapDrawable(context.resources, icon.bitmap)
+                }
+                Icon.TYPE_ADAPTIVE_BITMAP -> {
+                    AdaptiveIconDrawable(null, BitmapDrawable(context.resources, icon.bitmap))
+                }
+                Icon.TYPE_DATA -> {
                     loadDrawableSync(
-                        ImageDecoder.createSource(it, icon.resId),
+                        ImageDecoder.createSource(icon.dataBytes, icon.dataOffset, icon.dataLength),
                         maxWidth,
                         maxHeight,
                         allocator
                     )
                 }
-                // Fallback to non-ImageDecoder load if the attempt failed (e.g. the resource
-                // is a Vector drawable which ImageDecoder doesn't support.)
-                ?: loadIconDrawable(icon, context)
+                else -> {
+                    // We don't recognize this icon, just fallback.
+                    loadIconDrawable(icon, context)
+                }
+            }?.let { drawable ->
+                // Icons carry tint which we need to propagate down to a Drawable.
+                tintDrawable(icon, drawable)
+                drawable
             }
-            Icon.TYPE_BITMAP -> {
-                BitmapDrawable(context.resources, icon.bitmap)
-            }
-            Icon.TYPE_ADAPTIVE_BITMAP -> {
-                AdaptiveIconDrawable(null, BitmapDrawable(context.resources, icon.bitmap))
-            }
-            Icon.TYPE_DATA -> {
-                loadDrawableSync(
-                    ImageDecoder.createSource(icon.dataBytes, icon.dataOffset, icon.dataLength),
-                    maxWidth,
-                    maxHeight,
-                    allocator
-                )
-            }
-            else -> {
-                // We don't recognize this icon, just fallback.
-                loadIconDrawable(icon, context)
-            }
-        }?.let { drawable ->
-            // Icons carry tint which we need to propagate down to a Drawable.
-            tintDrawable(icon, drawable)
-            drawable
         }
-    }
 
     @WorkerThread
     fun loadIconDrawable(icon: Icon, context: Context): Drawable? {
-        icon.loadDrawable(context)?.let { return it }
+        icon.loadDrawable(context)?.let {
+            return it
+        }
 
         Log.w(TAG, "Failed to load drawable for $icon")
         return null
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
index cd49c6a..4a8ada7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -67,6 +68,7 @@
     broadcastDispatcher: BroadcastDispatcher,
     private val accessibilityManager: AccessibilityManagerWrapper,
     private val pulsingGestureListener: PulsingGestureListener,
+    private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
 ) {
     /** Whether the long-press handling feature should be enabled. */
     val isLongPressHandlingEnabled: StateFlow<Boolean> =
@@ -129,7 +131,8 @@
         }
     }
 
-    /** Notifies that the user has long-pressed on the lock screen.
+    /**
+     * Notifies that the user has long-pressed on the lock screen.
      *
      * @param isA11yAction: Whether the action was performed as an a11y action
      */
@@ -174,6 +177,7 @@
     /** Notifies that the lockscreen has been clicked at position [x], [y]. */
     fun onClick(x: Float, y: Float) {
         pulsingGestureListener.onSingleTapUp(x, y)
+        faceAuthInteractor.onNotificationPanelClicked()
     }
 
     /** Notifies that the lockscreen has been double clicked. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 51ce355..9ccfb50 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -79,6 +79,7 @@
 import com.android.systemui.monet.ColorScheme
 import com.android.systemui.monet.Style
 import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.WeatherData
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shared.clocks.ClockRegistry
@@ -188,6 +189,7 @@
     init {
         coroutineScope = CoroutineScope(applicationScope.coroutineContext + Job())
         disposables += DisposableHandle { coroutineScope.cancel() }
+        clockController.setFallbackWeatherData(WeatherData.getPlaceholderWeatherData())
 
         if (KeyguardBottomAreaRefactor.isEnabled) {
             quickAffordancesCombinedViewModel.enablePreviewMode(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
index 143d66b..24c57be 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
@@ -16,9 +16,8 @@
 
 package com.android.systemui.media.controls.domain.pipeline
 
+import android.annotation.MainThread
 import android.annotation.SuppressLint
-import android.app.ActivityOptions
-import android.app.BroadcastOptions
 import android.app.Notification
 import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
 import android.app.PendingIntent
@@ -39,7 +38,6 @@
 import android.content.pm.PackageManager
 import android.graphics.Bitmap
 import android.graphics.ImageDecoder
-import android.graphics.drawable.Animatable
 import android.graphics.drawable.Icon
 import android.media.MediaDescription
 import android.media.MediaMetadata
@@ -62,8 +60,10 @@
 import com.android.internal.logging.InstanceId
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.Dumpable
+import com.android.systemui.Flags
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
@@ -86,7 +86,6 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.res.R
-import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
 import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
 import com.android.systemui.statusbar.notification.row.HybridGroupManager
 import com.android.systemui.tuner.TunerService
@@ -97,8 +96,13 @@
 import com.android.systemui.util.time.SystemClock
 import java.io.IOException
 import java.io.PrintWriter
+import java.util.Collections
 import java.util.concurrent.Executor
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 // URI fields to try loading album art from
 private val ART_URIS =
@@ -167,8 +171,11 @@
 class LegacyMediaDataManagerImpl(
     private val context: Context,
     @Background private val backgroundExecutor: Executor,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
     @Main private val uiExecutor: Executor,
     @Main private val foregroundExecutor: DelayableExecutor,
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    @Application private val applicationScope: CoroutineScope,
     private val mediaControllerFactory: MediaControllerFactory,
     private val broadcastDispatcher: BroadcastDispatcher,
     dumpManager: DumpManager,
@@ -188,6 +195,7 @@
     private val logger: MediaUiEventLogger,
     private val smartspaceManager: SmartspaceManager?,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val mediaDataLoader: dagger.Lazy<MediaDataLoader>,
 ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener, MediaDataManager {
 
     companion object {
@@ -219,7 +227,12 @@
     // listeners are listeners that depend on MediaDataManager.
     // TODO(b/159539991#comment5): Move internal listeners to separate package.
     private val internalListeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
-    private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
+    private val mediaEntries: MutableMap<String, MediaData> =
+        if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+            Collections.synchronizedMap(LinkedHashMap())
+        } else {
+            LinkedHashMap()
+        }
     // There should ONLY be at most one Smartspace media recommendation.
     var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
     @Keep private var smartspaceSession: SmartspaceSession? = null
@@ -245,8 +258,11 @@
     constructor(
         context: Context,
         threadFactory: ThreadFactory,
+        @Background backgroundDispatcher: CoroutineDispatcher,
         @Main uiExecutor: Executor,
         @Main foregroundExecutor: DelayableExecutor,
+        @Main mainDispatcher: CoroutineDispatcher,
+        @Application applicationScope: CoroutineScope,
         mediaControllerFactory: MediaControllerFactory,
         dumpManager: DumpManager,
         broadcastDispatcher: BroadcastDispatcher,
@@ -264,13 +280,17 @@
         logger: MediaUiEventLogger,
         smartspaceManager: SmartspaceManager?,
         keyguardUpdateMonitor: KeyguardUpdateMonitor,
+        mediaDataLoader: dagger.Lazy<MediaDataLoader>,
     ) : this(
         context,
         // Loading bitmap for UMO background can take longer time, so it cannot run on the default
         // background thread. Use a custom thread for media.
         threadFactory.buildExecutorOnNewThread(TAG),
+        backgroundDispatcher,
         uiExecutor,
         foregroundExecutor,
+        mainDispatcher,
+        applicationScope,
         mediaControllerFactory,
         broadcastDispatcher,
         dumpManager,
@@ -290,6 +310,7 @@
         logger,
         smartspaceManager,
         keyguardUpdateMonitor,
+        mediaDataLoader,
     )
 
     private val appChangeReceiver =
@@ -464,16 +485,31 @@
             logSingleVsMultipleMediaAdded(appUid, packageName, instanceId)
             logger.logResumeMediaAdded(appUid, packageName, instanceId)
         }
-        backgroundExecutor.execute {
-            loadMediaDataInBgForResumption(
-                userId,
-                desc,
-                action,
-                token,
-                appName,
-                appIntent,
-                packageName
-            )
+
+        if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+            applicationScope.launch {
+                loadMediaDataForResumption(
+                    userId,
+                    desc,
+                    action,
+                    token,
+                    appName,
+                    appIntent,
+                    packageName
+                )
+            }
+        } else {
+            backgroundExecutor.execute {
+                loadMediaDataInBgForResumption(
+                    userId,
+                    desc,
+                    action,
+                    token,
+                    appName,
+                    appIntent,
+                    packageName
+                )
+            }
         }
     }
 
@@ -498,9 +534,90 @@
         oldKey: String?,
         isNewlyActiveEntry: Boolean = false,
     ) {
-        backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, isNewlyActiveEntry) }
+        if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+            applicationScope.launch {
+                loadMediaDataWithLoader(key, sbn, oldKey, isNewlyActiveEntry)
+            }
+        } else {
+            backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, isNewlyActiveEntry) }
+        }
     }
 
+    private suspend fun loadMediaDataWithLoader(
+        key: String,
+        sbn: StatusBarNotification,
+        oldKey: String?,
+        isNewlyActiveEntry: Boolean = false,
+    ) =
+        withContext(backgroundDispatcher) {
+            val lastActive = systemClock.elapsedRealtime()
+            val result = mediaDataLoader.get().loadMediaData(key, sbn)
+            if (result == null) {
+                Log.d(TAG, "No result from loadMediaData")
+                return@withContext
+            }
+
+            val currentEntry = mediaEntries[key]
+            val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
+            val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L
+            val resumeAction: Runnable? = currentEntry?.resumeAction
+            val hasCheckedForResume = currentEntry?.hasCheckedForResume == true
+            val active = currentEntry?.active ?: true
+
+            // We need to log the correct media added.
+            if (isNewlyActiveEntry) {
+                logSingleVsMultipleMediaAdded(result.appUid, sbn.packageName, instanceId)
+                logger.logActiveMediaAdded(
+                    result.appUid,
+                    sbn.packageName,
+                    instanceId,
+                    result.playbackLocation
+                )
+            } else if (result.playbackLocation != currentEntry?.playbackLocation) {
+                logger.logPlaybackLocationChange(
+                    result.appUid,
+                    sbn.packageName,
+                    instanceId,
+                    result.playbackLocation
+                )
+            }
+
+            withContext(mainDispatcher) {
+                onMediaDataLoaded(
+                    key,
+                    oldKey,
+                    MediaData(
+                        userId = sbn.normalizedUserId,
+                        initialized = true,
+                        app = result.appName,
+                        appIcon = result.appIcon,
+                        artist = result.artist,
+                        song = result.song,
+                        artwork = result.artworkIcon,
+                        actions = result.actionIcons,
+                        actionsToShowInCompact = result.actionsToShowInCompact,
+                        semanticActions = result.semanticActions,
+                        packageName = sbn.packageName,
+                        token = result.token,
+                        clickIntent = result.clickIntent,
+                        device = result.device,
+                        active = active,
+                        resumeAction = resumeAction,
+                        playbackLocation = result.playbackLocation,
+                        notificationKey = key,
+                        hasCheckedForResume = hasCheckedForResume,
+                        isPlaying = result.isPlaying,
+                        isClearable = !sbn.isOngoing,
+                        lastActive = lastActive,
+                        createdTimestampMillis = createdTimestampMillis,
+                        instanceId = instanceId,
+                        appUid = result.appUid,
+                        isExplicit = result.isExplicit,
+                    )
+                )
+            }
+        }
+
     /** Add a listener for changes in this class */
     override fun addListener(listener: MediaDataManager.Listener) {
         // mediaDataFilter is the current end of the internal pipeline. Register external
@@ -697,6 +814,75 @@
         notifySmartspaceMediaDataLoaded(smartspaceMediaData.targetId, smartspaceMediaData)
     }
 
+    private suspend fun loadMediaDataForResumption(
+        userId: Int,
+        desc: MediaDescription,
+        resumeAction: Runnable,
+        token: MediaSession.Token,
+        appName: String,
+        appIntent: PendingIntent,
+        packageName: String
+    ) =
+        withContext(backgroundDispatcher) {
+            val lastActive = systemClock.elapsedRealtime()
+            val currentEntry = mediaEntries[packageName]
+            val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L
+            val result =
+                mediaDataLoader
+                    .get()
+                    .loadMediaDataForResumption(
+                        userId,
+                        desc,
+                        resumeAction,
+                        currentEntry,
+                        token,
+                        appName,
+                        appIntent,
+                        packageName
+                    )
+            if (result == null || desc.title.isNullOrBlank()) {
+                Log.d(TAG, "No MediaData result for resumption")
+                mediaEntries.remove(packageName)
+                return@withContext
+            }
+
+            val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
+            withContext(mainDispatcher) {
+                onMediaDataLoaded(
+                    packageName,
+                    null,
+                    MediaData(
+                        userId = userId,
+                        initialized = true,
+                        app = result.appName,
+                        appIcon = null,
+                        artist = result.artist,
+                        song = result.song,
+                        artwork = result.artworkIcon,
+                        actions = result.actionIcons,
+                        actionsToShowInCompact = result.actionsToShowInCompact,
+                        semanticActions = result.semanticActions,
+                        packageName = packageName,
+                        token = result.token,
+                        clickIntent = result.clickIntent,
+                        device = result.device,
+                        active = false,
+                        resumeAction = resumeAction,
+                        resumption = true,
+                        notificationKey = packageName,
+                        hasCheckedForResume = true,
+                        lastActive = lastActive,
+                        createdTimestampMillis = createdTimestampMillis,
+                        instanceId = instanceId,
+                        appUid = result.appUid,
+                        isExplicit = result.isExplicit,
+                        resumeProgress = result.resumeProgress,
+                    )
+                )
+            }
+        }
+
+    @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
     private fun loadMediaDataInBgForResumption(
         userId: Int,
         desc: MediaDescription,
@@ -780,6 +966,7 @@
         }
     }
 
+    @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
     fun loadMediaDataInBg(
         key: String,
         sbn: StatusBarNotification,
@@ -802,8 +989,7 @@
             notif.extras.getParcelable(
                 Notification.EXTRA_BUILDER_APPLICATION_INFO,
                 ApplicationInfo::class.java
-            )
-                ?: getAppInfoFromPackage(sbn.packageName)
+            ) ?: getAppInfoFromPackage(sbn.packageName)
 
         // App name
         val appName = getAppName(sbn, appInfo)
@@ -894,7 +1080,7 @@
         var actionsToShowCollapsed: List<Int> = emptyList()
         val semanticActions = createActionsFromState(sbn.packageName, mediaController, sbn.user)
         if (semanticActions == null) {
-            val actions = createActionsFromNotification(sbn)
+            val actions = createActionsFromNotification(context, activityStarter, sbn)
             actionIcons = actions.first
             actionsToShowCollapsed = actions.second
         }
@@ -975,6 +1161,7 @@
         }
     }
 
+    @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
     private fun getAppInfoFromPackage(packageName: String): ApplicationInfo? {
         try {
             return context.packageManager.getApplicationInfo(packageName, 0)
@@ -984,6 +1171,7 @@
         return null
     }
 
+    @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
     private fun getAppName(sbn: StatusBarNotification, appInfo: ApplicationInfo?): String {
         val name = sbn.notification.extras.getString(EXTRA_SUBSTITUTE_APP_NAME)
         if (name != null) {
@@ -997,264 +1185,19 @@
         }
     }
 
-    /** Generate action buttons based on notification actions */
-    private fun createActionsFromNotification(
-        sbn: StatusBarNotification
-    ): Pair<List<MediaAction>, List<Int>> {
-        val notif = sbn.notification
-        val actionIcons: MutableList<MediaAction> = ArrayList()
-        val actions = notif.actions
-        var actionsToShowCollapsed =
-            notif.extras.getIntArray(Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList()
-                ?: mutableListOf()
-        if (actionsToShowCollapsed.size > MAX_COMPACT_ACTIONS) {
-            Log.e(
-                TAG,
-                "Too many compact actions for ${sbn.key}," +
-                    "limiting to first $MAX_COMPACT_ACTIONS"
-            )
-            actionsToShowCollapsed = actionsToShowCollapsed.subList(0, MAX_COMPACT_ACTIONS)
-        }
-
-        if (actions != null) {
-            for ((index, action) in actions.withIndex()) {
-                if (index == MAX_NOTIFICATION_ACTIONS) {
-                    Log.w(
-                        TAG,
-                        "Too many notification actions for ${sbn.key}," +
-                            " limiting to first $MAX_NOTIFICATION_ACTIONS"
-                    )
-                    break
-                }
-                if (action.getIcon() == null) {
-                    if (DEBUG) Log.i(TAG, "No icon for action $index ${action.title}")
-                    actionsToShowCollapsed.remove(index)
-                    continue
-                }
-                val runnable =
-                    if (action.actionIntent != null) {
-                        Runnable {
-                            if (action.actionIntent.isActivity) {
-                                activityStarter.startPendingIntentDismissingKeyguard(
-                                    action.actionIntent
-                                )
-                            } else if (action.isAuthenticationRequired()) {
-                                activityStarter.dismissKeyguardThenExecute(
-                                    {
-                                        var result = sendPendingIntent(action.actionIntent)
-                                        result
-                                    },
-                                    {},
-                                    true
-                                )
-                            } else {
-                                sendPendingIntent(action.actionIntent)
-                            }
-                        }
-                    } else {
-                        null
-                    }
-                val mediaActionIcon =
-                    if (action.getIcon()?.getType() == Icon.TYPE_RESOURCE) {
-                            Icon.createWithResource(sbn.packageName, action.getIcon()!!.getResId())
-                        } else {
-                            action.getIcon()
-                        }
-                        .setTint(themeText)
-                        .loadDrawable(context)
-                val mediaAction = MediaAction(mediaActionIcon, runnable, action.title, null)
-                actionIcons.add(mediaAction)
-            }
-        }
-        return Pair(actionIcons, actionsToShowCollapsed)
-    }
-
-    /**
-     * Generates action button info for this media session based on the PlaybackState
-     *
-     * @param packageName Package name for the media app
-     * @param controller MediaController for the current session
-     * @return a Pair consisting of a list of media actions, and a list of ints representing which
-     *
-     * ```
-     *      of those actions should be shown in the compact player
-     * ```
-     */
     private fun createActionsFromState(
         packageName: String,
         controller: MediaController,
         user: UserHandle
     ): MediaButton? {
-        val state = controller.playbackState
-        if (state == null || !mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
+        if (!mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
             return null
         }
-
-        // First, check for standard actions
-        val playOrPause =
-            if (isConnectingState(state.state)) {
-                // Spinner needs to be animating to render anything. Start it here.
-                val drawable =
-                    context.getDrawable(com.android.internal.R.drawable.progress_small_material)
-                (drawable as Animatable).start()
-                MediaAction(
-                    drawable,
-                    null, // no action to perform when clicked
-                    context.getString(R.string.controls_media_button_connecting),
-                    context.getDrawable(R.drawable.ic_media_connecting_container),
-                    // Specify a rebind id to prevent the spinner from restarting on later binds.
-                    com.android.internal.R.drawable.progress_small_material
-                )
-            } else if (isPlayingState(state.state)) {
-                getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE)
-            } else {
-                getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY)
-            }
-        val prevButton =
-            getStandardAction(controller, state.actions, PlaybackState.ACTION_SKIP_TO_PREVIOUS)
-        val nextButton =
-            getStandardAction(controller, state.actions, PlaybackState.ACTION_SKIP_TO_NEXT)
-
-        // Then, create a way to build any custom actions that will be needed
-        val customActions =
-            state.customActions
-                .asSequence()
-                .filterNotNull()
-                .map { getCustomAction(state, packageName, controller, it) }
-                .iterator()
-        fun nextCustomAction() = if (customActions.hasNext()) customActions.next() else null
-
-        // Finally, assign the remaining button slots: play/pause A B C D
-        // A = previous, else custom action (if not reserved)
-        // B = next, else custom action (if not reserved)
-        // C and D are always custom actions
-        val reservePrev =
-            controller.extras?.getBoolean(
-                MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
-            ) == true
-        val reserveNext =
-            controller.extras?.getBoolean(
-                MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
-            ) == true
-
-        val prevOrCustom =
-            if (prevButton != null) {
-                prevButton
-            } else if (!reservePrev) {
-                nextCustomAction()
-            } else {
-                null
-            }
-
-        val nextOrCustom =
-            if (nextButton != null) {
-                nextButton
-            } else if (!reserveNext) {
-                nextCustomAction()
-            } else {
-                null
-            }
-
-        return MediaButton(
-            playOrPause,
-            nextOrCustom,
-            prevOrCustom,
-            nextCustomAction(),
-            nextCustomAction(),
-            reserveNext,
-            reservePrev
-        )
-    }
-
-    /**
-     * Create a [MediaAction] for a given action and media session
-     *
-     * @param controller MediaController for the session
-     * @param stateActions The actions included with the session's [PlaybackState]
-     * @param action A [PlaybackState.Actions] value representing what action to generate. One of:
-     * ```
-     *      [PlaybackState.ACTION_PLAY]
-     *      [PlaybackState.ACTION_PAUSE]
-     *      [PlaybackState.ACTION_SKIP_TO_PREVIOUS]
-     *      [PlaybackState.ACTION_SKIP_TO_NEXT]
-     * @return
-     * ```
-     *
-     * A [MediaAction] with correct values set, or null if the state doesn't support it
-     */
-    private fun getStandardAction(
-        controller: MediaController,
-        stateActions: Long,
-        @PlaybackState.Actions action: Long
-    ): MediaAction? {
-        if (!includesAction(stateActions, action)) {
-            return null
-        }
-
-        return when (action) {
-            PlaybackState.ACTION_PLAY -> {
-                MediaAction(
-                    context.getDrawable(R.drawable.ic_media_play),
-                    { controller.transportControls.play() },
-                    context.getString(R.string.controls_media_button_play),
-                    context.getDrawable(R.drawable.ic_media_play_container)
-                )
-            }
-            PlaybackState.ACTION_PAUSE -> {
-                MediaAction(
-                    context.getDrawable(R.drawable.ic_media_pause),
-                    { controller.transportControls.pause() },
-                    context.getString(R.string.controls_media_button_pause),
-                    context.getDrawable(R.drawable.ic_media_pause_container)
-                )
-            }
-            PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
-                MediaAction(
-                    context.getDrawable(R.drawable.ic_media_prev),
-                    { controller.transportControls.skipToPrevious() },
-                    context.getString(R.string.controls_media_button_prev),
-                    null
-                )
-            }
-            PlaybackState.ACTION_SKIP_TO_NEXT -> {
-                MediaAction(
-                    context.getDrawable(R.drawable.ic_media_next),
-                    { controller.transportControls.skipToNext() },
-                    context.getString(R.string.controls_media_button_next),
-                    null
-                )
-            }
-            else -> null
-        }
-    }
-
-    /** Check whether the actions from a [PlaybackState] include a specific action */
-    private fun includesAction(stateActions: Long, @PlaybackState.Actions action: Long): Boolean {
-        if (
-            (action == PlaybackState.ACTION_PLAY || action == PlaybackState.ACTION_PAUSE) &&
-                (stateActions and PlaybackState.ACTION_PLAY_PAUSE > 0L)
-        ) {
-            return true
-        }
-        return (stateActions and action != 0L)
-    }
-
-    /** Get a [MediaAction] representing a [PlaybackState.CustomAction] */
-    private fun getCustomAction(
-        state: PlaybackState,
-        packageName: String,
-        controller: MediaController,
-        customAction: PlaybackState.CustomAction
-    ): MediaAction {
-        return MediaAction(
-            Icon.createWithResource(packageName, customAction.icon).loadDrawable(context),
-            { controller.transportControls.sendCustomAction(customAction, customAction.extras) },
-            customAction.name,
-            null
-        )
+        return createActionsFromState(context, packageName, controller)
     }
 
     /** Load a bitmap from the various Art metadata URIs */
+    @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
     private fun loadBitmapFromUri(metadata: MediaMetadata): Bitmap? {
         for (uri in ART_URIS) {
             val uriString = metadata.getString(uri)
@@ -1269,21 +1212,6 @@
         return null
     }
 
-    private fun sendPendingIntent(intent: PendingIntent): Boolean {
-        return try {
-            val options = BroadcastOptions.makeBasic()
-            options.setInteractive(true)
-            options.setPendingIntentBackgroundActivityStartMode(
-                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
-            )
-            intent.send(options.toBundle())
-            true
-        } catch (e: PendingIntent.CanceledException) {
-            Log.d(TAG, "Intent canceled", e)
-            false
-        }
-    }
-
     /** Returns a bitmap if the user can access the given URI, else null */
     private fun loadBitmapFromUriForUser(
         uri: Uri,
@@ -1364,6 +1292,7 @@
         )
     }
 
+    @MainThread
     fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) =
         traceSection("MediaDataManager#onMediaDataLoaded") {
             Assert.isMainThread()
@@ -1619,6 +1548,7 @@
      * - If resumption is disabled, we only want to show active players
      */
     override fun hasAnyMedia() = mediaDataFilter.hasAnyMedia()
+
     override fun isRecommendationActive() = smartspaceMediaData.isActive
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
new file mode 100644
index 0000000..70189b7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline
+
+import android.app.ActivityOptions
+import android.app.BroadcastOptions
+import android.app.Notification
+import android.app.PendingIntent
+import android.content.Context
+import android.graphics.drawable.Animatable
+import android.graphics.drawable.Icon
+import android.media.session.MediaController
+import android.media.session.PlaybackState
+import android.service.notification.StatusBarNotification
+import android.util.Log
+import androidx.media.utils.MediaConstants
+import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManagerImpl.Companion.MAX_COMPACT_ACTIONS
+import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManagerImpl.Companion.MAX_NOTIFICATION_ACTIONS
+import com.android.systemui.media.controls.shared.model.MediaAction
+import com.android.systemui.media.controls.shared.model.MediaButton
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
+import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
+import com.android.systemui.util.kotlin.logI
+
+private const val TAG = "MediaActions"
+
+/**
+ * Generates action button info for this media session based on the PlaybackState
+ *
+ * @param packageName Package name for the media app
+ * @param controller MediaController for the current session
+ * @return a Pair consisting of a list of media actions, and a list of ints representing which of
+ *   those actions should be shown in the compact player
+ */
+fun createActionsFromState(
+    context: Context,
+    packageName: String,
+    controller: MediaController,
+): MediaButton? {
+    val state = controller.playbackState ?: return null
+    // First, check for standard actions
+    val playOrPause =
+        if (isConnectingState(state.state)) {
+            // Spinner needs to be animating to render anything. Start it here.
+            val drawable =
+                context.getDrawable(com.android.internal.R.drawable.progress_small_material)
+            (drawable as Animatable).start()
+            MediaAction(
+                drawable,
+                null, // no action to perform when clicked
+                context.getString(R.string.controls_media_button_connecting),
+                context.getDrawable(R.drawable.ic_media_connecting_container),
+                // Specify a rebind id to prevent the spinner from restarting on later binds.
+                com.android.internal.R.drawable.progress_small_material
+            )
+        } else if (isPlayingState(state.state)) {
+            getStandardAction(context, controller, state.actions, PlaybackState.ACTION_PAUSE)
+        } else {
+            getStandardAction(context, controller, state.actions, PlaybackState.ACTION_PLAY)
+        }
+    val prevButton =
+        getStandardAction(context, controller, state.actions, PlaybackState.ACTION_SKIP_TO_PREVIOUS)
+    val nextButton =
+        getStandardAction(context, controller, state.actions, PlaybackState.ACTION_SKIP_TO_NEXT)
+
+    // Then, create a way to build any custom actions that will be needed
+    val customActions =
+        state.customActions
+            .asSequence()
+            .filterNotNull()
+            .map { getCustomAction(context, packageName, controller, it) }
+            .iterator()
+    fun nextCustomAction() = if (customActions.hasNext()) customActions.next() else null
+
+    // Finally, assign the remaining button slots: play/pause A B C D
+    // A = previous, else custom action (if not reserved)
+    // B = next, else custom action (if not reserved)
+    // C and D are always custom actions
+    val reservePrev =
+        controller.extras?.getBoolean(
+            MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
+        ) == true
+    val reserveNext =
+        controller.extras?.getBoolean(
+            MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
+        ) == true
+
+    val prevOrCustom =
+        if (prevButton != null) {
+            prevButton
+        } else if (!reservePrev) {
+            nextCustomAction()
+        } else {
+            null
+        }
+
+    val nextOrCustom =
+        if (nextButton != null) {
+            nextButton
+        } else if (!reserveNext) {
+            nextCustomAction()
+        } else {
+            null
+        }
+
+    return MediaButton(
+        playOrPause,
+        nextOrCustom,
+        prevOrCustom,
+        nextCustomAction(),
+        nextCustomAction(),
+        reserveNext,
+        reservePrev
+    )
+}
+
+/**
+ * Create a [MediaAction] for a given action and media session
+ *
+ * @param controller MediaController for the session
+ * @param stateActions The actions included with the session's [PlaybackState]
+ * @param action A [PlaybackState.Actions] value representing what action to generate. One of:
+ *   [PlaybackState.ACTION_PLAY] [PlaybackState.ACTION_PAUSE]
+ *   [PlaybackState.ACTION_SKIP_TO_PREVIOUS] [PlaybackState.ACTION_SKIP_TO_NEXT]
+ * @return A [MediaAction] with correct values set, or null if the state doesn't support it
+ */
+private fun getStandardAction(
+    context: Context,
+    controller: MediaController,
+    stateActions: Long,
+    @PlaybackState.Actions action: Long
+): MediaAction? {
+    if (!includesAction(stateActions, action)) {
+        return null
+    }
+
+    return when (action) {
+        PlaybackState.ACTION_PLAY -> {
+            MediaAction(
+                context.getDrawable(R.drawable.ic_media_play),
+                { controller.transportControls.play() },
+                context.getString(R.string.controls_media_button_play),
+                context.getDrawable(R.drawable.ic_media_play_container)
+            )
+        }
+        PlaybackState.ACTION_PAUSE -> {
+            MediaAction(
+                context.getDrawable(R.drawable.ic_media_pause),
+                { controller.transportControls.pause() },
+                context.getString(R.string.controls_media_button_pause),
+                context.getDrawable(R.drawable.ic_media_pause_container)
+            )
+        }
+        PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
+            MediaAction(
+                context.getDrawable(R.drawable.ic_media_prev),
+                { controller.transportControls.skipToPrevious() },
+                context.getString(R.string.controls_media_button_prev),
+                null
+            )
+        }
+        PlaybackState.ACTION_SKIP_TO_NEXT -> {
+            MediaAction(
+                context.getDrawable(R.drawable.ic_media_next),
+                { controller.transportControls.skipToNext() },
+                context.getString(R.string.controls_media_button_next),
+                null
+            )
+        }
+        else -> null
+    }
+}
+
+/** Get a [MediaAction] representing a [PlaybackState.CustomAction] */
+private fun getCustomAction(
+    context: Context,
+    packageName: String,
+    controller: MediaController,
+    customAction: PlaybackState.CustomAction
+): MediaAction {
+    return MediaAction(
+        Icon.createWithResource(packageName, customAction.icon).loadDrawable(context),
+        { controller.transportControls.sendCustomAction(customAction, customAction.extras) },
+        customAction.name,
+        null
+    )
+}
+
+/** Check whether the actions from a [PlaybackState] include a specific action */
+private fun includesAction(stateActions: Long, @PlaybackState.Actions action: Long): Boolean {
+    if (
+        (action == PlaybackState.ACTION_PLAY || action == PlaybackState.ACTION_PAUSE) &&
+            (stateActions and PlaybackState.ACTION_PLAY_PAUSE > 0L)
+    ) {
+        return true
+    }
+    return (stateActions and action != 0L)
+}
+
+/** Generate action buttons based on notification actions */
+fun createActionsFromNotification(
+    context: Context,
+    activityStarter: ActivityStarter,
+    sbn: StatusBarNotification
+): Pair<List<MediaAction>, List<Int>> {
+    val notif = sbn.notification
+    val actionIcons: MutableList<MediaAction> = ArrayList()
+    val actions = notif.actions
+    var actionsToShowCollapsed =
+        notif.extras.getIntArray(Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList()
+            ?: mutableListOf()
+    if (actionsToShowCollapsed.size > MAX_COMPACT_ACTIONS) {
+        Log.e(
+            TAG,
+            "Too many compact actions for ${sbn.key}, limiting to first $MAX_COMPACT_ACTIONS"
+        )
+        actionsToShowCollapsed = actionsToShowCollapsed.subList(0, MAX_COMPACT_ACTIONS)
+    }
+
+    actions?.let {
+        if (it.size > MAX_NOTIFICATION_ACTIONS) {
+            Log.w(
+                TAG,
+                "Too many notification actions for ${sbn.key}, " +
+                    "limiting to first $MAX_NOTIFICATION_ACTIONS"
+            )
+        }
+
+        for ((index, action) in it.take(MAX_NOTIFICATION_ACTIONS).withIndex()) {
+            if (action.getIcon() == null) {
+                logI(TAG) { "No icon for action $index ${action.title}" }
+                actionsToShowCollapsed.remove(index)
+                continue
+            }
+
+            val runnable =
+                action.actionIntent?.let { actionIntent ->
+                    Runnable {
+                        when {
+                            actionIntent.isActivity ->
+                                activityStarter.startPendingIntentDismissingKeyguard(
+                                    action.actionIntent
+                                )
+                            action.isAuthenticationRequired ->
+                                activityStarter.dismissKeyguardThenExecute(
+                                    { sendPendingIntent(action.actionIntent) },
+                                    {},
+                                    true
+                                )
+                            else -> sendPendingIntent(actionIntent)
+                        }
+                    }
+                }
+
+            val themeText =
+                com.android.settingslib.Utils.getColorAttr(
+                        context,
+                        com.android.internal.R.attr.textColorPrimary
+                    )
+                    .defaultColor
+
+            val mediaActionIcon =
+                when (action.getIcon().type) {
+                        Icon.TYPE_RESOURCE ->
+                            Icon.createWithResource(sbn.packageName, action.getIcon()!!.getResId())
+                        else -> action.getIcon()
+                    }
+                    .setTint(themeText)
+                    .loadDrawable(context)
+
+            val mediaAction = MediaAction(mediaActionIcon, runnable, action.title, null)
+            actionIcons.add(mediaAction)
+        }
+    }
+    return Pair(actionIcons, actionsToShowCollapsed)
+}
+
+private fun sendPendingIntent(intent: PendingIntent): Boolean {
+    return try {
+        intent.send(
+            BroadcastOptions.makeBasic()
+                .apply {
+                    setInteractive(true)
+                    setPendingIntentBackgroundActivityStartMode(
+                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+                    )
+                }
+                .toBundle()
+        )
+        true
+    } catch (e: PendingIntent.CanceledException) {
+        Log.d(TAG, "Intent canceled", e)
+        false
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
new file mode 100644
index 0000000..f9fef8e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
@@ -0,0 +1,530 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline
+
+import android.annotation.WorkerThread
+import android.app.Notification
+import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
+import android.app.PendingIntent
+import android.app.StatusBarManager
+import android.app.UriGrantsManager
+import android.content.ContentProvider
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.graphics.ImageDecoder
+import android.graphics.drawable.Icon
+import android.media.MediaDescription
+import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.net.Uri
+import android.os.Process
+import android.os.UserHandle
+import android.service.notification.StatusBarNotification
+import android.support.v4.media.MediaMetadataCompat
+import android.text.TextUtils
+import android.util.Log
+import android.util.Pair
+import androidx.media.utils.MediaConstants
+import com.android.app.tracing.coroutines.traceCoroutine
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.graphics.ImageLoader
+import com.android.systemui.media.controls.shared.model.MediaAction
+import com.android.systemui.media.controls.shared.model.MediaButton
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDeviceData
+import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.MediaDataUtils
+import com.android.systemui.media.controls.util.MediaFlags
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
+import com.android.systemui.statusbar.notification.row.HybridGroupManager
+import com.android.systemui.util.kotlin.logD
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+import kotlin.coroutines.coroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.async
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.ensureActive
+
+/** Loads media information from media style [StatusBarNotification] classes. */
+@SysUISingleton
+class MediaDataLoader
+@Inject
+constructor(
+    @Application val context: Context,
+    @Main val mainDispatcher: CoroutineDispatcher,
+    @Background val backgroundScope: CoroutineScope,
+    private val activityStarter: ActivityStarter,
+    private val mediaControllerFactory: MediaControllerFactory,
+    private val mediaFlags: MediaFlags,
+    private val imageLoader: ImageLoader,
+    private val statusBarManager: StatusBarManager,
+) {
+    private val mediaProcessingJobs = ConcurrentHashMap<JobKey, Job>()
+
+    private val artworkWidth: Int =
+        context.resources.getDimensionPixelSize(
+            com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize
+        )
+    private val artworkHeight: Int =
+        context.resources.getDimensionPixelSize(R.dimen.qs_media_session_height_expanded)
+
+    private val themeText =
+        com.android.settingslib.Utils.getColorAttr(
+                context,
+                com.android.internal.R.attr.textColorPrimary
+            )
+            .defaultColor
+
+    /**
+     * Loads media data for a given [StatusBarNotification]. It does the loading on the background
+     * thread.
+     *
+     * Returns a [MediaDataLoaderResult] if loaded data or `null` if loading failed. The method
+     * suspends until loading has completed or failed.
+     *
+     * If a new [loadMediaData] is issued while existing load is in progress, the existing (old)
+     * load will be cancelled.
+     */
+    suspend fun loadMediaData(key: String, sbn: StatusBarNotification): MediaDataLoaderResult? {
+        logD(TAG) { "Loading media data for $key..." }
+        val jobKey = JobKey(key, sbn)
+        val loadMediaJob = backgroundScope.async { loadMediaDataInBackground(key, sbn) }
+        loadMediaJob.invokeOnCompletion { mediaProcessingJobs.remove(jobKey) }
+        val existingJob = mediaProcessingJobs.put(jobKey, loadMediaJob)
+        existingJob?.cancel("New processing job incoming.")
+        return loadMediaJob.await()
+    }
+
+    /** Loads media data, should be called from [backgroundScope]. */
+    @WorkerThread
+    private suspend fun loadMediaDataInBackground(
+        key: String,
+        sbn: StatusBarNotification,
+    ): MediaDataLoaderResult? =
+        traceCoroutine("MediaDataLoader#loadMediaData") {
+            val token =
+                sbn.notification.extras.getParcelable(
+                    Notification.EXTRA_MEDIA_SESSION,
+                    MediaSession.Token::class.java
+                )
+            if (token == null) {
+                Log.i(TAG, "Token was null, not loading media info")
+                return null
+            }
+            val mediaController = mediaControllerFactory.create(token)
+            val metadata = mediaController.metadata
+            val notification: Notification = sbn.notification
+
+            val appInfo =
+                notification.extras.getParcelable(
+                    Notification.EXTRA_BUILDER_APPLICATION_INFO,
+                    ApplicationInfo::class.java
+                ) ?: getAppInfoFromPackage(sbn.packageName)
+
+            // App name
+            val appName = getAppName(sbn, appInfo)
+
+            // Song name
+            var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
+            if (song.isNullOrBlank()) {
+                song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE)
+            }
+            if (song.isNullOrBlank()) {
+                song = HybridGroupManager.resolveTitle(notification)
+            }
+            if (song.isNullOrBlank()) {
+                // For apps that don't include a title, log and add a placeholder
+                song = context.getString(R.string.controls_media_empty_title, appName)
+                try {
+                    statusBarManager.logBlankMediaTitle(sbn.packageName, sbn.user.identifier)
+                } catch (e: RuntimeException) {
+                    Log.e(TAG, "Error reporting blank media title for package ${sbn.packageName}")
+                }
+            }
+
+            // Don't attempt to load bitmaps if the job was cancelled.
+            coroutineContext.ensureActive()
+
+            // Album art
+            var artworkBitmap = metadata?.let { loadBitmapFromUri(it) }
+            if (artworkBitmap == null) {
+                artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ART)
+            }
+            if (artworkBitmap == null) {
+                artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
+            }
+            val artworkIcon =
+                if (artworkBitmap == null) {
+                    notification.getLargeIcon()
+                } else {
+                    Icon.createWithBitmap(artworkBitmap)
+                }
+
+            // Don't continue if we were cancelled during slow bitmap load.
+            coroutineContext.ensureActive()
+
+            // App Icon
+            val smallIcon = sbn.notification.smallIcon
+
+            // Explicit Indicator
+            val isExplicit =
+                MediaMetadataCompat.fromMediaMetadata(metadata)
+                    ?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
+                    MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+
+            // Artist name
+            var artist: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST)
+            if (artist.isNullOrBlank()) {
+                artist = HybridGroupManager.resolveText(notification)
+            }
+
+            // Device name (used for remote cast notifications)
+            val device: MediaDeviceData? = getDeviceInfoForRemoteCast(key, sbn)
+
+            // Control buttons
+            // If flag is enabled and controller has a PlaybackState, create actions from session
+            // info
+            // Otherwise, use the notification actions
+            var actionIcons: List<MediaAction> = emptyList()
+            var actionsToShowCollapsed: List<Int> = emptyList()
+            val semanticActions = createActionsFromState(sbn.packageName, mediaController, sbn.user)
+            logD(TAG) { "Semantic actions: $semanticActions" }
+            if (semanticActions == null) {
+                val actions = createActionsFromNotification(context, activityStarter, sbn)
+                actionIcons = actions.first
+                actionsToShowCollapsed = actions.second
+                logD(TAG) { "[!!] Semantic actions: $semanticActions" }
+            }
+
+            val playbackLocation = getPlaybackLocation(sbn, mediaController)
+            val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) }
+
+            val appUid = appInfo?.uid ?: Process.INVALID_UID
+            return MediaDataLoaderResult(
+                appName = appName,
+                appIcon = smallIcon,
+                artist = artist,
+                song = song,
+                artworkIcon = artworkIcon,
+                actionIcons = actionIcons,
+                actionsToShowInCompact = actionsToShowCollapsed,
+                semanticActions = semanticActions,
+                token = token,
+                clickIntent = notification.contentIntent,
+                device = device,
+                playbackLocation = playbackLocation,
+                isPlaying = isPlaying,
+                appUid = appUid,
+                isExplicit = isExplicit
+            )
+        }
+
+    /**
+     * Loads media data in background for a given set of resumption parameters. The method suspends
+     * until loading is complete or fails.
+     *
+     * Returns a [MediaDataLoaderResult] if loaded data or `null` if loading failed.
+     */
+    suspend fun loadMediaDataForResumption(
+        userId: Int,
+        desc: MediaDescription,
+        resumeAction: Runnable,
+        currentEntry: MediaData?,
+        token: MediaSession.Token,
+        appName: String,
+        appIntent: PendingIntent,
+        packageName: String
+    ): MediaDataLoaderResult? {
+        val mediaData =
+            backgroundScope.async {
+                loadMediaDataForResumptionInBackground(
+                    userId,
+                    desc,
+                    resumeAction,
+                    currentEntry,
+                    token,
+                    appName,
+                    appIntent,
+                    packageName
+                )
+            }
+        return mediaData.await()
+    }
+
+    /** Loads media data for resumption, should be called from [backgroundScope]. */
+    @WorkerThread
+    private suspend fun loadMediaDataForResumptionInBackground(
+        userId: Int,
+        desc: MediaDescription,
+        resumeAction: Runnable,
+        currentEntry: MediaData?,
+        token: MediaSession.Token,
+        appName: String,
+        appIntent: PendingIntent,
+        packageName: String
+    ): MediaDataLoaderResult? =
+        traceCoroutine("MediaDataLoader#loadMediaDataForResumption") {
+            if (desc.title.isNullOrBlank()) {
+                Log.e(TAG, "Description incomplete")
+                return null
+            }
+
+            logD(TAG) { "adding track for $userId from browser: $desc" }
+
+            val appUid = currentEntry?.appUid ?: Process.INVALID_UID
+
+            // Album art
+            var artworkBitmap = desc.iconBitmap
+            if (artworkBitmap == null && desc.iconUri != null) {
+                artworkBitmap =
+                    loadBitmapFromUriForUser(desc.iconUri!!, userId, appUid, packageName)
+            }
+            val artworkIcon =
+                if (artworkBitmap != null) {
+                    Icon.createWithBitmap(artworkBitmap)
+                } else {
+                    null
+                }
+
+            val isExplicit =
+                desc.extras?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
+                    MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+
+            val progress =
+                if (mediaFlags.isResumeProgressEnabled()) {
+                    MediaDataUtils.getDescriptionProgress(desc.extras)
+                } else null
+
+            val mediaAction = getResumeMediaAction(resumeAction)
+            return MediaDataLoaderResult(
+                appName = appName,
+                appIcon = null,
+                artist = desc.subtitle,
+                song = desc.title,
+                artworkIcon = artworkIcon,
+                actionIcons = listOf(mediaAction),
+                actionsToShowInCompact = listOf(0),
+                semanticActions = MediaButton(playOrPause = mediaAction),
+                token = token,
+                clickIntent = appIntent,
+                device = null,
+                playbackLocation = 0,
+                isPlaying = null,
+                appUid = appUid,
+                isExplicit = isExplicit,
+                resumeAction = resumeAction,
+                resumeProgress = progress
+            )
+        }
+
+    private fun createActionsFromState(
+        packageName: String,
+        controller: MediaController,
+        user: UserHandle
+    ): MediaButton? {
+        if (!mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
+            return null
+        }
+
+        return createActionsFromState(context, packageName, controller)
+    }
+
+    private fun getPlaybackLocation(sbn: StatusBarNotification, mediaController: MediaController) =
+        when {
+            isRemoteCastNotification(sbn) -> MediaData.PLAYBACK_CAST_REMOTE
+            mediaController.playbackInfo?.playbackType ==
+                MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> MediaData.PLAYBACK_LOCAL
+            else -> MediaData.PLAYBACK_CAST_LOCAL
+        }
+
+    /**
+     * Returns [MediaDeviceData] if the [StatusBarNotification] is a remote cast notification.
+     * `null` otherwise.
+     */
+    private fun getDeviceInfoForRemoteCast(
+        key: String,
+        sbn: StatusBarNotification
+    ): MediaDeviceData? {
+        val extras = sbn.notification.extras
+        val deviceName = extras.getCharSequence(Notification.EXTRA_MEDIA_REMOTE_DEVICE, null)
+        val deviceIcon = extras.getInt(Notification.EXTRA_MEDIA_REMOTE_ICON, -1)
+        val deviceIntent =
+            extras.getParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT, PendingIntent::class.java)
+        logD(TAG) { "$key is RCN for $deviceName" }
+
+        if (deviceName != null && deviceIcon > -1) {
+            // Name and icon must be present, but intent may be null
+            val enabled = deviceIntent != null && deviceIntent.isActivity
+            val deviceDrawable =
+                Icon.createWithResource(sbn.packageName, deviceIcon)
+                    .loadDrawable(sbn.getPackageContext(context))
+            return MediaDeviceData(
+                enabled,
+                deviceDrawable,
+                deviceName,
+                deviceIntent,
+                showBroadcastButton = false
+            )
+        }
+        return null
+    }
+
+    private fun getAppInfoFromPackage(packageName: String): ApplicationInfo? {
+        try {
+            return context.packageManager.getApplicationInfo(packageName, 0)
+        } catch (e: PackageManager.NameNotFoundException) {
+            Log.w(TAG, "Could not get app info for $packageName", e)
+            return null
+        }
+    }
+
+    private fun getAppName(sbn: StatusBarNotification, appInfo: ApplicationInfo?): String {
+        val name = sbn.notification.extras.getString(EXTRA_SUBSTITUTE_APP_NAME)
+        return when {
+            name != null -> name
+            appInfo != null -> context.packageManager.getApplicationLabel(appInfo).toString()
+            else -> sbn.packageName
+        }
+    }
+
+    /** Load a bitmap from the various Art metadata URIs */
+    private suspend fun loadBitmapFromUri(metadata: MediaMetadata): Bitmap? {
+        for (uri in ART_URIS) {
+            val uriString = metadata.getString(uri)
+            if (!TextUtils.isEmpty(uriString)) {
+                val albumArt = loadBitmapFromUri(Uri.parse(uriString))
+                // If we got cancelled during slow album art load, cancel the rest of
+                // the process.
+                coroutineContext.ensureActive()
+                if (albumArt != null) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "loaded art from $uri")
+                    }
+                    return albumArt
+                }
+            }
+        }
+        return null
+    }
+
+    private suspend fun loadBitmapFromUri(uri: Uri): Bitmap? {
+        // ImageDecoder requires a scheme of the following types
+        if (
+            uri.scheme !in
+                listOf(
+                    ContentResolver.SCHEME_CONTENT,
+                    ContentResolver.SCHEME_ANDROID_RESOURCE,
+                    ContentResolver.SCHEME_FILE
+                )
+        ) {
+            Log.w(TAG, "Invalid album art uri $uri")
+            return null
+        }
+
+        val source = ImageLoader.Uri(uri)
+        return imageLoader.loadBitmap(
+            source,
+            artworkWidth,
+            artworkHeight,
+            allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+        )
+    }
+
+    private suspend fun loadBitmapFromUriForUser(
+        uri: Uri,
+        userId: Int,
+        appUid: Int,
+        packageName: String
+    ): Bitmap? {
+        try {
+            val ugm = UriGrantsManager.getService()
+            ugm.checkGrantUriPermission_ignoreNonSystem(
+                appUid,
+                packageName,
+                ContentProvider.getUriWithoutUserId(uri),
+                Intent.FLAG_GRANT_READ_URI_PERMISSION,
+                ContentProvider.getUserIdFromUri(uri, userId)
+            )
+            return loadBitmapFromUri(uri)
+        } catch (e: SecurityException) {
+            Log.e(TAG, "Failed to get URI permission: $e")
+        }
+        return null
+    }
+
+    /** Check whether this notification is an RCN */
+    private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean =
+        sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE)
+
+    private fun getResumeMediaAction(action: Runnable): MediaAction {
+        return MediaAction(
+            Icon.createWithResource(context, R.drawable.ic_media_play)
+                .setTint(themeText)
+                .loadDrawable(context),
+            action,
+            context.getString(R.string.controls_media_resume),
+            context.getDrawable(R.drawable.ic_media_play_container)
+        )
+    }
+
+    private data class JobKey(val key: String, val sbn: StatusBarNotification) :
+        Pair<String, StatusBarNotification>(key, sbn)
+
+    companion object {
+        private const val TAG = "MediaDataLoader"
+        private val ART_URIS =
+            arrayOf(
+                MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
+                MediaMetadata.METADATA_KEY_ART_URI,
+                MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI
+            )
+    }
+
+    /** Returned data from loader. */
+    data class MediaDataLoaderResult(
+        val appName: String?,
+        val appIcon: Icon?,
+        val artist: CharSequence?,
+        val song: CharSequence?,
+        val artworkIcon: Icon?,
+        val actionIcons: List<MediaAction>,
+        val actionsToShowInCompact: List<Int>,
+        val semanticActions: MediaButton?,
+        val token: MediaSession.Token?,
+        val clickIntent: PendingIntent?,
+        val device: MediaDeviceData?,
+        val playbackLocation: Int,
+        val isPlaying: Boolean?,
+        val appUid: Int,
+        val isExplicit: Boolean,
+        val resumeAction: Runnable? = null,
+        val resumeProgress: Double? = null
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index 2dbe2aa..bf2aa7e 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -20,7 +20,7 @@
 import android.annotation.UserIdInt
 import android.app.ActivityManager.RecentTaskInfo
 import android.content.ComponentName
-import com.android.wm.shell.util.SplitBounds
+import com.android.wm.shell.shared.split.SplitBounds
 
 data class RecentTask(
     val taskId: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index 01b1be9..82e58cc 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -23,7 +23,7 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.kotlin.getOrNull
 import com.android.wm.shell.recents.RecentTasks
-import com.android.wm.shell.util.GroupedRecentTaskInfo
+import com.android.wm.shell.shared.GroupedRecentTaskInfo
 import java.util.Optional
 import java.util.concurrent.Executor
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index dd1fa76..bb4d894 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -36,10 +36,10 @@
 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
 import com.android.systemui.res.R
 import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration
+import com.android.wm.shell.shared.split.SplitBounds
 import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
 import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
 import com.android.wm.shell.splitscreen.SplitScreen
-import com.android.wm.shell.util.SplitBounds
 import java.util.Optional
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
index 0e127e3..83c3335 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -41,10 +41,11 @@
             if (Flags.modesApi() && Flags.modesUi() && Flags.modesUiIcons() && data.icon != null) {
                 icon = { data.icon }
             } else {
-                val defaultIconRes =
+                val iconRes =
                     if (data.isActivated) R.drawable.qs_dnd_icon_on else R.drawable.qs_dnd_icon_off
-                iconRes = defaultIconRes
-                icon = { resources.getDrawable(defaultIconRes, theme).asIcon() }
+                val icon = resources.getDrawable(iconRes, theme).asIcon()
+                this.iconRes = iconRes
+                this.icon = { icon }
             }
             activationState =
                 if (data.isActivated) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index f74c9a6..e9292f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -79,6 +79,7 @@
             // NOTE: NotificationEntry.isClearable will internally check group children to ensure
             //  the group itself definitively clearable.
             val isClearable = !isSensitiveContentProtectionActive && entry.isClearable
+                    && !entry.isSensitive.value
             when {
                 isSilent && isClearable -> hasClearableSilentNotifs = true
                 isSilent && !isClearable -> hasNonClearableSilentNotifs = true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 6a2c602..a16129b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -511,10 +511,12 @@
     }
 
     public int getTopPadding() {
+        SceneContainerFlag.assertInLegacyMode();
         return mTopPadding;
     }
 
     public void setTopPadding(int topPadding) {
+        SceneContainerFlag.assertInLegacyMode();
         mTopPadding = topPadding;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index f26f840..d0c51bc2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -851,7 +851,7 @@
             return; // the rest of the fields are not important in Flexiglass
         }
 
-        y = getTopPadding();
+        y = mAmbientState.getTopPadding();
         drawDebugInfo(canvas, y, Color.RED, /* label= */ "getTopPadding() = " + y);
 
         y = getLayoutHeight();
@@ -1231,9 +1231,11 @@
 
     @Override
     public void setStackTop(float stackTop) {
-        mAmbientState.setStackTop(stackTop);
-        // TODO(b/332574413): replace the following with using stackTop
-        updateTopPadding(stackTop, isAddOrRemoveAnimationPending());
+        if (mAmbientState.getStackTop() != stackTop) {
+            mAmbientState.setStackTop(stackTop);
+            onTopPaddingChanged(/* animate = */ isAddOrRemoveAnimationPending());
+            setExpandedHeight(mExpandedHeight);
+        }
     }
 
     @Override
@@ -1386,28 +1388,30 @@
     }
 
     public int getTopPadding() {
-        return mAmbientState.getTopPadding();
+        // TODO(b/332574413) replace all usages of getTopPadding()
+        if (SceneContainerFlag.isEnabled()) {
+            return (int) mAmbientState.getStackTop();
+        } else {
+            return mAmbientState.getTopPadding();
+        }
     }
 
-    private void setTopPadding(int topPadding, boolean animate) {
-        if (getTopPadding() != topPadding) {
-            mAmbientState.setTopPadding(topPadding);
-            boolean shouldAnimate = animate || mAnimateNextTopPaddingChange;
-            updateAlgorithmHeightAndPadding();
-            updateContentHeight();
-            if (mAmbientState.isOnKeyguard()
-                    && !mShouldUseSplitNotificationShade
-                    && mShouldSkipTopPaddingAnimationAfterFold) {
-                mShouldSkipTopPaddingAnimationAfterFold = false;
-            } else if (shouldAnimate && mAnimationsEnabled && mIsExpanded) {
-                mTopPaddingNeedsAnimation = true;
-                mNeedsAnimation = true;
-            }
-            updateStackPosition();
-            requestChildrenUpdate();
-            notifyHeightChangeListener(null, shouldAnimate);
-            mAnimateNextTopPaddingChange = false;
+    private void onTopPaddingChanged(boolean animate) {
+        boolean shouldAnimate = animate || mAnimateNextTopPaddingChange;
+        updateAlgorithmHeightAndPadding();
+        updateContentHeight();
+        if (mAmbientState.isOnKeyguard()
+                && !mShouldUseSplitNotificationShade
+                && mShouldSkipTopPaddingAnimationAfterFold) {
+            mShouldSkipTopPaddingAnimationAfterFold = false;
+        } else if (shouldAnimate && mAnimationsEnabled && mIsExpanded) {
+            mTopPaddingNeedsAnimation = true;
+            mNeedsAnimation = true;
         }
+        updateStackPosition();
+        requestChildrenUpdate();
+        notifyHeightChangeListener(null, shouldAnimate);
+        mAnimateNextTopPaddingChange = false;
     }
 
     /**
@@ -1435,6 +1439,11 @@
      * @param listenerNeedsAnimation does the listener need to animate?
      */
     private void updateStackPosition(boolean listenerNeedsAnimation) {
+        // When scene container is active, we only want to recalculate stack heights.
+        if (SceneContainerFlag.isEnabled()) {
+            updateStackEndHeightAndStackHeight(mAmbientState.getExpansionFraction());
+            return;
+        }
         float topOverscrollAmount = mShouldUseSplitNotificationShade
                 ? getCurrentOverScrollAmount(true /* top */) : 0f;
         final float endTopPosition = getTopPadding() + mExtraTopInsetForFullShadeTransition
@@ -1447,10 +1456,8 @@
         if (mAmbientState.isBouncerInTransit() && mQsExpansionFraction > 0f) {
             fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction);
         }
-        if (!SceneContainerFlag.isEnabled()) {
-            final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
-            mAmbientState.setStackY(stackY);
-        }
+        final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
+        mAmbientState.setStackY(stackY);
 
         if (mOnStackYChanged != null) {
             mOnStackYChanged.accept(listenerNeedsAnimation);
@@ -2708,6 +2715,7 @@
      * @param animate  whether to animate the change
      */
     public void updateTopPadding(float qsHeight, boolean animate) {
+        SceneContainerFlag.assertInLegacyMode();
         int topPadding = (int) qsHeight;
         int minStackHeight = getLayoutMinHeightInternal();
         if (topPadding + minStackHeight > getHeight()) {
@@ -2715,7 +2723,10 @@
         } else {
             mTopPaddingOverflow = 0;
         }
-        setTopPadding(topPadding, animate && !mKeyguardBypassEnabled);
+        if (mAmbientState.getTopPadding() != topPadding) {
+            mAmbientState.setTopPadding(topPadding);
+            onTopPaddingChanged(/* animate = */ animate && !mKeyguardBypassEnabled);
+        }
         setExpandedHeight(mExpandedHeight);
     }
 
@@ -4829,6 +4840,7 @@
     }
 
     public boolean isBelowLastNotification(float touchX, float touchY) {
+        SceneContainerFlag.assertInLegacyMode();
         int childCount = getChildCount();
         for (int i = childCount - 1; i >= 0; i--) {
             ExpandableView child = getChildAtIndex(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 55f0566..c25b30d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1235,6 +1235,7 @@
     }
 
     public boolean isBelowLastNotification(float x, float y) {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.isBelowLastNotification(x, y);
     }
 
@@ -1328,6 +1329,7 @@
     }
 
     public int getTopPadding() {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.getTopPadding();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 3dd265b..e3242d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2365,9 +2365,14 @@
                 // lock screen where users can use the UDFPS affordance to enter the device
                 mStatusBarKeyguardViewManager.reset(true);
             } else if (mState == StatusBarState.KEYGUARD
-                    && !mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()
-                    && mStatusBarKeyguardViewManager.isSecure()) {
-                if (!relockWithPowerButtonImmediately()) {
+                    && !mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()) {
+                boolean needsBouncer = mStatusBarKeyguardViewManager.isSecure();
+                if (relockWithPowerButtonImmediately()) {
+                    // Only request if SIM bouncer is needed
+                    needsBouncer = mStatusBarKeyguardViewManager.needsFullscreenBouncer();
+                }
+
+                if (needsBouncer) {
                     Log.d(TAG, "showBouncerOrLockScreenIfKeyguard, showingBouncer");
                     if (SceneContainerFlag.isEnabled()) {
                         mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 28ac2c0..055671c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -28,6 +28,7 @@
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -62,7 +63,9 @@
 /**
  * Collect information for the given [flow], calling [consumer] for each emitted event. Defaults to
  * [LifeCycle.State.CREATED] to better align with legacy ViewController usage of attaching listeners
- * during onViewAttached() and removing during onViewRemoved()
+ * during onViewAttached() and removing during onViewRemoved().
+ *
+ * @return a disposable handle in order to cancel the flow in the future.
  */
 @JvmOverloads
 fun <T> collectFlow(
@@ -71,8 +74,8 @@
     consumer: Consumer<T>,
     coroutineContext: CoroutineContext = EmptyCoroutineContext,
     state: Lifecycle.State = Lifecycle.State.CREATED,
-) {
-    view.repeatWhenAttached(coroutineContext) {
+): DisposableHandle {
+    return view.repeatWhenAttached(coroutineContext) {
         repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 50efc21..5f6ad92 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -60,7 +60,6 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.kotlin.JavaAdapter;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.desktopmode.DesktopMode;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.onehanded.OneHanded;
@@ -70,6 +69,7 @@
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.recents.RecentTasks;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.sysui.ShellInterface;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
index 5600b87..a18d272 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
@@ -711,6 +711,16 @@
     }
 
     @Test
+    public void testDestroy_cleansUpHandler() {
+        final TouchHandler touchHandler = createTouchHandler();
+
+        final Environment environment = new Environment(Stream.of(touchHandler)
+                .collect(Collectors.toCollection(HashSet::new)), mKosmos);
+        environment.destroyMonitor();
+        verify(touchHandler).onDestroy();
+    }
+
+    @Test
     public void testLastSessionPop_createsNewInputSession() {
         final TouchHandler touchHandler = createTouchHandler();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
index e60848b..6e9b24f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
@@ -123,19 +123,19 @@
                 FakeWidgetMetadata(
                     widgetId = 11,
                     componentName = "com.android.fakePackage1/fakeWidget1",
-                    rank = 3,
+                    rank = 0,
                     userSerialNumber = 0,
                 ),
                 FakeWidgetMetadata(
                     widgetId = 12,
                     componentName = "com.android.fakePackage2/fakeWidget2",
-                    rank = 2,
+                    rank = 1,
                     userSerialNumber = 0,
                 ),
                 FakeWidgetMetadata(
                     widgetId = 13,
                     componentName = "com.android.fakePackage3/fakeWidget3",
-                    rank = 1,
+                    rank = 2,
                     userSerialNumber = 10,
                 ),
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
index 983a435..edc8c83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
@@ -69,19 +69,19 @@
                 FakeWidgetMetadata(
                     widgetId = 11,
                     componentName = "com.android.fakePackage1/fakeWidget1",
-                    rank = 3,
+                    rank = 0,
                     userSerialNumber = 0,
                 ),
                 FakeWidgetMetadata(
                     widgetId = 12,
                     componentName = "com.android.fakePackage2/fakeWidget2",
-                    rank = 2,
+                    rank = 1,
                     userSerialNumber = 0,
                 ),
                 FakeWidgetMetadata(
                     widgetId = 13,
                     componentName = "com.android.fakePackage3/fakeWidget3",
-                    rank = 1,
+                    rank = 2,
                     userSerialNumber = 10,
                 ),
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
index eb0ab78..ad25502 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
@@ -72,6 +72,82 @@
         databaseV2.verifyWidgetsV2(fakeWidgetsV1.map { it.getV2() })
     }
 
+    @Test
+    fun migrate2To3_noGapBetweenRanks_ranksReversed() {
+        // Create a communal database in version 2
+        val databaseV2 = migrationTestHelper.createDatabase(DATABASE_NAME, version = 2)
+
+        // Populate some fake data
+        val fakeRanks =
+            listOf(
+                FakeCommunalItemRank(3),
+                FakeCommunalItemRank(2),
+                FakeCommunalItemRank(1),
+                FakeCommunalItemRank(0),
+            )
+        databaseV2.insertRanks(fakeRanks)
+
+        // Verify fake ranks populated
+        databaseV2.verifyRanksInOrder(fakeRanks)
+
+        // Run migration and get database V3
+        val databaseV3 =
+            migrationTestHelper.runMigrationsAndValidate(
+                name = DATABASE_NAME,
+                version = 3,
+                validateDroppedTables = false,
+                CommunalDatabase.MIGRATION_2_3,
+            )
+
+        // Verify ranks are reversed
+        databaseV3.verifyRanksInOrder(
+            listOf(
+                FakeCommunalItemRank(0),
+                FakeCommunalItemRank(1),
+                FakeCommunalItemRank(2),
+                FakeCommunalItemRank(3),
+            )
+        )
+    }
+
+    @Test
+    fun migrate2To3_withGapBetweenRanks_ranksReversed() {
+        // Create a communal database in version 2
+        val databaseV2 = migrationTestHelper.createDatabase(DATABASE_NAME, version = 2)
+
+        // Populate some fake data with gaps between ranks
+        val fakeRanks =
+            listOf(
+                FakeCommunalItemRank(9),
+                FakeCommunalItemRank(7),
+                FakeCommunalItemRank(2),
+                FakeCommunalItemRank(0),
+            )
+        databaseV2.insertRanks(fakeRanks)
+
+        // Verify fake ranks populated
+        databaseV2.verifyRanksInOrder(fakeRanks)
+
+        // Run migration and get database V3
+        val databaseV3 =
+            migrationTestHelper.runMigrationsAndValidate(
+                name = DATABASE_NAME,
+                version = 3,
+                validateDroppedTables = false,
+                CommunalDatabase.MIGRATION_2_3,
+            )
+
+        // Verify ranks are reversed
+        databaseV3.verifyRanksInOrder(
+            listOf(
+                FakeCommunalItemRank(0),
+                FakeCommunalItemRank(2),
+                FakeCommunalItemRank(7),
+                FakeCommunalItemRank(9),
+            )
+        )
+    }
+
     private fun SupportSQLiteDatabase.insertWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) {
         widgets.forEach { widget ->
             execSQL(
@@ -117,6 +193,25 @@
         assertThat(cursor.isAfterLast).isTrue()
     }
 
+    private fun SupportSQLiteDatabase.insertRanks(ranks: List<FakeCommunalItemRank>) {
+        ranks.forEach { rank ->
+            execSQL("INSERT INTO communal_item_rank_table(rank) VALUES(${rank.rank})")
+        }
+    }
+
+    private fun SupportSQLiteDatabase.verifyRanksInOrder(ranks: List<FakeCommunalItemRank>) {
+        val cursor = query("SELECT * FROM communal_item_rank_table ORDER BY uid")
+        assertThat(cursor.moveToFirst()).isTrue()
+
+        ranks.forEach { rank ->
+            assertThat(cursor.getInt(cursor.getColumnIndex("rank"))).isEqualTo(rank.rank)
+            cursor.moveToNext()
+        }
+
+        // Verify there is no more columns
+        assertThat(cursor.isAfterLast).isTrue()
+    }
+
     /**
      * Returns the expected data after migration from V1 to V2, which is simply that the new user
      * serial number field is now set to [CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED].
@@ -143,6 +238,10 @@
         val userSerialNumber: Int,
     )
 
+    private data class FakeCommunalItemRank(
+        val rank: Int,
+    )
+
     companion object {
         private const val DATABASE_NAME = "communal_db"
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
index d670508..d4d966a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
@@ -67,11 +67,11 @@
     @Test
     fun addWidget_readValueInDb() =
         testScope.runTest {
-            val (widgetId, provider, priority, userSerialNumber) = widgetInfo1
+            val (widgetId, provider, rank, userSerialNumber) = widgetInfo1
             communalWidgetDao.addWidget(
                 widgetId = widgetId,
                 provider = provider,
-                priority = priority,
+                rank = rank,
                 userSerialNumber = userSerialNumber,
             )
             val entry = communalWidgetDao.getWidgetByIdNow(id = 1)
@@ -81,11 +81,11 @@
     @Test
     fun deleteWidget_notInDb_returnsFalse() =
         testScope.runTest {
-            val (widgetId, provider, priority, userSerialNumber) = widgetInfo1
+            val (widgetId, provider, rank, userSerialNumber) = widgetInfo1
             communalWidgetDao.addWidget(
                 widgetId = widgetId,
                 provider = provider,
-                priority = priority,
+                rank = rank,
                 userSerialNumber = userSerialNumber,
             )
             assertThat(communalWidgetDao.deleteWidgetById(widgetId = 123)).isFalse()
@@ -97,11 +97,11 @@
             val widgetsToAdd = listOf(widgetInfo1, widgetInfo2)
             val widgets = collectLastValue(communalWidgetDao.getWidgets())
             widgetsToAdd.forEach {
-                val (widgetId, provider, priority, userSerialNumber) = it
+                val (widgetId, provider, rank, userSerialNumber) = it
                 communalWidgetDao.addWidget(
                     widgetId = widgetId,
                     provider = provider,
-                    priority = priority,
+                    rank = rank,
                     userSerialNumber = userSerialNumber
                 )
             }
@@ -115,17 +115,48 @@
         }
 
     @Test
+    fun addWidget_rankNotSpecified_widgetAddedAtTheEnd(): Unit =
+        testScope.runTest {
+            val widgets by collectLastValue(communalWidgetDao.getWidgets())
+
+            // Verify database is empty
+            assertThat(widgets).isEmpty()
+
+            // Add widgets one by one without specifying rank
+            val widgetsToAdd = listOf(widgetInfo1, widgetInfo2, widgetInfo3)
+            widgetsToAdd.forEach {
+                val (widgetId, provider, _, userSerialNumber) = it
+                communalWidgetDao.addWidget(
+                    widgetId = widgetId,
+                    provider = provider,
+                    userSerialNumber = userSerialNumber
+                )
+            }
+
+            // Verify new each widget is added at the end
+            assertThat(widgets)
+                .containsExactly(
+                    communalItemRankEntry1,
+                    communalWidgetItemEntry1,
+                    communalItemRankEntry2,
+                    communalWidgetItemEntry2,
+                    communalItemRankEntry3,
+                    communalWidgetItemEntry3,
+                )
+        }
+
+    @Test
     fun deleteWidget_emitsActiveWidgetsInDb() =
         testScope.runTest {
             val widgetsToAdd = listOf(widgetInfo1, widgetInfo2)
             val widgets = collectLastValue(communalWidgetDao.getWidgets())
 
             widgetsToAdd.forEach {
-                val (widgetId, provider, priority, userSerialNumber) = it
+                val (widgetId, provider, rank, userSerialNumber) = it
                 communalWidgetDao.addWidget(
                     widgetId = widgetId,
                     provider = provider,
-                    priority = priority,
+                    rank = rank,
                     userSerialNumber = userSerialNumber,
                 )
             }
@@ -148,32 +179,32 @@
             val widgets = collectLastValue(communalWidgetDao.getWidgets())
 
             widgetsToAdd.forEach {
-                val (widgetId, provider, priority, userSerialNumber) = it
+                val (widgetId, provider, rank, userSerialNumber) = it
                 communalWidgetDao.addWidget(
                     widgetId = widgetId,
                     provider = provider,
-                    priority = priority,
+                    rank = rank,
                     userSerialNumber = userSerialNumber,
                 )
             }
             assertThat(widgets())
                 .containsExactly(
-                    communalItemRankEntry2,
-                    communalWidgetItemEntry2,
                     communalItemRankEntry1,
                     communalWidgetItemEntry1,
+                    communalItemRankEntry2,
+                    communalWidgetItemEntry2,
                 )
                 .inOrder()
 
-            // swapped priorities
-            val widgetIdsToPriorityMap = mapOf(widgetInfo1.widgetId to 2, widgetInfo2.widgetId to 1)
-            communalWidgetDao.updateWidgetOrder(widgetIdsToPriorityMap)
+            // swapped ranks
+            val widgetIdsToRankMap = mapOf(widgetInfo1.widgetId to 1, widgetInfo2.widgetId to 0)
+            communalWidgetDao.updateWidgetOrder(widgetIdsToRankMap)
             assertThat(widgets())
                 .containsExactly(
-                    communalItemRankEntry1.copy(rank = 2),
+                    communalItemRankEntry2.copy(rank = 0),
+                    communalWidgetItemEntry2,
+                    communalItemRankEntry1.copy(rank = 1),
                     communalWidgetItemEntry1,
-                    communalItemRankEntry2.copy(rank = 1),
-                    communalWidgetItemEntry2
                 )
                 .inOrder()
         }
@@ -181,53 +212,56 @@
     @Test
     fun addNewWidgetWithReorder_emitsWidgetsInNewOrder() =
         testScope.runTest {
-            val existingWidgets = listOf(widgetInfo1, widgetInfo2)
+            val existingWidgets = listOf(widgetInfo1, widgetInfo2, widgetInfo3)
             val widgets = collectLastValue(communalWidgetDao.getWidgets())
 
             existingWidgets.forEach {
-                val (widgetId, provider, priority, userSerialNumber) = it
+                val (widgetId, provider, rank, userSerialNumber) = it
                 communalWidgetDao.addWidget(
                     widgetId = widgetId,
                     provider = provider,
-                    priority = priority,
+                    rank = rank,
                     userSerialNumber = userSerialNumber,
                 )
             }
             assertThat(widgets())
                 .containsExactly(
-                    communalItemRankEntry2,
-                    communalWidgetItemEntry2,
                     communalItemRankEntry1,
                     communalWidgetItemEntry1,
+                    communalItemRankEntry2,
+                    communalWidgetItemEntry2,
+                    communalItemRankEntry3,
+                    communalWidgetItemEntry3,
                 )
                 .inOrder()
 
-            // map with no item in the middle at index 1
-            val widgetIdsToIndexMap = mapOf(widgetInfo1.widgetId to 1, widgetInfo2.widgetId to 3)
-            communalWidgetDao.updateWidgetOrder(widgetIdsToIndexMap)
-            assertThat(widgets())
-                .containsExactly(
-                    communalItemRankEntry2.copy(rank = 3),
-                    communalWidgetItemEntry2,
-                    communalItemRankEntry1.copy(rank = 1),
-                    communalWidgetItemEntry1,
-                )
-                .inOrder()
-            // add the new middle item that we left space for.
+            // add a new widget at rank 1.
             communalWidgetDao.addWidget(
-                widgetId = widgetInfo3.widgetId,
-                provider = widgetInfo3.provider,
-                priority = 2,
-                userSerialNumber = widgetInfo3.userSerialNumber,
+                widgetId = 4,
+                provider = ComponentName("pk_name", "cls_name_4"),
+                rank = 1,
+                userSerialNumber = 0,
             )
+
+            val newRankEntry = CommunalItemRank(uid = 4L, rank = 1)
+            val newWidgetEntry =
+                CommunalWidgetItem(
+                    uid = 4L,
+                    widgetId = 4,
+                    componentName = "pk_name/cls_name_4",
+                    itemId = 4L,
+                    userSerialNumber = 0,
+                )
             assertThat(widgets())
                 .containsExactly(
-                    communalItemRankEntry2.copy(rank = 3),
-                    communalWidgetItemEntry2,
-                    communalItemRankEntry3.copy(rank = 2),
-                    communalWidgetItemEntry3,
-                    communalItemRankEntry1.copy(rank = 1),
+                    communalItemRankEntry1.copy(rank = 0),
                     communalWidgetItemEntry1,
+                    newRankEntry,
+                    newWidgetEntry,
+                    communalItemRankEntry2.copy(rank = 2),
+                    communalWidgetItemEntry2,
+                    communalItemRankEntry3.copy(rank = 3),
+                    communalWidgetItemEntry3,
                 )
                 .inOrder()
         }
@@ -261,11 +295,11 @@
             assertThat(widgets).containsExactlyEntriesIn(expected)
         }
 
-    private fun addWidget(metadata: FakeWidgetMetadata, priority: Int? = null) {
+    private fun addWidget(metadata: FakeWidgetMetadata, rank: Int? = null) {
         communalWidgetDao.addWidget(
             widgetId = metadata.widgetId,
             provider = metadata.provider,
-            priority = priority ?: metadata.priority,
+            rank = rank ?: metadata.rank,
             userSerialNumber = metadata.userSerialNumber,
         )
     }
@@ -273,7 +307,7 @@
     data class FakeWidgetMetadata(
         val widgetId: Int,
         val provider: ComponentName,
-        val priority: Int,
+        val rank: Int,
         val userSerialNumber: Int,
     )
 
@@ -282,26 +316,26 @@
             FakeWidgetMetadata(
                 widgetId = 1,
                 provider = ComponentName("pk_name", "cls_name_1"),
-                priority = 1,
+                rank = 0,
                 userSerialNumber = 0,
             )
         val widgetInfo2 =
             FakeWidgetMetadata(
                 widgetId = 2,
                 provider = ComponentName("pk_name", "cls_name_2"),
-                priority = 2,
+                rank = 1,
                 userSerialNumber = 0,
             )
         val widgetInfo3 =
             FakeWidgetMetadata(
                 widgetId = 3,
                 provider = ComponentName("pk_name", "cls_name_3"),
-                priority = 3,
+                rank = 2,
                 userSerialNumber = 10,
             )
-        val communalItemRankEntry1 = CommunalItemRank(uid = 1L, rank = widgetInfo1.priority)
-        val communalItemRankEntry2 = CommunalItemRank(uid = 2L, rank = widgetInfo2.priority)
-        val communalItemRankEntry3 = CommunalItemRank(uid = 3L, rank = widgetInfo3.priority)
+        val communalItemRankEntry1 = CommunalItemRank(uid = 1L, rank = widgetInfo1.rank)
+        val communalItemRankEntry2 = CommunalItemRank(uid = 2L, rank = widgetInfo2.rank)
+        val communalItemRankEntry3 = CommunalItemRank(uid = 3L, rank = widgetInfo3.rank)
         val communalWidgetItemEntry1 =
             CommunalWidgetItem(
                 uid = 1L,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index 0b7a3ed..6aecc0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -22,6 +22,7 @@
 import android.os.PowerManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.keyguard.keyguardUpdateMonitor
 import com.android.keyguard.trustManager
 import com.android.systemui.SysuiTestCase
@@ -37,6 +38,7 @@
 import com.android.systemui.deviceentry.data.repository.fakeFaceWakeUpTriggersConfig
 import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
 import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
@@ -52,12 +54,15 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.eq
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -113,6 +118,7 @@
                 powerInteractor,
                 fakeBiometricSettingsRepository,
                 trustManager,
+                { kosmos.sceneInteractor },
                 deviceEntryFaceAuthStatusInteractor,
             )
     }
@@ -279,6 +285,22 @@
         }
 
     @Test
+    @EnableSceneContainer
+    fun withSceneContainerEnabled_faceAuthIsRequestedWhenPrimaryBouncerIsVisible() =
+        testScope.runTest {
+            underTest.start()
+
+            kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "for-test")
+            kosmos.sceneInteractor.setTransitionState(
+                MutableStateFlow(ObservableTransitionState.Idle(Scenes.Bouncer))
+            )
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN, false))
+        }
+
+    @Test
     fun faceAuthIsRequestedWhenAlternateBouncerIsVisible() =
         testScope.runTest {
             underTest.start()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index fc7f693..d13419e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
 import com.android.systemui.dock.DockManagerFake
 import com.android.systemui.doze.util.BurnInHelperWrapper
 import com.android.systemui.flags.FakeFeatureFlags
@@ -152,9 +153,7 @@
         dockManager = DockManagerFake()
         biometricSettingsRepository = FakeBiometricSettingsRepository()
         val featureFlags =
-            FakeFeatureFlags().apply {
-                set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
-            }
+            FakeFeatureFlags().apply { set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false) }
 
         val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
         val keyguardInteractor = withDeps.keyguardInteractor
@@ -223,6 +222,7 @@
                 broadcastDispatcher = broadcastDispatcher,
                 accessibilityManager = accessibilityManager,
                 pulsingGestureListener = kosmos.pulsingGestureListener,
+                faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor,
             )
         underTest =
             KeyguardBottomAreaViewModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
index bdee936..fd53b5ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
@@ -38,19 +38,28 @@
 import android.media.session.PlaybackState
 import android.net.Uri
 import android.os.Bundle
+import android.platform.test.flag.junit.FlagsParameterization
 import android.provider.Settings
 import android.service.notification.StatusBarNotification
 import android.testing.TestableLooper.RunWithLooper
 import androidx.media.utils.MediaConstants
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.dx.mockito.inline.extended.ExtendedMockito
 import com.android.internal.logging.InstanceId
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags
 import com.android.systemui.InstanceIdSequenceFake
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.Flags.MEDIA_REMOTE_RESUME
+import com.android.systemui.flags.Flags.MEDIA_RESUME_PROGRESS
+import com.android.systemui.flags.Flags.MEDIA_RETAIN_RECOMMENDATIONS
+import com.android.systemui.flags.Flags.MEDIA_RETAIN_SESSIONS
+import com.android.systemui.flags.Flags.MEDIA_SESSION_ACTIONS
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.controls.domain.resume.MediaResumeListener
 import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
 import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
@@ -58,16 +67,21 @@
 import com.android.systemui.media.controls.shared.model.MediaData
 import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
 import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
-import com.android.systemui.media.controls.util.MediaControllerFactory
-import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
-import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
+import com.android.systemui.media.controls.util.mediaFlags
+import com.android.systemui.plugins.activityStarter
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.SbnBuilder
+import com.android.systemui.testKosmos
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
@@ -91,6 +105,8 @@
 import org.mockito.kotlin.capture
 import org.mockito.kotlin.eq
 import org.mockito.quality.Strictness
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 private const val KEY = "KEY"
 private const val KEY_2 = "KEY_2"
@@ -111,13 +127,13 @@
     return Mockito.anyObject<T>()
 }
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidJUnit4::class)
-class LegacyMediaDataManagerImplTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() {
 
     @JvmField @Rule val mockito = MockitoJUnit.rule()
-    @Mock lateinit var mediaControllerFactory: MediaControllerFactory
     @Mock lateinit var controller: MediaController
     @Mock lateinit var transportControls: MediaController.TransportControls
     @Mock lateinit var playbackInfo: MediaController.PlaybackInfo
@@ -136,7 +152,6 @@
     @Mock lateinit var mediaDataFilter: LegacyMediaDataFilterImpl
     @Mock lateinit var listener: MediaDataManager.Listener
     @Mock lateinit var pendingIntent: PendingIntent
-    @Mock lateinit var activityStarter: ActivityStarter
     @Mock lateinit var smartspaceManager: SmartspaceManager
     @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
@@ -144,7 +159,6 @@
     @Mock private lateinit var mediaRecommendationItem: SmartspaceAction
     lateinit var validRecommendationList: List<SmartspaceAction>
     @Mock private lateinit var mediaSmartspaceBaseAction: SmartspaceAction
-    @Mock private lateinit var mediaFlags: MediaFlags
     @Mock private lateinit var logger: MediaUiEventLogger
     lateinit var mediaDataManager: LegacyMediaDataManagerImpl
     lateinit var mediaNotification: StatusBarNotification
@@ -159,6 +173,26 @@
     @Mock private lateinit var ugm: IUriGrantsManager
     @Mock private lateinit var imageSource: ImageDecoder.Source
 
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.progressionOf(
+                Flags.FLAG_MEDIA_LOAD_METADATA_VIA_MEDIA_DATA_LOADER
+            )
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
+    private val kosmos = testKosmos()
+    private val testDispatcher = kosmos.testDispatcher
+    private val testScope = kosmos.testScope
+    private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic
+    private val activityStarter = kosmos.activityStarter
+    private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
     private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
 
     private val originalSmartspaceSetting =
@@ -188,12 +222,16 @@
             Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
             1
         )
+
         mediaDataManager =
             LegacyMediaDataManagerImpl(
                 context = context,
                 backgroundExecutor = backgroundExecutor,
+                backgroundDispatcher = testDispatcher,
                 uiExecutor = uiExecutor,
                 foregroundExecutor = foregroundExecutor,
+                mainDispatcher = testDispatcher,
+                applicationScope = testScope,
                 mediaControllerFactory = mediaControllerFactory,
                 broadcastDispatcher = broadcastDispatcher,
                 dumpManager = dumpManager,
@@ -209,10 +247,11 @@
                 useQsMediaPlayer = true,
                 systemClock = clock,
                 tunerService = tunerService,
-                mediaFlags = mediaFlags,
+                mediaFlags = kosmos.mediaFlags,
                 logger = logger,
                 smartspaceManager = smartspaceManager,
                 keyguardUpdateMonitor = keyguardUpdateMonitor,
+                mediaDataLoader = { kosmos.mediaDataLoader },
             )
         verify(tunerService)
             .addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -248,7 +287,7 @@
                 putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
             }
         verify(smartspaceManager).createSmartspaceSession(capture(smartSpaceConfigBuilderCaptor))
-        whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
+        mediaControllerFactory.setControllerForToken(session.sessionToken, controller)
         whenever(controller.transportControls).thenReturn(transportControls)
         whenever(controller.playbackInfo).thenReturn(playbackInfo)
         whenever(controller.metadata).thenReturn(metadataBuilder.build())
@@ -278,10 +317,11 @@
         whenever(mediaSmartspaceTarget.iconGrid).thenReturn(validRecommendationList)
         whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(SMARTSPACE_CREATION_TIME)
         whenever(mediaSmartspaceTarget.expiryTimeMillis).thenReturn(SMARTSPACE_EXPIRY_TIME)
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false)
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(false)
-        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
-        whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(false)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, false)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, false)
+        fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, false)
+        fakeFeatureFlags.set(MEDIA_REMOTE_RESUME, false)
+        fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, false)
         whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
         whenever(keyguardUpdateMonitor.isUserInLockdown(any())).thenReturn(false)
     }
@@ -310,49 +350,51 @@
     }
 
     @Test
-    fun testsetInactive_resume_dismissesMedia() {
-        // WHEN resume controls are present, and time out
-        val desc =
-            MediaDescription.Builder().run {
-                setTitle(SESSION_TITLE)
-                build()
-            }
-        mediaDataManager.addResumptionControls(
-            USER_ID,
-            desc,
-            Runnable {},
-            session.sessionToken,
-            APP_NAME,
-            pendingIntent,
-            PACKAGE_NAME
-        )
-
-        backgroundExecutor.runAllReady()
-        foregroundExecutor.runAllReady()
-        verify(listener)
-            .onMediaDataLoaded(
-                eq(PACKAGE_NAME),
-                eq(null),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
+    fun testsetInactive_resume_dismissesMedia() =
+        testScope.runTest {
+            // WHEN resume controls are present, and time out
+            val desc =
+                MediaDescription.Builder().run {
+                    setTitle(SESSION_TITLE)
+                    build()
+                }
+            mediaDataManager.addResumptionControls(
+                USER_ID,
+                desc,
+                Runnable {},
+                session.sessionToken,
+                APP_NAME,
+                pendingIntent,
+                PACKAGE_NAME
             )
 
-        mediaDataManager.setInactive(PACKAGE_NAME, timedOut = true)
-        verify(logger)
-            .logMediaTimeout(anyInt(), eq(PACKAGE_NAME), eq(mediaDataCaptor.value.instanceId))
+            runCurrent()
+            backgroundExecutor.runAllReady()
+            foregroundExecutor.runAllReady()
+            verify(listener)
+                .onMediaDataLoaded(
+                    eq(PACKAGE_NAME),
+                    eq(null),
+                    capture(mediaDataCaptor),
+                    eq(true),
+                    eq(0),
+                    eq(false)
+                )
 
-        // THEN it is removed and listeners are informed
-        foregroundExecutor.advanceClockToLast()
-        foregroundExecutor.runAllReady()
-        verify(listener).onMediaDataRemoved(PACKAGE_NAME, false)
-    }
+            mediaDataManager.setInactive(PACKAGE_NAME, timedOut = true)
+            verify(logger)
+                .logMediaTimeout(anyInt(), eq(PACKAGE_NAME), eq(mediaDataCaptor.value.instanceId))
+
+            // THEN it is removed and listeners are informed
+            foregroundExecutor.advanceClockToLast()
+            foregroundExecutor.runAllReady()
+            verify(listener).onMediaDataRemoved(PACKAGE_NAME, false)
+        }
 
     @Test
     fun testLoadsMetadataOnBackground() {
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
-        assertThat(backgroundExecutor.numPending()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 0, background = 1)
     }
 
     @Test
@@ -370,8 +412,7 @@
         mediaDataManager.addListener(listener)
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
 
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -389,8 +430,7 @@
         mediaDataManager.addListener(listener)
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
 
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -417,11 +457,9 @@
 
     @Test
     fun testOnMetaDataLoaded_conservesActiveFlag() {
-        whenever(mediaControllerFactory.create(anyObject())).thenReturn(controller)
         mediaDataManager.addListener(listener)
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -465,8 +503,7 @@
             }
 
         mediaDataManager.onNotificationAdded(KEY, notif)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -552,8 +589,7 @@
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
 
         // Then a media control is created with a placeholder title string
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -583,8 +619,7 @@
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
 
         // Then a media control is created with a placeholder title string
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -625,8 +660,7 @@
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
 
         // Then the media control is added using the notification's title
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -734,8 +768,7 @@
         // GIVEN that the manager has two notifications with resume actions
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         mediaDataManager.onNotificationAdded(KEY_2, mediaNotification)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(2)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(2)
+        testScope.assertRunAllReady(foreground = 2, background = 2)
 
         verify(listener)
             .onMediaDataLoaded(
@@ -822,7 +855,7 @@
     @Test
     fun testOnNotificationRemoved_withResumption_isRemoteAndRemoteAllowed() {
         // With the flag enabled to allow remote media to resume
-        whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_REMOTE_RESUME, true)
 
         // GIVEN that the manager has a notification with a resume action, but is not local
         whenever(controller.metadata).thenReturn(metadataBuilder.build())
@@ -853,7 +886,7 @@
     @Test
     fun testOnNotificationRemoved_withResumption_isRcnAndRemoteAllowed() {
         // With the flag enabled to allow remote media to resume
-        whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_REMOTE_RESUME, true)
 
         // GIVEN that the manager has a remote cast notification
         addNotificationAndLoad(remoteCastNotification)
@@ -972,7 +1005,7 @@
 
     @Test
     fun testAddResumptionControls_hasPartialProgress() {
-        whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
 
         // WHEN resumption controls are added with partial progress
         val progress = 0.5
@@ -999,7 +1032,7 @@
 
     @Test
     fun testAddResumptionControls_hasNotPlayedProgress() {
-        whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
 
         // WHEN resumption controls are added that have not been played
         val extras =
@@ -1024,7 +1057,7 @@
 
     @Test
     fun testAddResumptionControls_hasFullProgress() {
-        whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
 
         // WHEN resumption controls are added with progress info
         val extras =
@@ -1050,7 +1083,7 @@
 
     @Test
     fun testAddResumptionControls_hasNoExtras() {
-        whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
 
         // WHEN resumption controls are added that do not have any extras
         val desc =
@@ -1068,7 +1101,7 @@
 
     @Test
     fun testAddResumptionControls_hasEmptyTitle() {
-        whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
 
         // WHEN resumption controls are added that have empty title
         val desc =
@@ -1087,8 +1120,7 @@
         )
 
         // Resumption controls are not added.
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(0)
+        testScope.assertRunAllReady(foreground = 0, background = 1)
         verify(listener, never())
             .onMediaDataLoaded(
                 eq(PACKAGE_NAME),
@@ -1102,7 +1134,7 @@
 
     @Test
     fun testAddResumptionControls_hasBlankTitle() {
-        whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
 
         // WHEN resumption controls are added that have a blank title
         val desc =
@@ -1121,8 +1153,7 @@
         )
 
         // Resumption controls are not added.
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(0)
+        testScope.assertRunAllReady(foreground = 0, background = 1)
         verify(listener, never())
             .onMediaDataLoaded(
                 eq(PACKAGE_NAME),
@@ -1189,8 +1220,7 @@
         mediaDataManager.onNotificationAdded(KEY, notif)
 
         // THEN it still loads
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -1307,7 +1337,7 @@
 
     @Test
     fun testOnSmartspaceMediaDataLoaded_persistentEnabled_headphoneTrigger_isActive() {
-        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
         smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
         val instanceId = instanceIdSequence.lastInstanceId
 
@@ -1333,7 +1363,7 @@
 
     @Test
     fun testOnSmartspaceMediaDataLoaded_persistentEnabled_periodicTrigger_notActive() {
-        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
         val extras =
             Bundle().apply {
                 putString("package_name", PACKAGE_NAME)
@@ -1367,7 +1397,7 @@
 
     @Test
     fun testOnSmartspaceMediaDataLoaded_persistentEnabled_noTargets_inactive() {
-        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
 
         smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
         val instanceId = instanceIdSequence.lastInstanceId
@@ -1399,7 +1429,7 @@
 
     @Test
     fun testSetRecommendationInactive_notifiesListeners() {
-        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
 
         smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
         val instanceId = instanceIdSequence.lastInstanceId
@@ -1479,8 +1509,7 @@
     fun testOnMediaDataTimedOut_updatesLastActiveTime() {
         // GIVEN that the manager has a notification
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
 
         // WHEN the notification times out
         clock.advanceTime(100)
@@ -1588,8 +1617,7 @@
 
         // WHEN the notification is loaded
         mediaDataManager.onNotificationAdded(KEY, notif)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
 
         // THEN only the first MAX_COMPACT_ACTIONS are actually set
         verify(listener)
@@ -1624,8 +1652,7 @@
 
         // WHEN the notification is loaded
         mediaDataManager.onNotificationAdded(KEY, notif)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
 
         // THEN only the first MAX_NOTIFICATION_ACTIONS are actually included
         verify(listener)
@@ -1644,7 +1671,7 @@
     @Test
     fun testPlaybackActions_noState_usesNotification() {
         val desc = "Notification Action"
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         whenever(controller.playbackState).thenReturn(null)
 
         val notifWithAction =
@@ -1659,8 +1686,7 @@
             }
         mediaDataManager.onNotificationAdded(KEY, notifWithAction)
 
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -1679,7 +1705,7 @@
     @Test
     fun testPlaybackActions_hasPrevNext() {
         val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         val stateActions =
             PlaybackState.ACTION_PLAY or
                 PlaybackState.ACTION_SKIP_TO_PREVIOUS or
@@ -1723,7 +1749,7 @@
     @Test
     fun testPlaybackActions_noPrevNext_usesCustom() {
         val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4", "custom 5")
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         val stateActions = PlaybackState.ACTION_PLAY
         val stateBuilder = PlaybackState.Builder().setActions(stateActions)
         customDesc.forEach {
@@ -1755,7 +1781,7 @@
 
     @Test
     fun testPlaybackActions_connecting() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         val stateActions = PlaybackState.ACTION_PLAY
         val stateBuilder =
             PlaybackState.Builder()
@@ -1776,7 +1802,7 @@
     @Test
     fun testPlaybackActions_reservedSpace() {
         val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         val stateActions = PlaybackState.ACTION_PLAY
         val stateBuilder = PlaybackState.Builder().setActions(stateActions)
         customDesc.forEach {
@@ -1814,7 +1840,7 @@
 
     @Test
     fun testPlaybackActions_playPause_hasButton() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         val stateActions = PlaybackState.ACTION_PLAY_PAUSE
         val stateBuilder = PlaybackState.Builder().setActions(stateActions)
         whenever(controller.playbackState).thenReturn(stateBuilder.build())
@@ -1851,8 +1877,7 @@
 
         // update to remote cast
         mediaDataManager.onNotificationAdded(KEY, remoteCastNotification)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(logger)
             .logPlaybackLocationChange(
                 anyInt(),
@@ -1914,7 +1939,7 @@
 
     @Test
     fun testPlaybackState_PauseWhenFlagTrue_keyExists_callsListener() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         val state = PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 1f).build()
         whenever(controller.playbackState).thenReturn(state)
 
@@ -1935,46 +1960,48 @@
     }
 
     @Test
-    fun testPlaybackState_PauseStateAfterAddingResumption_keyExists_callsListener() {
-        val desc =
-            MediaDescription.Builder().run {
-                setTitle(SESSION_TITLE)
-                build()
-            }
-        val state =
-            PlaybackState.Builder()
-                .setState(PlaybackState.STATE_PAUSED, 0L, 1f)
-                .setActions(PlaybackState.ACTION_PLAY_PAUSE)
-                .build()
+    fun testPlaybackState_PauseStateAfterAddingResumption_keyExists_callsListener() =
+        testScope.runTest {
+            val desc =
+                MediaDescription.Builder().run {
+                    setTitle(SESSION_TITLE)
+                    build()
+                }
+            val state =
+                PlaybackState.Builder()
+                    .setState(PlaybackState.STATE_PAUSED, 0L, 1f)
+                    .setActions(PlaybackState.ACTION_PLAY_PAUSE)
+                    .build()
 
-        // Add resumption controls in order to have semantic actions.
-        // To make sure that they are not null after changing state.
-        mediaDataManager.addResumptionControls(
-            USER_ID,
-            desc,
-            Runnable {},
-            session.sessionToken,
-            APP_NAME,
-            pendingIntent,
-            PACKAGE_NAME
-        )
-        backgroundExecutor.runAllReady()
-        foregroundExecutor.runAllReady()
-
-        stateCallbackCaptor.value.invoke(PACKAGE_NAME, state)
-
-        verify(listener)
-            .onMediaDataLoaded(
-                eq(PACKAGE_NAME),
-                eq(PACKAGE_NAME),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
+            // Add resumption controls in order to have semantic actions.
+            // To make sure that they are not null after changing state.
+            mediaDataManager.addResumptionControls(
+                USER_ID,
+                desc,
+                Runnable {},
+                session.sessionToken,
+                APP_NAME,
+                pendingIntent,
+                PACKAGE_NAME
             )
-        assertThat(mediaDataCaptor.value.isPlaying).isFalse()
-        assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
-    }
+            runCurrent()
+            backgroundExecutor.runAllReady()
+            foregroundExecutor.runAllReady()
+
+            stateCallbackCaptor.value.invoke(PACKAGE_NAME, state)
+
+            verify(listener)
+                .onMediaDataLoaded(
+                    eq(PACKAGE_NAME),
+                    eq(PACKAGE_NAME),
+                    capture(mediaDataCaptor),
+                    eq(true),
+                    eq(0),
+                    eq(false)
+                )
+            assertThat(mediaDataCaptor.value.isPlaying).isFalse()
+            assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
+        }
 
     @Test
     fun testPlaybackStateNull_Pause_keyExists_callsListener() {
@@ -2036,7 +2063,7 @@
 
     @Test
     fun testRetain_notifPlayer_notifRemoved_setToResume() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
 
         // When a media control based on notification is added, times out, and then removed
         addNotificationAndLoad()
@@ -2066,7 +2093,7 @@
 
     @Test
     fun testRetain_notifPlayer_sessionDestroyed_doesNotChange() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
 
         // When a media control based on notification is added and times out
         addNotificationAndLoad()
@@ -2084,7 +2111,7 @@
 
     @Test
     fun testRetain_notifPlayer_removeWhileActive_fullyRemoved() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
 
         // When a media control based on notification is added and then removed, without timing out
         addNotificationAndLoad()
@@ -2101,7 +2128,7 @@
 
     @Test
     fun testRetain_canResume_removeWhileActive_setToResume() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
 
         // When a media control that supports resumption is added
         addNotificationAndLoad()
@@ -2133,8 +2160,8 @@
 
     @Test
     fun testRetain_sessionPlayer_notifRemoved_doesNotChange() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         addPlaybackStateAction()
 
         // When a media control with PlaybackState actions is added, times out,
@@ -2153,8 +2180,8 @@
 
     @Test
     fun testRetain_sessionPlayer_sessionDestroyed_setToResume() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         addPlaybackStateAction()
 
         // When a media control with PlaybackState actions is added, times out,
@@ -2187,8 +2214,8 @@
 
     @Test
     fun testRetain_sessionPlayer_destroyedWhileActive_noResume_fullyRemoved() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         addPlaybackStateAction()
 
         // When a media control using session actions is added, and then the session is destroyed
@@ -2207,8 +2234,8 @@
 
     @Test
     fun testRetain_sessionPlayer_canResume_destroyedWhileActive_setToResume() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         addPlaybackStateAction()
 
         // When a media control using session actions and that does allow resumption is added,
@@ -2241,7 +2268,7 @@
 
     @Test
     fun testSessionPlayer_sessionDestroyed_noResume_fullyRemoved() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         addPlaybackStateAction()
 
         // When a media control with PlaybackState actions is added, times out,
@@ -2268,7 +2295,7 @@
 
     @Test
     fun testSessionPlayer_destroyedWhileActive_noResume_fullyRemoved() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         addPlaybackStateAction()
 
         // When a media control using session actions is added, and then the session is destroyed
@@ -2287,7 +2314,7 @@
 
     @Test
     fun testSessionPlayer_canResume_destroyedWhileActive_setToResume() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
         addPlaybackStateAction()
 
         // When a media control using session actions and that does allow resumption is added,
@@ -2320,8 +2347,8 @@
 
     @Test
     fun testSessionDestroyed_noNotificationKey_stillRemoved() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+        fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
 
         // When a notiifcation is added and then removed before it is fully processed
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
@@ -2392,6 +2419,23 @@
         assertThat(mediaDataCaptor.value.artwork).isNull()
     }
 
+    private fun TestScope.assertRunAllReady(foreground: Int = 0, background: Int = 0) {
+        runCurrent()
+        if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+            // It doesn't make much sense to count tasks when we use coroutines in loader
+            // so this check is skipped in that scenario.
+            backgroundExecutor.runAllReady()
+            foregroundExecutor.runAllReady()
+        } else {
+            if (background > 0) {
+                assertThat(backgroundExecutor.runAllReady()).isEqualTo(background)
+            }
+            if (foreground > 0) {
+                assertThat(foregroundExecutor.runAllReady()).isEqualTo(foreground)
+            }
+        }
+    }
+
     /** Helper function to add a basic media notification and capture the resulting MediaData */
     private fun addNotificationAndLoad() {
         addNotificationAndLoad(mediaNotification)
@@ -2400,8 +2444,7 @@
     /** Helper function to add the given notification and capture the resulting MediaData */
     private fun addNotificationAndLoad(sbn: StatusBarNotification) {
         mediaDataManager.onNotificationAdded(KEY, sbn)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -2435,8 +2478,8 @@
             pendingIntent,
             packageName
         )
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+
+        testScope.assertRunAllReady(foreground = 1, background = 1)
 
         verify(listener)
             .onMediaDataLoaded(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index f531a3f..3e3aa4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -16,9 +16,9 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.wm.shell.recents.RecentTasks
+import com.android.wm.shell.shared.GroupedRecentTaskInfo
+import com.android.wm.shell.shared.split.SplitBounds
 import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50
-import com.android.wm.shell.util.GroupedRecentTaskInfo
-import com.android.wm.shell.util.SplitBounds
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
 import java.util.function.Consumer
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
index 8fbd3c8..69b7b2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
@@ -29,8 +29,8 @@
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
 import com.android.systemui.util.mockito.mock
+import com.android.wm.shell.shared.split.SplitBounds
 import com.android.wm.shell.splitscreen.SplitScreen
-import com.android.wm.shell.util.SplitBounds
 import com.google.common.truth.Expect
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index ad6aca1..3c583f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
 import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
 import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
@@ -45,8 +46,8 @@
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations.initMocks
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -66,6 +67,7 @@
         SensitiveNotificationProtectionController
     @Mock private lateinit var stackController: NotifStackController
     @Mock private lateinit var section: NotifSection
+    @Mock private lateinit var row: ExpandableNotificationRow
 
     @Before
     fun setUp() {
@@ -74,6 +76,8 @@
         whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(false)
 
         entry = NotificationEntryBuilder().setSection(section).build()
+        entry.row = row
+        entry.setSensitive(false, false)
         coordinator =
             StackCoordinator(
                 groupExpansionManagerImpl,
@@ -189,4 +193,17 @@
             .setNotifStats(NotifStats(1, false, false, true, false))
         verifyZeroInteractions(stackController)
     }
+
+    @Test
+    @EnableFlags(
+        FooterViewRefactor.FLAG_NAME
+    )
+    fun testSetNotificationStats_footerFlagOn_nonClearableRedacted() {
+        entry.setSensitive(true, true)
+        whenever(section.bucket).thenReturn(BUCKET_ALERTING)
+        afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+        verify(activeNotificationsInteractor)
+            .setNotifStats(NotifStats(1, hasNonClearableAlertingNotifs = true, false, false, false))
+        verifyZeroInteractions(stackController)
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl b/packages/SystemUI/tests/utils/src/android/app/StatusBarManagerKosmos.kt
similarity index 76%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
copy to packages/SystemUI/tests/utils/src/android/app/StatusBarManagerKosmos.kt
index c968e80..6251ae9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
+++ b/packages/SystemUI/tests/utils/src/android/app/StatusBarManagerKosmos.kt
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.desktopmode;
+package android.app
 
-parcelable DesktopModeTransitionSource;
\ No newline at end of file
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.statusBarManager by Kosmos.Fixture { mock<StatusBarManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index c00454f..5d7e7c7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -30,7 +30,7 @@
     override fun addWidget(
         provider: ComponentName,
         user: UserHandle,
-        priority: Int,
+        rank: Int?,
         configurator: WidgetConfigurator?
     ) {
         coroutineScope.launch {
@@ -38,7 +38,7 @@
             val providerInfo = AppWidgetProviderInfo().apply { this.provider = provider }
             val configured = configurator?.configureWidget(id) ?: true
             if (configured) {
-                onConfigured(id, providerInfo, priority)
+                onConfigured(id, providerInfo, rank ?: -1)
             }
         }
     }
@@ -46,14 +46,14 @@
     fun addWidget(
         appWidgetId: Int,
         componentName: String = "pkg/cls",
-        priority: Int = 0,
+        rank: Int = 0,
         category: Int = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
         userId: Int = 0,
     ) {
         fakeDatabase[appWidgetId] =
             CommunalWidgetContentModel.Available(
                 appWidgetId = appWidgetId,
-                priority = priority,
+                rank = rank,
                 providerInfo =
                     AppWidgetProviderInfo().apply {
                         provider = ComponentName.unflattenFromString(componentName)!!
@@ -73,14 +73,14 @@
     fun addPendingWidget(
         appWidgetId: Int,
         componentName: String = "pkg/cls",
-        priority: Int = 0,
+        rank: Int = 0,
         icon: Bitmap? = null,
         userId: Int = 0,
     ) {
         fakeDatabase[appWidgetId] =
             CommunalWidgetContentModel.Pending(
                 appWidgetId = appWidgetId,
-                priority = priority,
+                rank = rank,
                 componentName = ComponentName.unflattenFromString(componentName)!!,
                 icon = icon,
                 user = UserHandle(userId),
@@ -97,8 +97,8 @@
 
     override fun abortRestoreWidgets() {}
 
-    private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, priority: Int) {
+    private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, rank: Int) {
         _communalWidgets.value +=
-            listOf(CommunalWidgetContentModel.Available(id, providerInfo, priority))
+            listOf(CommunalWidgetContentModel.Available(id, providerInfo, rank))
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
index b9be04d..3dfe0ee 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.user.data.repository.userRepository
 import com.android.systemui.util.mockito.mock
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -57,6 +58,7 @@
             powerInteractor = powerInteractor,
             biometricSettingsRepository = biometricSettingsRepository,
             trustManager = trustManager,
+            sceneInteractor = { sceneInteractor },
             deviceEntryFaceAuthStatusInteractor = deviceEntryFaceAuthStatusInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
index 73799b6..769612c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
@@ -20,6 +20,7 @@
 import android.view.accessibility.accessibilityManagerWrapper
 import com.android.internal.logging.uiEventLogger
 import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
 import com.android.systemui.flags.featureFlagsClassic
 import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.kosmos.Kosmos
@@ -38,5 +39,6 @@
             broadcastDispatcher = broadcastDispatcher,
             accessibilityManager = accessibilityManagerWrapper,
             pulsingGestureListener = pulsingGestureListener,
+            faceAuthInteractor = deviceEntryFaceAuthInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
new file mode 100644
index 0000000..a5690a0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline
+
+import android.app.statusBarManager
+import android.content.testableContext
+import com.android.systemui.graphics.imageLoader
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
+import com.android.systemui.media.controls.util.mediaFlags
+import com.android.systemui.plugins.activityStarter
+
+val Kosmos.mediaDataLoader by
+    Kosmos.Fixture {
+        MediaDataLoader(
+            testableContext,
+            testDispatcher,
+            testScope,
+            activityStarter,
+            fakeMediaControllerFactory,
+            mediaFlags,
+            imageLoader,
+            statusBarManager
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
index cc1ad1f..2127a88 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
@@ -28,7 +28,7 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.media.controls.data.repository.mediaDataRepository
 import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
-import com.android.systemui.media.controls.util.mediaControllerFactory
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
 import com.android.systemui.media.controls.util.mediaFlags
 import com.android.systemui.media.controls.util.mediaUiEventLogger
 import com.android.systemui.plugins.activityStarter
@@ -46,7 +46,7 @@
             uiExecutor = fakeExecutor,
             foregroundExecutor = fakeExecutor,
             handler = fakeExecutorHandler,
-            mediaControllerFactory = mediaControllerFactory,
+            mediaControllerFactory = fakeMediaControllerFactory,
             broadcastDispatcher = broadcastDispatcher,
             dumpManager = dumpManager,
             activityStarter = activityStarter,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt
index b98f557..c479ce6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt
@@ -22,8 +22,8 @@
 import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.systemui.concurrency.fakeExecutor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
 import com.android.systemui.media.controls.util.localMediaManagerFactory
-import com.android.systemui.media.controls.util.mediaControllerFactory
 import com.android.systemui.media.muteawait.mediaMuteAwaitConnectionManagerFactory
 import com.android.systemui.statusbar.policy.configurationController
 
@@ -31,7 +31,7 @@
     Kosmos.Fixture {
         MediaDeviceManager(
             context = applicationContext,
-            controllerFactory = mediaControllerFactory,
+            controllerFactory = fakeMediaControllerFactory,
             localMediaManagerFactory = localMediaManagerFactory,
             mr2manager = { MediaRouter2Manager.getInstance(applicationContext) },
             muteAwaitConnectionManagerFactory = mediaMuteAwaitConnectionManagerFactory,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt
index 6ec6378..b7660e0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.concurrency.fakeExecutor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.media.controls.util.mediaControllerFactory
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
 import com.android.systemui.media.controls.util.mediaFlags
 import com.android.systemui.plugins.statusbar.statusBarStateController
 import com.android.systemui.util.time.systemClock
@@ -27,7 +27,7 @@
 val Kosmos.mediaTimeoutListener by
     Kosmos.Fixture {
         MediaTimeoutListener(
-            mediaControllerFactory = mediaControllerFactory,
+            mediaControllerFactory = fakeMediaControllerFactory,
             mainExecutor = fakeExecutor,
             logger = MediaTimeoutLogger(logcatLogBuffer("MediaTimeoutLogBuffer")),
             statusBarStateController = statusBarStateController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
new file mode 100644
index 0000000..7f8348e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.media.session.MediaSession.Token
+
+class FakeMediaControllerFactory(context: Context) : MediaControllerFactory(context) {
+
+    private val mediaControllersForToken = mutableMapOf<Token, MediaController>()
+
+    override fun create(token: MediaSession.Token): android.media.session.MediaController {
+        if (token !in mediaControllersForToken) {
+            super.create(token)
+        }
+        return mediaControllersForToken[token]!!
+    }
+
+    fun setControllerForToken(token: Token, mediaController: MediaController) {
+        mediaControllersForToken[token] = mediaController
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaControllerFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaControllerFactoryKosmos.kt
index 1ce6e82..7ee58fa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaControllerFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaControllerFactoryKosmos.kt
@@ -19,4 +19,5 @@
 import android.content.applicationContext
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.mediaControllerFactory by Kosmos.Fixture { MediaControllerFactory(applicationContext) }
+val Kosmos.fakeMediaControllerFactory by
+    Kosmos.Fixture { FakeMediaControllerFactory(applicationContext) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
index ae8b411..f84c3bd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.scene.domain.resolver.sceneFamilyResolvers
 import com.android.systemui.scene.shared.logger.sceneLogger
 
-val Kosmos.sceneInteractor by
+val Kosmos.sceneInteractor: SceneInteractor by
     Kosmos.Fixture {
         SceneInteractor(
             applicationScope = applicationCoroutineScope,
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index a7b2eb1..8fe33d1 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -929,9 +929,9 @@
             // For ordered broadcast, check if the receivers for the new broadcast is a superset
             // of those for the previous one as skipping and removing only one of them could result
             // in an inconsistent state.
-            if (testRecord.ordered || testRecord.prioritized) {
+            if (testRecord.ordered) {
                 return containsAllReceivers(r, testRecord, recordsLookupCache);
-            } else if (testRecord.resultTo != null) {
+            } else if (testRecord.prioritized || testRecord.resultTo != null) {
                 return testRecord.getDeliveryState(testIndex) == DELIVERY_DEFERRED
                         ? r.containsReceiver(testRecord.receivers.get(testIndex))
                         : containsAllReceivers(r, testRecord, recordsLookupCache);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
index d9e9e00..cf2cdc1 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
@@ -33,6 +33,7 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Printer;
 import android.util.Slog;
@@ -115,7 +116,11 @@
         final var selectedImi = selectedIndex >= 0 ? items.get(selectedIndex).mImi : null;
         final var languageSettingsIntent = selectedImi != null
                 ? selectedImi.createImeLanguageSettingsActivityIntent() : null;
-        final boolean hasLanguageSettingsButton = languageSettingsIntent != null;
+        final boolean isDeviceProvisioned = Settings.Global.getInt(
+                dialogWindowContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED,
+                0) != 0;
+        final boolean hasLanguageSettingsButton = languageSettingsIntent != null
+                && isDeviceProvisioned;
         if (hasLanguageSettingsButton) {
             final View buttonBar = contentView
                     .requireViewById(com.android.internal.R.id.button_bar);
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index ac56043..b35a0a7 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -71,6 +71,7 @@
 import static com.android.server.stats.pull.ProcfsMemoryUtil.readCmdlineFromProcfs;
 import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
 import static com.android.server.stats.pull.netstats.NetworkStatsUtils.fromPublicNetworkStats;
+import static com.android.server.stats.pull.netstats.NetworkStatsUtils.isAddEntriesSupported;
 
 import static libcore.io.IoUtils.closeQuietly;
 
@@ -1388,14 +1389,22 @@
 
     @NonNull
     private static NetworkStats removeEmptyEntries(NetworkStats stats) {
-        NetworkStats ret = new NetworkStats(0, 1);
+        final ArrayList<NetworkStats.Entry> entries = new ArrayList<>();
         for (NetworkStats.Entry e : stats) {
             if (e.getRxBytes() != 0 || e.getRxPackets() != 0 || e.getTxBytes() != 0
                     || e.getTxPackets() != 0 || e.getOperations() != 0) {
-                ret = ret.addEntry(e);
+                entries.add(e);
             }
         }
-        return ret;
+        if (isAddEntriesSupported()) {
+            return new NetworkStats(0, entries.size()).addEntries(entries);
+        } else {
+            NetworkStats outputStats = new NetworkStats(0L, 1);
+            for (NetworkStats.Entry e : entries) {
+                outputStats = outputStats.addEntry(e);
+            }
+            return outputStats;
+        }
     }
 
     private void addNetworkStats(int atomTag, @NonNull List<StatsEvent> ret,
@@ -1720,11 +1729,19 @@
     @NonNull
     private NetworkStats sliceNetworkStats(@NonNull NetworkStats stats,
             @NonNull Function<NetworkStats.Entry, NetworkStats.Entry> slicer) {
-        NetworkStats ret = new NetworkStats(0, 1);
+        final ArrayList<NetworkStats.Entry> entries = new ArrayList();
         for (NetworkStats.Entry e : stats) {
-            ret = ret.addEntry(slicer.apply(e));
+            entries.add(slicer.apply(e));
         }
-        return ret;
+        if (isAddEntriesSupported()) {
+            return new NetworkStats(0, entries.size()).addEntries(entries);
+        } else {
+            NetworkStats outputStats = new NetworkStats(0L, 1);
+            for (NetworkStats.Entry e : entries) {
+                outputStats = outputStats.addEntry(e);
+            }
+            return outputStats;
+        }
     }
 
     private void registerWifiBytesTransferBackground() {
diff --git a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java
index de58852..0318bdd 100644
--- a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java
+++ b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java
@@ -24,6 +24,9 @@
 import android.app.usage.NetworkStats;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.stats.Flags;
+
+import java.util.ArrayList;
 
 /**
  * Utility methods for accessing {@link android.net.NetworkStats}.
@@ -35,12 +38,21 @@
      */
     public static android.net.NetworkStats fromPublicNetworkStats(
             NetworkStats publiceNetworkStats) {
-        android.net.NetworkStats stats = new android.net.NetworkStats(0L, 0);
+        final ArrayList<android.net.NetworkStats.Entry> entries = new ArrayList<>();
         while (publiceNetworkStats.hasNextBucket()) {
             NetworkStats.Bucket bucket = new NetworkStats.Bucket();
             publiceNetworkStats.getNextBucket(bucket);
-            final android.net.NetworkStats.Entry entry = fromBucket(bucket);
-            stats = stats.addEntry(entry);
+            entries.add(fromBucket(bucket));
+        }
+        android.net.NetworkStats stats = new android.net.NetworkStats(0L, 1);
+        // The new API is only supported on devices running the mainline version of `NetworkStats`.
+        // It should always be used when available for memory efficiency.
+        if (isAddEntriesSupported()) {
+            stats = stats.addEntries(entries);
+        } else {
+            for (android.net.NetworkStats.Entry entry : entries) {
+                stats = stats.addEntry(entry);
+            }
         }
         return stats;
     }
@@ -106,4 +118,8 @@
         }
         return 0;
     }
+
+    public static boolean isAddEntriesSupported() {
+        return Flags.netstatsUseAddEntries();
+    }
 }
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
index f360837..afea303 100644
--- a/services/core/java/com/android/server/stats/stats_flags.aconfig
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -1,6 +1,20 @@
 package: "com.android.server.stats"
 container: "system"
 
+# Note: To ensure compatibility across all release configurations, initiate the ramp-up process
+# only after the 'com.android.net.flags.netstats_add_entries' flag has been fully deployed.
+# This flag provides the necessary API from the Connectivity module.
+# The flag was added because the flag 'com.android.net.flags.netstats_add_entries' for API
+# is already being rolled out, and modifying behavior during an active rollout might
+# lead to unwanted issues.
+flag {
+    name: "netstats_use_add_entries"
+    namespace: "statsd"
+    description: "Use NetworkStats#addEntries to reduce memory footprint"
+    bug: "335680025"
+    is_fixed_read_only: true
+}
+
 flag {
     name: "add_mobile_bytes_transfer_by_proc_state_puller"
     namespace: "statsd"
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index c1e859d..e27b2be 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -864,9 +864,8 @@
                 if (transition != null) {
                     if (changed) {
                         // Always set as scene transition because it expects to be a jump-cut.
-                        transition.setOverrideAnimation(
-                                TransitionInfo.AnimationOptions.makeSceneTransitionAnimOptions(), r,
-                                null, null);
+                        transition.setOverrideAnimation(TransitionInfo.AnimationOptions
+                                .makeSceneTransitionAnimOptions(), null, null);
                         r.mTransitionController.requestStartTransition(transition,
                                 null /*startTask */, null /* remoteTransition */,
                                 null /* displayChange */);
@@ -911,9 +910,8 @@
                                 && under.returningOptions.getAnimationType()
                                         == ANIM_SCENE_TRANSITION) {
                             // Pass along the scene-transition animation-type
-                            transition.setOverrideAnimation(TransitionInfo
-                                            .AnimationOptions.makeSceneTransitionAnimOptions(), r,
-                                    null, null);
+                            transition.setOverrideAnimation(TransitionInfo.AnimationOptions
+                                    .makeSceneTransitionAnimOptions(), null, null);
                         }
                     } else {
                         transition.abort();
@@ -1510,7 +1508,7 @@
                         r.mOverrideTaskTransition);
                 r.mTransitionController.setOverrideAnimation(
                         TransitionInfo.AnimationOptions.makeCustomAnimOptions(packageName,
-                                enterAnim, exitAnim, backgroundColor, r.mOverrideTaskTransition), r,
+                                enterAnim, exitAnim, backgroundColor, r.mOverrideTaskTransition),
                         null /* startCallback */, null /* finishCallback */);
             }
         }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 6279521..47d4740 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -233,7 +233,6 @@
 import static com.android.server.wm.StartingData.AFTER_TRANSACTION_REMOVE_DIRECTLY;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
 import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE;
 import static com.android.server.wm.TaskPersister.DEBUG;
@@ -354,7 +353,6 @@
 import android.window.SplashScreenView;
 import android.window.SplashScreenView.SplashScreenViewParcelable;
 import android.window.TaskSnapshot;
-import android.window.TransitionInfo;
 import android.window.TransitionInfo.AnimationOptions;
 import android.window.WindowContainerToken;
 import android.window.WindowOnBackInvokedDispatcher;
@@ -4943,9 +4941,8 @@
         newIntents.add(intent);
     }
 
-    final boolean isSleeping() {
-        final Task rootTask = getRootTask();
-        return rootTask != null ? rootTask.shouldSleepActivities() : mAtmService.isSleepingLocked();
+    boolean isSleeping() {
+        return task != null ? task.shouldSleepActivities() : mAtmService.isSleepingLocked();
     }
 
     /**
@@ -4969,7 +4966,7 @@
         final ReferrerIntent rintent = new ReferrerIntent(intent, getFilteredReferrer(referrer),
                 callerToken);
         boolean unsent = true;
-        final boolean isTopActivityWhileSleeping = isTopRunningActivity() && isSleeping();
+        final boolean isTopActivityWhileSleeping = isSleeping() && isTopRunningActivity();
 
         // We want to immediately deliver the intent to the activity if:
         // - It is currently resumed or paused. i.e. it is currently visible to the user and we want
@@ -5036,8 +5033,7 @@
                 // controller but don't clear the animation information from the options since they
                 // need to be sent to the animating activity.
                 mTransitionController.setOverrideAnimation(
-                        TransitionInfo.AnimationOptions.makeSceneTransitionAnimOptions(), this,
-                        null, null);
+                        AnimationOptions.makeSceneTransitionAnimOptions(), null, null);
                 return;
             }
             applyOptionsAnimation(mPendingOptions, intent);
@@ -5160,8 +5156,7 @@
         }
 
         if (options != null) {
-            mTransitionController.setOverrideAnimation(options, this, startCallback,
-                    finishCallback);
+            mTransitionController.setOverrideAnimation(options, startCallback, finishCallback);
         }
     }
 
@@ -5546,10 +5541,7 @@
             return false;
         }
         if (!mDisplayContent.mAppTransition.isTransitionSet()) {
-            // Defer committing visibility for non-home app which is animating by recents.
-            if (isActivityTypeHome() || !isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
-                return false;
-            }
+            return false;
         }
         if (mWaitForEnteringPinnedMode && mVisible == visible) {
             // If the visibility is not changed during enter PIP, we don't want to include it in
@@ -5703,8 +5695,7 @@
     private void postApplyAnimation(boolean visible, boolean fromTransition) {
         final boolean usingShellTransitions = mTransitionController.isShellTransitionsEnabled();
         final boolean delayed = !usingShellTransitions && isAnimating(PARENTS | CHILDREN,
-                ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION
-                        | ANIMATION_TYPE_RECENTS);
+                ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION);
         if (!delayed && !usingShellTransitions) {
             // We aren't delayed anything, but exiting windows rely on the animation finished
             // callback being called in case the ActivityRecord was pretending to be delayed,
@@ -5726,7 +5717,7 @@
         // animation and aren't in RESUMED state. Otherwise, we'll update client visibility in
         // onAnimationFinished or activityStopped.
         if (visible || (mState != RESUMED && (usingShellTransitions || !isAnimating(
-                PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)))) {
+                PARENTS, ANIMATION_TYPE_APP_TRANSITION)))) {
             setClientVisible(visible);
         }
 
@@ -5838,7 +5829,7 @@
 
     void setState(State state, String reason) {
         ProtoLog.v(WM_DEBUG_STATES, "State movement: %s from:%s to:%s reason:%s",
-                this, getState(), state, reason);
+                this, mState, state, reason);
 
         if (state == mState) {
             // No need to do anything if state doesn't change.
@@ -6163,7 +6154,7 @@
         // Now for any activities that aren't visible to the user, make sure they no longer are
         // keeping the screen frozen.
         if (DEBUG_VISIBILITY) {
-            Slog.v(TAG_VISIBILITY, "Making invisible: " + this + ", state=" + getState());
+            Slog.v(TAG_VISIBILITY, "Making invisible: " + this + ", state=" + mState);
         }
         try {
             final boolean canEnterPictureInPicture = checkEnterPictureInPictureState(
@@ -6179,7 +6170,7 @@
             }
             setVisibility(false);
 
-            switch (getState()) {
+            switch (mState) {
                 case STOPPING:
                 case STOPPED:
                     // Reset the flag indicating that an app can enter picture-in-picture once the
@@ -7704,7 +7695,7 @@
                 // Ensure that the activity content is hidden when the decor surface is boosted to
                 // prevent UI redressing attack.
                 && !isDecorSurfaceBoosted)
-                || isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
+                || isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION
                         | ANIMATION_TYPE_PREDICT_BACK);
 
         if (mSurfaceControl != null) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 2d8e3db..0f108c5 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3617,6 +3617,11 @@
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
+                boolean isPowerModePreApplied = false;
+                if (mPowerModeReasons == 0) {
+                    startPowerMode(POWER_MODE_REASON_START_ACTIVITY);
+                    isPowerModePreApplied = true;
+                }
                 // Keyguard asked us to clear the home task snapshot before going away, so do that.
                 if ((flags & KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT) != 0) {
                     mActivityClientController.invalidateHomeTaskSnapshot(null /* token */);
@@ -3625,9 +3630,19 @@
                     mDemoteTopAppReasons |= DEMOTE_TOP_REASON_DURING_UNLOCKING;
                 }
 
-                mRootWindowContainer.forAllDisplays(displayContent -> {
-                    mKeyguardController.keyguardGoingAway(displayContent.getDisplayId(), flags);
-                });
+                boolean foundResumed = false;
+                for (int i = mRootWindowContainer.getChildCount() - 1; i >= 0; i--) {
+                    final DisplayContent dc = mRootWindowContainer.getChildAt(i);
+                    final boolean wasNoResumed = dc.mFocusedApp == null
+                            || !dc.mFocusedApp.isState(RESUMED);
+                    mKeyguardController.keyguardGoingAway(dc.mDisplayId, flags);
+                    if (wasNoResumed && dc.mFocusedApp != null && dc.mFocusedApp.isState(RESUMED)) {
+                        foundResumed = true;
+                    }
+                }
+                if (isPowerModePreApplied && !foundResumed) {
+                    endPowerMode(POWER_MODE_REASON_START_ACTIVITY);
+                }
             }
             WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal();
             if (wallpaperManagerInternal != null) {
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 06bdc04..197bd5a 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -68,7 +68,6 @@
 import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldAttachNavBarToApp;
 import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldStartNonAppWindowAnimationsForKeyguardExit;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.WallpaperAnimationAdapter.shouldStartWallpaperAnimation;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -173,41 +172,6 @@
                 || !transitionGoodToGoForTaskFragments()) {
             return;
         }
-        final boolean isRecentsInOpening = mDisplayContent.mOpeningApps.stream().anyMatch(
-                ConfigurationContainer::isActivityTypeRecents);
-        // In order to avoid visual clutter caused by a conflict between app transition
-        // animation and recents animation, app transition is delayed until recents finishes.
-        // One exceptional case. When 3P launcher is used and a user taps a task screenshot in
-        // task switcher (isRecentsInOpening=true), app transition must start even though
-        // recents is running. Otherwise app transition is blocked until timeout (b/232984498).
-        // When 1P launcher is used, this animation is controlled by the launcher outside of
-        // the app transition, so delaying app transition doesn't cause visible delay. After
-        // recents finishes, app transition is handled just to commit visibility on apps.
-        if (!isRecentsInOpening) {
-            final ArraySet<WindowContainer> participants = new ArraySet<>();
-            participants.addAll(mDisplayContent.mOpeningApps);
-            participants.addAll(mDisplayContent.mChangingContainers);
-            boolean deferForRecents = false;
-            for (int i = 0; i < participants.size(); i++) {
-                WindowContainer wc = participants.valueAt(i);
-                final ActivityRecord activity = getAppFromContainer(wc);
-                if (activity == null) {
-                    continue;
-                }
-                // Don't defer recents animation if one of activity isn't running for it, that one
-                // might be started from quickstep.
-                if (!activity.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
-                    deferForRecents = false;
-                    break;
-                }
-                deferForRecents = true;
-            }
-            if (deferForRecents) {
-                ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
-                        "Delaying app transition for recents animation to finish");
-                return;
-            }
-        }
 
         Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
 
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index f23dafe..ddbfd70 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -49,7 +49,6 @@
 import static java.lang.Integer.MAX_VALUE;
 
 import android.annotation.Nullable;
-import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Handler;
 import android.os.IBinder;
@@ -111,7 +110,7 @@
      * draw the live-tile above the recents activity, we also need to provide that activity as a
      * z-layering reference so that we can place the recents input consumer above it.
      */
-    private WeakReference<ActivityRecord> mActiveRecentsActivity = null;
+    private WeakReference<Task> mActiveRecentsTask = null;
     private WeakReference<Task> mActiveRecentsLayerRef = null;
 
     private class UpdateInputWindows implements Runnable {
@@ -388,13 +387,13 @@
 
     /**
      * Inform InputMonitor when recents is active so it can enable the recents input consumer.
-     * @param activity The active recents activity. {@code null} means recents is not active.
+     * @param task The active recents task. {@code null} means recents is not active.
      * @param layer A task whose Z-layer is used as a reference for how to sort the consumer.
      */
-    void setActiveRecents(@Nullable ActivityRecord activity, @Nullable Task layer) {
-        final boolean clear = activity == null;
-        final boolean wasActive = mActiveRecentsActivity != null && mActiveRecentsLayerRef != null;
-        mActiveRecentsActivity = clear ? null : new WeakReference<>(activity);
+    void setActiveRecents(@Nullable Task task, @Nullable Task layer) {
+        final boolean clear = task == null;
+        final boolean wasActive = mActiveRecentsTask != null && mActiveRecentsLayerRef != null;
+        mActiveRecentsTask = clear ? null : new WeakReference<>(task);
         mActiveRecentsLayerRef = clear ? null : new WeakReference<>(layer);
         if (clear && wasActive) {
             setUpdateInputWindowsNeededLw();
@@ -415,7 +414,7 @@
         if (recentsAnimationInputConsumer != null && focus != null) {
             // Apply recents input consumer when the focusing window is in recents animation.
             final boolean shouldApplyRecentsInputConsumer =
-                    getWeak(mActiveRecentsActivity) != null && focus.inTransition()
+                    getWeak(mActiveRecentsTask) != null && focus.inTransition()
                             // only take focus from the recents activity to avoid intercepting
                             // events before the gesture officially starts.
                             && focus.isActivityTypeHomeOrRecents();
@@ -564,7 +563,6 @@
         private boolean mAddRecentsAnimationInputConsumerHandle;
 
         private boolean mInDrag;
-        private final Rect mTmpRect = new Rect();
 
         private void updateInputWindows(boolean inDrag) {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateInputWindows");
@@ -581,18 +579,15 @@
 
             resetInputConsumers(mInputTransaction);
             // Update recents input consumer layer if active
-            final ActivityRecord activeRecents = getWeak(mActiveRecentsActivity);
+            final Task activeRecents = getWeak(mActiveRecentsTask);
             if (mAddRecentsAnimationInputConsumerHandle && activeRecents != null
                     && activeRecents.getSurfaceControl() != null) {
                 WindowContainer layer = getWeak(mActiveRecentsLayerRef);
                 layer = layer != null ? layer : activeRecents;
                 // Handle edge-case for SUW where windows don't exist yet
                 if (layer.getSurfaceControl() != null) {
-                    final WindowState targetAppMainWindow = activeRecents.findMainWindow();
-                    if (targetAppMainWindow != null) {
-                        targetAppMainWindow.getBounds(mTmpRect);
-                        mRecentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(mTmpRect);
-                    }
+                    mRecentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(
+                            activeRecents.getBounds());
                     mRecentsAnimationInputConsumer.show(mInputTransaction, layer);
                     mAddRecentsAnimationInputConsumerHandle = false;
                 }
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 9cfd396..57f9be0 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -32,8 +32,8 @@
 import android.view.SurfaceControl.Transaction;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.LogLevel;
 import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.common.LogLevel;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
@@ -585,7 +585,6 @@
             ANIMATION_TYPE_APP_TRANSITION,
             ANIMATION_TYPE_SCREEN_ROTATION,
             ANIMATION_TYPE_DIMMER,
-            ANIMATION_TYPE_RECENTS,
             ANIMATION_TYPE_WINDOW_ANIMATION,
             ANIMATION_TYPE_INSETS_CONTROL,
             ANIMATION_TYPE_TOKEN_TRANSFORM,
@@ -604,7 +603,6 @@
             case ANIMATION_TYPE_APP_TRANSITION: return "app_transition";
             case ANIMATION_TYPE_SCREEN_ROTATION: return "screen_rotation";
             case ANIMATION_TYPE_DIMMER: return "dimmer";
-            case ANIMATION_TYPE_RECENTS: return "recents_animation";
             case ANIMATION_TYPE_WINDOW_ANIMATION: return "window_animation";
             case ANIMATION_TYPE_INSETS_CONTROL: return "insets_animation";
             case ANIMATION_TYPE_TOKEN_TRANSFORM: return "token_transform";
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index d6bd55f..21be0fc 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -94,7 +94,6 @@
 import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE;
 import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
 import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_PINNABLE;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.TaskProto.AFFINITY;
 import static com.android.server.wm.TaskProto.BOUNDS;
 import static com.android.server.wm.TaskProto.CREATED_BY_ORGANIZER;
@@ -2966,8 +2965,7 @@
 
     /** Checking if self or its child tasks are animated by recents animation. */
     boolean isAnimatingByRecents() {
-        return isAnimating(CHILDREN, ANIMATION_TYPE_RECENTS)
-                || mTransitionController.isTransientHide(this);
+        return mTransitionController.isTransientHide(this);
     }
 
     WindowState getTopVisibleAppMainWindow() {
@@ -6196,26 +6194,6 @@
         ActivityOptions.abort(options);
     }
 
-    boolean shouldSleepActivities() {
-        final DisplayContent display = mDisplayContent;
-        final boolean isKeyguardGoingAway = (mDisplayContent != null)
-                ? mDisplayContent.isKeyguardGoingAway()
-                : mRootWindowContainer.getDefaultDisplay().isKeyguardGoingAway();
-
-        // Do not sleep activities in this root task if we're marked as focused and the keyguard
-        // is in the process of going away.
-        if (isKeyguardGoingAway && isFocusedRootTaskOnDisplay()
-                // Avoid resuming activities on secondary displays since we don't want bubble
-                // activities to be resumed while bubble is still collapsed.
-                // TODO(b/113840485): Having keyguard going away state for secondary displays.
-                && display != null
-                && display.isDefaultDisplay) {
-            return false;
-        }
-
-        return display != null ? display.isSleeping() : mAtmService.isSleepingLocked();
-    }
-
     private Rect getRawBounds() {
         return super.getBounds();
     }
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index c8139fa..f58b322 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2145,8 +2145,22 @@
     }
 
     boolean shouldSleepActivities() {
-        final Task task = getRootTask();
-        return task != null && task.shouldSleepActivities();
+        final DisplayContent dc = mDisplayContent;
+        if (dc == null) {
+            return mAtmService.isSleepingLocked();
+        }
+        if (!dc.isSleeping()) {
+            return false;
+        }
+        // In case the unlocking order is keyguard-going-away -> screen-turning-on (display is
+        // sleeping by screen-off-token which may be notified to release from power manager's
+        // thread), keep the activities resume-able to avoid extra activity lifecycle when
+        // performing keyguard-going-away. This only applies to default display because currently
+        // the per-display keyguard-going-away state is assigned from a global signal.
+        if (!dc.isDefaultDisplay || !dc.isKeyguardGoingAway()) {
+            return true;
+        }
+        return !shouldBeVisible(null /* starting */);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 4f12d31..e25db7e 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -950,13 +950,10 @@
      * Set animation options for collecting transition by ActivityRecord.
      * @param options AnimationOptions captured from ActivityOptions
      */
-    void setOverrideAnimation(@Nullable AnimationOptions options, @NonNull ActivityRecord r,
+    void setOverrideAnimation(@Nullable AnimationOptions options,
             @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
         if (!isCollecting()) return;
         mOverrideOptions = options;
-        if (mOverrideOptions != null) {
-            mOverrideOptions.setUserId(r.mUserId);
-        }
         sendRemoteCallback(mClientAnimationStartCallback);
         mClientAnimationStartCallback = startCallback;
         mClientAnimationFinishCallback = finishCallback;
@@ -1456,7 +1453,7 @@
             // Clean up input monitors (for recents)
             final DisplayContent dc =
                     mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId);
-            dc.getInputMonitor().setActiveRecents(null /* activity */, null /* layer */);
+            dc.getInputMonitor().setActiveRecents(null /* task */, null /* layer */);
             dc.getInputMonitor().updateInputWindowsLw(false /* force */);
         }
         if (mTransientLaunches != null) {
@@ -2252,7 +2249,7 @@
         // Recents has an input-consumer to grab input from the "live tile" app. Set that up here
         final InputConsumerImpl recentsAnimationInputConsumer =
                 dc.getInputMonitor().getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
-        ActivityRecord recentsActivity = null;
+        Task recentsTask = null;
         if (recentsAnimationInputConsumer != null) {
             // Find the top-most going-away task and the recents activity. The top-most
             // is used as layer reference while the recents is used for registering the consumer
@@ -2267,20 +2264,20 @@
                 final int activityType = taskInfo.topActivityType;
                 final boolean isRecents = activityType == ACTIVITY_TYPE_HOME
                         || activityType == ACTIVITY_TYPE_RECENTS;
-                if (isRecents && recentsActivity == null) {
-                    recentsActivity = task.getTopVisibleActivity();
+                if (isRecents && recentsTask == null) {
+                    recentsTask = task;
                 } else if (!isRecents && topNonRecentsTask == null) {
                     topNonRecentsTask = task;
                 }
             }
-            if (recentsActivity != null && topNonRecentsTask != null) {
+            if (recentsTask != null && topNonRecentsTask != null) {
                 recentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(
                         topNonRecentsTask.getBounds());
-                dc.getInputMonitor().setActiveRecents(recentsActivity, topNonRecentsTask);
+                dc.getInputMonitor().setActiveRecents(recentsTask, topNonRecentsTask);
             }
         }
 
-        if (recentsActivity == null) {
+        if (recentsTask == null) {
             // No recents activity on `dc`, its probably on a different display.
             return;
         }
@@ -2814,7 +2811,7 @@
                 }
             }
             final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
-                            "Transition Root: " + leashReference.getName())
+                    "Transition Root: " + leashReference.getName())
                     .setCallsite("Transition.calculateTransitionRoots").build();
             rootLeash.setUnreleasedWarningCallSite("Transition.calculateTransitionRoots");
             // Update layers to start transaction because we prevent assignment during collect, so
@@ -2985,7 +2982,7 @@
                         // Create the options based on this change's custom animations and layout
                         // parameters
                         animOptions = getOptions(activityRecord /* customAnimActivity */,
-                                activityRecord /* animLpActivity */);
+                                                 activityRecord /* animLpActivity */);
                         if (!change.hasFlags(FLAG_TRANSLUCENT)) {
                             // If this change is not translucent, its options are going to be
                             // inherited by the changes below
@@ -3010,7 +3007,6 @@
                     }
                 }
                 if (animOptions != null) {
-                    animOptions.setUserId(activityRecord.mUserId);
                     change.setAnimationOptions(animOptions);
                 }
             }
@@ -3071,9 +3067,6 @@
                     animOptions);
             animOptions = addCustomActivityTransition(customAnimActivity, false /* open */,
                     animOptions);
-            if (animOptions != null) {
-                animOptions.setUserId(customAnimActivity.mUserId);
-            }
         }
 
         // Layout parameters
@@ -3087,12 +3080,10 @@
             // are running an app starting animation, in which case we don't want the app to be
             // able to change its animation directly.
             if (animOptions != null) {
-                animOptions.setUserId(animLpActivity.mUserId);
                 animOptions.addOptionsFromLayoutParameters(animLp);
             } else {
                 animOptions = TransitionInfo.AnimationOptions
                         .makeAnimOptionsFromLayoutParameters(animLp);
-                animOptions.setUserId(animLpActivity.mUserId);
             }
         }
         return animOptions;
@@ -3118,7 +3109,6 @@
             if (animOptions == null) {
                 animOptions = TransitionInfo.AnimationOptions
                         .makeCommonAnimOptions(activity.packageName);
-                animOptions.setUserId(activity.mUserId);
             }
             animOptions.addCustomActivityTransition(open, customAnim.mEnterAnim,
                     customAnim.mExitAnim, customAnim.mBackgroundColor);
@@ -3247,7 +3237,7 @@
         // Remote animations always win, but fullscreen windows override non-fullscreen windows.
         ActivityRecord result = lookForTopWindowWithFilter(sortedTargets,
                 w -> w.getRemoteAnimationDefinition() != null
-                        && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
+                    && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
         if (result != null) {
             return result;
         }
@@ -3294,7 +3284,7 @@
     private void validateKeyguardOcclusion() {
         if ((mFlags & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) {
             mController.mStateValidators.add(
-                    mController.mAtm.mWindowManager.mPolicy::applyKeyguardOcclusionChange);
+                mController.mAtm.mWindowManager.mPolicy::applyKeyguardOcclusionChange);
         }
     }
 
@@ -3898,9 +3888,9 @@
 
         /** @return true if all tracked subtrees are ready. */
         boolean allReady() {
-            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                    " allReady query: used=%b " + "override=%b defer=%d states=[%s]", mUsed,
-                    mReadyOverride, mDeferReadyDepth, groupsToString());
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " allReady query: used=%b "
+                    + "override=%b defer=%d states=[%s]", mUsed, mReadyOverride, mDeferReadyDepth,
+                    groupsToString());
             // If the readiness has never been touched, mUsed will be false. We never want to
             // consider a transition ready if nothing has been reported on it.
             if (!mUsed) return false;
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 6b11ada..9bbf102 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -52,8 +52,8 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.ProtoLog;
 import com.android.internal.protolog.ProtoLogGroup;
+import com.android.internal.protolog.ProtoLog;
 import com.android.server.FgThread;
 import com.android.window.flags.Flags;
 
@@ -880,10 +880,10 @@
     }
 
     /** @see Transition#setOverrideAnimation */
-    void setOverrideAnimation(TransitionInfo.AnimationOptions options, ActivityRecord r,
+    void setOverrideAnimation(TransitionInfo.AnimationOptions options,
             @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
         if (mCollectingTransition == null) return;
-        mCollectingTransition.setOverrideAnimation(options, r, startCallback, finishCallback);
+        mCollectingTransition.setOverrideAnimation(options, startCallback, finishCallback);
     }
 
     void setNoAnimation(WindowContainer wc) {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 03342d3..13334a5 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -19,7 +19,6 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -218,8 +217,8 @@
     private void updateRunningExpensiveAnimationsLegacy() {
         final boolean runningExpensiveAnimations =
                 mService.mRoot.isAnimating(TRANSITION | CHILDREN /* flags */,
-                        ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_SCREEN_ROTATION
-                                | ANIMATION_TYPE_RECENTS /* typesToCheck */);
+                        ANIMATION_TYPE_APP_TRANSITION
+                                | ANIMATION_TYPE_SCREEN_ROTATION /* typesToCheck */);
         if (runningExpensiveAnimations && !mRunningExpensiveAnimations) {
             mService.mSnapshotController.setPause(true);
             mTransaction.setEarlyWakeupStart();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index a980b77..6995027 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -51,7 +51,6 @@
 import static com.android.server.wm.IdentifierProto.USER_ID;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -1242,8 +1241,7 @@
      */
     boolean inTransitionSelfOrParent() {
         if (!mTransitionController.isShellTransitionsEnabled()) {
-            return isAnimating(PARENTS | TRANSITION,
-                    ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
+            return isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_APP_TRANSITION);
         }
         return inTransition();
     }
diff --git a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
index ad88062..80f3c44 100644
--- a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
+++ b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
@@ -20,7 +20,7 @@
 import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.WindowContainerThumbnailProto.HEIGHT;
 import static com.android.server.wm.WindowContainerThumbnailProto.SURFACE_ANIMATOR;
 import static com.android.server.wm.WindowContainerThumbnailProto.WIDTH;
@@ -118,7 +118,7 @@
                         mWindowContainer.getDisplayContent().mAppTransition.canSkipFirstFrame(),
                         mWindowContainer.getDisplayContent().getWindowCornerRadius()),
                 mWindowContainer.mWmService.mSurfaceAnimationRunner), false /* hidden */,
-                ANIMATION_TYPE_RECENTS);
+                ANIMATION_TYPE_APP_TRANSITION);
     }
 
     private void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4b91e27..9b55ed2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -129,7 +129,6 @@
 import static com.android.server.wm.SensitiveContentPackages.PackageInfo;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
@@ -2725,8 +2724,7 @@
                         win.mTransitionController.mAnimatingExitWindows.add(win);
                         reason = "inTransition";
                     }
-                } else if (win.isAnimating(PARENTS | TRANSITION,
-                        ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)) {
+                } else if (win.isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_APP_TRANSITION)) {
                     // Already animating as part of a legacy app-transition.
                     reason = "inLegacyTransition";
                 }
@@ -8947,6 +8945,22 @@
             // display it's on to the top since that window won't be able to get focus anyway.
             return;
         }
+
+        final ActivityRecord touchedApp = t.getActivityRecord();
+        if (touchedApp != null && touchedApp.getTask() != null) {
+            final ActivityRecord top = touchedApp.getTask().topRunningActivity();
+            if (top != touchedApp && top.getTaskFragment().getBounds().contains(
+                    touchedApp.getTaskFragment().getBounds())) {
+                // This is a special case where the pointer-down-outside focus on an Activity that's
+                // entirely occluded by the task top running activity, this is possible if the
+                // pointer-down-outside-focus event is delayed (after new activity started on top).
+                // In that case, drop the event to prevent changing focus to a background activity.
+                Slog.w(TAG, "onPointerDownOutsideFocusLocked, drop event because " + touchedApp
+                        + " is occluded and should not be focused.");
+                return;
+            }
+        }
+
         clearPointerDownOutsideFocusRunnable();
 
         if (shouldDelayTouchOutside(t)) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 81bce18..c8e4b0a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -128,7 +128,6 @@
 import static com.android.server.wm.MoveAnimationSpecProto.TO;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_STARTING_REVEAL;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
@@ -581,7 +580,7 @@
      * is guaranteed to be cleared.
      */
     static final int EXIT_ANIMATING_TYPES = ANIMATION_TYPE_APP_TRANSITION
-            | ANIMATION_TYPE_WINDOW_ANIMATION | ANIMATION_TYPE_RECENTS;
+            | ANIMATION_TYPE_WINDOW_ANIMATION;
 
     /** Currently running an exit animation? */
     boolean mAnimatingExit;
diff --git a/services/core/java/com/android/server/wm/WindowTracingDataSource.java b/services/core/java/com/android/server/wm/WindowTracingDataSource.java
index 73ecbb4..dc048ef 100644
--- a/services/core/java/com/android/server/wm/WindowTracingDataSource.java
+++ b/services/core/java/com/android/server/wm/WindowTracingDataSource.java
@@ -89,6 +89,7 @@
                                 PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)
                         .build();
         register(params);
+        Log.i(TAG, "Registered with perfetto service");
     }
 
     @Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 0ba74c6..100b548 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -1176,6 +1176,17 @@
         verifyPendingRecords(greenQueue, List.of(screenOff, screenOn));
         verifyPendingRecords(redQueue, List.of(screenOff));
         verifyPendingRecords(blueQueue, List.of(screenOff, screenOn));
+
+        final BroadcastRecord screenOffRecord = makeBroadcastRecord(screenOff, screenOnOffOptions,
+                List.of(greenReceiver, redReceiver, blueReceiver), false);
+        screenOffRecord.setDeliveryState(2, BroadcastRecord.DELIVERY_DEFERRED,
+                "testDeliveryGroupPolicy_prioritized_diffReceivers");
+        mImpl.enqueueBroadcastLocked(screenOffRecord);
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, screenOnOffOptions,
+                List.of(greenReceiver, blueReceiver), false));
+        verifyPendingRecords(greenQueue, List.of(screenOff, screenOn));
+        verifyPendingRecords(redQueue, List.of(screenOff));
+        verifyPendingRecords(blueQueue, List.of(screenOn));
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 9950541..b6e393d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -39,10 +39,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import static org.junit.Assert.assertEquals;
@@ -51,9 +49,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
 
 import android.graphics.Rect;
 import android.os.Binder;
@@ -377,41 +373,6 @@
     }
 
     @Test
-    public void testDelayWhileRecents() {
-        final DisplayContent dc = createNewDisplay(Display.STATE_ON);
-        doReturn(false).when(dc).onDescendantOrientationChanged(any());
-        final Task task = createTask(dc);
-
-        // Simulate activity1 launches activity2.
-        final ActivityRecord activity1 = createActivityRecord(task);
-        activity1.setVisible(true);
-        activity1.setVisibleRequested(false);
-        activity1.allDrawn = true;
-        final ActivityRecord activity2 = createActivityRecord(task);
-        activity2.setVisible(false);
-        activity2.setVisibleRequested(true);
-        activity2.allDrawn = true;
-
-        dc.mClosingApps.add(activity1);
-        dc.mOpeningApps.add(activity2);
-        dc.prepareAppTransition(TRANSIT_OPEN);
-        assertTrue(dc.mAppTransition.containsTransitRequest(TRANSIT_OPEN));
-
-        // Wait until everything in animation handler get executed to prevent the exiting window
-        // from being removed during WindowSurfacePlacer Traversal.
-        waitUntilHandlersIdle();
-
-        // Start recents
-        doReturn(true).when(task)
-                .isSelfAnimating(anyInt(), eq(ANIMATION_TYPE_RECENTS));
-
-        dc.mAppTransitionController.handleAppTransitionReady();
-
-        verify(activity1, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
-        verify(activity2, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
-    }
-
-    @Test
     public void testGetAnimationStyleResId() {
         // Verify getAnimationStyleResId will return as LayoutParams.windowAnimations when without
         // specifying window type.
diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
index d8d5729..ea175a5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
@@ -24,6 +24,8 @@
 
 import android.graphics.PixelFormat;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.view.inputmethod.Flags;
 import android.view.inputmethod.ImeTracker;
 
 import androidx.test.filters.SmallTest;
@@ -72,6 +74,7 @@
      * Checks that scheduling with all the state set and manually triggering the show does succeed.
      */
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testScheduleShowIme() {
         final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
         makeWindowVisibleAndDrawn(ime);
@@ -99,6 +102,7 @@
      * all the state becomes available.
      */
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testScheduleShowIme_noInitialState() {
         final WindowState target = createWindow(null, TYPE_APPLICATION, "app");
 
@@ -126,6 +130,7 @@
      * does continue and succeed when the runnable is started.
      */
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testScheduleShowIme_delayedAfterPrepareSurfaces() {
         final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
         makeWindowVisibleAndDrawn(ime);
@@ -158,6 +163,7 @@
      * when the surface placement happens.
      */
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testScheduleShowIme_delayedSurfacePlacement() {
         final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
         makeWindowVisibleAndDrawn(ime);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 0dc56f8..964264d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -32,6 +32,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -202,6 +203,11 @@
         getController().onImeControlTargetChanged(base);
         base.setRequestedVisibleTypes(ime(), ime());
         getController().onRequestedVisibleTypesChanged(base, null /* statsToken */);
+        if (android.view.inputmethod.Flags.refactorInsetsController()) {
+            // to set the serverVisibility, the IME needs to be drawn and onPostLayout be called.
+            mImeWindow.mWinAnimator.mDrawState = HAS_DRAWN;
+            getController().onPostLayout();
+        }
 
         // Send our spy window (app) into the system so that we can detect the invocation.
         final WindowState win = createWindow(null, TYPE_APPLICATION, "app");
@@ -500,6 +506,12 @@
         getController().onRequestedVisibleTypesChanged(app, null /* statsToken */);
         assertTrue(ime.getControllableInsetProvider().getSource().isVisible());
 
+        if (android.view.inputmethod.Flags.refactorInsetsController()) {
+            // The IME is only set to shown, after onPostLayout is called and all preconditions
+            // (serverVisible, no givenInsetsPending, etc.) are fulfilled
+            getController().getImeSourceProvider().onPostLayout();
+        }
+
         getController().updateAboveInsetsState(true /* notifyInsetsChange */);
         assertNotNull(app.getInsetsState().peekSource(ID_IME));
         verify(app, atLeastOnce()).notifyInsetsChanged();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 9007733..6adf0fe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -1260,25 +1260,40 @@
 
     @Test
     public void testShouldSleepActivities() {
+        final Task task = new TaskBuilder(mSupervisor).build();
+        task.mDisplayContent = mock(DisplayContent.class);
         // When focused activity and keyguard is going away, we should not sleep regardless
         // of the display state, but keyguard-going-away should only take effects on default
-        // display since there is no keyguard on secondary displays (yet).
-        verifyShouldSleepActivities(true /* focusedRootTask */, true /*keyguardGoingAway*/,
+        // display because the keyguard-going-away state of secondary displays are already the
+        // same as default display.
+        verifyShouldSleepActivities(task, true /* isVisibleTask */, true /* keyguardGoingAway */,
                 true /* displaySleeping */, true /* isDefaultDisplay */, false /* expected */);
-        verifyShouldSleepActivities(true /* focusedRootTask */, true /*keyguardGoingAway*/,
+        verifyShouldSleepActivities(task, true /* isVisibleTask */, true /* keyguardGoingAway */,
                 true /* displaySleeping */, false /* isDefaultDisplay */, true /* expected */);
 
         // When not the focused root task, defer to display sleeping state.
-        verifyShouldSleepActivities(false /* focusedRootTask */, true /*keyguardGoingAway*/,
+        verifyShouldSleepActivities(task, false /* isVisibleTask */, true /* keyguardGoingAway */,
                 true /* displaySleeping */, true /* isDefaultDisplay */, true /* expected */);
 
         // If keyguard is going away, defer to the display sleeping state.
-        verifyShouldSleepActivities(true /* focusedRootTask */, false /*keyguardGoingAway*/,
+        verifyShouldSleepActivities(task, true /* isVisibleTask */, false /* keyguardGoingAway */,
                 true /* displaySleeping */, true /* isDefaultDisplay */, true /* expected */);
-        verifyShouldSleepActivities(true /* focusedRootTask */, false /*keyguardGoingAway*/,
+        verifyShouldSleepActivities(task, true /* isVisibleTask */, false /* keyguardGoingAway */,
                 false /* displaySleeping */, true /* isDefaultDisplay */, false /* expected */);
     }
 
+    private static void verifyShouldSleepActivities(Task task, boolean isVisibleTask,
+            boolean keyguardGoingAway, boolean displaySleeping, boolean isDefaultDisplay,
+            boolean expected) {
+        final DisplayContent display = task.mDisplayContent;
+        display.isDefaultDisplay = isDefaultDisplay;
+        doReturn(keyguardGoingAway).when(display).isKeyguardGoingAway();
+        doReturn(displaySleeping).when(display).isSleeping();
+        doReturn(isVisibleTask).when(task).shouldBeVisible(null /* starting */);
+
+        assertEquals(expected, task.shouldSleepActivities());
+    }
+
     @Test
     public void testNavigateUpTo() {
         final ActivityStartController controller = mock(ActivityStartController.class);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
index 6c8a7ac..9981a4d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -23,7 +23,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -96,18 +95,18 @@
     @Test
     public void testRunAnimation() {
         mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
-                ANIMATION_TYPE_RECENTS);
+                ANIMATION_TYPE_APP_TRANSITION);
         final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
                 OnAnimationFinishedCallback.class);
         assertAnimating(mAnimatable);
         verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash));
-        verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_RECENTS),
+        verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION),
                 callbackCaptor.capture());
 
-        callbackCaptor.getValue().onAnimationFinished(ANIMATION_TYPE_RECENTS, mSpec);
+        callbackCaptor.getValue().onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, mSpec);
         assertNotAnimating(mAnimatable);
         assertTrue(mAnimatable.mFinishedCallbackCalled);
-        assertEquals(ANIMATION_TYPE_RECENTS, mAnimatable.mFinishedAnimationType);
+        assertEquals(ANIMATION_TYPE_APP_TRANSITION, mAnimatable.mFinishedAnimationType);
         verify(mTransaction).remove(eq(mAnimatable.mLeash));
         // TODO: Verify reparenting once we use mPendingTransaction to reparent it back
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 6c6edd1..56fca31 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -2004,10 +2004,10 @@
     @DisableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testOverrideAnimationOptionsToInfoIfNecessary_disableAnimOptionsPerChange() {
-        ActivityRecord r = initializeOverrideAnimationOptionsTest();
+        initializeOverrideAnimationOptionsTest();
         TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
                 .makeCommonAnimOptions("testPackage");
-        mTransition.setOverrideAnimation(options, r, null /* startCallback */,
+        mTransition.setOverrideAnimation(options, null /* startCallback */,
                 null /* finishCallback */);
 
         mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
@@ -2018,10 +2018,10 @@
     @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testOverrideAnimationOptionsToInfoIfNecessary_fromStyleAnimOptions() {
-        ActivityRecord r = initializeOverrideAnimationOptionsTest();
+        initializeOverrideAnimationOptionsTest();
         TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
                 .makeCommonAnimOptions("testPackage");
-        mTransition.setOverrideAnimation(options, r, null /* startCallback */,
+        mTransition.setOverrideAnimation(options, null /* startCallback */,
                 null /* finishCallback */);
 
         mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
@@ -2044,10 +2044,10 @@
     @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testOverrideAnimationOptionsToInfoIfNecessary_sceneAnimOptions() {
-        ActivityRecord r = initializeOverrideAnimationOptionsTest();
+        initializeOverrideAnimationOptionsTest();
         TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
                 .makeSceneTransitionAnimOptions();
-        mTransition.setOverrideAnimation(options, r, null /* startCallback */,
+        mTransition.setOverrideAnimation(options, null /* startCallback */,
                 null /* finishCallback */);
 
         mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
@@ -2070,10 +2070,10 @@
     @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testOverrideAnimationOptionsToInfoIfNecessary_crossProfileAnimOptions() {
-        ActivityRecord r = initializeOverrideAnimationOptionsTest();
+        initializeOverrideAnimationOptionsTest();
         TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
                 .makeCrossProfileAnimOptions();
-        mTransition.setOverrideAnimation(options, r, null /* startCallback */,
+        mTransition.setOverrideAnimation(options, null /* startCallback */,
                 null /* finishCallback */);
 
         final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
@@ -2098,13 +2098,13 @@
     @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testOverrideAnimationOptionsToInfoIfNecessary_customAnimOptions() {
-        ActivityRecord r = initializeOverrideAnimationOptionsTest();
+        initializeOverrideAnimationOptionsTest();
         TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
                 .makeCustomAnimOptions("testPackage", Resources.ID_NULL,
                         TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
                         TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
                         Color.GREEN, false /* overrideTaskTransition */);
-        mTransition.setOverrideAnimation(options, r, null /* startCallback */,
+        mTransition.setOverrideAnimation(options, null /* startCallback */,
                 null /* finishCallback */);
 
         mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
@@ -2131,7 +2131,7 @@
     @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testOverrideAnimationOptionsToInfoIfNecessary_haveTaskFragmentAnimParams() {
-        ActivityRecord r = initializeOverrideAnimationOptionsTest();
+        initializeOverrideAnimationOptionsTest();
 
         final TaskFragment embeddedTf = mTransition.mTargets.get(2).mContainer.asTaskFragment();
         embeddedTf.setAnimationParams(new TaskFragmentAnimationParams.Builder()
@@ -2144,7 +2144,7 @@
                         TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
                         TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
                         Color.GREEN, false /* overrideTaskTransition */);
-        mTransition.setOverrideAnimation(options, r, null /* startCallback */,
+        mTransition.setOverrideAnimation(options, null /* startCallback */,
                 null /* finishCallback */);
 
         final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
@@ -2180,13 +2180,13 @@
     @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
     @Test
     public void testOverrideAnimationOptionsToInfoIfNecessary_customAnimOptionsWithTaskOverride() {
-        ActivityRecord r = initializeOverrideAnimationOptionsTest();
+        initializeOverrideAnimationOptionsTest();
         TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
                 .makeCustomAnimOptions("testPackage", Resources.ID_NULL,
                         TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
                         TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
                         Color.GREEN, true /* overrideTaskTransition */);
-        mTransition.setOverrideAnimation(options, r, null /* startCallback */,
+        mTransition.setOverrideAnimation(options, null /* startCallback */,
                 null /* finishCallback */);
 
         mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
@@ -2212,7 +2212,7 @@
                 options.getBackgroundColor(), activityChange.getBackgroundColor());
     }
 
-    private ActivityRecord initializeOverrideAnimationOptionsTest() {
+    private void initializeOverrideAnimationOptionsTest() {
         mTransition = createTestTransition(TRANSIT_OPEN);
 
         // Test set AnimationOptions for Activity and Task.
@@ -2240,7 +2240,6 @@
                 embeddedTf.getAnimationLeash()));
         mInfo.addChange(new TransitionInfo.Change(null /* container */,
                 nonEmbeddedActivity.getAnimationLeash()));
-        return nonEmbeddedActivity;
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index d537bd7..88ce3a6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -40,14 +40,12 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.WindowStateAnimator.PRESERVED_SURFACE_LAYER;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
 
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
@@ -405,30 +403,6 @@
     }
 
     @Test
-    public void testAssignWindowLayers_ForImeOnAppWithRecentsAnimating() {
-        final WindowState imeAppTarget = createWindow(null, TYPE_APPLICATION,
-                mAppWindow.mActivityRecord, "imeAppTarget");
-        mDisplayContent.setImeInputTarget(imeAppTarget);
-        mDisplayContent.setImeLayeringTarget(imeAppTarget);
-        mDisplayContent.setImeControlTarget(imeAppTarget);
-        mDisplayContent.updateImeParent();
-
-        // Simulate the ime layering target task is animating with recents animation.
-        final Task imeAppTargetTask = imeAppTarget.getTask();
-        final SurfaceAnimator imeTargetTaskAnimator = imeAppTargetTask.mSurfaceAnimator;
-        spyOn(imeTargetTaskAnimator);
-        doReturn(ANIMATION_TYPE_RECENTS).when(imeTargetTaskAnimator).getAnimationType();
-        doReturn(true).when(imeTargetTaskAnimator).isAnimating();
-
-        mDisplayContent.assignChildLayers(mTransaction);
-
-        // Ime should on top of the application window when in recents animation and keep
-        // attached on app.
-        assertTrue(mDisplayContent.shouldImeAttachedToApp());
-        assertWindowHigher(mImeWindow, imeAppTarget);
-    }
-
-    @Test
     public void testAssignWindowLayers_ForImeOnPopupImeLayeringTarget() {
         final WindowState imeAppTarget = createWindow(null, TYPE_APPLICATION,
                 mAppWindow.mActivityRecord, "imeAppTarget");
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 3e8b326..7481daa 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1777,8 +1777,8 @@
 
     /**
      * Used as an int value for {@link #EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE}
-     * to indicate user to decide whether current SIM should be preferred for all
-     * data / voice / sms. {@link #EXTRA_SUBSCRIPTION_ID} will specified to indicate
+     * to indicate the current SIM should be preferred for all data / voice / sms.
+     * {@link #EXTRA_SUBSCRIPTION_ID} will specified to indicate
      * which subscription should be the default subscription.
      * @hide
      */
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index b0f68f7..f68ae2c 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -98,6 +98,18 @@
         }
 
         @Override
+        public void onServiceDisconnected() {
+            if (mCallback != null) {
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    mExecutor.execute(() -> mCallback.onServiceDisconnected());
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        }
+
+        @Override
         public void onHotspotNetworksUpdated(@NonNull List<HotspotNetwork> networks) {
             if (mCallback != null) {
                 final long token = Binder.clearCallingIdentity();
@@ -247,13 +259,13 @@
                 mService = null;
                 synchronized (mProxyDataLock) {
                     if (!mCallbackProxyCache.isEmpty()) {
-                        mCallbackProxyCache.keySet().forEach(
-                                SharedConnectivityClientCallback::onServiceDisconnected);
+                        mCallbackProxyCache.values().forEach(
+                                SharedConnectivityCallbackProxy::onServiceDisconnected);
                         mCallbackProxyCache.clear();
                     }
                     if (!mProxyMap.isEmpty()) {
-                        mProxyMap.keySet().forEach(
-                                SharedConnectivityClientCallback::onServiceDisconnected);
+                        mProxyMap.values().forEach(
+                                SharedConnectivityCallbackProxy::onServiceDisconnected);
                         mProxyMap.clear();
                     }
                 }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
index 521f943..7b892af 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
@@ -32,4 +32,5 @@
     oneway void onKnownNetworkConnectionStatusChanged(in KnownNetworkConnectionStatus status);
     oneway void onSharedConnectivitySettingsChanged(in SharedConnectivitySettingsState state);
     oneway void onServiceConnected();
+    oneway void onServiceDisconnected();
 }