Merge changes I8a889842,I07d46bd9,I7e8ce558,I45ab6ad5 into main

* changes:
  Convert NotificationActivityStarter to kotlin
  Make empty shade text react to Modes changes
  Make EmptyShadeView a LaunchableView
  Migrate EmptyShadeView to recommended architecture
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 108b5f4..b139017 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -31,6 +31,16 @@
 }
 
 flag {
+  name: "modes_ui_empty_shade"
+  namespace: "systemui"
+  description: "Shows mode that is currently blocking notifications in the empty shade; dependent on flags modes_api and modes_ui"
+  bug: "366003631"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "modes_ui_test"
   namespace: "systemui"
   description: "Guards new CTS tests for Modes; dependent on flags modes_api and modes_ui"
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
index 712ddc8..5eeb49a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
@@ -167,6 +167,13 @@
         return this;
     }
 
+    public TestModeBuilder setVisualEffect(int effect, boolean allowed) {
+        ZenPolicy newPolicy = new ZenPolicy.Builder(mRule.getZenPolicy())
+                .showVisualEffect(effect, allowed).build();
+        setZenPolicy(newPolicy);
+        return this;
+    }
+
     public TestModeBuilder setEnabled(boolean enabled) {
         return setEnabled(enabled, /* byUser= */ false);
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
new file mode 100644
index 0000000..f9b7769
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel
+
+import android.app.Flags
+import android.app.NotificationManager.Policy
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
+import android.provider.Settings
+import android.service.notification.ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST
+import androidx.test.filters.SmallTest
+import com.android.settingslib.notification.data.repository.updateNotificationPolicy
+import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
+import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(ParameterizedAndroidJunit4::class)
+@SmallTest
+@EnableFlags(FooterViewRefactor.FLAG_NAME)
+class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val zenModeRepository = kosmos.zenModeRepository
+    private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+
+    private val underTest = kosmos.emptyShadeViewModel
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
+    @Test
+    fun areNotificationsHiddenInShade_true() =
+        testScope.runTest {
+            val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
+
+            zenModeRepository.updateNotificationPolicy(
+                suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST
+            )
+            zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+            runCurrent()
+
+            assertThat(hidden).isTrue()
+        }
+
+    @Test
+    fun areNotificationsHiddenInShade_false() =
+        testScope.runTest {
+            val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
+
+            zenModeRepository.updateNotificationPolicy(
+                suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST
+            )
+            zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_OFF)
+            runCurrent()
+
+            assertThat(hidden).isFalse()
+        }
+
+    @Test
+    fun hasFilteredOutSeenNotifications_true() =
+        testScope.runTest {
+            val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+            activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
+            runCurrent()
+
+            assertThat(hasFilteredNotifs).isTrue()
+        }
+
+    @Test
+    fun hasFilteredOutSeenNotifications_false() =
+        testScope.runTest {
+            val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+            activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
+            runCurrent()
+
+            assertThat(hasFilteredNotifs).isFalse()
+        }
+
+    @Test
+    @EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
+    @DisableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
+    fun text_changesWhenNotifsHiddenInShade() =
+        testScope.runTest {
+            val text by collectLastValue(underTest.text)
+
+            zenModeRepository.updateNotificationPolicy(
+                suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST
+            )
+            zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_OFF)
+            runCurrent()
+
+            assertThat(text).isEqualTo("No notifications")
+
+            zenModeRepository.updateNotificationPolicy(
+                suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST
+            )
+            zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+            runCurrent()
+
+            assertThat(text).isEqualTo("Notifications paused by Do Not Disturb")
+        }
+
+    @Test
+    @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
+    fun text_reflectsModesHidingNotifications() =
+        testScope.runTest {
+            val text by collectLastValue(underTest.text)
+
+            assertThat(text).isEqualTo("No notifications")
+
+            zenModeRepository.addMode(
+                TestModeBuilder()
+                    .setId("Do not disturb")
+                    .setName("Do not disturb")
+                    .setActive(true)
+                    .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false)
+                    .build()
+            )
+            runCurrent()
+            assertThat(text).isEqualTo("Notifications paused by Do not disturb")
+
+            zenModeRepository.addMode(
+                TestModeBuilder()
+                    .setId("Work")
+                    .setName("Work")
+                    .setActive(true)
+                    .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false)
+                    .build()
+            )
+            runCurrent()
+            assertThat(text).isEqualTo("Notifications paused by Do not disturb and one other mode")
+
+            zenModeRepository.addMode(
+                TestModeBuilder()
+                    .setId("Gym")
+                    .setName("Gym")
+                    .setActive(true)
+                    .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false)
+                    .build()
+            )
+            runCurrent()
+            assertThat(text).isEqualTo("Notifications paused by Do not disturb and 2 other modes")
+
+            zenModeRepository.deactivateMode("Do not disturb")
+            zenModeRepository.deactivateMode("Work")
+            runCurrent()
+            assertThat(text).isEqualTo("Notifications paused by Gym")
+        }
+
+    @Test
+    @EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
+    fun footer_isVisibleWhenSeenNotifsAreFilteredOut() =
+        testScope.runTest {
+            val footerVisible by collectLastValue(underTest.footer.isVisible)
+
+            activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
+            runCurrent()
+
+            assertThat(footerVisible).isFalse()
+
+            activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
+            runCurrent()
+
+            assertThat(footerVisible).isTrue()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index 26e1a4d..d12d6f6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -18,12 +18,9 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
-import android.app.NotificationManager.Policy
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
-import android.provider.Settings
 import androidx.test.filters.SmallTest
-import com.android.settingslib.notification.data.repository.updateNotificationPolicy
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.DisableSceneContainer
@@ -46,7 +43,6 @@
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
 import com.android.systemui.statusbar.policy.data.repository.fakeUserSetupRepository
-import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
 import com.android.systemui.statusbar.policy.fakeConfigurationController
 import com.android.systemui.testKosmos
 import com.android.systemui.util.ui.isAnimating
@@ -79,7 +75,6 @@
     private val fakeRemoteInputRepository = kosmos.fakeRemoteInputRepository
     private val fakeUserSetupRepository = kosmos.fakeUserSetupRepository
     private val headsUpRepository = kosmos.headsUpNotificationRepository
-    private val zenModeRepository = kosmos.zenModeRepository
 
     private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
 
@@ -266,56 +261,6 @@
         }
 
     @Test
