Merge "Model hiding the footer in the NotificationListViewModel" into main
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index f792898..adcbbfb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -58,6 +58,7 @@
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.
@@ -110,6 +111,20 @@
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);
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 b42c07d..5eaccd9 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
@@ -594,15 +594,16 @@
);
if (view instanceof FooterView) {
if (FooterViewRefactor.isEnabled()) {
- 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));
+ if (((FooterView) view).shouldBeHidden()) {
+ viewState.hidden = true;
+ } else {
+ final float footerEnd = algorithmState.mCurrentExpandedYPosition
+ + view.getIntrinsicHeight();
+ final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight();
+ ((FooterView.FooterViewState) viewState).hideContent =
+ noSpaceForFooter || (ambientState.isClearAllInProgress()
+ && !hasNonClearableNotifs(algorithmState));
+ }
} else {
final boolean shadeClosed = !ambientState.isShadeExpanded();
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 97cbbe8..18bb5119 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
@@ -193,13 +193,14 @@
},
)
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()
}
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 a6ca027..5a7433d 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
@@ -31,7 +31,6 @@
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
@@ -111,7 +110,32 @@
}
}
- 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 {
@@ -120,34 +144,30 @@
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)
@@ -180,9 +200,9 @@
}
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.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index 138e1fa..c308a98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -130,35 +130,35 @@
}
@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)
@@ -167,13 +167,13 @@
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)
@@ -185,13 +185,13 @@
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)
@@ -200,13 +200,13 @@
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)
@@ -215,13 +215,13 @@
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)
@@ -232,7 +232,7 @@
runCurrent()
// THEN empty shade is not visible
- assertThat(shouldShow).isFalse()
+ assertThat(shouldInclude).isFalse()
}
@Test
@@ -282,9 +282,9 @@
}
@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)
@@ -294,13 +294,13 @@
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)
@@ -310,13 +310,13 @@
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)
@@ -325,13 +325,13 @@
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)
@@ -343,13 +343,13 @@
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)
@@ -361,13 +361,13 @@
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)
@@ -380,13 +380,13 @@
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)
@@ -401,13 +401,13 @@
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)
@@ -419,29 +419,13 @@
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)
-
- // WHEN has notifs
- activeNotificationListRepository.setActiveNotifs(count = 2)
- // AND shade is closed
- fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- fakeShadeRepository.setLegacyShadeExpansion(0f)
- runCurrent()
-
- // THEN footer is not visible
- assertThat(shouldShow?.value).isFalse()
- }
-
- @Test
- fun testShouldShowFooterView_animatesWhenShade() =
- testScope.runTest {
- val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+ val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView)
// WHEN has notifs
activeNotificationListRepository.setActiveNotifs(count = 2)
@@ -451,13 +435,13 @@
runCurrent()
// THEN footer visibility animates
- assertThat(shouldShow?.isAnimating).isTrue()
+ assertThat(shouldInclude?.isAnimating).isTrue()
}
@Test
- fun testShouldShowFooterView_notAnimatingOnKeyguard() =
+ fun testShouldIncludeFooterView_notAnimatingOnKeyguard() =
testScope.runTest {
- val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+ val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView)
// WHEN has notifs
activeNotificationListRepository.setActiveNotifs(count = 2)
@@ -467,7 +451,35 @@
runCurrent()
// THEN footer visibility does not animate
- assertThat(shouldShow?.isAnimating).isFalse()
+ assertThat(shouldInclude?.isAnimating).isFalse()
+ }
+
+ @Test
+ fun testShouldHideFooterView_trueWhenShadeIsClosed() =
+ testScope.runTest {
+ val shouldHide by collectLastValue(underTest.shouldHideFooterView)
+
+ // 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 is hidden
+ assertThat(shouldHide).isFalse()
}
@Test