Merge "Trace callbacks for user switcher" into main
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index f2d6c6a..aa2ada5 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6024,8 +6024,9 @@
         /**
          * @param isHeader If the notification is a notification header
          * @return An instance of mColors after resolving the palette
+         * @hide
          */
-        private Colors getColors(boolean isHeader) {
+        public Colors getColors(boolean isHeader) {
             mColors.resolvePalette(mContext, mN.color, !isHeader && mN.isColorized(), mInNightMode);
             return mColors;
         }
@@ -14765,7 +14766,6 @@
      * A utility which stores and calculates the palette of colors used to color notifications.
      * @hide
      */
-    @VisibleForTesting
     public static class Colors {
         private int mPaletteIsForRawColor = COLOR_INVALID;
         private boolean mPaletteIsForColorized = false;
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index a88a172..c800119 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -486,7 +486,7 @@
     }
 
     /**
-     * Sets to containers adjacent to each other. Containers below two visible adjacent roots will
+     * Sets two containers adjacent to each other. Containers below two visible adjacent roots will
      * be made invisible. This currently only applies to TaskFragment containers created by
      * organizer.
      * @param root1 the first root.
@@ -495,9 +495,64 @@
     @NonNull
     public WindowContainerTransaction setAdjacentRoots(
             @NonNull WindowContainerToken root1, @NonNull WindowContainerToken root2) {
-        mHierarchyOps.add(HierarchyOp.createForAdjacentRoots(
-                root1.asBinder(),
-                root2.asBinder()));
+        if (!Flags.allowMultipleAdjacentTaskFragments()) {
+            mHierarchyOps.add(HierarchyOp.createForAdjacentRoots(
+                    root1.asBinder(),
+                    root2.asBinder()));
+            return this;
+        }
+        return setAdjacentRootSet(root1, root2);
+    }
+
+    /**
+     * Sets multiple containers adjacent to each other. Containers below the visible adjacent roots
+     * will be made invisible. This currently only applies to Task containers created by organizer.
+     *
+     * To remove one container from the adjacent roots, one can call {@link #clearAdjacentRoots}
+     * with the target container.
+     * To remove all containers from the adjacent roots, one much call {@link #clearAdjacentRoots}
+     * on each container if there were more than two containers in the set.
+     *
+     * For non-Task TaskFragment, use {@link #setAdjacentTaskFragments} instead.
+     *
+     * @param roots the Tasks that should be adjacent to each other.
+     * @throws IllegalArgumentException if roots have size < 2.
+     * @hide // TODO(b/373709676) Rename to setAdjacentRoots and update CTS.
+     */
+    @NonNull
+    public WindowContainerTransaction setAdjacentRootSet(
+            @NonNull WindowContainerToken... roots) {
+        if (!Flags.allowMultipleAdjacentTaskFragments()) {
+            throw new IllegalArgumentException("allowMultipleAdjacentTaskFragments is not enabled."
+                    + " Use #setAdjacentRoots instead.");
+        }
+        if (roots.length < 2) {
+            throw new IllegalArgumentException("setAdjacentRootSet must have size >= 2");
+        }
+        final IBinder[] rootTokens = new IBinder[roots.length];
+        for (int i = 0; i < roots.length; i++) {
+            rootTokens[i] = roots[i].asBinder();
+        }
+        mHierarchyOps.add(
+                new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS)
+                        .setContainers(rootTokens)
+                        .build());
+        return this;
+    }
+
+    /**
+     * Clears container adjacent.
+     * If {@link #setAdjacentRootSet} is called with more than 2 roots, calling this will only
+     * remove the given root from the adjacent set. The rest of roots will stay adjacent to each
+     * other.
+     *
+     * @param root the root container to clear the adjacent roots for.
+     * @hide
+     */
+    @NonNull
+    public WindowContainerTransaction clearAdjacentRoots(
+            @NonNull WindowContainerToken root) {
+        mHierarchyOps.add(HierarchyOp.createForClearAdjacentRoots(root.asBinder()));
         return this;
     }
 
@@ -967,18 +1022,6 @@
     }
 
     /**
-     * Clears container adjacent.
-     * @param root the root container to clear the adjacent roots for.
-     * @hide
-     */
-    @NonNull
-    public WindowContainerTransaction clearAdjacentRoots(
-            @NonNull WindowContainerToken root) {
-        mHierarchyOps.add(HierarchyOp.createForClearAdjacentRoots(root.asBinder()));
-        return this;
-    }
-
-    /**
      * Sets/removes the reparent leaf task flag for this {@code windowContainer}.
      * When this is set, the server side will try to reparent the leaf task to task display area
      * if there is an existing activity in history during the activity launch. This operation only
@@ -1520,6 +1563,9 @@
         @Nullable
         private IBinder mContainer;
 
+        @Nullable
+        private IBinder[] mContainers;
+
         // If this is same as mContainer, then only change position, don't reparent.
         @Nullable
         private IBinder mReparent;
@@ -1704,6 +1750,7 @@
         public HierarchyOp(@NonNull HierarchyOp copy) {
             mType = copy.mType;
             mContainer = copy.mContainer;
+            mContainers = copy.mContainers;
             mBounds = copy.mBounds;
             mIncludingParents = copy.mIncludingParents;
             mReparent = copy.mReparent;
@@ -1729,6 +1776,7 @@
         protected HierarchyOp(Parcel in) {
             mType = in.readInt();
             mContainer = in.readStrongBinder();
+            mContainers = in.createBinderArray();
             mBounds = in.readTypedObject(Rect.CREATOR);
             mIncludingParents = in.readBoolean();
             mReparent = in.readStrongBinder();
@@ -1780,6 +1828,13 @@
         }
 
         @NonNull
+        public IBinder[] getContainers() {
+            return mContainers;
+        }
+
+        /** @deprecated b/373709676 replace with {@link #getContainers()}. */
+        @Deprecated
+        @NonNull
         public IBinder getAdjacentRoot() {
             return mReparent;
         }
@@ -1869,7 +1924,7 @@
                 case HIERARCHY_OP_TYPE_REORDER: return "reorder";
                 case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: return "childrenTasksReparent";
                 case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: return "setLaunchRoot";
-                case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: return "setAdjacentRoot";
+                case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: return "setAdjacentRoots";
                 case HIERARCHY_OP_TYPE_LAUNCH_TASK: return "launchTask";
                 case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT: return "setAdjacentFlagRoot";
                 case HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT:
@@ -1883,7 +1938,7 @@
                 case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: return "setAlwaysOnTop";
                 case HIERARCHY_OP_TYPE_REMOVE_TASK: return "removeTask";
                 case HIERARCHY_OP_TYPE_FINISH_ACTIVITY: return "finishActivity";
-                case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS: return "clearAdjacentRoot";
+                case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS: return "clearAdjacentRoots";
                 case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH:
                     return "setReparentLeafTaskIfRelaunch";
                 case HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION:
@@ -1923,8 +1978,18 @@
                     sb.append(mContainer).append(" to ").append(mToTop ? "top" : "bottom");
                     break;
                 case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS:
-                    sb.append("container=").append(mContainer)
-                            .append(" adjacentRoot=").append(mReparent);
+                    if (Flags.allowMultipleAdjacentTaskFragments()) {
+                        for (IBinder container : mContainers) {
+                            if (container == mContainers[0]) {
+                                sb.append("adjacentRoots=").append(container);
+                            } else {
+                                sb.append(", ").append(container);
+                            }
+                        }
+                    } else {
+                        sb.append("container=").append(mContainer)
+                                .append(" adjacentRoot=").append(mReparent);
+                    }
                     break;
                 case HIERARCHY_OP_TYPE_LAUNCH_TASK:
                     sb.append(mLaunchOptions);
@@ -1997,6 +2062,7 @@
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeInt(mType);
             dest.writeStrongBinder(mContainer);
+            dest.writeBinderArray(mContainers);
             dest.writeTypedObject(mBounds, flags);
             dest.writeBoolean(mIncludingParents);
             dest.writeStrongBinder(mReparent);
@@ -2044,6 +2110,9 @@
             private IBinder mContainer;
 
             @Nullable
+            private IBinder[] mContainers;
+
+            @Nullable
             private IBinder mReparent;
 
             @Nullable
@@ -2104,6 +2173,11 @@
                 return this;
             }
 
+            Builder setContainers(@Nullable IBinder[] containers) {
+                mContainers = containers;
+                return this;
+            }
+
             Builder setReparentContainer(@Nullable IBinder reparentContainer) {
                 mReparent = reparentContainer;
                 return this;
@@ -2209,6 +2283,7 @@
             HierarchyOp build() {
                 final HierarchyOp hierarchyOp = new HierarchyOp(mType);
                 hierarchyOp.mContainer = mContainer;
+                hierarchyOp.mContainers = mContainers;
                 hierarchyOp.mReparent = mReparent;
                 hierarchyOp.mWindowingModes = mWindowingModes != null
                         ? Arrays.copyOf(mWindowingModes, mWindowingModes.length)
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt
index 92b6fd4..2a27a30 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt
@@ -59,9 +59,9 @@
         private const val INJECT_ANNOTATION = "javax.inject.Inject"
         private const val APPLICATION_ANNOTATION =
             "com.android.systemui.dagger.qualifiers.Application"
-        private const val GLOBAL_CONFIG_ANNOTATION = "com.android.systemui.common.ui.GlobalConfig"
         private const val SHADE_DISPLAY_AWARE_ANNOTATION =
             "com.android.systemui.shade.ShadeDisplayAware"
+        private const val MAIN_ANNOTATION = "com.android.systemui.dagger.qualifiers.Main"
 
         private const val CONTEXT = "android.content.Context"
         private const val WINDOW_MANAGER = "android.view.WindowManager"
@@ -108,13 +108,10 @@
 
             // check if the parameter is a context-dependent class relevant to shade
             if (className !in CONTEXT_DEPENDENT_SHADE_CLASSES) return false
-            // check if it has @ShadeDisplayAware
-            if (hasAnnotation(SHADE_DISPLAY_AWARE_ANNOTATION)) return false
+            if (hasAnnotation(SHADE_DISPLAY_AWARE_ANNOTATION) || hasAnnotation(MAIN_ANNOTATION))
+                return false
             // check if its a @Application-annotated Context
             if (className == CONTEXT && hasAnnotation(APPLICATION_ANNOTATION)) return false
-            // check if its a @GlobalConfig-annotated ConfigurationState, ConfigurationController
-            // or ConfigurationInteractor
-            if (className in CONFIG_CLASSES && hasAnnotation(GLOBAL_CONFIG_ANNOTATION)) return false
 
             return true
         }
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt
index 79f1907..638d7cb7 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt
@@ -73,12 +73,12 @@
             )
             .indented()
 
-    private val globalConfigStub: TestFile =
+    private val mainStub: TestFile =
         kotlin(
                 """
-                package com.android.systemui.common.ui
+                package com.android.systemui.dagger.qualifiers
 
-                @Retention(AnnotationRetention.RUNTIME) annotation class GlobalConfig
+                @Retention(AnnotationRetention.RUNTIME) annotation class Main
                 """
             )
             .indented()
@@ -119,7 +119,7 @@
             qsContext,
             shadeDisplayAwareStub,
             applicationStub,
-            globalConfigStub,
+            mainStub,
             configStateStub,
             configControllerStub,
             configInteractorStub,