-    fun areNotificationsHiddenInShade_true() =
-        testScope.runTest {
-            val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
-
-            zenModeRepository.updateNotificationPolicy(
-                suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST
-            )
-            zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
-            runCurrent()
-
-            assertThat(hidden).isTrue()
-        }
-
-    @Test
-    fun areNotificationsHiddenInShade_false() =
-        testScope.runTest {
-            val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
-
-            zenModeRepository.updateNotificationPolicy(
-                suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST
-            )
-            zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_OFF)
-            runCurrent()
-
-            assertThat(hidden).isFalse()
-        }
-
-    @Test
-    fun hasFilteredOutSeenNotifications_true() =
-        testScope.runTest {
-            val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
-
-            activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
-            runCurrent()
-
-            assertThat(hasFilteredNotifs).isTrue()
-        }
-
-    @Test
-    fun hasFilteredOutSeenNotifications_false() =
-        testScope.runTest {
-            val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
-
-            activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
-            runCurrent()
-
-            assertThat(hasFilteredNotifs).isFalse()
-        }
-
-    @Test
     fun shouldIncludeFooterView_trueWhenShade() =
         testScope.runTest {
             val shouldIncludeFooterView by collectFooterViewVisibility()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index 0f6dc07..c5ccf9e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -25,6 +25,7 @@
 import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
 import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
 import android.service.notification.SystemZenRules
+import android.service.notification.ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.R
@@ -34,6 +35,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
+import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
 import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
 import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
 import com.android.systemui.testKosmos
@@ -379,4 +381,46 @@
 
             assertThat(dndMode!!.isActive).isTrue()
         }
+
+    @Test
+    @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
+    fun modesHidingNotifications_onlyIncludesModesWithNotifListSuppression() =
+        testScope.runTest {
+            val modesHidingNotifications by collectLastValue(underTest.modesHidingNotifications)
+
+            zenModeRepository.addModes(
+                listOf(
+                    TestModeBuilder()
+                        .setName("Not active, no list suppression")
+                        .setActive(false)
+                        .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ true)
+                        .build(),
+                    TestModeBuilder()
+                        .setName("Not active, has list suppression")
+                        .setActive(false)
+                        .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false)
+                        .build(),
+                    TestModeBuilder()
+                        .setName("No list suppression")
+                        .setActive(true)
+                        .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ true)
+                        .build(),
+                    TestModeBuilder()
+                        .setName("Has list suppression 1")
+                        .setActive(true)
+                        .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false)
+                        .build(),
+                    TestModeBuilder()
+                        .setName("Has list suppression 2")
+                        .setActive(true)
+                        .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false)
+                        .build(),
+                )
+            )
+            runCurrent()
+
+            assertThat(modesHidingNotifications?.map { it.name })
+                .containsExactly("Has list suppression 1", "Has list suppression 2")
+                .inOrder()
+        }
 }
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 24b6579..75389b1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1473,6 +1473,16 @@
     <!-- The text to show in the notifications shade when dnd is suppressing notifications. [CHAR LIMIT=100] -->
     <string name="dnd_suppressing_shade_text">Notifications paused by Do Not Disturb</string>
 
