Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java +15 −0 Original line number Diff line number Diff line Loading @@ -58,6 +58,7 @@ public class FooterView extends StackScrollerDecorView { private FooterViewButton mClearAllButton; private FooterViewButton mManageOrHistoryButton; private boolean mShouldBeHidden; private boolean mShowHistory; // String cache, for performance reasons. // Reading them from a Resources object can be quite slow sometimes. Loading Loading @@ -110,6 +111,20 @@ public class FooterView extends StackScrollerDecorView { setSecondaryVisible(visible, animate, onAnimationEnded); } /** See {@link this#setShouldBeHidden} below. */ public boolean shouldBeHidden() { return mShouldBeHidden; } /** * Whether this view's visibility should be set to INVISIBLE. Note that this is different from * the {@link StackScrollerDecorView#setVisible} method, which in turn handles visibility * transitions between VISIBLE and GONE. */ public void setShouldBeHidden(boolean hide) { mShouldBeHidden = hide; } @Override public void dump(PrintWriter pwOriginal, String[] args) { IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +10 −9 Original line number Diff line number Diff line Loading @@ -594,15 +594,16 @@ public class StackScrollAlgorithm { ); if (view instanceof FooterView) { if (FooterViewRefactor.isEnabled()) { if (((FooterView) view).shouldBeHidden()) { viewState.hidden = true; } else { final float footerEnd = algorithmState.mCurrentExpandedYPosition + view.getIntrinsicHeight(); final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight(); // TODO(b/293167744): May be able to keep only noSpaceForFooter here if we add an // emission when clearAllNotifications is called, and then use that in the footer // visibility flow. ((FooterView.FooterViewState) viewState).hideContent = noSpaceForFooter || (ambientState.isClearAllInProgress() && !hasNonClearableNotifs(algorithmState)); } } else { final boolean shadeClosed = !ambientState.isShadeExpanded(); Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +2 −1 Original line number Diff line number Diff line Loading @@ -193,13 +193,14 @@ constructor( }, ) launch { viewModel.shouldShowFooterView.collect { animatedVisibility -> viewModel.shouldIncludeFooterView.collect { animatedVisibility -> footerView.setVisible( /* visible = */ animatedVisibility.value, /* animate = */ animatedVisibility.isAnimating, ) } } launch { viewModel.shouldHideFooterView.collect { footerView.setShouldBeHidden(it) } } disposableHandle.awaitCancellationThenDispose() } Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt +37 −17 Original line number Diff line number Diff line Loading @@ -31,7 +31,6 @@ import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.Notificati 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.combine import com.android.systemui.util.kotlin.sample import com.android.systemui.util.ui.AnimatableEvent import com.android.systemui.util.ui.AnimatedValue Loading Loading @@ -111,7 +110,32 @@ constructor( } } val shouldShowFooterView: Flow<AnimatedValue<Boolean>> by lazy { /** * Whether the footer should not be visible for the user, even if it's present in the list (as * per [shouldIncludeFooterView] below). * * This essentially corresponds to having the view set to INVISIBLE. */ val shouldHideFooterView: Flow<Boolean> by lazy { if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { flowOf(false) } else { // When the shade is closed, the footer is still present in the list, but not visible. // This prevents the footer from being shown when a HUN is present, while still allowing // the footer to be counted as part of the shade for measurements. shadeInteractor.shadeExpansion.map { it == 0f }.distinctUntilChanged() } } /** * Whether the footer should be part of the list or not, and whether the transition from one * state to another should be animated. This essentially corresponds to transitioning the view * visibility from VISIBLE to GONE and vice versa. * * Note that this value being true doesn't necessarily mean that the footer is visible. It could * be hidden by another condition (see [shouldHideFooterView] above). */ val shouldIncludeFooterView: Flow<AnimatedValue<Boolean>> by lazy { if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { flowOf(AnimatedValue.NotAnimating(false)) } else { Loading @@ -120,34 +144,30 @@ constructor( userSetupInteractor.isUserSetUp, notificationStackInteractor.isShowingOnLockscreen, shadeInteractor.isQsFullscreen, remoteInputInteractor.isRemoteInputActive, shadeInteractor.shadeExpansion.map { it == 0f }.distinctUntilChanged(), remoteInputInteractor.isRemoteInputActive ) { hasNotifications, isUserSetUp, isShowingOnLockscreen, qsFullScreen, isRemoteInputActive, isShadeClosed -> isRemoteInputActive -> when { !hasNotifications -> VisibilityChange.HIDE_WITH_ANIMATION !hasNotifications -> VisibilityChange.DISAPPEAR_WITH_ANIMATION // Hide the footer until the user setup is complete, to prevent access // to settings (b/193149550). !isUserSetUp -> VisibilityChange.HIDE_WITH_ANIMATION !isUserSetUp -> VisibilityChange.DISAPPEAR_WITH_ANIMATION // 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 -> VisibilityChange.HIDE_WITHOUT_ANIMATION isShowingOnLockscreen -> VisibilityChange.DISAPPEAR_WITHOUT_ANIMATION // Do not show the footer if quick settings are fully expanded (except // for the foldable split shade view). See b/201427195 && b/222699879. qsFullScreen -> VisibilityChange.HIDE_WITH_ANIMATION qsFullScreen -> VisibilityChange.DISAPPEAR_WITH_ANIMATION // Hide the footer if remote input is active (i.e. user is replying to a // notification). See b/75984847. isRemoteInputActive -> VisibilityChange.HIDE_WITH_ANIMATION // Never show the footer if the shade is collapsed (e.g. when HUNing). isShadeClosed -> VisibilityChange.HIDE_WITHOUT_ANIMATION else -> VisibilityChange.SHOW_WITH_ANIMATION isRemoteInputActive -> VisibilityChange.DISAPPEAR_WITH_ANIMATION else -> VisibilityChange.APPEAR_WITH_ANIMATION } } .flowOn(bgDispatcher) Loading Loading @@ -180,9 +200,9 @@ constructor( } enum class VisibilityChange(val visible: Boolean, val canAnimate: Boolean) { HIDE_WITHOUT_ANIMATION(visible = false, canAnimate = false), HIDE_WITH_ANIMATION(visible = false, canAnimate = true), SHOW_WITH_ANIMATION(visible = true, canAnimate = true) 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. Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt +77 −65 Original line number Diff line number Diff line Loading @@ -130,35 +130,35 @@ class NotificationListViewModelTest : SysuiTestCase() { } @Test fun testShouldShowEmptyShadeView_trueWhenNoNotifs() = fun testShouldIncludeEmptyShadeView_trueWhenNoNotifs() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) runCurrent() // THEN empty shade is visible assertThat(shouldShow).isTrue() assertThat(shouldInclude).isTrue() } @Test fun testShouldShowEmptyShadeView_falseWhenNotifs() = fun testShouldIncludeEmptyShadeView_falseWhenNotifs() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) runCurrent() // THEN empty shade is not visible assertThat(shouldShow).isFalse() assertThat(shouldInclude).isFalse() } @Test fun testShouldShowEmptyShadeView_falseWhenQsExpandedDefault() = fun testShouldIncludeEmptyShadeView_falseWhenQsExpandedDefault() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) Loading @@ -167,13 +167,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN empty shade is not visible assertThat(shouldShow).isFalse() assertThat(shouldInclude).isFalse() } @Test fun testShouldShowEmptyShadeView_trueWhenQsExpandedInSplitShade() = fun testShouldIncludeEmptyShadeView_trueWhenQsExpandedInSplitShade() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) Loading @@ -185,13 +185,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN empty shade is visible assertThat(shouldShow).isTrue() assertThat(shouldInclude).isTrue() } @Test fun testShouldShowEmptyShadeView_trueWhenLockedShade() = fun testShouldIncludeEmptyShadeView_trueWhenLockedShade() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) Loading @@ -200,13 +200,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN empty shade is visible assertThat(shouldShow).isTrue() assertThat(shouldInclude).isTrue() } @Test fun testShouldShowEmptyShadeView_falseWhenKeyguard() = fun testShouldIncludeEmptyShadeView_falseWhenKeyguard() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) Loading @@ -215,13 +215,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN empty shade is not visible assertThat(shouldShow).isFalse() assertThat(shouldInclude).isFalse() } @Test fun testShouldShowEmptyShadeView_falseWhenStartingToSleep() = fun testShouldIncludeEmptyShadeView_falseWhenStartingToSleep() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) Loading @@ -232,7 +232,7 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN empty shade is not visible assertThat(shouldShow).isFalse() assertThat(shouldInclude).isFalse() } @Test Loading Loading @@ -282,9 +282,9 @@ class NotificationListViewModelTest : SysuiTestCase() { } @Test fun testShouldShowFooterView_trueWhenShade() = fun testShouldIncludeFooterView_trueWhenShade() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -294,13 +294,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is visible assertThat(shouldShow?.value).isTrue() assertThat(shouldInclude?.value).isTrue() } @Test fun testShouldShowFooterView_trueWhenLockedShade() = fun testShouldIncludeFooterView_trueWhenLockedShade() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -310,13 +310,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is visible assertThat(shouldShow?.value).isTrue() assertThat(shouldInclude?.value).isTrue() } @Test fun testShouldShowFooterView_falseWhenKeyguard() = fun testShouldIncludeFooterView_falseWhenKeyguard() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -325,13 +325,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is not visible assertThat(shouldShow?.value).isFalse() assertThat(shouldInclude?.value).isFalse() } @Test fun testShouldShowFooterView_falseWhenUserNotSetUp() = fun testShouldIncludeFooterView_falseWhenUserNotSetUp() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -343,13 +343,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is not visible assertThat(shouldShow?.value).isFalse() assertThat(shouldInclude?.value).isFalse() } @Test fun testShouldShowFooterView_falseWhenStartingToSleep() = fun testShouldIncludeFooterView_falseWhenStartingToSleep() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -361,13 +361,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is not visible assertThat(shouldShow?.value).isFalse() assertThat(shouldInclude?.value).isFalse() } @Test fun testShouldShowFooterView_falseWhenQsExpandedDefault() = fun testShouldIncludeFooterView_falseWhenQsExpandedDefault() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -380,13 +380,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is not visible assertThat(shouldShow?.value).isFalse() assertThat(shouldInclude?.value).isFalse() } @Test fun testShouldShowFooterView_trueWhenQsExpandedSplitShade() = fun testShouldIncludeFooterView_trueWhenQsExpandedSplitShade() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -401,13 +401,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is visible assertThat(shouldShow?.value).isTrue() assertThat(shouldInclude?.value).isTrue() } @Test fun testShouldShowFooterView_falseWhenRemoteInputActive() = fun testShouldIncludeFooterView_falseWhenRemoteInputActive() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -419,55 +419,67 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is not visible assertThat(shouldShow?.value).isFalse() assertThat(shouldInclude?.value).isFalse() } @Test fun testShouldShowFooterView_falseWhenShadeIsClosed() = fun testShouldIncludeFooterView_animatesWhenShade() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) // AND shade is closed // AND shade is open and fully expanded fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) fakeShadeRepository.setLegacyShadeExpansion(0f) fakeShadeRepository.setLegacyShadeExpansion(1f) runCurrent() // THEN footer is not visible assertThat(shouldShow?.value).isFalse() // THEN footer visibility animates assertThat(shouldInclude?.isAnimating).isTrue() } @Test fun testShouldShowFooterView_animatesWhenShade() = fun testShouldIncludeFooterView_notAnimatingOnKeyguard() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) // AND shade is open and fully expanded fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) // AND we are on the keyguard fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) fakeShadeRepository.setLegacyShadeExpansion(1f) runCurrent() // THEN footer visibility animates assertThat(shouldShow?.isAnimating).isTrue() // THEN footer visibility does not animate assertThat(shouldInclude?.isAnimating).isFalse() } @Test fun testShouldShowFooterView_notAnimatingOnKeyguard() = fun testShouldHideFooterView_trueWhenShadeIsClosed() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) val shouldHide by collectLastValue(underTest.shouldHideFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) // AND we are on the keyguard fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) // WHEN shade is closed fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) fakeShadeRepository.setLegacyShadeExpansion(0f) runCurrent() // THEN footer is hidden assertThat(shouldHide).isTrue() } @Test fun testShouldHideFooterView_falseWhenShadeIsOpen() = testScope.runTest { val shouldHide by collectLastValue(underTest.shouldHideFooterView) // WHEN shade is open fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) fakeShadeRepository.setLegacyShadeExpansion(1f) runCurrent() // THEN footer visibility does not animate assertThat(shouldShow?.isAnimating).isFalse() // THEN footer is hidden assertThat(shouldHide).isFalse() } @Test Loading Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java +15 −0 Original line number Diff line number Diff line Loading @@ -58,6 +58,7 @@ public class FooterView extends StackScrollerDecorView { private FooterViewButton mClearAllButton; private FooterViewButton mManageOrHistoryButton; private boolean mShouldBeHidden; private boolean mShowHistory; // String cache, for performance reasons. // Reading them from a Resources object can be quite slow sometimes. Loading Loading @@ -110,6 +111,20 @@ public class FooterView extends StackScrollerDecorView { setSecondaryVisible(visible, animate, onAnimationEnded); } /** See {@link this#setShouldBeHidden} below. */ public boolean shouldBeHidden() { return mShouldBeHidden; } /** * Whether this view's visibility should be set to INVISIBLE. Note that this is different from * the {@link StackScrollerDecorView#setVisible} method, which in turn handles visibility * transitions between VISIBLE and GONE. */ public void setShouldBeHidden(boolean hide) { mShouldBeHidden = hide; } @Override public void dump(PrintWriter pwOriginal, String[] args) { IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +10 −9 Original line number Diff line number Diff line Loading @@ -594,15 +594,16 @@ public class StackScrollAlgorithm { ); if (view instanceof FooterView) { if (FooterViewRefactor.isEnabled()) { if (((FooterView) view).shouldBeHidden()) { viewState.hidden = true; } else { final float footerEnd = algorithmState.mCurrentExpandedYPosition + view.getIntrinsicHeight(); final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight(); // TODO(b/293167744): May be able to keep only noSpaceForFooter here if we add an // emission when clearAllNotifications is called, and then use that in the footer // visibility flow. ((FooterView.FooterViewState) viewState).hideContent = noSpaceForFooter || (ambientState.isClearAllInProgress() && !hasNonClearableNotifs(algorithmState)); } } else { final boolean shadeClosed = !ambientState.isShadeExpanded(); Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +2 −1 Original line number Diff line number Diff line Loading @@ -193,13 +193,14 @@ constructor( }, ) launch { viewModel.shouldShowFooterView.collect { animatedVisibility -> viewModel.shouldIncludeFooterView.collect { animatedVisibility -> footerView.setVisible( /* visible = */ animatedVisibility.value, /* animate = */ animatedVisibility.isAnimating, ) } } launch { viewModel.shouldHideFooterView.collect { footerView.setShouldBeHidden(it) } } disposableHandle.awaitCancellationThenDispose() } Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt +37 −17 Original line number Diff line number Diff line Loading @@ -31,7 +31,6 @@ import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.Notificati 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.combine import com.android.systemui.util.kotlin.sample import com.android.systemui.util.ui.AnimatableEvent import com.android.systemui.util.ui.AnimatedValue Loading Loading @@ -111,7 +110,32 @@ constructor( } } val shouldShowFooterView: Flow<AnimatedValue<Boolean>> by lazy { /** * Whether the footer should not be visible for the user, even if it's present in the list (as * per [shouldIncludeFooterView] below). * * This essentially corresponds to having the view set to INVISIBLE. */ val shouldHideFooterView: Flow<Boolean> by lazy { if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { flowOf(false) } else { // When the shade is closed, the footer is still present in the list, but not visible. // This prevents the footer from being shown when a HUN is present, while still allowing // the footer to be counted as part of the shade for measurements. shadeInteractor.shadeExpansion.map { it == 0f }.distinctUntilChanged() } } /** * Whether the footer should be part of the list or not, and whether the transition from one * state to another should be animated. This essentially corresponds to transitioning the view * visibility from VISIBLE to GONE and vice versa. * * Note that this value being true doesn't necessarily mean that the footer is visible. It could * be hidden by another condition (see [shouldHideFooterView] above). */ val shouldIncludeFooterView: Flow<AnimatedValue<Boolean>> by lazy { if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { flowOf(AnimatedValue.NotAnimating(false)) } else { Loading @@ -120,34 +144,30 @@ constructor( userSetupInteractor.isUserSetUp, notificationStackInteractor.isShowingOnLockscreen, shadeInteractor.isQsFullscreen, remoteInputInteractor.isRemoteInputActive, shadeInteractor.shadeExpansion.map { it == 0f }.distinctUntilChanged(), remoteInputInteractor.isRemoteInputActive ) { hasNotifications, isUserSetUp, isShowingOnLockscreen, qsFullScreen, isRemoteInputActive, isShadeClosed -> isRemoteInputActive -> when { !hasNotifications -> VisibilityChange.HIDE_WITH_ANIMATION !hasNotifications -> VisibilityChange.DISAPPEAR_WITH_ANIMATION // Hide the footer until the user setup is complete, to prevent access // to settings (b/193149550). !isUserSetUp -> VisibilityChange.HIDE_WITH_ANIMATION !isUserSetUp -> VisibilityChange.DISAPPEAR_WITH_ANIMATION // 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 -> VisibilityChange.HIDE_WITHOUT_ANIMATION isShowingOnLockscreen -> VisibilityChange.DISAPPEAR_WITHOUT_ANIMATION // Do not show the footer if quick settings are fully expanded (except // for the foldable split shade view). See b/201427195 && b/222699879. qsFullScreen -> VisibilityChange.HIDE_WITH_ANIMATION qsFullScreen -> VisibilityChange.DISAPPEAR_WITH_ANIMATION // Hide the footer if remote input is active (i.e. user is replying to a // notification). See b/75984847. isRemoteInputActive -> VisibilityChange.HIDE_WITH_ANIMATION // Never show the footer if the shade is collapsed (e.g. when HUNing). isShadeClosed -> VisibilityChange.HIDE_WITHOUT_ANIMATION else -> VisibilityChange.SHOW_WITH_ANIMATION isRemoteInputActive -> VisibilityChange.DISAPPEAR_WITH_ANIMATION else -> VisibilityChange.APPEAR_WITH_ANIMATION } } .flowOn(bgDispatcher) Loading Loading @@ -180,9 +200,9 @@ constructor( } enum class VisibilityChange(val visible: Boolean, val canAnimate: Boolean) { HIDE_WITHOUT_ANIMATION(visible = false, canAnimate = false), HIDE_WITH_ANIMATION(visible = false, canAnimate = true), SHOW_WITH_ANIMATION(visible = true, canAnimate = true) 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. Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt +77 −65 Original line number Diff line number Diff line Loading @@ -130,35 +130,35 @@ class NotificationListViewModelTest : SysuiTestCase() { } @Test fun testShouldShowEmptyShadeView_trueWhenNoNotifs() = fun testShouldIncludeEmptyShadeView_trueWhenNoNotifs() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) runCurrent() // THEN empty shade is visible assertThat(shouldShow).isTrue() assertThat(shouldInclude).isTrue() } @Test fun testShouldShowEmptyShadeView_falseWhenNotifs() = fun testShouldIncludeEmptyShadeView_falseWhenNotifs() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) runCurrent() // THEN empty shade is not visible assertThat(shouldShow).isFalse() assertThat(shouldInclude).isFalse() } @Test fun testShouldShowEmptyShadeView_falseWhenQsExpandedDefault() = fun testShouldIncludeEmptyShadeView_falseWhenQsExpandedDefault() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) Loading @@ -167,13 +167,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN empty shade is not visible assertThat(shouldShow).isFalse() assertThat(shouldInclude).isFalse() } @Test fun testShouldShowEmptyShadeView_trueWhenQsExpandedInSplitShade() = fun testShouldIncludeEmptyShadeView_trueWhenQsExpandedInSplitShade() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) Loading @@ -185,13 +185,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN empty shade is visible assertThat(shouldShow).isTrue() assertThat(shouldInclude).isTrue() } @Test fun testShouldShowEmptyShadeView_trueWhenLockedShade() = fun testShouldIncludeEmptyShadeView_trueWhenLockedShade() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) Loading @@ -200,13 +200,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN empty shade is visible assertThat(shouldShow).isTrue() assertThat(shouldInclude).isTrue() } @Test fun testShouldShowEmptyShadeView_falseWhenKeyguard() = fun testShouldIncludeEmptyShadeView_falseWhenKeyguard() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) Loading @@ -215,13 +215,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN empty shade is not visible assertThat(shouldShow).isFalse() assertThat(shouldInclude).isFalse() } @Test fun testShouldShowEmptyShadeView_falseWhenStartingToSleep() = fun testShouldIncludeEmptyShadeView_falseWhenStartingToSleep() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) Loading @@ -232,7 +232,7 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN empty shade is not visible assertThat(shouldShow).isFalse() assertThat(shouldInclude).isFalse() } @Test Loading Loading @@ -282,9 +282,9 @@ class NotificationListViewModelTest : SysuiTestCase() { } @Test fun testShouldShowFooterView_trueWhenShade() = fun testShouldIncludeFooterView_trueWhenShade() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -294,13 +294,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is visible assertThat(shouldShow?.value).isTrue() assertThat(shouldInclude?.value).isTrue() } @Test fun testShouldShowFooterView_trueWhenLockedShade() = fun testShouldIncludeFooterView_trueWhenLockedShade() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -310,13 +310,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is visible assertThat(shouldShow?.value).isTrue() assertThat(shouldInclude?.value).isTrue() } @Test fun testShouldShowFooterView_falseWhenKeyguard() = fun testShouldIncludeFooterView_falseWhenKeyguard() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -325,13 +325,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is not visible assertThat(shouldShow?.value).isFalse() assertThat(shouldInclude?.value).isFalse() } @Test fun testShouldShowFooterView_falseWhenUserNotSetUp() = fun testShouldIncludeFooterView_falseWhenUserNotSetUp() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -343,13 +343,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is not visible assertThat(shouldShow?.value).isFalse() assertThat(shouldInclude?.value).isFalse() } @Test fun testShouldShowFooterView_falseWhenStartingToSleep() = fun testShouldIncludeFooterView_falseWhenStartingToSleep() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -361,13 +361,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is not visible assertThat(shouldShow?.value).isFalse() assertThat(shouldInclude?.value).isFalse() } @Test fun testShouldShowFooterView_falseWhenQsExpandedDefault() = fun testShouldIncludeFooterView_falseWhenQsExpandedDefault() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -380,13 +380,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is not visible assertThat(shouldShow?.value).isFalse() assertThat(shouldInclude?.value).isFalse() } @Test fun testShouldShowFooterView_trueWhenQsExpandedSplitShade() = fun testShouldIncludeFooterView_trueWhenQsExpandedSplitShade() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -401,13 +401,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is visible assertThat(shouldShow?.value).isTrue() assertThat(shouldInclude?.value).isTrue() } @Test fun testShouldShowFooterView_falseWhenRemoteInputActive() = fun testShouldIncludeFooterView_falseWhenRemoteInputActive() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -419,55 +419,67 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is not visible assertThat(shouldShow?.value).isFalse() assertThat(shouldInclude?.value).isFalse() } @Test fun testShouldShowFooterView_falseWhenShadeIsClosed() = fun testShouldIncludeFooterView_animatesWhenShade() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) // AND shade is closed // AND shade is open and fully expanded fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) fakeShadeRepository.setLegacyShadeExpansion(0f) fakeShadeRepository.setLegacyShadeExpansion(1f) runCurrent() // THEN footer is not visible assertThat(shouldShow?.value).isFalse() // THEN footer visibility animates assertThat(shouldInclude?.isAnimating).isTrue() } @Test fun testShouldShowFooterView_animatesWhenShade() = fun testShouldIncludeFooterView_notAnimatingOnKeyguard() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) // AND shade is open and fully expanded fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) // AND we are on the keyguard fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) fakeShadeRepository.setLegacyShadeExpansion(1f) runCurrent() // THEN footer visibility animates assertThat(shouldShow?.isAnimating).isTrue() // THEN footer visibility does not animate assertThat(shouldInclude?.isAnimating).isFalse() } @Test fun testShouldShowFooterView_notAnimatingOnKeyguard() = fun testShouldHideFooterView_trueWhenShadeIsClosed() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) val shouldHide by collectLastValue(underTest.shouldHideFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) // AND we are on the keyguard fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) // WHEN shade is closed fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) fakeShadeRepository.setLegacyShadeExpansion(0f) runCurrent() // THEN footer is hidden assertThat(shouldHide).isTrue() } @Test fun testShouldHideFooterView_falseWhenShadeIsOpen() = testScope.runTest { val shouldHide by collectLastValue(underTest.shouldHideFooterView) // WHEN shade is open fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) fakeShadeRepository.setLegacyShadeExpansion(1f) runCurrent() // THEN footer visibility does not animate assertThat(shouldShow?.isAnimating).isFalse() // THEN footer is hidden assertThat(shouldHide).isFalse() } @Test Loading