diff options
4 files changed, 135 insertions, 22 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt index 840aa92548c8..26e1a4d9e961 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt @@ -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 @@ -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 @@ -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 @@ -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) @@ -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) @@ -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) @@ -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 @@ -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 @@ -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) @@ -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) @@ -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) @@ -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) @@ -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 @@ -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) @@ -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) @@ -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) @@ -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) @@ -506,6 +525,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas } @Test + @DisableSceneContainer fun shouldHideFooterView_falseWhenShadeIsOpen() = testScope.runTest { val shouldHide by collectLastValue(underTest.shouldHideFooterView) @@ -520,6 +540,7 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas } @Test + @DisableSceneContainer fun shouldHideFooterView_falseWhenQSPartiallyOpen() = testScope.runTest { val shouldHide by collectLastValue(underTest.shouldHideFooterView) @@ -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 + ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index ef1bcfc45879..cccac4b479dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -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()) { - viewState.hidden = true; + // Note: This is no longer necessary in flexiglass. + if (!SceneContainerFlag.isEnabled()) { + viewState.hidden = true; + } } else { final float footerEnd = algorithmState.mCurrentExpandedYPosition + view.getIntrinsicHeight(); @@ -691,7 +694,6 @@ public class StackScrollAlgorithm { noSpaceForFooter || (ambientState.isClearAllInProgress() && !hasNonClearableNotifs(algorithmState)); } - } else { final boolean shadeClosed = !ambientState.isShadeExpanded(); final boolean isShelfShowing = algorithmState.firstViewInShelf != null; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt index d770b2003f3b..dc9615c25ada 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt @@ -188,15 +188,26 @@ constructor( .startHistoryIntent(view, /* showHistory= */ true) }, ) - launch { - viewModel.shouldIncludeFooterView.collect { animatedVisibility -> - footerView.setVisible( - /* visible = */ animatedVisibility.value, - /* animate = */ animatedVisibility.isAnimating, - ) + if (SceneContainerFlag.isEnabled) { + launch { + viewModel.shouldShowFooterView.collect { animatedVisibility -> + footerView.setVisible( + /* visible = */ animatedVisibility.value, + /* animate = */ animatedVisibility.isAnimating, + ) + } + } + } else { + launch { + viewModel.shouldIncludeFooterView.collect { animatedVisibility -> + footerView.setVisible( + /* visible = */ animatedVisibility.value, + /* animate = */ animatedVisibility.isAnimating, + ) + } } + launch { viewModel.shouldHideFooterView.collect { footerView.setShouldBeHidden(it) } } } - launch { viewModel.shouldHideFooterView.collect { footerView.setShouldBeHidden(it) } } disposableHandle.awaitCancellationThenDispose() } 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 e55492e67d02..4e2a46d78a5d 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 @@ -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 @@ -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 { @@ -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 { @@ -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), |