+    <!-- The text to show in the notifications shade when a mode is suppressing notifications. [CHAR LIMIT=100] -->
+    <string name="modes_suppressing_shade_text">
+        {count, plural, offset:1
+            =0 {No notifications}
+            =1 {Notifications paused by {mode}}
+            =2 {Notifications paused by {mode} and one other mode}
+            other {Notifications paused by {mode} and # other modes}
+        }
+    </string>
+
     <!-- Media projection permission dialog action text. [CHAR LIMIT=60] -->
     <string name="media_projection_action_text">Start now</string>
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 0b775ab..820c102 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -35,6 +35,8 @@
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.shared.flag.DualShade
 import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
+import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor
 import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
 import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
@@ -56,6 +58,8 @@
         NotificationAvalancheSuppression.token dependsOn VisualInterruptionRefactor.token
         PriorityPeopleSection.token dependsOn SortBySectionTimeFlag.token
         NotificationMinimalismPrototype.token dependsOn NotificationThrottleHun.token
+        ModesEmptyShadeFix.token dependsOn FooterViewRefactor.token
+        ModesEmptyShadeFix.token dependsOn modesUi
 
         // SceneContainer dependencies
         SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.kt
similarity index 60%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.kt
index ec3c7d0..0f93b5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,37 +13,36 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.systemui.statusbar.notification
 
-package com.android.systemui.statusbar.notification;
-
-import android.content.Intent;
-import android.view.View;
-
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import android.content.Intent
+import android.view.View
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 
 /**
  * Component responsible for handling actions on a notification which cause activites to start.
  * (e.g. clicking on a notification, tapping on the settings icon in the notification guts)
  */
-public interface NotificationActivityStarter {
+interface NotificationActivityStarter {
     /** Called when the user clicks on the notification bubble icon. */
-    void onNotificationBubbleIconClicked(NotificationEntry entry);
+    fun onNotificationBubbleIconClicked(entry: NotificationEntry?)
 
     /** Called when the user clicks on the surface of a notification. */
-    void onNotificationClicked(NotificationEntry entry, ExpandableNotificationRow row);
+    fun onNotificationClicked(entry: NotificationEntry?, row: ExpandableNotificationRow?)
 
     /** Called when the user clicks on a button in the notification guts which fires an intent. */
-    void startNotificationGutsIntent(Intent intent, int appUid,
-            ExpandableNotificationRow row);
+    fun startNotificationGutsIntent(intent: Intent?, appUid: Int, row: ExpandableNotificationRow?)
 
-    /** Called when the user clicks "Manage" or "History" in the Shade. */
-    void startHistoryIntent(View view, boolean showHistory);
+    /**
+     * Called when the user clicks "Manage" or "History" in the Shade, or the "No notifications"
+     * text.
+     */
+    fun startHistoryIntent(view: View?, showHistory: Boolean)
 
     /** Called when the user succeed to drop notification to proper target view. */
-    void onDragSuccess(NotificationEntry entry);
+    fun onDragSuccess(entry: NotificationEntry?)
 
-    default boolean isCollapsingToShowActivityOverLockscreen() {
-        return false;
-    }
+    val isCollapsingToShowActivityOverLockscreen: Boolean
+        get() = false
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/shared/ModesEmptyShadeFix.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/shared/ModesEmptyShadeFix.kt
new file mode 100644
index 0000000..f1fc275
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/shared/ModesEmptyShadeFix.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.emptyshade.shared
+
+import android.app.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the modes_ui_empty_shade flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object ModesEmptyShadeFix {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_MODES_UI_EMPTY_SHADE
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.modesUiEmptyShade()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is not enabled to ensure that the refactor author catches issues in testing.
+     * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+     */
+    @JvmStatic
+    inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/view/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/view/EmptyShadeView.java
index 850e944..73477da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/view/EmptyShadeView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/view/EmptyShadeView.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.View;
@@ -29,26 +30,71 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.systemui.animation.LaunchableView;
+import com.android.systemui.animation.LaunchableViewDelegate;
 import com.android.systemui.res.R;
+import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 
-public class EmptyShadeView extends StackScrollerDecorView {
+import kotlin.Unit;
+
+import java.util.Objects;
+
+public class EmptyShadeView extends StackScrollerDecorView implements LaunchableView {
 
     private TextView mEmptyText;
     private TextView mEmptyFooterText;
 
-    private @StringRes int mText = R.string.empty_shade_text;
+    private @StringRes int mTextId = R.string.empty_shade_text;
+    private String mTextString;
 
-    private @DrawableRes int mFooterIcon = R.drawable.ic_friction_lock_closed;
-    private @StringRes int mFooterText = R.string.unlock_to_see_notif_text;
+    private @DrawableRes int mFooterIcon;
+    private @StringRes int mFooterText;
+    // This view is initially gone in the xml.
     private @Visibility int mFooterVisibility = View.GONE;
     private int mSize;
 
+    private LaunchableViewDelegate mLaunchableViewDelegate = new LaunchableViewDelegate(this,
+            visibility -> {
+                super.setVisibility(visibility);
+                return Unit.INSTANCE;
+            });
+
     public EmptyShadeView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mSize = getResources().getDimensionPixelSize(
                 R.dimen.notifications_unseen_footer_icon_size);
+        if (ModesEmptyShadeFix.isEnabled()) {
+            mTextString = getContext().getString(R.string.empty_shade_text);
+        } else {
+            // These will be set by the binder when appropriate if ModesEmptyShadeFix is on.
+            mFooterIcon = R.drawable.ic_friction_lock_closed;
+            mFooterText = R.string.unlock_to_see_notif_text;
+        }
+    }
+
+    @Override
+    public void setVisibility(int visibility) {
+        mLaunchableViewDelegate.setVisibility(visibility);
+    }
+
+    @Override
+    public void setShouldBlockVisibilityChanges(boolean block) {
+        /* check if */ ModesEmptyShadeFix.isUnexpectedlyInLegacyMode();
+        mLaunchableViewDelegate.setShouldBlockVisibilityChanges(block);
+    }
+
+    @Override
+    public void onActivityLaunchAnimationEnd() {
+        /* check if */ ModesEmptyShadeFix.isUnexpectedlyInLegacyMode();
+    }
+
+    @Override
+    @NonNull
+    public Rect getPaddingForLaunchAnimation() {
+        /* check if */ ModesEmptyShadeFix.isUnexpectedlyInLegacyMode();
+        return new Rect();
     }
 
     @Override
@@ -56,7 +102,11 @@
         super.onConfigurationChanged(newConfig);
         mSize = getResources().getDimensionPixelSize(
                 R.dimen.notifications_unseen_footer_icon_size);
-        mEmptyText.setText(mText);
+        if (ModesEmptyShadeFix.isEnabled()) {
+            mEmptyText.setText(mTextString);
+        } else {
+            mEmptyText.setText(mTextId);
+        }
         mEmptyFooterText.setVisibility(mFooterVisibility);
         setFooterText(mFooterText);
         setFooterIcon(mFooterIcon);
@@ -72,25 +122,45 @@
         return findViewById(R.id.no_notifications_footer);
     }
 
+    /** Update view colors. */
     public void setTextColors(@ColorInt int onSurface, @ColorInt int onSurfaceVariant) {
         mEmptyText.setTextColor(onSurfaceVariant);
         mEmptyFooterText.setTextColor(onSurface);
         mEmptyFooterText.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
     }
 
+    /** Set the resource ID for the main text shown by the view. */
     public void setText(@StringRes int text) {
-        mText = text;
-        mEmptyText.setText(mText);
+        ModesEmptyShadeFix.assertInLegacyMode();
+        mTextId = text;
+        mEmptyText.setText(mTextId);
     }
 
+    /** Set the string for the main text shown by the view. */
+    public void setText(String text) {
+        if (ModesEmptyShadeFix.isUnexpectedlyInLegacyMode() || Objects.equals(mTextString, text)) {
+            return;
+        }
+        mTextString = text;
+        mEmptyText.setText(text);
+    }
+
+    /** Visibility for the footer (the additional icon+text shown below the main text). */
     public void setFooterVisibility(@Visibility int visibility) {
+        if (ModesEmptyShadeFix.isEnabled() && mFooterVisibility == visibility) {
+            return; // nothing to change
+        }
         mFooterVisibility = visibility;
         setSecondaryVisible(/* visible = */ visibility == View.VISIBLE,
                 /* animate = */false,
                 /* onAnimationEnded = */ null);
     }
 
+    /** Text resource ID for the footer (the additional icon+text shown below the main text). */
     public void setFooterText(@StringRes int text) {
+        if (ModesEmptyShadeFix.isEnabled() && mFooterText == text) {
+            return; // nothing to change
+        }
         mFooterText = text;
         if (text != 0) {
             mEmptyFooterText.setText(mFooterText);
@@ -99,7 +169,11 @@
         }
     }
 
+    /** Icon resource ID for the footer (the additional icon+text shown below the main text). */
     public void setFooterIcon(@DrawableRes int icon) {
+        if (ModesEmptyShadeFix.isEnabled() && mFooterIcon == icon) {
+            return; // nothing to change
+        }
         mFooterIcon = icon;
         Drawable drawable;
         if (icon == 0) {
@@ -111,18 +185,24 @@
         mEmptyFooterText.setCompoundDrawablesRelative(drawable, null, null, null);
     }
 
+    /** Get resource ID for main text. */
     @StringRes
     public int getTextResource() {
-        return mText;
+        ModesEmptyShadeFix.assertInLegacyMode();
+        return mTextId;
     }
 
+    /** Get resource ID for footer text. */
     @StringRes
     public int getFooterTextResource() {
+        ModesEmptyShadeFix.assertInLegacyMode();
         return mFooterText;
     }
 
+    /** Get resource ID for footer icon. */
     @DrawableRes
     public int getFooterIconResource() {
+        ModesEmptyShadeFix.assertInLegacyMode();
         return mFooterIcon;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewbinder/EmptyShadeViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewbinder/EmptyShadeViewBinder.kt
new file mode 100644
index 0000000..102a11c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewbinder/EmptyShadeViewBinder.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.emptyshade.ui.viewbinder
+
+import android.view.View
+import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView
+import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.EmptyShadeViewModel
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+object EmptyShadeViewBinder {
+    suspend fun bind(
+        view: EmptyShadeView,
+        viewModel: EmptyShadeViewModel,
+        launchNotificationSettings: View.OnClickListener,
+        launchNotificationHistory: View.OnClickListener,
+    ) = coroutineScope {
+        launch { viewModel.text.collect { view.setText(it) } }
+
+        launch {
+            viewModel.tappingShouldLaunchHistory.collect { shouldLaunchHistory ->
+                if (shouldLaunchHistory) {
+                    view.setOnClickListener(launchNotificationHistory)
+                } else {
+                    view.setOnClickListener(launchNotificationSettings)
+                }
+            }
+        }
+
+        launch { bindFooter(view, viewModel) }
+    }
+
+    private suspend fun bindFooter(view: EmptyShadeView, viewModel: EmptyShadeViewModel) =
+        coroutineScope {
+            // Bind the resource IDs
+            view.setFooterText(viewModel.footer.messageId)
+            view.setFooterIcon(viewModel.footer.iconId)
+
+            launch {
+                viewModel.footer.isVisible.collect { visible ->
+                    view.setFooterVisibility(if (visible) View.VISIBLE else View.GONE)
+                }
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt
new file mode 100644
index 0000000..d5417e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel
+
+import android.content.Context
+import android.icu.text.MessageFormat
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.modes.shared.ModesUi
+import com.android.systemui.res.R
+import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
+import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterMessageViewModel
+import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import com.android.systemui.util.kotlin.FlowDumperImpl
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.Locale
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/**
+ * ViewModel for the empty shade (aka the "No notifications" text shown when there are no
+ * notifications.
+ */
+class EmptyShadeViewModel
+@AssistedInject
+constructor(
+    private val context: Context,
+    zenModeInteractor: ZenModeInteractor,
+    seenNotificationsInteractor: SeenNotificationsInteractor,
+    notificationSettingsInteractor: NotificationSettingsInteractor,
+    dumpManager: DumpManager,
+) : FlowDumperImpl(dumpManager) {
+    val areNotificationsHiddenInShade: Flow<Boolean> by lazy {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+            flowOf(false)
+        } else {
+            zenModeInteractor.areNotificationsHiddenInShade.dumpWhileCollecting(
+                "areNotificationsHiddenInShade"
+            )
+        }
+    }
+
+    val hasFilteredOutSeenNotifications: StateFlow<Boolean> by lazy {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+            MutableStateFlow(false)
+        } else {
+            seenNotificationsInteractor.hasFilteredOutSeenNotifications.dumpValue(
+                "hasFilteredOutSeenNotifications"
+            )
+        }
+    }
+
+    val text: Flow<String> by lazy {
+        if (ModesEmptyShadeFix.isUnexpectedlyInLegacyMode()) {
+            flowOf(context.getString(R.string.empty_shade_text))
+        } else {
+            // Note: Flag modes_ui_empty_shade includes two pieces: refactoring the empty shade to
+            // recommended architecture, and making it so it reacts to changes for the new Modes.
+            // The former does not depend on the modes flags being on, but the latter does.
+            if (ModesUi.isEnabled) {
+                zenModeInteractor.modesHidingNotifications.map { modes ->
+                    // Create a string that is either "No notifications" if no modes are filtering
+                    // them
+                    // out, or something like "Notifications paused by SomeMode" otherwise.
+                    val msgFormat =
+                        MessageFormat(
+                            context.getString(R.string.modes_suppressing_shade_text),
+                            Locale.getDefault(),
+                        )
+                    val count = modes.count()
+                    val args: MutableMap<String, Any> = HashMap()
+                    args["count"] = count
+                    if (count >= 1) {
+                        args["mode"] = modes[0].name
+                    }
+                    msgFormat.format(args)
+                }
+            } else {
+                areNotificationsHiddenInShade.map { areNotificationsHiddenInShade ->
+                    if (areNotificationsHiddenInShade) {
+                        context.getString(R.string.dnd_suppressing_shade_text)
+                    } else {
+                        context.getString(R.string.empty_shade_text)
+                    }
+                }
+            }
+        }
+    }
+
+    val footer: FooterMessageViewModel by lazy {
+        ModesEmptyShadeFix.assertInNewMode()
+        FooterMessageViewModel(
+            messageId = R.string.unlock_to_see_notif_text,
+            iconId = R.drawable.ic_friction_lock_closed,
+            isVisible = hasFilteredOutSeenNotifications,
+        )
+    }
+
+    val tappingShouldLaunchHistory by lazy {
+        ModesEmptyShadeFix.assertInNewMode()
+        notificationSettingsInteractor.isNotificationHistoryEnabled
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): EmptyShadeViewModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index 291dc13..cd228e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -72,14 +72,24 @@
     }
 
     /**
+     * See {@link #setVisible(boolean, boolean, Consumer)}.
+     */
+    public void setVisible(boolean visible, boolean animate) {
+        setVisible(visible, animate, null /* onAnimationEnded */);
+    }
+
+    /**
      * Make this view visible. If {@code false} is passed, the view will fade out its content
      * and set the view Visibility to GONE. If only the content should be changed,
      * {@link #setContentVisibleAnimated(boolean)} can be used.
      *
      * @param visible True if the contents should be visible.
      * @param animate True if we should fade to new visibility.
+     * @param onAnimationEnded Callback to run after visibility updates, takes a boolean as a
+     *                         parameter that represents whether the animation was cancelled.
      */
-    public void setVisible(boolean visible, boolean animate) {
+    public void setVisible(boolean visible, boolean animate,
+            Consumer<Boolean> onAnimationEnded) {
         if (mIsVisible != visible) {
             mIsVisible = visible;
             if (animate) {
@@ -90,10 +100,10 @@
                 } else {
                     setWillBeGone(true);
                 }
-                setContentVisible(visible, true /* animate */, null /* onAnimationEnded */);
+                setContentVisible(visible, true /* animate */, onAnimationEnded);
             } else {
                 setVisibility(visible ? VISIBLE : GONE);
-                setContentVisible(visible, false /* animate */, null /* onAnimationEnded */);
+                setContentVisible(visible, false /* animate */, onAnimationEnded);
                 setWillBeGone(false);
                 notifyHeightChanged(false /* needsAnimation */);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index cd3516d..b2b2c2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -104,6 +104,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix;
 import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView;
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
@@ -686,7 +687,9 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        inflateEmptyShadeView();
+        if (!ModesEmptyShadeFix.isEnabled()) {
+            inflateEmptyShadeView();
+        }
         if (!FooterViewRefactor.isEnabled()) {
             inflateFooterView();
         }
@@ -729,7 +732,9 @@
             inflateFooterView();
             updateFooter();
         }
-        inflateEmptyShadeView();
+        if (!ModesEmptyShadeFix.isEnabled()) {
+            inflateEmptyShadeView();
+        }
         mSectionsManager.reinflateViews();
     }
 
@@ -4835,6 +4840,8 @@
     /** Trigger an update for the empty shade resources and visibility. */
     public void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade,
             boolean hasFilteredOutSeenNotifications) {
+        ModesEmptyShadeFix.assertInLegacyMode();
+
         mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
 
         if (areNotificationsHiddenInShade) {
@@ -4853,6 +4860,8 @@
             @StringRes int newTextRes,
             @StringRes int newFooterTextRes,
             @DrawableRes int newFooterIconRes) {
+        ModesEmptyShadeFix.assertInLegacyMode();
+
         int oldTextRes = mEmptyShadeView.getTextResource();
         if (oldTextRes != newTextRes) {
             mEmptyShadeView.setText(newTextRes);
@@ -4874,6 +4883,9 @@
 
     public boolean isEmptyShadeViewVisible() {
         SceneContainerFlag.assertInLegacyMode();
+        if (mEmptyShadeView == null) {
+            return false;
+        }
         return mEmptyShadeView.isVisible();
     }
 
@@ -5361,7 +5373,7 @@
 
     public float getOpeningHeight() {
         SceneContainerFlag.assertInLegacyMode();
-        if (mEmptyShadeView.getVisibility() == GONE) {
+        if (mEmptyShadeView == null || mEmptyShadeView.getVisibility() == GONE) {
             return getMinExpansionHeight();
         } else {
             return FooterViewRefactor.isEnabled() ? getAppearEndPosition()
@@ -5710,6 +5722,8 @@
     }
 
     private void inflateEmptyShadeView() {
+        ModesEmptyShadeFix.assertInLegacyMode();
+
         EmptyShadeView oldView = mEmptyShadeView;
         EmptyShadeView view = (EmptyShadeView) LayoutInflater.from(mContext).inflate(
                 R.layout.status_bar_no_notifications, this, false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index dc9615c..3dad326 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.stack.ui.viewbinder
 
 import android.view.LayoutInflater
+import android.view.View
 import androidx.lifecycle.lifecycleScope
 import com.android.app.tracing.TraceUtils.traceAsync
 import com.android.internal.logging.MetricsLogger
@@ -25,6 +26,7 @@
 import com.android.systemui.common.ui.view.setImportantForAccessibilityYesNo
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.lifecycle.repeatWhenAttachedToWindow
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -32,6 +34,10 @@
 import com.android.systemui.statusbar.notification.NotificationActivityStarter
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
 import com.android.systemui.statusbar.notification.dagger.SilentHeader
+import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
+import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView
+import com.android.systemui.statusbar.notification.emptyshade.ui.viewbinder.EmptyShadeViewBinder
+import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.EmptyShadeViewModel
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
 import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
@@ -49,6 +55,7 @@
 import com.android.systemui.util.kotlin.awaitCancellationThenDispose
 import com.android.systemui.util.kotlin.getOrNull
 import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.stopAnimating
 import com.android.systemui.util.ui.value
 import java.util.Optional
 import javax.inject.Inject
@@ -84,7 +91,7 @@
 
     fun bindWhileAttached(
         view: NotificationStackScrollLayout,
-        viewController: NotificationStackScrollLayoutController
+        viewController: NotificationStackScrollLayoutController,
     ) {
         val shelf =
             LayoutInflater.from(view.context)
@@ -103,7 +110,13 @@
                     val hasNonClearableSilentNotifications: StateFlow<Boolean> =
                         viewModel.hasNonClearableSilentNotifications.stateIn(this)
                     launch { reinflateAndBindFooter(view, hasNonClearableSilentNotifications) }
-                    launch { bindEmptyShade(view) }
+                    launch {
+                        if (ModesEmptyShadeFix.isEnabled) {
+                            reinflateAndBindEmptyShade(view)
+                        } else {
+                            bindEmptyShadeLegacy(viewModel.emptyShadeViewFactory.create(), view)
+                        }
+                    }
                     launch {
                         bindSilentHeaderClickListener(view, hasNonClearableSilentNotifications)
                     }
@@ -121,17 +134,12 @@
     }
 
     private suspend fun bindShelf(shelf: NotificationShelf) {
-        NotificationShelfViewBinder.bind(
-            shelf,
-            viewModel.shelf,
-            falsingManager,
-            nicBinder,
-        )
+        NotificationShelfViewBinder.bind(shelf, viewModel.shelf, falsingManager, nicBinder)
     }
 
     private suspend fun reinflateAndBindFooter(
         parentView: NotificationStackScrollLayout,
-        hasNonClearableSilentNotifications: StateFlow<Boolean>
+        hasNonClearableSilentNotifications: StateFlow<Boolean>,
     ) {
         viewModel.footer.getOrNull()?.let { footerViewModel ->
             // The footer needs to be re-inflated every time the theme or the font size changes.
@@ -149,7 +157,7 @@
                             footerView,
                             footerViewModel,
                             parentView,
-                            hasNonClearableSilentNotifications
+                            hasNonClearableSilentNotifications,
                         )
                     }
                 }
@@ -163,13 +171,13 @@
         footerView: FooterView,
         footerViewModel: FooterViewModel,
         parentView: NotificationStackScrollLayout,
-        hasNonClearableSilentNotifications: StateFlow<Boolean>
+        hasNonClearableSilentNotifications: StateFlow<Boolean>,
     ): Unit = coroutineScope {
         val disposableHandle =
             FooterViewBinder.bindWhileAttached(
                 footerView,
                 footerViewModel,
-                clearAllNotifications = {
+                {
                     clearAllNotifications(
                         parentView,
                         // Hide the silent section header (if present) if there will be
@@ -177,16 +185,8 @@
                         hideSilentSection = !hasNonClearableSilentNotifications.value,
                     )
                 },
-                launchNotificationSettings = { view ->
-                    notificationActivityStarter
-                        .get()
-                        .startHistoryIntent(view, /* showHistory= */ false)
-                },
-                launchNotificationHistory = { view ->
-                    notificationActivityStarter
-                        .get()
-                        .startHistoryIntent(view, /* showHistory= */ true)
-                },
+                launchNotificationSettings,
+                launchNotificationHistory,
             )
         if (SceneContainerFlag.isEnabled) {
             launch {
@@ -194,7 +194,9 @@
                     footerView.setVisible(
                         /* visible = */ animatedVisibility.value,
                         /* animate = */ animatedVisibility.isAnimating,
-                    )
+                    ) {
+                        animatedVisibility.stopAnimating()
+                    }
                 }
             }
         } else {
@@ -211,20 +213,71 @@
         disposableHandle.awaitCancellationThenDispose()
     }
 
-    private suspend fun bindEmptyShade(parentView: NotificationStackScrollLayout) {
+    private val launchNotificationSettings: (View) -> Unit = { view: View ->
+        notificationActivityStarter.get().startHistoryIntent(view, /* showHistory= */ false)
+    }
+
+    private val launchNotificationHistory: (View) -> Unit = { view ->
+        notificationActivityStarter.get().startHistoryIntent(view, /* showHistory= */ true)
+    }
+
+    private suspend fun reinflateAndBindEmptyShade(parentView: NotificationStackScrollLayout) {
+        ModesEmptyShadeFix.assertInNewMode()
+        // The empty shade needs to be re-inflated every time the theme or the font size
+        // changes.
+        configuration
+            .inflateLayout<EmptyShadeView>(
+                R.layout.status_bar_no_notifications,
+                parentView,
+                attachToRoot = false,
+            )
+            .flowOn(backgroundDispatcher)
+            .collectLatest { emptyShadeView: EmptyShadeView ->
+                traceAsync("bind EmptyShadeView") {
+                    parentView.setEmptyShadeView(emptyShadeView)
+                    bindEmptyShade(emptyShadeView, viewModel.emptyShadeViewFactory.create())
+                }
+            }
+    }
+
+    private suspend fun bindEmptyShadeLegacy(
+        emptyShadeViewModel: EmptyShadeViewModel,
+        parentView: NotificationStackScrollLayout,
+    ) {
+        ModesEmptyShadeFix.assertInLegacyMode()
         combine(
                 viewModel.shouldShowEmptyShadeView,
-                viewModel.areNotificationsHiddenInShade,
-                viewModel.hasFilteredOutSeenNotifications,
-                ::Triple
+                emptyShadeViewModel.areNotificationsHiddenInShade,
+                emptyShadeViewModel.hasFilteredOutSeenNotifications,
+                ::Triple,
             )
             .collect { (shouldShow, areNotifsHidden, hasFilteredNotifs) ->
-                parentView.updateEmptyShadeView(
-                    shouldShow,
-                    areNotifsHidden,
-                    hasFilteredNotifs,
+                parentView.updateEmptyShadeView(shouldShow, areNotifsHidden, hasFilteredNotifs)
+            }
+    }
+
+    private suspend fun bindEmptyShade(
+        emptyShadeView: EmptyShadeView,
+        emptyShadeViewModel: EmptyShadeViewModel,
+    ): Unit = coroutineScope {
+        ModesEmptyShadeFix.assertInNewMode()
+        launch {
+            emptyShadeView.repeatWhenAttachedToWindow {
+                EmptyShadeViewBinder.bind(
+                    emptyShadeView,
+                    emptyShadeViewModel,
+                    launchNotificationSettings,
+                    launchNotificationHistory,
                 )
             }
+        }
+        launch {
+            viewModel.shouldShowEmptyShadeViewAnimated.collect { shouldShow ->
+                emptyShadeView.setVisible(shouldShow.value, shouldShow.isAnimating) {
+                    shouldShow.stopAnimating()
+                }
+            }
+        }
     }
 
     private suspend fun bindSilentHeaderClickListener(
@@ -261,7 +314,7 @@
     private fun clearSilentNotifications(
         view: NotificationStackScrollLayout,
         closeShade: Boolean,
-        hideSilentSection: Boolean
+        hideSilentSection: Boolean,
     ) {
         view.clearSilentNotifications(closeShade, hideSilentSection)
     }
@@ -270,11 +323,7 @@
         if (NotificationsLiveDataStoreRefactor.isEnabled) {
             viewModel.logger.getOrNull()?.let { viewModel ->
                 loggerOptional.getOrNull()?.let { logger ->
-                    NotificationStatsLoggerBinder.bindLogger(
-                        view,
-                        logger,
-                        viewModel,
-                    )
+                    NotificationStatsLoggerBinder.bindLogger(view, logger, viewModel)
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 4e2a46d..935e2a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -23,14 +23,14 @@
 import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
-import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
+import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.EmptyShadeViewModel
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
 import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
 import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
 import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackInteractor
 import com.android.systemui.statusbar.policy.domain.interactor.UserSetupInteractor
-import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
 import com.android.systemui.util.kotlin.FlowDumperImpl
 import com.android.systemui.util.kotlin.combine
 import com.android.systemui.util.kotlin.sample
@@ -48,22 +48,24 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 
-/** ViewModel for the list of notifications. */
+/**
+ * ViewModel for the list of notifications, including child elements like the Clear all/Manage
+ * button at the bottom (the footer) and the "No notifications" text (the empty shade).
+ */
 class NotificationListViewModel
 @Inject
 constructor(
     val shelf: NotificationShelfViewModel,
     val hideListViewModel: HideListViewModel,
     val footer: Optional<FooterViewModel>,
+    val emptyShadeViewFactory: EmptyShadeViewModel.Factory,
     val logger: Optional<NotificationLoggerViewModel>,
     activeNotificationsInteractor: ActiveNotificationsInteractor,
     notificationStackInteractor: NotificationStackInteractor,
     private val headsUpNotificationInteractor: HeadsUpNotificationInteractor,
     remoteInputInteractor: RemoteInputInteractor,
-    seenNotificationsInteractor: SeenNotificationsInteractor,
     shadeInteractor: ShadeInteractor,
     userSetupInteractor: UserSetupInteractor,
-    zenModeInteractor: ZenModeInteractor,
     @Background bgDispatcher: CoroutineDispatcher,
     dumpManager: DumpManager,
 ) : FlowDumperImpl(dumpManager) {
@@ -90,6 +92,7 @@
     }
 
     val shouldShowEmptyShadeView: Flow<Boolean> by lazy {
+        ModesEmptyShadeFix.assertInLegacyMode()
         if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
             flowOf(false)
         } else {
@@ -114,6 +117,45 @@
         }
     }
 
+    val shouldShowEmptyShadeViewAnimated: Flow<AnimatedValue<Boolean>> by lazy {
+        if (ModesEmptyShadeFix.isUnexpectedlyInLegacyMode()) {
+            flowOf(AnimatedValue.NotAnimating(false))
+        } else {
+            combine(
+                    activeNotificationsInteractor.areAnyNotificationsPresent,
+                    shadeInteractor.isQsFullscreen,
+                    notificationStackInteractor.isShowingOnLockscreen,
+                ) { hasNotifications, isQsFullScreen, isShowingOnLockscreen ->
+                    when {
+                        hasNotifications -> false
+                        isQsFullScreen -> false
+                        // Do not show the empty shade if the lockscreen is visible (including AOD
+                        // b/228790482 and bouncer b/267060171), except if the shade is opened on
+                        // top.
+                        isShowingOnLockscreen -> false
+                        else -> true
+                    }
+                }
+                .distinctUntilChanged()
+                .sample(
+                    // TODO(b/322167853): This check is currently duplicated in FooterViewModel
+                    //  but instead it should be a field in ShadeAnimationInteractor.
+                    combine(
+                            shadeInteractor.isShadeFullyExpanded,
+                            shadeInteractor.isShadeTouchable,
+                            ::Pair,
+                        )
+                        .onStart { emit(Pair(false, false)) }
+                ) { visible, (isShadeFullyExpanded, animationsEnabled) ->
+                    val shouldAnimate = isShadeFullyExpanded && animationsEnabled
+                    AnimatableEvent(visible, shouldAnimate)
+                }
+                .toAnimatedValueFlow()
+                .dumpWhileCollecting("shouldShowEmptyShadeViewAnimated")
+                .flowOn(bgDispatcher)
+        }
+    }
+
     /**
      * Whether the footer should not be visible for the user, even if it's present in the list (as
      * per [shouldIncludeFooterView] below).
@@ -154,7 +196,7 @@
                     userSetupInteractor.isUserSetUp,
                     notificationStackInteractor.isShowingOnLockscreen,
                     shadeInteractor.isQsFullscreen,
-                    remoteInputInteractor.isRemoteInputActive
+                    remoteInputInteractor.isRemoteInputActive,
                 ) {
                     hasNotifications,
                     isUserSetUp,
@@ -193,7 +235,7 @@
                     combine(
                             shadeInteractor.isShadeFullyExpanded,
                             shadeInteractor.isShadeTouchable,
-                            ::Pair
+                            ::Pair,
                         )
                         .onStart { emit(Pair(false, false)) }
                 ) { visibilityChange, (isShadeFullyExpanded, animationsEnabled) ->
@@ -263,7 +305,7 @@
                     combine(
                             shadeInteractor.isShadeFullyExpanded,
                             shadeInteractor.isShadeTouchable,
-                            ::Pair
+                            ::Pair,
                         )
                         .onStart { emit(Pair(false, false)) }
                 ) { visibilityChange, (isShadeFullyExpanded, animationsEnabled) ->
@@ -283,29 +325,7 @@
     enum class VisibilityChange(val visible: Boolean, val canAnimate: Boolean) {
         DISAPPEAR_WITHOUT_ANIMATION(visible = false, canAnimate = false),
         DISAPPEAR_WITH_ANIMATION(visible = false, canAnimate = true),
-        APPEAR_WITH_ANIMATION(visible = true, canAnimate = true)
-    }
-
-    // TODO(b/308591475): This should be tracked separately by the empty shade.
-    val areNotificationsHiddenInShade: Flow<Boolean> by lazy {
-        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
-            flowOf(false)
-        } else {
-            zenModeInteractor.areNotificationsHiddenInShade.dumpWhileCollecting(
-                "areNotificationsHiddenInShade"
-            )
-        }
-    }
-
-    // TODO(b/308591475): This should be tracked separately by the empty shade.
-    val hasFilteredOutSeenNotifications: Flow<Boolean> by lazy {
-        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
-            flowOf(false)
-        } else {
-            seenNotificationsInteractor.hasFilteredOutSeenNotifications.dumpWhileCollecting(
-                "hasFilteredOutSeenNotifications"
-            )
-        }
+        APPEAR_WITH_ANIMATION(visible = true, canAnimate = true),
     }
 
     val hasClearableAlertingNotifications: Flow<Boolean> by lazy {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
index ba45942..daba109 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -20,6 +20,7 @@
 import android.provider.Settings
 import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
 import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
+import android.service.notification.ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST
 import android.util.Log
 import androidx.concurrent.futures.await
 import com.android.settingslib.notification.data.repository.ZenModeRepository
@@ -29,6 +30,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.modes.shared.ModesUi
 import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
+import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
 import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
 import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
 import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes
@@ -39,6 +41,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 
@@ -54,8 +57,8 @@
     private val notificationSettingsRepository: NotificationSettingsRepository,
     @Background private val bgDispatcher: CoroutineDispatcher,
     private val iconLoader: ZenIconLoader,
-    private val deviceProvisioningRepository: DeviceProvisioningRepository,
-    private val userSetupRepository: UserSetupRepository,
+    deviceProvisioningRepository: DeviceProvisioningRepository,
+    userSetupRepository: UserSetupRepository,
 ) {
     val isZenAvailable: Flow<Boolean> =
         combine(
@@ -126,6 +129,25 @@
     val mainActiveMode: Flow<ZenModeInfo?> =
         activeModes.map { a -> a.mainMode }.distinctUntilChanged()
 
+    val modesHidingNotifications: Flow<List<ZenMode>> by lazy {
+        if (ModesEmptyShadeFix.isUnexpectedlyInLegacyMode() || !ModesUi.isEnabled) {
+            flowOf(listOf())
+        } else {
+            modes
+                .map { modes ->
+                    modes.filter { mode ->
+                        mode.isActive &&
+                            !mode.policy.isVisualEffectAllowed(
+                                /* effect = */ VISUAL_EFFECT_NOTIFICATION_LIST,
+                                /* defaultVal = */ true,
+                            )
+                    }
+                }
+                .flowOn(bgDispatcher)
+                .distinctUntilChanged()
+        }
+    }
+
     suspend fun getModeIcon(mode: ZenMode): ZenIcon {
         return iconLoader.getIcon(context, mode).await()
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelKosmos.kt
new file mode 100644
index 0000000..8fdb948
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.shared.notifications.domain.interactor.notificationSettingsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
+
+val Kosmos.emptyShadeViewModel by
+    Kosmos.Fixture {
+        EmptyShadeViewModel(
+            applicationContext,
+            zenModeInteractor,
+            seenNotificationsInteractor,
+            notificationSettingsInteractor,
+            dumpManager,
+        )
+    }
+
+val Kosmos.emptyShadeViewModelFactory: EmptyShadeViewModel.Factory by
+    Kosmos.Fixture {
+        object : EmptyShadeViewModel.Factory {
+            override fun create() = emptyShadeViewModel
+        }
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
index de8b350..c3bc744 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
@@ -23,13 +23,12 @@
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
-import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
+import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.emptyShadeViewModelFactory
 import com.android.systemui.statusbar.notification.footer.ui.viewmodel.footerViewModel
 import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.notificationShelfViewModel
 import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackInteractor
 import com.android.systemui.statusbar.policy.domain.interactor.userSetupInteractor
-import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
 import java.util.Optional
 
 val Kosmos.notificationListViewModel by Fixture {
@@ -37,15 +36,14 @@
         notificationShelfViewModel,
         hideListViewModel,
         Optional.of(footerViewModel),
+        emptyShadeViewModelFactory,
         Optional.of(notificationListLoggerViewModel),
         activeNotificationsInteractor,
         notificationStackInteractor,
         headsUpNotificationInteractor,
         remoteInputInteractor,
-        seenNotificationsInteractor,
         shadeInteractor,
         userSetupInteractor,
-        zenModeInteractor,
         testDispatcher,
         dumpManager,
     )