Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a7703c68 authored by Ioana Alexandru's avatar Ioana Alexandru
Browse files

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
parent 29c2a246
Loading
Loading
Loading
Loading
+69 −56
Original line number Diff line number Diff line
@@ -74,6 +74,10 @@ constructor(
        } 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 @@ constructor(
            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 @@ constructor(
            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?
                    // A pair of (visible, canAnimate)
                    when {
                            !hasNotifications -> false
                        !hasNotifications -> Pair(false, true)
                        // Hide the footer until the user setup is complete, to prevent access
                        // to settings (b/193149550).
                            !isUserSetUp -> false
                        !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.
                            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 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 -> false
                        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 -> false
                        isRemoteInputActive -> Pair(false, true)
                        // 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
                    )
                        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 @@ constructor(
                            ::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()) {
+29 −25
Original line number Diff line number Diff line
@@ -31,8 +31,6 @@ import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
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 @@ class NotificationListViewModelTest : SysuiTestCase() {
            activeNotificationListRepository.setActiveNotifs(count = 0)
            runCurrent()

            // THEN should show
            // THEN empty shade is visible
            assertThat(shouldShow).isTrue()
        }

@@ -163,7 +161,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
            activeNotificationListRepository.setActiveNotifs(count = 2)
            runCurrent()

            // THEN should not show
            // THEN empty shade is not visible
            assertThat(shouldShow).isFalse()
        }

@@ -178,7 +176,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
            fakeShadeRepository.legacyQsFullscreen.value = true
            runCurrent()

            // THEN should not show
            // THEN empty shade is not visible
            assertThat(shouldShow).isFalse()
        }

@@ -196,48 +194,54 @@ class NotificationListViewModelTest : SysuiTestCase() {
            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 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 should not show
            // 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()
        }