Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt +40 −13 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ 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 import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags import com.android.systemui.flags.andSceneContainer Loading @@ -36,6 +37,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.power.shared.model.WakefulnessState import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.shadeTestUtil import com.android.systemui.statusbar.data.repository.fakeRemoteInputRepository import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository Loading @@ -51,6 +53,7 @@ import com.android.systemui.util.ui.isAnimating import com.android.systemui.util.ui.value import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before Loading Loading @@ -153,7 +156,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas fun shouldShowEmptyShadeView_trueWhenNoNotifs() = testScope.runTest { val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView) val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView) val shouldIncludeFooterView by collectFooterViewVisibility() // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) Loading Loading @@ -196,7 +199,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas fun shouldShowEmptyShadeView_trueWhenQsExpandedInSplitShade() = testScope.runTest { val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView) val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView) val shouldIncludeFooterView by collectFooterViewVisibility() // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) Loading @@ -217,7 +220,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas fun shouldShowEmptyShadeView_trueWhenLockedShade() = testScope.runTest { val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView) val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView) val shouldIncludeFooterView by collectFooterViewVisibility() // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) Loading Loading @@ -315,7 +318,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Test fun shouldIncludeFooterView_trueWhenShade() = testScope.runTest { val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView) val shouldIncludeFooterView by collectFooterViewVisibility() val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has notifs Loading @@ -333,7 +336,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Test fun shouldIncludeFooterView_trueWhenLockedShade() = testScope.runTest { val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView) val shouldIncludeFooterView by collectFooterViewVisibility() val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has notifs Loading @@ -351,7 +354,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Test fun shouldIncludeFooterView_falseWhenKeyguard() = testScope.runTest { val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) val shouldInclude by collectFooterViewVisibility() // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -366,7 +369,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Test fun shouldIncludeFooterView_falseWhenUserNotSetUp() = testScope.runTest { val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) val shouldInclude by collectFooterViewVisibility() // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -384,7 +387,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Test fun shouldIncludeFooterView_falseWhenStartingToSleep() = testScope.runTest { val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) val shouldInclude by collectFooterViewVisibility() // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -402,7 +405,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Test fun shouldIncludeFooterView_falseWhenQsExpandedDefault() = testScope.runTest { val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) val shouldInclude by collectFooterViewVisibility() // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -421,7 +424,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Test fun shouldIncludeFooterView_trueWhenQsExpandedSplitShade() = testScope.runTest { val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView) val shouldIncludeFooterView by collectFooterViewVisibility() val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has notifs Loading @@ -444,7 +447,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Test fun shouldIncludeFooterView_falseWhenRemoteInputActive() = testScope.runTest { val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) val shouldInclude by collectFooterViewVisibility() // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -462,7 +465,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Test fun shouldIncludeFooterView_animatesWhenShade() = testScope.runTest { val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) val shouldInclude by collectFooterViewVisibility() // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -478,7 +481,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Test fun shouldIncludeFooterView_notAnimatingOnKeyguard() = testScope.runTest { val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) val shouldInclude by collectFooterViewVisibility() // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -492,6 +495,22 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas } @Test @EnableSceneContainer fun shouldShowFooterView_falseWhenShadeIsClosed() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) // WHEN shade is closed fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) shadeTestUtil.setShadeExpansion(0f) runCurrent() // THEN footer is hidden assertThat(shouldShow?.value).isFalse() } @Test @DisableSceneContainer fun shouldHideFooterView_trueWhenShadeIsClosed() = testScope.runTest { val shouldHide by collectLastValue(underTest.shouldHideFooterView) Loading @@ -506,6 +525,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas } @Test @DisableSceneContainer fun shouldHideFooterView_falseWhenShadeIsOpen() = testScope.runTest { val shouldHide by collectLastValue(underTest.shouldHideFooterView) Loading @@ -520,6 +540,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas } @Test @DisableSceneContainer fun shouldHideFooterView_falseWhenQSPartiallyOpen() = testScope.runTest { val shouldHide by collectLastValue(underTest.shouldHideFooterView) Loading Loading @@ -642,4 +663,10 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas assertThat(animationsEnabled).isTrue() } private fun TestScope.collectFooterViewVisibility() = collectLastValue( if (SceneContainerFlag.isEnabled) underTest.shouldShowFooterView else underTest.shouldIncludeFooterView ) } packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +4 −2 Original line number Diff line number Diff line Loading @@ -682,7 +682,10 @@ public class StackScrollAlgorithm { // doesn't get updated quickly enough and can cause the footer to flash when // closing the shade. As such, we temporarily also check the ambientState directly. if (((FooterView) view).shouldBeHidden() || !ambientState.isShadeExpanded()) { // Note: This is no longer necessary in flexiglass. if (!SceneContainerFlag.isEnabled()) { viewState.hidden = true; } } else { final float footerEnd = algorithmState.mCurrentExpandedYPosition + view.getIntrinsicHeight(); Loading @@ -691,7 +694,6 @@ public class StackScrollAlgorithm { noSpaceForFooter || (ambientState.isClearAllInProgress() && !hasNonClearableNotifs(algorithmState)); } } else { final boolean shadeClosed = !ambientState.isShadeExpanded(); final boolean isShelfShowing = algorithmState.firstViewInShelf != null; Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +18 −7 Original line number Diff line number Diff line Loading @@ -188,6 +188,16 @@ constructor( .startHistoryIntent(view, /* showHistory= */ true) }, ) if (SceneContainerFlag.isEnabled) { launch { viewModel.shouldShowFooterView.collect { animatedVisibility -> footerView.setVisible( /* visible = */ animatedVisibility.value, /* animate = */ animatedVisibility.isAnimating, ) } } } else { launch { viewModel.shouldIncludeFooterView.collect { animatedVisibility -> footerView.setVisible( Loading @@ -197,6 +207,7 @@ constructor( } } launch { viewModel.shouldHideFooterView.collect { footerView.setShouldBeHidden(it) } } } disposableHandle.awaitCancellationThenDispose() } Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt +73 −0 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.Notif 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 import com.android.systemui.util.ui.AnimatableEvent import com.android.systemui.util.ui.AnimatedValue Loading Loading @@ -120,6 +121,7 @@ constructor( * This essentially corresponds to having the view set to INVISIBLE. */ val shouldHideFooterView: Flow<Boolean> by lazy { SceneContainerFlag.assertInLegacyMode() if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { flowOf(false) } else { Loading @@ -143,6 +145,7 @@ constructor( * be hidden by another condition (see [shouldHideFooterView] above). */ val shouldIncludeFooterView: Flow<AnimatedValue<Boolean>> by lazy { SceneContainerFlag.assertInLegacyMode() if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { flowOf(AnimatedValue.NotAnimating(false)) } else { Loading Loading @@ -207,6 +210,76 @@ constructor( } } // This flow replaces shouldHideFooterView+shouldIncludeFooterView in flexiglass. val shouldShowFooterView: Flow<AnimatedValue<Boolean>> by lazy { if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) { flowOf(AnimatedValue.NotAnimating(false)) } else { combine( activeNotificationsInteractor.areAnyNotificationsPresent, userSetupInteractor.isUserSetUp, notificationStackInteractor.isShowingOnLockscreen, shadeInteractor.isQsFullscreen, remoteInputInteractor.isRemoteInputActive, shadeInteractor.shadeExpansion.map { it < 0.5f }.distinctUntilChanged(), ) { hasNotifications, isUserSetUp, isShowingOnLockscreen, qsFullScreen, isRemoteInputActive, shadeLessThanHalfwayExpanded -> when { !hasNotifications -> VisibilityChange.DISAPPEAR_WITH_ANIMATION // Hide the footer until the user setup is complete, to prevent access // to settings (b/193149550). !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.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.DISAPPEAR_WITH_ANIMATION // Hide the footer if remote input is active (i.e. user is replying to a // notification). See b/75984847. isRemoteInputActive -> VisibilityChange.DISAPPEAR_WITH_ANIMATION // If the shade is not expanded enough, the footer shouldn't be visible. shadeLessThanHalfwayExpanded -> VisibilityChange.DISAPPEAR_WITH_ANIMATION else -> VisibilityChange.APPEAR_WITH_ANIMATION } } .distinctUntilChanged( // Equivalent unless visibility changes areEquivalent = { a: VisibilityChange, b: VisibilityChange -> a.visible == b.visible } ) // Should we animate the visibility change? .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)) } ) { visibilityChange, (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 && visibilityChange.canAnimate AnimatableEvent(visibilityChange.visible, shouldAnimate) } .toAnimatedValueFlow() .dumpWhileCollecting("shouldShowFooterView") .flowOn(bgDispatcher) } } enum class VisibilityChange(val visible: Boolean, val canAnimate: Boolean) { DISAPPEAR_WITHOUT_ANIMATION(visible = false, canAnimate = false), DISAPPEAR_WITH_ANIMATION(visible = false, canAnimate = true), Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt +40 −13 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ 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 import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags import com.android.systemui.flags.andSceneContainer Loading @@ -36,6 +37,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.power.shared.model.WakefulnessState import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.shadeTestUtil import com.android.systemui.statusbar.data.repository.fakeRemoteInputRepository import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository Loading @@ -51,6 +53,7 @@ import com.android.systemui.util.ui.isAnimating import com.android.systemui.util.ui.value import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before Loading Loading @@ -153,7 +156,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas fun shouldShowEmptyShadeView_trueWhenNoNotifs() = testScope.runTest { val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView) val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView) val shouldIncludeFooterView by collectFooterViewVisibility() // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) Loading Loading @@ -196,7 +199,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas fun shouldShowEmptyShadeView_trueWhenQsExpandedInSplitShade() = testScope.runTest { val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView) val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView) val shouldIncludeFooterView by collectFooterViewVisibility() // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) Loading @@ -217,7 +220,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas fun shouldShowEmptyShadeView_trueWhenLockedShade() = testScope.runTest { val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView) val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView) val shouldIncludeFooterView by collectFooterViewVisibility() // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) Loading Loading @@ -315,7 +318,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Test fun shouldIncludeFooterView_trueWhenShade() = testScope.runTest { val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView) val shouldIncludeFooterView by collectFooterViewVisibility() val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has notifs Loading @@ -333,7 +336,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Test fun shouldIncludeFooterView_trueWhenLockedShade() = testScope.runTest { val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView) val shouldIncludeFooterView by collectFooterViewVisibility() val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has notifs Loading @@ -351,7 +354,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Test fun shouldIncludeFooterView_falseWhenKeyguard() = testScope.runTest { val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) val shouldInclude by collectFooterViewVisibility() // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -366,7 +369,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Test fun shouldIncludeFooterView_falseWhenUserNotSetUp() = testScope.runTest { val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) val shouldInclude by collectFooterViewVisibility() // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -384,7 +387,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Test fun shouldIncludeFooterView_falseWhenStartingToSleep() = testScope.runTest { val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) val shouldInclude by collectFooterViewVisibility() // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -402,7 +405,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Test fun shouldIncludeFooterView_falseWhenQsExpandedDefault() = testScope.runTest { val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) val shouldInclude by collectFooterViewVisibility() // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -421,7 +424,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Test fun shouldIncludeFooterView_trueWhenQsExpandedSplitShade() = testScope.runTest { val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView) val shouldIncludeFooterView by collectFooterViewVisibility() val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has notifs Loading @@ -444,7 +447,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Test fun shouldIncludeFooterView_falseWhenRemoteInputActive() = testScope.runTest { val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) val shouldInclude by collectFooterViewVisibility() // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -462,7 +465,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Test fun shouldIncludeFooterView_animatesWhenShade() = testScope.runTest { val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) val shouldInclude by collectFooterViewVisibility() // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -478,7 +481,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas @Test fun shouldIncludeFooterView_notAnimatingOnKeyguard() = testScope.runTest { val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) val shouldInclude by collectFooterViewVisibility() // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) Loading @@ -492,6 +495,22 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas } @Test @EnableSceneContainer fun shouldShowFooterView_falseWhenShadeIsClosed() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowFooterView) // WHEN shade is closed fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) shadeTestUtil.setShadeExpansion(0f) runCurrent() // THEN footer is hidden assertThat(shouldShow?.value).isFalse() } @Test @DisableSceneContainer fun shouldHideFooterView_trueWhenShadeIsClosed() = testScope.runTest { val shouldHide by collectLastValue(underTest.shouldHideFooterView) Loading @@ -506,6 +525,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas } @Test @DisableSceneContainer fun shouldHideFooterView_falseWhenShadeIsOpen() = testScope.runTest { val shouldHide by collectLastValue(underTest.shouldHideFooterView) Loading @@ -520,6 +540,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas } @Test @DisableSceneContainer fun shouldHideFooterView_falseWhenQSPartiallyOpen() = testScope.runTest { val shouldHide by collectLastValue(underTest.shouldHideFooterView) Loading Loading @@ -642,4 +663,10 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas assertThat(animationsEnabled).isTrue() } private fun TestScope.collectFooterViewVisibility() = collectLastValue( if (SceneContainerFlag.isEnabled) underTest.shouldShowFooterView else underTest.shouldIncludeFooterView ) }
packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +4 −2 Original line number Diff line number Diff line Loading @@ -682,7 +682,10 @@ public class StackScrollAlgorithm { // doesn't get updated quickly enough and can cause the footer to flash when // closing the shade. As such, we temporarily also check the ambientState directly. if (((FooterView) view).shouldBeHidden() || !ambientState.isShadeExpanded()) { // Note: This is no longer necessary in flexiglass. if (!SceneContainerFlag.isEnabled()) { viewState.hidden = true; } } else { final float footerEnd = algorithmState.mCurrentExpandedYPosition + view.getIntrinsicHeight(); Loading @@ -691,7 +694,6 @@ public class StackScrollAlgorithm { noSpaceForFooter || (ambientState.isClearAllInProgress() && !hasNonClearableNotifs(algorithmState)); } } else { final boolean shadeClosed = !ambientState.isShadeExpanded(); final boolean isShelfShowing = algorithmState.firstViewInShelf != null; Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +18 −7 Original line number Diff line number Diff line Loading @@ -188,6 +188,16 @@ constructor( .startHistoryIntent(view, /* showHistory= */ true) }, ) if (SceneContainerFlag.isEnabled) { launch { viewModel.shouldShowFooterView.collect { animatedVisibility -> footerView.setVisible( /* visible = */ animatedVisibility.value, /* animate = */ animatedVisibility.isAnimating, ) } } } else { launch { viewModel.shouldIncludeFooterView.collect { animatedVisibility -> footerView.setVisible( Loading @@ -197,6 +207,7 @@ constructor( } } launch { viewModel.shouldHideFooterView.collect { footerView.setShouldBeHidden(it) } } } disposableHandle.awaitCancellationThenDispose() } Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt +73 −0 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.Notif 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 import com.android.systemui.util.ui.AnimatableEvent import com.android.systemui.util.ui.AnimatedValue Loading Loading @@ -120,6 +121,7 @@ constructor( * This essentially corresponds to having the view set to INVISIBLE. */ val shouldHideFooterView: Flow<Boolean> by lazy { SceneContainerFlag.assertInLegacyMode() if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { flowOf(false) } else { Loading @@ -143,6 +145,7 @@ constructor( * be hidden by another condition (see [shouldHideFooterView] above). */ val shouldIncludeFooterView: Flow<AnimatedValue<Boolean>> by lazy { SceneContainerFlag.assertInLegacyMode() if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { flowOf(AnimatedValue.NotAnimating(false)) } else { Loading Loading @@ -207,6 +210,76 @@ constructor( } } // This flow replaces shouldHideFooterView+shouldIncludeFooterView in flexiglass. val shouldShowFooterView: Flow<AnimatedValue<Boolean>> by lazy { if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) { flowOf(AnimatedValue.NotAnimating(false)) } else { combine( activeNotificationsInteractor.areAnyNotificationsPresent, userSetupInteractor.isUserSetUp, notificationStackInteractor.isShowingOnLockscreen, shadeInteractor.isQsFullscreen, remoteInputInteractor.isRemoteInputActive, shadeInteractor.shadeExpansion.map { it < 0.5f }.distinctUntilChanged(), ) { hasNotifications, isUserSetUp, isShowingOnLockscreen, qsFullScreen, isRemoteInputActive, shadeLessThanHalfwayExpanded -> when { !hasNotifications -> VisibilityChange.DISAPPEAR_WITH_ANIMATION // Hide the footer until the user setup is complete, to prevent access // to settings (b/193149550). !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.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.DISAPPEAR_WITH_ANIMATION // Hide the footer if remote input is active (i.e. user is replying to a // notification). See b/75984847. isRemoteInputActive -> VisibilityChange.DISAPPEAR_WITH_ANIMATION // If the shade is not expanded enough, the footer shouldn't be visible. shadeLessThanHalfwayExpanded -> VisibilityChange.DISAPPEAR_WITH_ANIMATION else -> VisibilityChange.APPEAR_WITH_ANIMATION } } .distinctUntilChanged( // Equivalent unless visibility changes areEquivalent = { a: VisibilityChange, b: VisibilityChange -> a.visible == b.visible } ) // Should we animate the visibility change? .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)) } ) { visibilityChange, (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 && visibilityChange.canAnimate AnimatableEvent(visibilityChange.visible, shouldAnimate) } .toAnimatedValueFlow() .dumpWhileCollecting("shouldShowFooterView") .flowOn(bgDispatcher) } } enum class VisibilityChange(val visible: Boolean, val canAnimate: Boolean) { DISAPPEAR_WITHOUT_ANIMATION(visible = false, canAnimate = false), DISAPPEAR_WITH_ANIMATION(visible = false, canAnimate = true), Loading