Check StatusBarState in shouldShowEmptyShadeView.

Both the empty shade and the footer independently had different bugs
filed where they were showing up during aod/keyguard transitions. In the
old code, these were fixed in slightly different ways by different
people, but the root cause of these bugs was actually the same. Now that
we have these checks in one place, it's become clear that we can
actually check for the same conditions to make sure the empty shade and
the footer aren't showing when the device is locked (unless the shade is
open on top).

Bottom line is, although the old code was checking for specific
transitions to avoid showing the empty shade, these were just specific
edge cases we found and fixed individually. Instead of checking for edge
cases (and potentially risking new edge cases showing up), it's more
effective to simply define that the empty shade should never be showing
on the keyguard.

Bug: 293167744
Test: NotificationListViewModelTest + checked repro steps from
b/228790482 and b/267060171 to make sure there's no regression
Flag: ACONFIG com.android.systemui.notifications_footer_view_refactor STAGING

Change-Id: Ifc38554eab7acaa11333cf2a2516fb3e0b3e07ee
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 ff3d4c0..7fd2ee1 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
@@ -74,6 +74,10 @@
         } else {
             combine(
                     activeNotificationsInteractor.areAnyNotificationsPresent,
+                    // TODO(b/293167744): Check if it would be enough to just check for
+                    //  isShowingOnLockscreen here as well, so we don't need to depend on the
+                    //  keyguardTransitionInteractor when we don't actually care about _transitions_
+                    //  specifically.
                     keyguardTransitionInteractor.isFinishedInStateWhere {
                         KeyguardState.lockscreenVisibleInState(it)
                     }
@@ -91,26 +95,17 @@
             combine(
                     activeNotificationsInteractor.areAnyNotificationsPresent,
                     shadeInteractor.isQsFullscreen,
-                    // TODO(b/293167744): It looks like we're essentially trying to check the same
-                    //  things for the empty shade visibility as we do for the footer, just in a
-                    //  slightly different way. We should change this so we also check
-                    //  statusBarState and isAwake instead of specific keyguard transitions.
-                    keyguardTransitionInteractor.isInTransitionToState(KeyguardState.AOD).onStart {
-                        emit(false)
-                    },
-                    keyguardTransitionInteractor
-                        .isFinishedInState(KeyguardState.PRIMARY_BOUNCER)
-                        .onStart { emit(false) }
-                ) { hasNotifications, isQsFullScreen, transitioningToAOD, isBouncerShowing ->
-                    !hasNotifications &&
-                        !isQsFullScreen &&
-                        // Hide empty shade view when in transition to AOD.
-                        // That avoids "No Notifications" blinking when transitioning to AOD.
-                        // For more details, see b/228790482.
-                        !transitioningToAOD &&
-                        // Don't show any notification content if the bouncer is showing. See
-                        // b/267060171.
-                        !isBouncerShowing
+                    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()
         }
@@ -123,52 +118,47 @@
             combine(
                     activeNotificationsInteractor.areAnyNotificationsPresent,
                     userSetupInteractor.isUserSetUp,
-                    keyguardInteractor.statusBarState.map { it == StatusBarState.KEYGUARD },
+                    isShowingOnLockscreen,
                     shadeInteractor.qsExpansion,
                     shadeInteractor.isQsFullscreen,
-                    powerInteractor.isAsleep,
                     remoteInputInteractor.isRemoteInputActive,
                     shadeInteractor.shadeExpansion.map { it == 0f }
                 ) {
                     hasNotifications,
                     isUserSetUp,
-                    isOnKeyguard,
+                    isShowingOnLockscreen,
                     qsExpansion,
                     qsFullScreen,
-                    isAsleep,
                     isRemoteInputActive,
                     isShadeClosed ->
-                    Pair(
-                        // Should the footer be visible?
-                        when {
-                            !hasNotifications -> false
-                            // Hide the footer until the user setup is complete, to prevent access
-                            // to settings (b/193149550).
-                            !isUserSetUp -> false
-                            // Do not show the footer if the lockscreen is visible (incl. AOD),
-                            // except if the shade is opened on top. See also b/219680200.
-                            isOnKeyguard -> false
-                            // Make sure we're not showing the footer in the transition to AOD while
-                            // going to sleep (b/190227875). The StatusBarState is unfortunately not
-                            // updated quickly enough when the power button is pressed, so this is
-                            // necessary in addition to the isOnKeyguard check.
-                            isAsleep -> false
-                            // Do not show the footer if quick settings are fully expanded (except
-                            // for the foldable split shade view). See b/201427195 && b/222699879.
-                            qsExpansion == 1f && qsFullScreen -> false
-                            // Hide the footer if remote input is active (i.e. user is replying to a
-                            // notification). See b/75984847.
-                            isRemoteInputActive -> false
-                            // Never show the footer if the shade is collapsed (e.g. when HUNing).
-                            isShadeClosed -> false
-                            else -> true
-                        },
-                        // This could in theory be in the .sample below, but it tends to be
-                        // inconsistent, so we're passing it on to make sure we have the same state.
-                        isOnKeyguard
-                    )
+                    // A pair of (visible, canAnimate)
+                    when {
+                        !hasNotifications -> Pair(false, true)
+                        // Hide the footer until the user setup is complete, to prevent access
+                        // to settings (b/193149550).
+                        !isUserSetUp -> Pair(false, true)
+                        // Do not show the footer if the lockscreen is visible (incl. AOD),
+                        // except if the shade is opened on top. See also b/219680200.
+                        // Do not animate, as that makes the footer appear briefly when
+                        // transitioning between the shade and keyguard.
+                        isShowingOnLockscreen -> Pair(false, false)
+                        // Do not show the footer if quick settings are fully expanded (except
+                        // for the foldable split shade view). See b/201427195 && b/222699879.
+                        qsExpansion == 1f && qsFullScreen -> Pair(false, true)
+                        // Hide the footer if remote input is active (i.e. user is replying to a
+                        // notification). See b/75984847.
+                        isRemoteInputActive -> Pair(false, true)
+                        // Never show the footer if the shade is collapsed (e.g. when HUNing).
+                        isShadeClosed -> Pair(false, false)
+                        else -> Pair(true, true)
+                    }
                 }
-                .distinctUntilChanged()
+                .distinctUntilChanged(
+                    // Equivalent unless visibility changes
+                    areEquivalent = { a: Pair<Boolean, Boolean>, b: Pair<Boolean, Boolean> ->
+                        a.first == b.first
+                    }
+                )
                 // Should we animate the visibility change?
                 .sample(
                     // TODO(b/322167853): This check is currently duplicated in FooterViewModel,
@@ -179,17 +169,40 @@
                             ::Pair
                         )
                         .onStart { emit(Pair(false, false)) }
-                ) { (visible, isOnKeyguard), (isShadeFullyExpanded, animationsEnabled) ->
+                ) { (visible, canAnimate), (isShadeFullyExpanded, animationsEnabled) ->
                     // Animate if the shade is interactive, but NOT on the lockscreen. Having
                     // animations enabled while on the lockscreen makes the footer appear briefly
                     // when transitioning between the shade and keyguard.
-                    val shouldAnimate = isShadeFullyExpanded && animationsEnabled && !isOnKeyguard
+                    val shouldAnimate = isShadeFullyExpanded && animationsEnabled && canAnimate
                     AnimatableEvent(visible, shouldAnimate)
                 }
                 .toAnimatedValueFlow()
         }
     }
 