@@ -308,7 +308,7 @@
     }
 
     @Test
-    fun injectedConstructor_inRelevantPackage_withGlobalConfigAnnotatedConfigurationClass() {
+    fun injectedConstructor_inRelevantPackage_withMainAnnotatedConfigurationClass() {
         lint()
             .files(
                 TestFiles.kotlin(
@@ -317,11 +317,11 @@
 
                         import javax.inject.Inject
                         import com.android.systemui.common.ui.ConfigurationState
-                        import com.android.systemui.common.ui.GlobalConfig
+                        import com.android.systemui.dagger.qualifiers.Main
 
                         class ExampleClass
                             @Inject
-                            constructor(@GlobalConfig private val configState: ConfigurationState)
+                            constructor(@Main private val configState: ConfigurationState)
                     """
                         .trimIndent()
                 ),
diff --git a/packages/SystemUI/lint-baseline.xml b/packages/SystemUI/lint-baseline.xml
index 09abc37..523adc6 100644
--- a/packages/SystemUI/lint-baseline.xml
+++ b/packages/SystemUI/lint-baseline.xml
@@ -33959,5 +33959,6 @@
             file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt"
             line="53"
             column="5"/>
+    </issue>
 
 </issues>
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt
index ef1ae09..fd9f5f0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.display.data.repository.display
 import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.testKosmos
@@ -38,17 +39,31 @@
 class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
     private val kosmos = testKosmos().useUnconfinedTestDispatcher()
     private val testScope = kosmos.testScope
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
     private val displayRepository = kosmos.displayRepository
-    val underTest = StatusBarTouchShadeDisplayPolicy(displayRepository, testScope.backgroundScope)
+
+    private fun createUnderTest(
+        shadeOnDefaultDisplayWhenLocked: Boolean = false
+    ): StatusBarTouchShadeDisplayPolicy {
+        return StatusBarTouchShadeDisplayPolicy(
+            displayRepository,
+            keyguardRepository,
+            testScope.backgroundScope,
+            shadeOnDefaultDisplayWhenLocked = shadeOnDefaultDisplayWhenLocked,
+        )
+    }
 
     @Test
     fun displayId_defaultToDefaultDisplay() {
+        val underTest = createUnderTest()
+
         assertThat(underTest.displayId.value).isEqualTo(Display.DEFAULT_DISPLAY)
     }
 
     @Test
     fun onStatusBarTouched_called_updatesDisplayId() =
         testScope.runTest {
+            val underTest = createUnderTest()
             val displayId by collectLastValue(underTest.displayId)
 
             displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
@@ -60,6 +75,7 @@
     @Test
     fun onStatusBarTouched_notExistentDisplay_displayIdNotUpdated() =
         testScope.runTest {
+            val underTest = createUnderTest()
             val displayIds by collectValues(underTest.displayId)
             assertThat(displayIds).isEqualTo(listOf(Display.DEFAULT_DISPLAY))
 
@@ -72,6 +88,7 @@
     @Test
     fun onStatusBarTouched_afterDisplayRemoved_goesBackToDefaultDisplay() =
         testScope.runTest {
+            val underTest = createUnderTest()
             val displayId by collectLastValue(underTest.displayId)
 
             displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
@@ -83,4 +100,40 @@
 
             assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
         }
+
+    @Test
+    fun onStatusBarTouched_afterKeyguardVisible_goesBackToDefaultDisplay() =
+        testScope.runTest {
+            val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true)
+            val displayId by collectLastValue(underTest.displayId)
+
+            displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
+            underTest.onStatusBarTouched(2)
+
+            assertThat(displayId).isEqualTo(2)
+
+            keyguardRepository.setKeyguardShowing(true)
+
+            assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+        }
+
+    @Test
+    fun onStatusBarTouched_afterKeyguardHides_goesBackToPreviousDisplay() =
+        testScope.runTest {
+            val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true)
+            val displayId by collectLastValue(underTest.displayId)
+
+            displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
+            underTest.onStatusBarTouched(2)
+
+            assertThat(displayId).isEqualTo(2)
+
+            keyguardRepository.setKeyguardShowing(true)
+
+            assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+
+            keyguardRepository.setKeyguardShowing(false)
+
+            assertThat(displayId).isEqualTo(2)
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
index f06bab7..63efc55 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
@@ -46,7 +47,12 @@
         kosmos.runTest {
             val icon = mock<StatusBarIconView>()
             val startingNotif =
-                activeNotificationModel(key = "notif1", statusBarChipIcon = icon, whenTime = 5432)
+                activeNotificationModel(
+                    key = "notif1",
+                    statusBarChipIcon = icon,
+                    whenTime = 5432,
+                    promotedContent = PROMOTED_CONTENT,
+                )
 
             val underTest = factory.create(startingNotif)
 
@@ -63,7 +69,11 @@
             val originalIconView = mock<StatusBarIconView>()
             val underTest =
                 factory.create(
-                    activeNotificationModel(key = "notif1", statusBarChipIcon = originalIconView)
+                    activeNotificationModel(
+                        key = "notif1",
+                        statusBarChipIcon = originalIconView,
+                        promotedContent = PROMOTED_CONTENT,
+                    )
                 )
 
             val latest by collectLastValue(underTest.notificationChip)
@@ -74,6 +84,7 @@
                     key = "notif1",
                     statusBarChipIcon = newIconView,
                     whenTime = 6543,
+                    promotedContent = PROMOTED_CONTENT,
                 )
             )
 
@@ -88,14 +99,22 @@
             val originalIconView = mock<StatusBarIconView>()
             val underTest =
                 factory.create(
-                    activeNotificationModel(key = "notif1", statusBarChipIcon = originalIconView)
+                    activeNotificationModel(
+                        key = "notif1",
+                        statusBarChipIcon = originalIconView,
+                        promotedContent = PROMOTED_CONTENT,
+                    )
                 )
 
             val latest by collectLastValue(underTest.notificationChip)
 
             val newIconView = mock<StatusBarIconView>()
             underTest.setNotification(
-                activeNotificationModel(key = "other_notif", statusBarChipIcon = newIconView)
+                activeNotificationModel(
+                    key = "other_notif",
+                    statusBarChipIcon = newIconView,
+                    promotedContent = PROMOTED_CONTENT,
+                )
             )
 
             assertThat(latest!!.key).isEqualTo("notif1")
@@ -103,10 +122,43 @@
         }
 
     @Test
+    fun notificationChip_ignoresSetWithNullPromotedContent() =
+        kosmos.runTest {
+            val originalIconView = mock<StatusBarIconView>()
+            val underTest =
+                factory.create(
+                    activeNotificationModel(
+                        key = "notif1",
+                        statusBarChipIcon = originalIconView,
+                        promotedContent = PROMOTED_CONTENT,
+                    )
+                )
+
+            val latest by collectLastValue(underTest.notificationChip)
+
+            val newIconView = mock<StatusBarIconView>()
+            underTest.setNotification(
+                activeNotificationModel(
+                    key = "notif1",
+                    statusBarChipIcon = newIconView,
+                    promotedContent = null,
+                )
+            )
+
+            assertThat(latest!!.statusBarChipIconView).isEqualTo(originalIconView)
+        }
+
+    @Test
     fun notificationChip_missingStatusBarIconChipView_inConstructor_emitsNull() =
         kosmos.runTest {
             val underTest =
-                factory.create(activeNotificationModel(key = "notif1", statusBarChipIcon = null))
+                factory.create(
+                    activeNotificationModel(
+                        key = "notif1",
+                        statusBarChipIcon = null,
+                        promotedContent = PROMOTED_CONTENT,
+                    )
+                )
 
             val latest by collectLastValue(underTest.notificationChip)
 
@@ -123,6 +175,7 @@
                         key = "notif1",
                         statusBarChipIcon = null,
                         whenTime = 123L,
+                        promotedContent = PROMOTED_CONTENT,
                     )
                 )
 
@@ -130,20 +183,34 @@
 
             assertThat(latest)
                 .isEqualTo(
-                    NotificationChipModel("notif1", statusBarChipIconView = null, whenTime = 123L)
+                    NotificationChipModel(
+                        "notif1",
+                        statusBarChipIconView = null,
+                        whenTime = 123L,
+                        promotedContent = PROMOTED_CONTENT,
+                    )
                 )
         }
 
     @Test
     fun notificationChip_missingStatusBarIconChipView_inSet_emitsNull() =
         kosmos.runTest {
-            val startingNotif = activeNotificationModel(key = "notif1", statusBarChipIcon = mock())
+            val startingNotif =
+                activeNotificationModel(
+                    key = "notif1",
+                    statusBarChipIcon = mock(),
+                    promotedContent = PROMOTED_CONTENT,
+                )
             val underTest = factory.create(startingNotif)
             val latest by collectLastValue(underTest.notificationChip)
             assertThat(latest).isNotNull()
 
             underTest.setNotification(
-                activeNotificationModel(key = "notif1", statusBarChipIcon = null)
+                activeNotificationModel(
+                    key = "notif1",
+                    statusBarChipIcon = null,
+                    promotedContent = PROMOTED_CONTENT,
+                )
             )
 
             assertThat(latest).isNull()
@@ -153,13 +220,23 @@
     @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
     fun notificationChip_missingStatusBarIconChipView_inSet_cdEnabled_emitsNotNull() =
         kosmos.runTest {
-            val startingNotif = activeNotificationModel(key = "notif1", statusBarChipIcon = mock())
+            val startingNotif =
+                activeNotificationModel(
+                    key = "notif1",
+                    statusBarChipIcon = mock(),
+                    promotedContent = PROMOTED_CONTENT,
+                )
             val underTest = factory.create(startingNotif)
             val latest by collectLastValue(underTest.notificationChip)
             assertThat(latest).isNotNull()
 
             underTest.setNotification(
-                activeNotificationModel(key = "notif1", statusBarChipIcon = null, whenTime = 123L)
+                activeNotificationModel(
+                    key = "notif1",
+                    statusBarChipIcon = null,
+                    whenTime = 123L,
+                    promotedContent = PROMOTED_CONTENT,
+                )
             )
 
             assertThat(latest)
@@ -168,18 +245,41 @@
                         key = "notif1",
                         statusBarChipIconView = null,
                         whenTime = 123L,
+                        promotedContent = PROMOTED_CONTENT,
                     )
                 )
         }
 
     @Test
+    fun notificationChip_missingPromotedContent_inConstructor_emitsNull() =
+        kosmos.runTest {
+            val underTest =
+                factory.create(
+                    activeNotificationModel(
+                        key = "notif1",
+                        statusBarChipIcon = mock(),
+                        promotedContent = null,
+                    )
+                )
+
+            val latest by collectLastValue(underTest.notificationChip)
+
+            assertThat(latest).isNull()
+        }
+
+    @Test
     fun notificationChip_appIsVisibleOnCreation_emitsNull() =
         kosmos.runTest {
             activityManagerRepository.fake.startingIsAppVisibleValue = true
 
             val underTest =
                 factory.create(
-                    activeNotificationModel(key = "notif", uid = UID, statusBarChipIcon = mock())
+                    activeNotificationModel(
+                        key = "notif",
+                        uid = UID,
+                        statusBarChipIcon = mock(),
+                        promotedContent = PROMOTED_CONTENT,
+                    )
                 )
 
             val latest by collectLastValue(underTest.notificationChip)
@@ -194,7 +294,12 @@
 
             val underTest =
                 factory.create(
-                    activeNotificationModel(key = "notif", uid = UID, statusBarChipIcon = mock())
+                    activeNotificationModel(
+                        key = "notif",
+                        uid = UID,
+                        statusBarChipIcon = mock(),
+                        promotedContent = PROMOTED_CONTENT,
+                    )
                 )
 
             val latest by collectLastValue(underTest.notificationChip)
@@ -207,7 +312,12 @@
         kosmos.runTest {
             val underTest =
                 factory.create(
-                    activeNotificationModel(key = "notif", uid = UID, statusBarChipIcon = mock())
+                    activeNotificationModel(
+                        key = "notif",
+                        uid = UID,
+                        statusBarChipIcon = mock(),
+                        promotedContent = PROMOTED_CONTENT,
+                    )
                 )
 
             val latest by collectLastValue(underTest.notificationChip)
@@ -239,6 +349,7 @@
                         key = "notif",
                         uid = hiddenUid,
                         statusBarChipIcon = mock(),
+                        promotedContent = PROMOTED_CONTENT,
                     )
                 )
             val latest by collectLastValue(underTest.notificationChip)
@@ -247,7 +358,12 @@
             // WHEN the notif gets a new UID that starts as visible
             activityManagerRepository.fake.startingIsAppVisibleValue = true
             underTest.setNotification(
-                activeNotificationModel(key = "notif", uid = shownUid, statusBarChipIcon = mock())
+                activeNotificationModel(
+                    key = "notif",
+                    uid = shownUid,
+                    statusBarChipIcon = mock(),
+                    promotedContent = PROMOTED_CONTENT,
+                )
             )
 
             // THEN we re-fetch the app visibility state with the new UID, and since that UID is
@@ -257,5 +373,6 @@
 
     companion object {
         private const val UID = 885
+        private val PROMOTED_CONTENT = PromotedNotificationContentModel.Builder("notif1").build()
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index 8a4ddce..b174fce 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
@@ -132,6 +133,36 @@
         }
 
     @Test
+    fun chips_onePromotedNotif_colorMatches() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.chips)
+
+            val promotedContentBuilder =
+                PromotedNotificationContentModel.Builder("notif").apply {
+                    this.colors =
+                        PromotedNotificationContentModel.Colors(
+                            backgroundColor = 56,
+                            primaryTextColor = 89,
+                        )
+                }
+            setNotifs(
+                listOf(
+                    activeNotificationModel(
+                        key = "notif",
+                        statusBarChipIcon = mock<StatusBarIconView>(),
+                        promotedContent = promotedContentBuilder.build(),
+                    )
+                )
+            )
+
+            assertThat(latest).hasSize(1)
+            val colors = latest!![0].colors
+            assertThat(colors).isInstanceOf(ColorsModel.Custom::class.java)
+            assertThat((colors as ColorsModel.Custom).backgroundColorInt).isEqualTo(56)
+            assertThat((colors as ColorsModel.Custom).primaryTextColorInt).isEqualTo(89)
+        }
+
+    @Test
     fun chips_onlyForPromotedNotifs() =
         kosmos.runTest {
             val latest by collectLastValue(underTest.chips)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 316849d..d0cb507 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -714,12 +714,12 @@
         onDialogDismissed(reason);
     }
     @Inject
-    public AuthController(Context context,
+    public AuthController(@Main Context context,
             @Application CoroutineScope applicationCoroutineScope,
             Execution execution,
             CommandQueue commandQueue,
             ActivityTaskManager activityTaskManager,
-            @NonNull WindowManager windowManager,
+            @NonNull @Main WindowManager windowManager,
             @Nullable FingerprintManager fingerprintManager,
             @Nullable FaceManager faceManager,
             Optional<AuthContextPlugins> contextPlugins,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index f6cc724..22d2aaf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.biometrics.data.repository.FacePropertyRepository
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.deviceentry.domain.interactor.AuthRippleInteractor
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
@@ -69,9 +70,9 @@
 class AuthRippleController
 @Inject
 constructor(
-    private val sysuiContext: Context,
+    @Main private val sysuiContext: Context,
     private val authController: AuthController,
-    private val configurationController: ConfigurationController,
+    @Main private val configurationController: ConfigurationController,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val keyguardStateController: KeyguardStateController,
     private val wakefulnessLifecycle: WakefulnessLifecycle,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java
index 027f674..8376850 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java
@@ -22,6 +22,7 @@
 import android.hardware.biometrics.BiometricSourceType;
 
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 
 import javax.inject.Inject;
 
@@ -43,7 +44,7 @@
     private final BiometricNotificationDialogFactory mNotificationDialogFactory;
     @Inject
     BiometricNotificationBroadcastReceiver(
-            Context context,
+            @Main Context context,
             BiometricNotificationDialogFactory notificationDialogFactory) {
         mContext = context;
         mNotificationDialogFactory = notificationDialogFactory;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
index 3b49ce2..e5c2267 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
@@ -45,6 +45,7 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -145,7 +146,7 @@
     };
 
     @Inject
-    public BiometricNotificationService(@NonNull Context context,
+    public BiometricNotificationService(@NonNull @Main Context context,
             @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
             @NonNull KeyguardStateController keyguardStateController,
             @NonNull Handler handler, @NonNull NotificationManager notificationManager,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
index 178e111..d9ed9ca 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.shade.ShadeDisplayAware
+import com.android.systemui.dagger.qualifiers.Main
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
@@ -43,7 +43,7 @@
     @Application private val applicationScope: CoroutineScope,
     @Application private val context: Context,
     repository: FingerprintPropertyRepository,
-    @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
+    @Main configurationInteractor: ConfigurationInteractor,
     displayStateInteractor: DisplayStateInteractor,
     udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
index c3dc2d4..52e8557 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.isDefaultOrientation
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -48,9 +49,9 @@
 class SideFpsSensorInteractor
 @Inject
 constructor(
-    private val context: Context,
+    @Main private val context: Context,
     fingerprintPropertyRepository: FingerprintPropertyRepository,
-    windowManager: WindowManager,
+    @Main windowManager: WindowManager,
     displayStateInteractor: DisplayStateInteractor,
     fingerprintInteractiveToAuthProvider: Optional<FingerprintInteractiveToAuthProvider>,
     biometricSettingsRepository: BiometricSettingsRepository,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt
index a42ae03..cdd1b3c 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt
@@ -18,7 +18,6 @@
 
 import android.content.res.Resources
 import com.android.internal.R
-import com.android.systemui.common.ui.GlobalConfig
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -37,7 +36,7 @@
 constructor(
     @Application private val applicationScope: CoroutineScope,
     @Main private val resources: Resources,
-    @GlobalConfig configurationRepository: ConfigurationRepository,
+    @Main configurationRepository: ConfigurationRepository,
 ) {
     /**
      * Whether to enable emergency services calls while the SIM card is locked. This is disabled in
@@ -49,7 +48,7 @@
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
-                initialValue = getEnableEmergencyCallWhileSimLocked()
+                initialValue = getEnableEmergencyCallWhileSimLocked(),
             )
 
     private fun getEnableEmergencyCallWhileSimLocked(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationModule.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationModule.kt
index 7f50e4a..ad504d5 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationModule.kt
@@ -21,21 +21,11 @@
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.policy.ConfigurationController
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
-import javax.inject.Qualifier
-
-/**
- * Annotates elements that provide information from the global configuration.
- *
- * The global configuration is the one associated with the main display. Secondary displays will
- * apply override to the global configuration. Elements annotated with this shouldn't be used for
- * secondary displays.
- */
-@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class GlobalConfig
 
 @Module
 interface ConfigurationModule {
@@ -45,32 +35,32 @@
      * now, without annotation the global config associated state is provided.
      */
     @Binds
-    @Deprecated("Use the @GlobalConfig annotated one instead of this.")
+    @Deprecated("Use the @Main annotated one instead of this.")
     fun provideGlobalConfigurationState(
-        @GlobalConfig configurationState: ConfigurationState
+        @Main configurationState: ConfigurationState
     ): ConfigurationState
 
     @Binds
-    @Deprecated("Use the @GlobalConfig annotated one instead of this.")
+    @Deprecated("Use the @Main annotated one instead of this.")
     fun provideDefaultConfigurationState(
-        @GlobalConfig configurationState: ConfigurationInteractor
+        @Main configurationState: ConfigurationInteractor
     ): ConfigurationInteractor
 
     companion object {
         @SysUISingleton
         @Provides
-        @GlobalConfig
+        @Main
         fun provideGlobalConfigurationState(
             configStateFactory: ConfigurationStateImpl.Factory,
             configurationController: ConfigurationController,
-            @Application context: Context,
+            @Main context: Context,
         ): ConfigurationState {
             return configStateFactory.create(context, configurationController)
         }
 
         @SysUISingleton
         @Provides
-        @GlobalConfig
+        @Main
         fun provideGlobalConfigurationInteractor(
             configurationRepository: ConfigurationRepository
         ): ConfigurationInteractor {
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
index df89152..4d804d0 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
@@ -23,9 +23,9 @@
 import androidx.annotation.DimenRes
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.common.ui.GlobalConfig
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.wrapper.DisplayUtilsWrapper
 import dagger.Binds
@@ -162,19 +162,19 @@
      * injected.
      */
     @Binds
-    @Deprecated("Use the ConfigurationRepository annotated with @GlobalConfig instead.")
+    @Deprecated("Use the ConfigurationRepository annotated with @Main instead.")
     @SysUISingleton
     abstract fun provideDefaultConfigRepository(
-        @GlobalConfig configurationRepository: ConfigurationRepository
+        @Main configurationRepository: ConfigurationRepository
     ): ConfigurationRepository
 
     companion object {
         @Provides
-        @GlobalConfig
+        @Main
         @SysUISingleton
         fun provideGlobalConfigRepository(
             context: Context,
-            @GlobalConfig configurationController: ConfigurationController,
+            @Main configurationController: ConfigurationController,
             factory: ConfigurationRepositoryImpl.Factory,
         ): ConfigurationRepository {
             return factory.create(context, configurationController)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 78a8a42..9ae106c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -708,6 +708,14 @@
         return context.getSystemService(WindowManager.class);
     }
 
+    /** A window manager working for the default display only. */
+    @Provides
+    @Singleton
+    @Main
+    static WindowManager provideMainWindowManager(WindowManager windowManager) {
+        return windowManager;
+    }
+
     @Provides
     @Singleton
     static ViewCaptureAwareWindowManager provideViewCaptureAwareWindowManager(
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
index 3072f74..ddc88a8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
@@ -21,6 +21,7 @@
 import android.view.Display;
 
 import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.PluginsModule;
 import com.android.systemui.unfold.UnfoldTransitionModule;
 import com.android.systemui.util.concurrency.GlobalConcurrencyModule;
@@ -62,6 +63,13 @@
         return context.getApplicationContext();
     }
 
+    /** Provides the default content with the main annotation. */
+    @Provides
+    @Main
+    public Context provideMainContext(Context context) {
+        return context;
+    }
+
     /**
      * @deprecated Deprecdated because {@link Display#getMetrics} is deprecated.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 580896c..00eead6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -21,7 +21,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.InitController;
 import com.android.systemui.SystemUIAppComponentFactoryBase;
-import com.android.systemui.common.ui.GlobalConfig;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.PerUser;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
@@ -128,14 +128,14 @@
      * Creates a ConfigurationController.
      */
     @SysUISingleton
-    @GlobalConfig
+    @Main
     ConfigurationController getConfigurationController();
 
     /**
      * Creates a ConfigurationForwarder.
      */
     @SysUISingleton
-    @GlobalConfig
+    @Main
     ConfigurationForwarder getConfigurationForwarder();
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt
index 84a423e..c3e6f0c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt
@@ -21,11 +21,10 @@
 import android.view.WindowManager
 import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.settingslib.Utils
-import com.android.systemui.common.ui.GlobalConfig
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyboard.docking.domain.interactor.KeyboardDockingIndicationInteractor
 import com.android.systemui.surfaceeffects.glowboxeffect.GlowBoxConfig
 import javax.inject.Inject
@@ -37,10 +36,10 @@
 class KeyboardDockingIndicationViewModel
 @Inject
 constructor(
-    private val windowManager: WindowManager,
-    @Application private val context: Context,
+    @Main private val windowManager: WindowManager,
+    @Main private val context: Context,
     keyboardDockingIndicationInteractor: KeyboardDockingIndicationInteractor,
-    @GlobalConfig configurationInteractor: ConfigurationInteractor,
+    @Main configurationInteractor: ConfigurationInteractor,
     @Background private val backgroundScope: CoroutineScope,
 ) {
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt
index 2a9fe83..1e99697 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt
@@ -93,7 +93,7 @@
             scrubbing = false,
             elapsedTime = null,
             duration = 0,
-            listening = false
+            listening = false,
         )
         set(value) {
             val enabledChanged = value.enabled != field.enabled
@@ -135,7 +135,6 @@
 
             override fun onMetadataChanged(metadata: MediaMetadata?) {
                 if (!Flags.mediaControlsPostsOptimization()) return
-
                 val (enabled, duration) = getEnabledStateAndDuration(metadata)
                 if (_data.duration != duration) {
                     _data = _data.copy(enabled = enabled, duration = duration)
@@ -323,7 +322,7 @@
                     bgExecutor.executeRepeatedly(
                         this::checkPlaybackPosition,
                         0L,
-                        POSITION_UPDATE_INTERVAL_MILLIS
+                        POSITION_UPDATE_INTERVAL_MILLIS,
                     )
                 cancel = Runnable {
                     cancelPolling.run()
@@ -331,6 +330,7 @@
                 }
             }
         } else {
+            checkPlaybackPosition()
             cancel?.run()
             cancel = null
         }
@@ -542,7 +542,7 @@
             eventStart: MotionEvent?,
             event: MotionEvent,
             distanceX: Float,
-            distanceY: Float
+            distanceY: Float,
         ): Boolean {
             return shouldGoToSeekBar
         }
@@ -556,7 +556,7 @@
             eventStart: MotionEvent?,
             event: MotionEvent,
             velocityX: Float,
-            velocityY: Float
+            velocityY: Float,
         ): Boolean {
             if (Math.abs(velocityX) > flingVelocity || Math.abs(velocityY) > flingVelocity) {
                 viewModel.onSeekFalse()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index a002aa5..d31868c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -26,12 +26,12 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.common.ui.ConfigurationStateImpl
-import com.android.systemui.common.ui.GlobalConfig
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.res.R
 import com.android.systemui.scene.ui.view.WindowRootView
 import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository
@@ -49,6 +49,7 @@
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
 import javax.inject.Provider
+import javax.inject.Qualifier
 
 /**
  * Module responsible for managing display-specific components and resources for the notification
@@ -133,7 +134,7 @@
     fun provideShadeWindowConfigurationController(
         @ShadeDisplayAware shadeContext: Context,
         factory: ConfigurationControllerImpl.Factory,
-        @GlobalConfig globalConfigController: ConfigurationController,
+        @Main globalConfigController: ConfigurationController,
     ): ConfigurationController {
         return if (ShadeWindowGoesAround.isEnabled) {
             factory.create(shadeContext)
@@ -159,7 +160,7 @@
         factory: ConfigurationStateImpl.Factory,
         @ShadeDisplayAware configurationController: ConfigurationController,
         @ShadeDisplayAware context: Context,
-        @GlobalConfig configurationState: ConfigurationState,
+        @Main configurationState: ConfigurationState,
     ): ConfigurationState {
         return if (ShadeWindowGoesAround.isEnabled) {
             factory.create(context, configurationController)
@@ -175,7 +176,7 @@
         factory: ConfigurationRepositoryImpl.Factory,
         @ShadeDisplayAware configurationController: ConfigurationController,
         @ShadeDisplayAware context: Context,
-        @GlobalConfig globalConfigurationRepository: ConfigurationRepository,
+        @Main globalConfigurationRepository: ConfigurationRepository,
     ): ConfigurationRepository {
         return if (ShadeWindowGoesAround.isEnabled) {
             factory.create(context, configurationController)
@@ -189,7 +190,7 @@
     @ShadeDisplayAware
     fun provideShadeAwareConfigurationInteractor(
         @ShadeDisplayAware configurationRepository: ConfigurationRepository,
-        @GlobalConfig configurationInteractor: ConfigurationInteractor,
+        @Main configurationInteractor: ConfigurationInteractor,
     ): ConfigurationInteractor {
         return if (ShadeWindowGoesAround.isEnabled) {
             ConfigurationInteractorImpl(configurationRepository)
@@ -237,9 +238,23 @@
             CoreStartable.NOP
         }
     }
+
+    @Provides
+    @ShadeOnDefaultDisplayWhenLocked
+    fun provideShadeOnDefaultDisplayWhenLocked(): Boolean = true
 }
 
 @Module
 internal interface OptionalShadeDisplayAwareBindings {
     @BindsOptionalOf fun bindOptionalOfWindowRootView(): WindowRootView
 }
+
+/**
+ * Annotates the boolean value that defines whether the shade window should go back to the default
+ * display when the keyguard is visible.
+ *
+ * As of today (Dec 2024), This is a configuration parameter provided in the dagger graph as the
+ * final policy around keyguard display is still under discussion, and will be evaluated based on
+ * how well this solution behaves from the performance point of view.
+ */
+@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class ShadeOnDefaultDisplayWhenLocked
diff --git a/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
index ae36e81..29c7aa0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
@@ -21,13 +21,18 @@
 import android.view.GestureDetector.SimpleOnGestureListener
 import android.view.MotionEvent
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
 import javax.inject.Inject
 
 /** Accepts touch events, detects long press, and calls ShadeViewController#onStatusBarLongPress. */
 @SysUISingleton
 class StatusBarLongPressGestureDetector
 @Inject
-constructor(context: Context, val shadeViewController: ShadeViewController) {
+constructor(
+    // TODO b/383125226 - Make this class per-display
+    @Main context: Context,
+    val shadeViewController: ShadeViewController,
+) {
     val gestureDetector =
         GestureDetector(
             context,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
index 22e9487..30b086f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
@@ -22,34 +22,55 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.shade.ShadeOnDefaultDisplayWhenLocked
 import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
 
 /**
  * Moves the shade on the last display that received a status bar touch.
  *
- * If the display is removed, falls back to the default one.
+ * If the display is removed, falls back to the default one. When [shadeOnDefaultDisplayWhenLocked]
+ * is true, the shade falls back to the default display when the keyguard is visible.
  */
 @SysUISingleton
 class StatusBarTouchShadeDisplayPolicy
 @Inject
-constructor(displayRepository: DisplayRepository, @Background val backgroundScope: CoroutineScope) :
-    ShadeDisplayPolicy {
-    override val name: String
-        get() = "status_bar_latest_touch"
+constructor(
+    displayRepository: DisplayRepository,
+    keyguardRepository: KeyguardRepository,
+    @Background val backgroundScope: CoroutineScope,
+    @ShadeOnDefaultDisplayWhenLocked val shadeOnDefaultDisplayWhenLocked: Boolean,
+) : ShadeDisplayPolicy {
+    override val name: String = "status_bar_latest_touch"
 
     private val currentDisplayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
     private val availableDisplayIds: StateFlow<Set<Int>> = displayRepository.displayIds
 
-    override val displayId: StateFlow<Int>
-        get() = currentDisplayId
+    override val displayId: StateFlow<Int> =
+        if (shadeOnDefaultDisplayWhenLocked) {
+            keyguardRepository.isKeyguardShowing
+                .combine(currentDisplayId) { isKeyguardShowing, currentDisplayId ->
+                    if (isKeyguardShowing) {
+                        Display.DEFAULT_DISPLAY
+                    } else {
+                        currentDisplayId
+                    }
+                }
+                .stateIn(backgroundScope, SharingStarted.WhileSubscribed(), currentDisplayId.value)
+        } else {
+            currentDisplayId
+        }
 
     private var removalListener: Job? = null
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
index 571a3e4..bbecde8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
@@ -57,6 +57,14 @@
     // top-level tag. It should instead be provided as the first string in each log message.
     private val extraLogTag = "SingleChipInteractor[key=$key]"
 
+    init {
+        if (startingModel.promotedContent == null) {
+            logger.e({ "$str1: Starting model has promotedContent=null, which shouldn't happen" }) {
+                str1 = extraLogTag
+            }
+        }
+    }
+
     private val _notificationModel = MutableStateFlow(startingModel)
 
     /**
@@ -71,6 +79,14 @@
             }
             return
         }
+        if (model.promotedContent == null) {
+            logger.e({
+                "$str1: received model with promotedContent=null, which shouldn't happen"
+            }) {
+                str1 = extraLogTag
+            }
+            return
+        }
         _notificationModel.value = model
     }
 
@@ -99,6 +115,15 @@
         }
 
     private fun ActiveNotificationModel.toNotificationChipModel(): NotificationChipModel? {
+        val promotedContent = this.promotedContent
+        if (promotedContent == null) {
+            logger.w({
+                "$str1: Can't show chip because promotedContent=null, which shouldn't happen"
+            }) {
+                str1 = extraLogTag
+            }
+            return null
+        }
         val statusBarChipIconView = this.statusBarChipIconView
         if (statusBarChipIconView == null) {
             if (!StatusBarConnectedDisplays.isEnabled) {
@@ -111,7 +136,8 @@
                 return null
             }
         }
-        return NotificationChipModel(key, statusBarChipIconView, whenTime)
+
+        return NotificationChipModel(key, statusBarChipIconView, whenTime, promotedContent)
     }
 
     @AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
index 4588b19..9f0638b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
@@ -17,10 +17,13 @@
 package com.android.systemui.statusbar.chips.notification.domain.model
 
 import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
 
 /** Modeling all the data needed to render a status bar notification chip. */
 data class NotificationChipModel(
     val key: String,
     val statusBarChipIconView: StatusBarIconView?,
+    // TODO(b/364653005): Use [PromotedNotificationContentModel.time] instead of a custom field.
     val whenTime: Long,
+    val promotedContent: PromotedNotificationContentModel,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index 2cd5bb3..e2c886a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
@@ -59,8 +59,11 @@
                 StatusBarConnectedDisplays.assertInNewMode()
                 OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(this.key)
             }
-        // TODO(b/364653005): Use the notification color if applicable.
-        val colors = ColorsModel.Themed
+        val colors =
+            ColorsModel.Custom(
+                backgroundColorInt = this.promotedContent.colors.backgroundColor,
+                primaryTextColorInt = this.promotedContent.colors.primaryTextColor,
+            )
         val onClickListener =
             View.OnClickListener {
                 // The notification pipeline needs everything to run on the main thread, so keep
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
index 4de4597..efedf41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
@@ -40,20 +40,25 @@
     }
 
     /**
-     * The chip should have the given background color, and text color that matches dark/light
-     * theme.
+     * The chip should have the given background color and primary text color.
+     *
+     * If [primaryTextColorInt] is null, the text color will match the current UI mode (light/dark).
      */
-    data class Custom(val backgroundColorInt: Int) : ColorsModel {
+    data class Custom(val backgroundColorInt: Int, val primaryTextColorInt: Int? = null) :
+        ColorsModel {
         override fun background(context: Context): ColorStateList =
             ColorStateList.valueOf(backgroundColorInt)
 
-        // TODO(b/361346412): When dark theme changes, the chip should automatically re-render with
+        // TODO(b/361346412): When UI mode changes, the chip should automatically re-render with
         // the right text color. Right now, it has the right text color when the chip is first
-        // created but the color doesn't update if dark theme changes.
-        override fun text(context: Context) =
-            context.getColor(
-                com.android.internal.R.color.materialColorOnSurface
-            )
+        // created but the color doesn't update if UI mode changes.
+        override fun text(context: Context): Int {
+            return primaryTextColorInt
+                ?: Utils.getColorAttrDefaultColor(
+                    context,
+                    com.android.internal.R.color.materialColorOnSurface,
+                )
+        }
     }
 
     /** The chip should have a red background with white text. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationControllerStore.kt
index 280d66b..6cf2c73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationControllerStore.kt
@@ -18,9 +18,9 @@
 
 import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR
 import com.android.systemui.CoreStartable
-import com.android.systemui.common.ui.GlobalConfig
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.display.data.repository.DisplayRepository
 import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
 import com.android.systemui.display.data.repository.PerDisplayStore
@@ -74,7 +74,7 @@
 @SysUISingleton
 class SingleDisplayStatusBarConfigurationControllerStore
 @Inject
-constructor(@GlobalConfig globalConfigurationController: ConfigurationController) :
+constructor(@Main globalConfigurationController: ConfigurationController) :
     StatusBarConfigurationControllerStore,
     PerDisplayStore<StatusBarConfigurationController> by SingleDisplayStore(
         globalConfigurationController as StatusBarConfigurationController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
index 0b188afa..d0c02f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
@@ -183,7 +183,7 @@
 
     @Inject
     public HeadsUpManagerImpl(
-            @NonNull final Context context,
+            @NonNull @ShadeDisplayAware final Context context,
             HeadsUpManagerLogger logger,
             StatusBarStateController statusBarStateController,
             KeyguardBypassController bypassController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
index 0f19d72..9dc3ed2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
@@ -18,13 +18,14 @@
 
 import android.app.Notification
 import android.content.Context
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.contentDescForNotification
 import javax.inject.Inject
 
 /** Testable wrapper around Context. */
-class IconBuilder @Inject constructor(private val context: Context) {
+class IconBuilder @Inject constructor(@Main private val context: Context) {
     @JvmOverloads
     fun createIconView(
         entry: NotificationEntry,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
index 38eaf27..e122ca8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
@@ -77,6 +77,13 @@
         contentBuilder.text = notification.text()
         contentBuilder.skeletonLargeIcon = null // TODO
 
+        val colorsFromNotif = recoveredBuilder.getColors(/* header= */ false)
+        contentBuilder.colors =
+            PromotedNotificationContentModel.Colors(
+                backgroundColor = colorsFromNotif.backgroundColor,
+                primaryTextColor = colorsFromNotif.primaryTextColor,
+            )
+
         recoveredBuilder.style?.extractContent(contentBuilder)
             ?: run { contentBuilder.style = Style.Ineligible }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
index 41ee3b9..0af4043 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
@@ -18,6 +18,7 @@
 
 import android.annotation.DrawableRes
 import android.graphics.drawable.Icon
+import androidx.annotation.ColorInt
 import com.android.internal.widget.NotificationProgressModel
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
@@ -39,6 +40,7 @@
     val title: CharSequence?,
     val text: CharSequence?,
     val skeletonLargeIcon: Icon?, // TODO(b/377568176): Make into an IconModel.
+    val colors: Colors,
     val style: Style,
 
     // for CallStyle:
@@ -61,6 +63,7 @@
         var text: CharSequence? = null
         var skeletonLargeIcon: Icon? = null
         var style: Style = Style.Ineligible
+        var colors: Colors = Colors(backgroundColor = 0, primaryTextColor = 0)
 
         // for CallStyle:
         var personIcon: Icon? = null
@@ -83,6 +86,7 @@
                 title = title,
                 text = text,
                 skeletonLargeIcon = skeletonLargeIcon,
+                colors = colors,
                 style = style,
                 personIcon = personIcon,
                 personName = personName,
@@ -102,6 +106,9 @@
         }
     }
 
+    /** The colors used to display the notification. */
+    data class Colors(@ColorInt val backgroundColor: Int, @ColorInt val primaryTextColor: Int)
+
     /** The promotion-eligible style of a notification, or [Style.Ineligible] if not. */
     enum class Style {
         BigPicture,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
index a8d59d8..6bfc9f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
@@ -40,6 +40,7 @@
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.res.R
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 private const val TAG = "ChannelDialogController"
@@ -58,11 +59,10 @@
  */
 @SysUISingleton
 class ChannelEditorDialogController @Inject constructor(
-    c: Context,
+    @ShadeDisplayAware private val context: Context,
     private val noMan: INotificationManager,
     private val dialogBuilder: ChannelEditorDialog.Builder
 ) {
-    val context: Context = c.applicationContext
 
     private var prepared = false
     private lateinit var dialog: ChannelEditorDialog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt
index 8f4279e..a324e6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.phone
 
 import com.android.systemui.CoreStartable
-import com.android.systemui.common.ui.GlobalConfig
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
 import javax.inject.Inject
@@ -27,8 +27,8 @@
 class ConfigurationControllerStartable
 @Inject
 constructor(
-    @GlobalConfig private val configurationController: ConfigurationController,
-    private val listeners: Set<@JvmSuppressWildcards ConfigurationListener>
+    @Main private val configurationController: ConfigurationController,
+    private val listeners: Set<@JvmSuppressWildcards ConfigurationListener>,
 ) : CoreStartable {
     override fun start() {
         listeners.forEach { configurationController.addCallback(it) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index 9187e3c..d1e807f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -23,7 +23,6 @@
 import com.android.internal.R;
 import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
 import com.android.settingslib.notification.modes.ZenIconLoader;
-import com.android.systemui.common.ui.GlobalConfig;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Application;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -111,7 +110,7 @@
      * wrong updates in case of secondary displays.
      */
     @Binds
-    ConfigurationController bindConfigurationController(@GlobalConfig ConfigurationController impl);
+    ConfigurationController bindConfigurationController(@Main ConfigurationController impl);
 
     /** */
     @Binds
@@ -189,14 +188,14 @@
     /** */
     @Binds
     @SysUISingleton
-    @GlobalConfig
+    @Main
     ConfigurationForwarder provideGlobalConfigurationForwarder(
-            @GlobalConfig ConfigurationController configurationController);
+            @Main ConfigurationController configurationController);
 
     /** */
     @Provides
     @SysUISingleton
-    @GlobalConfig
+    @Main
     static ConfigurationController provideGlobalConfigurationController(
             @Application Context context, ConfigurationControllerImpl.Factory factory) {
         return factory.create(context);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt
index 4da7b2a..e035a02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt
@@ -64,9 +64,11 @@
             override fun executeOnDiskIO(runnable: Runnable) {
                 runnable.run()
             }
+
             override fun postToMainThread(runnable: Runnable) {
                 runnable.run()
             }
+
             override fun isMainThread(): Boolean {
                 return true
             }
@@ -805,4 +807,32 @@
         fakeExecutor.runAllReady()
         verify(mockController).unregisterCallback(any())
     }
+
+    @Test
+    fun positionUpdatedWhileStopped() {
+        // When playback is stopped at one position
+        val firstPosition = 200L
+        val state =
+            PlaybackState.Builder().run {
+                setState(PlaybackState.STATE_STOPPED, firstPosition, 1f)
+                build()
+            }
+        whenever(mockController.playbackState).thenReturn(state)
+        val captor = ArgumentCaptor.forClass(MediaController.Callback::class.java)
+        viewModel.updateController(mockController)
+        verify(mockController).registerCallback(captor.capture())
+        assertThat(viewModel.progress.value!!.elapsedTime).isEqualTo(firstPosition.toInt())
+
+        // And the state is updated with a new position
+        val secondPosition = 42L
+        val secondState =
+            PlaybackState.Builder().run {
+                setState(PlaybackState.STATE_STOPPED, secondPosition, 1f)
+                build()
+            }
+        captor.value.onPlaybackStateChanged(secondState)
+
+        // THEN then elapsed time should be updated
+        assertThat(viewModel.progress.value!!.elapsedTime).isEqualTo(secondPosition.toInt())
+    }
 }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 452dc5f..0b633bd 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2042,7 +2042,7 @@
                         // handles stopping the projection.
                         Slog.w(TAG, "Content Recording: failed to start mirroring - "
                                 + "releasing virtual display " + displayId);
-                        releaseVirtualDisplayInternal(callback.asBinder(), callingUid);
+                        releaseVirtualDisplayInternal(callback.asBinder());
                         return Display.INVALID_DISPLAY;
                     } else if (projection != null) {
                         // Indicate that this projection has been used to record, and can't be used
@@ -2131,7 +2131,7 @@
         // Something weird happened and the logical display was not created.
         Slog.w(TAG, "Rejecting request to create virtual display "
                 + "because the logical display was not created.");
-        mVirtualDisplayAdapter.releaseVirtualDisplayLocked(callback.asBinder(), callingUid);
+        mVirtualDisplayAdapter.releaseVirtualDisplayLocked(callback.asBinder());
         mDisplayDeviceRepo.onDisplayDeviceEvent(device,
                 DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
         return -1;
@@ -2158,14 +2158,14 @@
         }
     }
 
-    private void releaseVirtualDisplayInternal(IBinder appToken, int callingUid) {
+    private void releaseVirtualDisplayInternal(IBinder appToken) {
         synchronized (mSyncRoot) {
             if (mVirtualDisplayAdapter == null) {
                 return;
             }
 
             DisplayDevice device =
-                    mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken, callingUid);
+                    mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken);
             Slog.d(TAG, "Virtual Display: Display Device released");
             if (device != null) {
                 // TODO: multi-display - handle virtual displays the same as other display adapters.
@@ -4789,10 +4789,9 @@
 
         @Override // Binder call
         public void releaseVirtualDisplay(IVirtualDisplayCallback callback) {
-            final int callingUid = Binder.getCallingUid();
             final long token = Binder.clearCallingIdentity();
             try {
-                releaseVirtualDisplayInternal(callback.asBinder(), callingUid);
+                releaseVirtualDisplayInternal(callback.asBinder());
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 836f4ed..f14e452 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -91,6 +91,13 @@
 
     private final ArrayMap<IBinder, VirtualDisplayDevice> mVirtualDisplayDevices = new ArrayMap<>();
 
+    // When a virtual display is created, the mapping (appToken -> ownerUid) is stored here. That
+    // way, when the display is released later, we can retrieve the ownerUid and decrement
+    // the number of virtual displays that exist for that ownerUid. We can't use
+    // Binder.getCallingUid() because the display might be released by the system process and not
+    // the process that created the display.
+    private final ArrayMap<IBinder, Integer> mOwnerUids = new ArrayMap<>();
+
     private final int mMaxDevices;
     private final int mMaxDevicesPerPackage;
     private final SparseIntArray mNoOfDevicesPerPackage = new SparseIntArray();
@@ -194,6 +201,7 @@
         mVirtualDisplayDevices.put(appToken, device);
         if (getFeatureFlags().isVirtualDisplayLimitEnabled()) {
             mNoOfDevicesPerPackage.put(ownerUid, noOfDevices + 1);
+            mOwnerUids.put(appToken, ownerUid);
         }
 
         try {
@@ -205,7 +213,7 @@
             appToken.linkToDeath(device, 0);
         } catch (RemoteException ex) {
             Slog.e(TAG, "Virtual Display: error while setting up VirtualDisplayDevice", ex);
-            removeVirtualDisplayDeviceLocked(appToken, ownerUid);
+            removeVirtualDisplayDeviceLocked(appToken);
             device.destroyLocked(false);
             return null;
         }
@@ -252,12 +260,10 @@
     /**
      * Release a virtual display that was previously created
      * @param appToken The token to identify the display
-     * @param ownerUid The UID of the package, used to keep track of and limit the number of
-     *                 displays created per package
      * @return The display device that has been removed
      */
-    public DisplayDevice releaseVirtualDisplayLocked(IBinder appToken, int ownerUid) {
-        VirtualDisplayDevice device = removeVirtualDisplayDeviceLocked(appToken, ownerUid);
+    public DisplayDevice releaseVirtualDisplayLocked(IBinder appToken) {
+        VirtualDisplayDevice device = removeVirtualDisplayDeviceLocked(appToken);
         if (device != null) {
             Slog.v(TAG, "Release VirtualDisplay " + device.mName);
             device.destroyLocked(true);
@@ -299,11 +305,13 @@
         }
     }
 
-    private VirtualDisplayDevice removeVirtualDisplayDeviceLocked(IBinder appToken, int ownerUid) {
-        int noOfDevices = mNoOfDevicesPerPackage.get(ownerUid, /* valueIfKeyNotFound= */ 0);
+    private VirtualDisplayDevice removeVirtualDisplayDeviceLocked(IBinder appToken) {
         if (getFeatureFlags().isVirtualDisplayLimitEnabled()) {
+            int ownerUid = mOwnerUids.get(appToken);
+            int noOfDevices = mNoOfDevicesPerPackage.get(ownerUid, /* valueIfKeyNotFound= */ 0);
             if (noOfDevices <= 1) {
                 mNoOfDevicesPerPackage.delete(ownerUid);
+                mOwnerUids.remove(appToken);
             } else {
                 mNoOfDevicesPerPackage.put(ownerUid, noOfDevices - 1);
             }
@@ -378,7 +386,7 @@
         @Override
         public void binderDied() {
             synchronized (getSyncRoot()) {
-                removeVirtualDisplayDeviceLocked(mAppToken, mOwnerUid);
+                removeVirtualDisplayDeviceLocked(mAppToken);
                 Slog.i(TAG, "Virtual display device released because application token died: "
                     + mOwnerPackageName);
                 destroyLocked(false);
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 585fc44..78bd41b 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -93,6 +93,10 @@
             com.android.graphics.surfaceflinger.flags.Flags.FLAG_ENABLE_SMALL_AREA_DETECTION,
             com.android.graphics.surfaceflinger.flags.Flags::enableSmallAreaDetection);
 
+    private final FlagState mDisplayConfigErrorHalFlagState = new FlagState(
+            com.android.graphics.surfaceflinger.flags.Flags.FLAG_DISPLAY_CONFIG_ERROR_HAL,
+            com.android.graphics.surfaceflinger.flags.Flags::displayConfigErrorHal);
+
     private final FlagState mBrightnessIntRangeUserPerceptionFlagState = new FlagState(
             Flags.FLAG_BRIGHTNESS_INT_RANGE_USER_PERCEPTION,
             Flags::brightnessIntRangeUserPerception);
@@ -361,6 +365,10 @@
         return mSmallAreaDetectionFlagState.isEnabled();
     }
 
+    public boolean isDisplayConfigErrorHalEnabled() {
+        return mDisplayConfigErrorHalFlagState.isEnabled();
+    }
+
     public boolean isBrightnessIntRangeUserPerceptionEnabled() {
         return mBrightnessIntRangeUserPerceptionFlagState.isEnabled();
     }
@@ -591,6 +599,7 @@
         pw.println(" " + mPowerThrottlingClamperFlagState);
         pw.println(" " + mEvenDimmerFlagState);
         pw.println(" " + mSmallAreaDetectionFlagState);
+        pw.println(" " + mDisplayConfigErrorHalFlagState);
         pw.println(" " + mBrightnessIntRangeUserPerceptionFlagState);
         pw.println(" " + mRestrictDisplayModes);
         pw.println(" " + mBrightnessWearBedtimeModeClamperFlagState);
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 8423e19..02e2882 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -136,6 +136,7 @@
     private final ProximitySensorObserver mSensorObserver;
     private final HbmObserver mHbmObserver;
     private final SkinThermalStatusObserver mSkinThermalStatusObserver;
+    private final ModeChangeObserver mModeChangeObserver;
 
     @Nullable
     private final SystemRequestObserver mSystemRequestObserver;
@@ -247,6 +248,7 @@
         mDisplayObserver = new DisplayObserver(context, handler, mVotesStorage, injector);
         mSensorObserver = new ProximitySensorObserver(mVotesStorage, injector);
         mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, mVotesStorage);
+        mModeChangeObserver = new ModeChangeObserver(mVotesStorage, injector, handler.getLooper());
         mHbmObserver = new HbmObserver(injector, mVotesStorage, BackgroundThread.getHandler(),
                 mDeviceConfigDisplaySettings);
         if (displayManagerFlags.isRestrictDisplayModesEnabled()) {
@@ -275,6 +277,9 @@
         mSensorObserver.observe();
         mHbmObserver.observe();
         mSkinThermalStatusObserver.observe();
+        if (mDisplayManagerFlags.isDisplayConfigErrorHalEnabled()) {
+            mModeChangeObserver.observe();
+        }
         synchronized (mLock) {
             // We may have a listener already registered before the call to start, so go ahead and
             // notify them to pick up our newly initialized state.
diff --git a/services/core/java/com/android/server/display/mode/ModeChangeObserver.java b/services/core/java/com/android/server/display/mode/ModeChangeObserver.java
new file mode 100644
index 0000000..bbc13cc
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/ModeChangeObserver.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.mode;
+
+import android.os.Looper;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayAddress;
+import android.view.DisplayEventReceiver;
+
+import com.android.internal.annotations.KeepForWeakReference;
+
+import java.util.HashSet;
+import java.util.Set;
+
+final class ModeChangeObserver {
+    private static final String TAG = "ModeChangeObserver";
+
+    private final VotesStorage mVotesStorage;
+    private final DisplayModeDirector.Injector mInjector;
+
+    @SuppressWarnings("unused")
+    @KeepForWeakReference
+    private DisplayEventReceiver mModeChangeListener;
+    private final SparseArray<Set<Integer>> mRejectedModesByDisplay = new SparseArray<>();
+    private Looper mLooper;
+
+    ModeChangeObserver(VotesStorage votesStorage, DisplayModeDirector.Injector injector,
+                    Looper looper) {
+        mVotesStorage = votesStorage;
+        mInjector = injector;
+        mLooper = looper;
+    }
+
+    void observe() {
+        mModeChangeListener = new DisplayEventReceiver(mLooper) {
+            @Override
+            public void onModeRejected(long physicalDisplayId, int modeId) {
+                Slog.d(TAG, "Mode Rejected event received");
+                int displayId = getLogicalDisplayId(physicalDisplayId);
+                if (displayId < 0) {
+                    Slog.e(TAG, "Logical Display Id not found");
+                    return;
+                }
+                populateRejectedModesListByDisplay(displayId, modeId);
+            }
+
+            @Override
+            public void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected) {
+                Slog.d(TAG, "Hotplug event received");
+                if (!connected) {
+                    int displayId = getLogicalDisplayId(physicalDisplayId);
+                    if (displayId < 0) {
+                        Slog.e(TAG, "Logical Display Id not found");
+                        return;
+                    }
+                    clearRejectedModesListByDisplay(displayId);
+                }
+            }
+        };
+    }
+
+    private int getLogicalDisplayId(long rejectedModePhysicalDisplayId) {
+        Display[] displays = mInjector.getDisplays();
+
+        for (Display display : displays) {
+            DisplayAddress address = display.getAddress();
+            if (address instanceof DisplayAddress.Physical physical) {
+                long physicalDisplayId = physical.getPhysicalDisplayId();
+                if (physicalDisplayId == rejectedModePhysicalDisplayId) {
+                    return display.getDisplayId();
+                }
+            }
+        }
+        return -1;
+    }
+
+    private void populateRejectedModesListByDisplay(int displayId, int rejectedModeId) {
+        Set<Integer> alreadyRejectedModes = mRejectedModesByDisplay.get(displayId);
+        if (alreadyRejectedModes == null) {
+            alreadyRejectedModes = new HashSet<>();
+            mRejectedModesByDisplay.put(displayId, alreadyRejectedModes);
+        }
+        alreadyRejectedModes.add(rejectedModeId);
+        mVotesStorage.updateVote(displayId, Vote.PRIORITY_REJECTED_MODES,
+                Vote.forRejectedModes(alreadyRejectedModes));
+    }
+
+    private void clearRejectedModesListByDisplay(int displayId) {
+        mRejectedModesByDisplay.remove(displayId);
+        mVotesStorage.updateVote(displayId, Vote.PRIORITY_REJECTED_MODES, null);
+    }
+}
diff --git a/services/core/java/com/android/server/display/mode/RejectedModesVote.java b/services/core/java/com/android/server/display/mode/RejectedModesVote.java
new file mode 100644
index 0000000..db8c85278
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/RejectedModesVote.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.mode;
+
+import android.annotation.NonNull;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class RejectedModesVote implements Vote {
+
+    final Set<Integer> mModeIds;
+
+    RejectedModesVote(Set<Integer> modeIds) {
+        mModeIds = Collections.unmodifiableSet(modeIds);
+    }
+    @Override
+    public void updateSummary(@NonNull VoteSummary summary) {
+        summary.rejectedModeIds.addAll(mModeIds);
+    }
+
+    @Override
+    public String toString() {
+        return "RejectedModesVote{ mModeIds=" + mModeIds + " }";
+    }
+}
diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java
index f5abb05..428cced 100644
--- a/services/core/java/com/android/server/display/mode/Vote.java
+++ b/services/core/java/com/android/server/display/mode/Vote.java
@@ -25,6 +25,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 interface Vote {
     // DEFAULT_RENDER_FRAME_RATE votes for render frame rate [0, DEFAULT]. As the lowest
@@ -82,68 +83,73 @@
 
     int PRIORITY_APP_REQUEST_SIZE = 7;
 
+    // PRIORITY_REJECTED_MODES rejects the modes for which the mode config failed
+    // so that the modeset can be retried for next available mode after filtering
+    // out the rejected modes for the connected display
+    int PRIORITY_REJECTED_MODES = 8;
+
     // PRIORITY_USER_SETTING_PEAK_REFRESH_RATE restricts physical refresh rate to
     // [0, max(PEAK, MIN)], depending on user settings peakRR/minRR values
-    int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 8;
+    int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 9;
 
     // PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE has a higher priority than
     // PRIORITY_USER_SETTING_PEAK_REFRESH_RATE and will limit render rate to [0, max(PEAK, MIN)]
     // in case physical refresh rate vote is discarded (due to other high priority votes),
     // render rate vote can still apply
-    int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 9;
+    int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 10;
 
     // Restrict all displays physical refresh rate to 60Hz when external display is connected.
     // It votes [59Hz, 61Hz].
-    int PRIORITY_SYNCHRONIZED_REFRESH_RATE = 10;
+    int PRIORITY_SYNCHRONIZED_REFRESH_RATE = 11;
 
     // PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE has a higher priority than
     // PRIORITY_SYNCHRONIZED_REFRESH_RATE and will limit render rate to [59Hz, 61Hz].
     // In case physical refresh rate vote discarded (due to physical refresh rate not supported),
     // render rate vote can still apply.
-    int PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE = 11;
+    int PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE = 12;
 
     // Restrict displays max available resolution and refresh rates. It votes [0, LIMIT]
-    int PRIORITY_LIMIT_MODE = 12;
+    int PRIORITY_LIMIT_MODE = 13;
 
     // To avoid delay in switching between 60HZ -> 90HZ when activating LHBM, set refresh
     // rate to max value (same as for PRIORITY_UDFPS) on lock screen
-    int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 13;
+    int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 14;
 
     // For concurrent displays we want to limit refresh rate on all displays
-    int PRIORITY_LAYOUT_LIMITED_REFRESH_RATE = 14;
+    int PRIORITY_LAYOUT_LIMITED_REFRESH_RATE = 15;
 
     // For concurrent displays we want to limit refresh rate on all displays
-    int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 15;
+    int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 16;
 
     // For internal application to limit display modes to specific ids
-    int PRIORITY_SYSTEM_REQUESTED_MODES = 16;
+    int PRIORITY_SYSTEM_REQUESTED_MODES = 17;
 
     // PRIORITY_LOW_POWER_MODE_MODES limits display modes to specific refreshRate-vsync pairs if
     // Settings.Global.LOW_POWER_MODE is on.
     // Lower priority that PRIORITY_LOW_POWER_MODE_RENDER_RATE and if discarded (due to other
     // higher priority votes), render rate limit can still apply
-    int PRIORITY_LOW_POWER_MODE_MODES = 17;
+    int PRIORITY_LOW_POWER_MODE_MODES = 18;
 
     // PRIORITY_LOW_POWER_MODE_RENDER_RATE force the render frame rate to [0, 60HZ] if
     // Settings.Global.LOW_POWER_MODE is on.
-    int PRIORITY_LOW_POWER_MODE_RENDER_RATE = 18;
+    int PRIORITY_LOW_POWER_MODE_RENDER_RATE = 19;
 
     // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
     // higher priority voters' result is a range, it will fix the rate to a single choice.
     // It's used to avoid refresh rate switches in certain conditions which may result in the
     // user seeing the display flickering when the switches occur.
-    int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 19;
+    int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 20;
 
     // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
-    int PRIORITY_SKIN_TEMPERATURE = 20;
+    int PRIORITY_SKIN_TEMPERATURE = 21;
 
     // The proximity sensor needs the refresh rate to be locked in order to function, so this is
     // set to a high priority.
-    int PRIORITY_PROXIMITY = 21;
+    int PRIORITY_PROXIMITY = 22;
 
     // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
     // to function, so this needs to be the highest priority of all votes.
-    int PRIORITY_UDFPS = 22;
+    int PRIORITY_UDFPS = 23;
 
     @IntDef(prefix = { "PRIORITY_" }, value = {
             PRIORITY_DEFAULT_RENDER_FRAME_RATE,
@@ -154,6 +160,7 @@
             PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
             PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
             PRIORITY_APP_REQUEST_SIZE,
+            PRIORITY_REJECTED_MODES,
             PRIORITY_USER_SETTING_PEAK_REFRESH_RATE,
             PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
             PRIORITY_SYNCHRONIZED_REFRESH_RATE,
@@ -245,6 +252,10 @@
         return new SupportedModesVote(modeIds);
     }
 
+    static Vote forRejectedModes(Set<Integer> modeIds) {
+        return new RejectedModesVote(modeIds);
+    }
+
     static String priorityToString(int priority) {
         switch (priority) {
             case PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE:
@@ -253,6 +264,8 @@
                 return "PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE";
             case PRIORITY_APP_REQUEST_SIZE:
                 return "PRIORITY_APP_REQUEST_SIZE";
+            case PRIORITY_REJECTED_MODES:
+                return "PRIORITY_REJECTED_MODES";
             case PRIORITY_DEFAULT_RENDER_FRAME_RATE:
                 return "PRIORITY_DEFAULT_REFRESH_RATE";
             case PRIORITY_FLICKER_REFRESH_RATE:
diff --git a/services/core/java/com/android/server/display/mode/VoteSummary.java b/services/core/java/com/android/server/display/mode/VoteSummary.java
index 00a9226..4166493 100644
--- a/services/core/java/com/android/server/display/mode/VoteSummary.java
+++ b/services/core/java/com/android/server/display/mode/VoteSummary.java
@@ -55,6 +55,11 @@
     @Nullable
     public List<Integer> supportedModeIds;
 
+    /**
+     * set of rejected modes due to mode config failure for connected display
+     */
+    public Set<Integer> rejectedModeIds = new HashSet<>();
+
     final boolean mIsDisplayResolutionRangeVotingEnabled;
 
     private final boolean mSupportedModesVoteEnabled;
@@ -132,6 +137,9 @@
             if (!validateModeSupported(mode)) {
                 continue;
             }
+            if (!validateModeRejected(mode)) {
+                continue;
+            }
             if (!validateModeSize(mode)) {
                 continue;
             }
@@ -285,6 +293,22 @@
         return false;
     }
 
+    private boolean validateModeRejected(Display.Mode mode) {
+        if (rejectedModeIds == null) {
+            return true;
+        }
+        if (!rejectedModeIds.contains(mode.getModeId())) {
+            return true;
+        }
+        if (mLoggingEnabled) {
+            Slog.w(TAG, "Discarding mode" + mode.getModeId()
+                    + ", is a rejectedMode"
+                    + ": mode.modeId=" + mode.getModeId()
+                    + ", rejectedModeIds=" + rejectedModeIds);
+        }
+        return false;
+    }
+
     private boolean validateRefreshRatesSupported(Display.Mode mode) {
         if (supportedRefreshRates == null || !mSupportedModesVoteEnabled) {
             return true;
@@ -397,6 +421,7 @@
         requestedRefreshRates.clear();
         supportedRefreshRates = null;
         supportedModeIds = null;
+        rejectedModeIds.clear();
         if (mLoggingEnabled) {
             Slog.i(TAG, "Summary reset: " + this);
         }
@@ -421,6 +446,7 @@
                 + ", requestRefreshRates=" + requestedRefreshRates
                 + ", supportedRefreshRates=" + supportedRefreshRates
                 + ", supportedModeIds=" + supportedModeIds
+                + ", rejectedModeIds=" + rejectedModeIds
                 + ", mIsDisplayResolutionRangeVotingEnabled="
                 + mIsDisplayResolutionRangeVotingEnabled
                 + ", mSupportedModesVoteEnabled=" + mSupportedModesVoteEnabled
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 1eb8465..fb197c5 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -2201,31 +2201,60 @@
     }
 
     private int setAdjacentRootsHierarchyOp(WindowContainerTransaction.HierarchyOp hop) {
-        final WindowContainer wc1 = WindowContainer.fromBinder(hop.getContainer());
-        if (wc1 == null || !wc1.isAttached()) {
-            Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc1);
-            return TRANSACT_EFFECTS_NONE;
-        }
-        final TaskFragment root1 = wc1.asTaskFragment();
-        final WindowContainer wc2 = WindowContainer.fromBinder(hop.getAdjacentRoot());
-        if (wc2 == null || !wc2.isAttached()) {
-            Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc2);
-            return TRANSACT_EFFECTS_NONE;
-        }
-        final TaskFragment root2 = wc2.asTaskFragment();
-        if (!root1.mCreatedByOrganizer || !root2.mCreatedByOrganizer) {
-            throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by"
-                    + " organizer root1=" + root1 + " root2=" + root2);
-        }
-        if (root1.isAdjacentTo(root2)) {
-            return TRANSACT_EFFECTS_NONE;
-        }
-        if (Flags.allowMultipleAdjacentTaskFragments()) {
-            // TODO(b/373709676): allow three roots.
-            root1.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(root1, root2));
-        } else {
+        if (!Flags.allowMultipleAdjacentTaskFragments()) {
+            final WindowContainer wc1 = WindowContainer.fromBinder(hop.getContainer());
+            if (wc1 == null || !wc1.isAttached()) {
+                Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc1);
+                return TRANSACT_EFFECTS_NONE;
+            }
+            final TaskFragment root1 = wc1.asTaskFragment();
+            final WindowContainer wc2 = WindowContainer.fromBinder(hop.getAdjacentRoot());
+            if (wc2 == null || !wc2.isAttached()) {
+                Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc2);
+                return TRANSACT_EFFECTS_NONE;
+            }
+            final TaskFragment root2 = wc2.asTaskFragment();
+            if (!root1.mCreatedByOrganizer || !root2.mCreatedByOrganizer) {
+                throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by"
+                        + " organizer root1=" + root1 + " root2=" + root2);
+            }
+            if (root1.isAdjacentTo(root2)) {
+                return TRANSACT_EFFECTS_NONE;
+            }
             root1.setAdjacentTaskFragment(root2);
+            return TRANSACT_EFFECTS_LIFECYCLE;
         }
+
+        final IBinder[] containers = hop.getContainers();
+        final ArraySet<TaskFragment> adjacentRoots = new ArraySet<>();
+        for (IBinder container : containers) {
+            final WindowContainer wc = WindowContainer.fromBinder(container);
+            if (wc == null || !wc.isAttached()) {
+                Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc);
+                return TRANSACT_EFFECTS_NONE;
+            }
+            final Task root = wc.asTask();
+            if (root == null) {
+                // Only support Task. Use WCT#setAdjacentTaskFragments for non-Task TaskFragment.
+                throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not called with"
+                        + " Task. wc=" + wc);
+            }
+            if (!root.mCreatedByOrganizer) {
+                throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by"
+                        + " organizer root=" + root);
+            }
+            if (adjacentRoots.contains(root)) {
+                throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: called with same"
+                        + " root twice=" + root);
+            }
+            adjacentRoots.add(root);
+        }
+        final TaskFragment root0 = adjacentRoots.valueAt(0);
+        final TaskFragment.AdjacentSet adjacentSet = new TaskFragment.AdjacentSet(adjacentRoots);
+        if (adjacentSet.equals(root0.getAdjacentTaskFragments())) {
+            return TRANSACT_EFFECTS_NONE;
+        }
+        root0.setAdjacentTaskFragments(adjacentSet);
         return TRANSACT_EFFECTS_LIFECYCLE;
     }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 365cbae..724f083 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -3708,7 +3708,7 @@
                 eq(config));
 
         bs.releaseVirtualDisplay(mMockAppToken);
-        verify(mMockVirtualDisplayAdapter).releaseVirtualDisplayLocked(binder, callingUid);
+        verify(mMockVirtualDisplayAdapter).releaseVirtualDisplayLocked(binder);
     }
 
     @Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
index dbd5c65..9287b30 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
@@ -118,14 +118,13 @@
     public void testCreateAndReleaseVirtualDisplay() {
         VirtualDisplayConfig config = new VirtualDisplayConfig.Builder("test", /* width= */ 1,
                 /* height= */ 1, /* densityDpi= */ 1).build();
-        int ownerUid = 10;
 
         DisplayDevice result = mAdapter.createVirtualDisplayLocked(mMockCallback,
-                /* projection= */ null, ownerUid, /* packageName= */ "testpackage",
+                /* projection= */ null, /* ownerUid= */ 10, /* packageName= */ "testpackage",
                 /* uniqueId= */ "uniqueId", /* surface= */ null, /* flags= */ 0, config);
         assertNotNull(result);
 
-        result = mAdapter.releaseVirtualDisplayLocked(mMockBinder, ownerUid);
+        result = mAdapter.releaseVirtualDisplayLocked(mMockBinder);
         assertNotNull(result);
     }
 
@@ -230,7 +229,6 @@
 
         // Displays for the same package
         for (int i = 0; i < MAX_DEVICES_PER_PACKAGE * 2; i++) {
-            // Same owner UID
             IVirtualDisplayCallback callback = createCallback();
             DisplayDevice device = mAdapter.createVirtualDisplayLocked(callback,
                     mMediaProjectionMock, 1234, "test.package", "123",
@@ -240,7 +238,6 @@
 
         // Displays for different packages
         for (int i = 0; i < MAX_DEVICES * 2; i++) {
-            // Same owner UID
             IVirtualDisplayCallback callback = createCallback();
             DisplayDevice device = mAdapter.createVirtualDisplayLocked(callback,
                     mMediaProjectionMock, 1234 + i, "test.package", "123",
@@ -270,8 +267,7 @@
         }
 
         // Release one display
-        DisplayDevice device = mAdapter.releaseVirtualDisplayLocked(callbacks.get(0).asBinder(),
-                ownerUid);
+        DisplayDevice device = mAdapter.releaseVirtualDisplayLocked(callbacks.get(0).asBinder());
         assertNotNull(device);
         callbacks.remove(0);
 
@@ -292,7 +288,7 @@
 
         // Release all the displays
         for (IVirtualDisplayCallback cb : callbacks) {
-            device = mAdapter.releaseVirtualDisplayLocked(cb.asBinder(), ownerUid);
+            device = mAdapter.releaseVirtualDisplayLocked(cb.asBinder());
             assertNotNull(device);
         }
         callbacks.clear();
@@ -342,8 +338,7 @@
         }
 
         // Release one display
-        DisplayDevice device = mAdapter.releaseVirtualDisplayLocked(callbacks.get(0).asBinder(),
-                firstOwnerUid);
+        DisplayDevice device = mAdapter.releaseVirtualDisplayLocked(callbacks.get(0).asBinder());
         assertNotNull(device);
         callbacks.remove(0);
 
@@ -363,9 +358,8 @@
         assertNull(device);
 
         // Release all the displays
-        for (int i = 0; i < callbacks.size(); i++) {
-            device = mAdapter.releaseVirtualDisplayLocked(callbacks.get(i).asBinder(),
-                    firstOwnerUid + i);
+        for (IVirtualDisplayCallback iVirtualDisplayCallback : callbacks) {
+            device = mAdapter.releaseVirtualDisplayLocked(iVirtualDisplayCallback.asBinder());
             assertNotNull(device);
         }
         callbacks.clear();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/RejectedModesVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/RejectedModesVoteTest.kt
new file mode 100644
index 0000000..dd3211d
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/RejectedModesVoteTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.mode
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RejectedModesVoteTest {
+    private val rejectedModes = setOf(1, 2)
+
+    private val otherMode = 2
+
+    private lateinit var rejectedModesVote: RejectedModesVote
+
+    @Before
+    fun setUp() {
+        rejectedModesVote = RejectedModesVote(rejectedModes)
+    }
+
+    @Test
+    fun addsRejectedModeIds_summaryIsEmpty() {
+        val summary = createVotesSummary()
+
+        rejectedModesVote.updateSummary(summary)
+
+        assertThat(summary.rejectedModeIds).containsExactlyElementsIn(rejectedModes)
+    }
+
+    @Test
+    fun addsRejectedModeIds_summaryIsNotEmpty() {
+        val summary = createVotesSummary()
+        summary.rejectedModeIds.add(otherMode)
+
+        rejectedModesVote.updateSummary(summary)
+
+        assertThat(summary.rejectedModeIds).containsExactlyElementsIn(rejectedModes + otherMode)
+    }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt
index 239e59b..958cf21 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt
@@ -186,6 +186,44 @@
 
         assertThat(result).hasSize(1)
     }
+
+    enum class RejectedModesTestCase(
+            internal val summaryRejectedModes: Set<Int>?,
+            val modesToFilter: Array<Display.Mode>,
+            val expectedModeIds: Set<Int>
+    ) {
+        HAS_NO_MATCHING_VOTE(
+                setOf(4, 5),
+                arrayOf(createMode(1, 90f, 90f),
+                        createMode(2, 90f, 60f),
+                        createMode(3, 60f, 90f)),
+                setOf(1, 2, 3)
+        ),
+        HAS_SINGLE_MATCHING_VOTE(
+                setOf(1),
+                arrayOf(createMode(1, 90f, 90f),
+                        createMode(2, 90f, 60f),
+                        createMode(3, 60f, 90f)),
+                setOf(2, 3)
+        ),
+        HAS_MULTIPLE_MATCHING_VOTES(
+                setOf(1, 2),
+                arrayOf(createMode(1, 90f, 90f),
+                        createMode(2, 90f, 60f),
+                        createMode(3, 60f, 90f)),
+                setOf(3)
+        ),
+    }
+
+    @Test
+    fun testFilterModes_rejectedModes(@TestParameter testCase: RejectedModesTestCase) {
+        val summary = createSummary()
+        summary.rejectedModeIds = testCase.summaryRejectedModes
+
+        val result = summary.filterModes(testCase.modesToFilter)
+
+        assertThat(result.map {it.modeId}).containsExactlyElementsIn(testCase.expectedModeIds)
+    }
 }
 
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index bb1e3ea..da4c522 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -926,6 +926,49 @@
         assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, null);
     }
 
+    @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS)
+    @Test
+    public void testSetAdjacentLaunchRootSet() {
+        final DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY);
+
+        final Task task1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+                dc, WINDOWING_MODE_MULTI_WINDOW, null);
+        final RunningTaskInfo info1 = task1.getTaskInfo();
+        final Task task2 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+                dc, WINDOWING_MODE_MULTI_WINDOW, null);
+        final RunningTaskInfo info2 = task2.getTaskInfo();
+        final Task task3 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+                dc, WINDOWING_MODE_MULTI_WINDOW, null);
+        final RunningTaskInfo info3 = task3.getTaskInfo();
+
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.setAdjacentRootSet(info1.token, info2.token, info3.token);
+        mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+        assertTrue(task1.hasAdjacentTaskFragment());
+        assertTrue(task2.hasAdjacentTaskFragment());
+        assertTrue(task3.hasAdjacentTaskFragment());
+        assertTrue(task1.isAdjacentTo(task2));
+        assertTrue(task1.isAdjacentTo(task3));
+        assertTrue(task2.isAdjacentTo(task3));
+
+        wct = new WindowContainerTransaction();
+        wct.clearAdjacentRoots(info1.token);
+        mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+        assertFalse(task1.hasAdjacentTaskFragment());
+        assertTrue(task2.hasAdjacentTaskFragment());
+        assertTrue(task3.hasAdjacentTaskFragment());
+        assertFalse(task1.isAdjacentTo(task2));
+        assertFalse(task1.isAdjacentTo(task3));
+        assertTrue(task2.isAdjacentTo(task3));
+
+        wct = new WindowContainerTransaction();
+        wct.clearAdjacentRoots(info2.token);
+        mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+        assertFalse(task2.hasAdjacentTaskFragment());
+        assertFalse(task3.hasAdjacentTaskFragment());
+        assertFalse(task2.isAdjacentTo(task3));
+    }
+
     @Test
     public void testTileAddRemoveChild() {
         final StubOrganizer listener = new StubOrganizer();
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index f44eacb..1e997b3 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -41,7 +41,6 @@
         "platform-test-annotations",
         "wm-flicker-common-app-helpers",
         "wm-shell-flicker-utils",
-        "systemui-tapl",
     ],
     data: [":FlickerTestApp"],
 }
diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
index da90c4f..ad70757 100644
--- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
+++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
@@ -16,8 +16,6 @@
 
 package com.android.server.wm.flicker.notification
 
-import android.platform.systemui_tapl.controller.NotificationIdentity
-import android.platform.systemui_tapl.ui.Root
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.platform.test.rule.DisableNotificationCooldownSettingRule
@@ -30,6 +28,8 @@
 import android.tools.traces.component.ComponentNameMatcher
 import android.view.WindowInsets
 import android.view.WindowManager
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.helpers.NotificationAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.navBarLayerIsVisibleAtEnd
@@ -87,9 +87,8 @@
             .withWindowSurfaceDisappeared(ComponentNameMatcher.NOTIFICATION_SHADE)
             .waitForAndVerify()
     }
-
     protected fun FlickerTestData.openAppFromNotification() {
-        doOpenAppAndWait()
+        doOpenAppAndWait(startY = 10, endY = 3 * device.displayHeight / 4, steps = 25)
     }
 
     protected fun FlickerTestData.openAppFromLockNotification() {
@@ -102,27 +101,25 @@
                 WindowInsets.Type.statusBars() or WindowInsets.Type.displayCutout()
             )
 
-        doOpenAppAndWait()
+        doOpenAppAndWait(startY = insets.top + 100, endY = device.displayHeight / 2, steps = 4)
     }
 
-    protected fun FlickerTestData.doOpenAppAndWait() {
-        val shade = Root.get().openNotificationShade()
+    protected fun FlickerTestData.doOpenAppAndWait(startY: Int, endY: Int, steps: Int) {
+        // Swipe down to show the notification shade
+        val x = device.displayWidth / 2
+        device.swipe(x, startY, x, endY, steps)
+        device.waitForIdle(2000)
+        instrumentation.uiAutomation.syncInputTransactions()
 
         // Launch the activity by clicking the notification
-        // Post notification and ensure that it's collapsed
         val notification =
-            shade.notificationStack.findNotification(
-                NotificationIdentity(
-                    type = NotificationIdentity.Type.BY_TEXT,
-                    text = "Flicker Test Notification",
-                )
-            )
+            device.wait(Until.findObject(By.text("Flicker Test Notification")), 2000L)
+        notification?.click() ?: error("Notification not found")
+        instrumentation.uiAutomation.syncInputTransactions()
 
-        notification.clickToApp()
         // Wait for the app to launch
         wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
     }
-
     @Presubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_warmStart()
 
     @Presubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_warmStart()