+    private val isShowingOnLockscreen: Flow<Boolean> by lazy {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+            flowOf(false)
+        } else {
+            combine(
+                    // Non-notification UI elements of the notification list should not be visible
+                    // on the lockscreen (incl. AOD and bouncer), except if the shade is opened on
+                    // top. See b/219680200 for the footer and b/228790482, b/267060171 for the
+                    // empty shade.
+                    // TODO(b/323187006): There's a plan to eventually get rid of StatusBarState
+                    //  entirely, so this will have to be replaced at some point.
+                    keyguardInteractor.statusBarState.map { it == StatusBarState.KEYGUARD },
+                    // The StatusBarState is unfortunately not updated quickly enough when the power
+                    // button is pressed, so this is necessary in addition to the KEYGUARD check to
+                    // cover the transition to AOD while going to sleep (b/190227875).
+                    powerInteractor.isAsleep,
+                ) { (isOnKeyguard, isAsleep) ->
+                    isOnKeyguard || isAsleep
+                }
+                .distinctUntilChanged()
+        }
+    }
+
     // TODO(b/308591475): This should be tracked separately by the empty shade.
     val areNotificationsHiddenInShade: Flow<Boolean> by lazy {
         if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index 3a7659d..7dff7cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -31,8 +31,6 @@
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.data.repository.fakePowerRepository
 import com.android.systemui.power.shared.model.WakefulnessState
@@ -150,7 +148,7 @@
             activeNotificationListRepository.setActiveNotifs(count = 0)
             runCurrent()
 
-            // THEN should show
+            // THEN empty shade is visible
             assertThat(shouldShow).isTrue()
         }
 
@@ -163,7 +161,7 @@
             activeNotificationListRepository.setActiveNotifs(count = 2)
             runCurrent()
 
-            // THEN should not show
+            // THEN empty shade is not visible
             assertThat(shouldShow).isFalse()
         }
 
@@ -178,7 +176,7 @@
             fakeShadeRepository.legacyQsFullscreen.value = true
             runCurrent()
 
-            // THEN should not show
+            // THEN empty shade is not visible
             assertThat(shouldShow).isFalse()
         }
 
@@ -196,48 +194,54 @@
             fakeConfigurationController.notifyConfigurationChanged()
             runCurrent()
 
-            // THEN should show
+            // THEN empty shade is visible
             assertThat(shouldShow).isTrue()
         }
 
     @Test
-    fun testShouldShowEmptyShadeView_falseWhenTransitioningToAOD() =
+    fun testShouldShowEmptyShadeView_trueWhenLockedShade() =
         testScope.runTest {
             val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
 
             // WHEN has no notifs
             activeNotificationListRepository.setActiveNotifs(count = 0)
-            // AND transitioning to AOD
-            fakeKeyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.AOD,
-                    value = 0f,
-                )
-            )
+            // AND shade is open
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
             runCurrent()
 
-            // THEN should not show
+            // THEN empty shade is visible
+            assertThat(shouldShow).isTrue()
+        }
+
+    @Test
+    fun testShouldShowEmptyShadeView_falseWhenKeyguard() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
+
+            // WHEN has no notifs
+            activeNotificationListRepository.setActiveNotifs(count = 0)
+            // AND shade is open
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+            runCurrent()
+
+            // THEN empty shade is not visible
             assertThat(shouldShow).isFalse()
         }
 
     @Test
-    fun testShouldShowEmptyShadeView_falseWhenBouncerShowing() =
+    fun testShouldShowEmptyShadeView_falseWhenStartingToSleep() =
         testScope.runTest {
             val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
 
             // WHEN has no notifs
             activeNotificationListRepository.setActiveNotifs(count = 0)
-            // AND is on bouncer
-            fakeKeyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.LOCKSCREEN,
-                to = KeyguardState.PRIMARY_BOUNCER,
-                testScope,
-            )
+            // AND shade is open
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            // AND device is starting to go to sleep
+            fakePowerRepository.updateWakefulness(WakefulnessState.STARTING_TO_SLEEP)
             runCurrent()
 
-            // THEN should not show
+            // THEN empty shade is not visible
             assertThat(shouldShow).isFalse()
         }