summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java148
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt93
2 files changed, 194 insertions, 47 deletions
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 b52048b854d3..aee1d3e08256 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
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.stack;
+import static androidx.core.math.MathUtils.clamp;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -899,11 +901,33 @@ public class StackScrollAlgorithm {
if (shouldHunBeVisibleWhenScrolled(row.mustStayOnScreen(),
childState.headsUpIsVisible, row.showingPulsing(),
ambientState.isOnKeyguard(), row.getEntry().isStickyAndNotDemoted())) {
+ // the height of this child before clamping it to the top
+ float unmodifiedChildHeight = childState.height;
clampHunToTop(
/* headsUpTop = */ headsUpTranslation,
/* collapsedHeight = */ row.getCollapsedHeight(),
/* viewState = */ childState
);
+ float baseZ = ambientState.getBaseZHeight();
+ if (headsUpTranslation < ambientState.getStackTop()) {
+ // HUN displayed above the stack top, it needs a fix shadow
+ childState.setZTranslation(baseZ + mPinnedZTranslationExtra);
+ } else {
+ // HUN displayed within the stack, add a shadow if it overlaps with
+ // other elements.
+ //
+ // Views stack vertically from the top. Add the HUN's original height
+ // (before clamping) to the stack top, to determine the starting
+ // point for the remaining content.
+ float scrollingContentTop =
+ ambientState.getStackTop() + unmodifiedChildHeight;
+ updateZTranslationForHunInStack(
+ /* scrollingContentTop = */ scrollingContentTop,
+ /* scrollingContentTopPadding = */ mGapHeight,
+ /* baseZ = */ baseZ,
+ /* viewState = */ childState
+ );
+ }
if (isTopEntry && row.isAboveShelf()) {
clampHunToMaxTranslation(
/* headsUpTop = */ headsUpTranslation,
@@ -1040,8 +1064,30 @@ public class StackScrollAlgorithm {
// Transition from collapsed pinned state to fully expanded state
// when the pinned HUN approaches its actual location (when scrolling back to top).
final float distToRealY = newTranslation - viewState.getYTranslation();
- viewState.height = (int) Math.max(viewState.height - distToRealY, collapsedHeight);
+ final float availableHeight = viewState.height - distToRealY;
+
viewState.setYTranslation(newTranslation);
+ viewState.height = (int) Math.max(availableHeight, collapsedHeight);
+ }
+
+ @VisibleForTesting
+ void updateZTranslationForHunInStack(float scrollingContentTop,
+ float scrollingContentTopPadding, float baseZ, ExpandableViewState viewState) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+ float hunBottom = viewState.getYTranslation() + viewState.height;
+ float overlap = Math.max(0f, hunBottom - scrollingContentTop);
+
+ float shadowFraction = 1f;
+ if (scrollingContentTopPadding > 0f) {
+ // scrollingContentTopPadding makes a gap between the bottom of the HUN and the top
+ // of the scrolling content. Use this to animate to the full shadow.
+ shadowFraction = clamp(overlap / scrollingContentTopPadding, 0f, 1f);
+ }
+
+ if (overlap > 0.0f) {
+ // add a shadow to this HUN, because it overlaps with the scrolling stack
+ viewState.setZTranslation(baseZ + shadowFraction * mPinnedZTranslationExtra);
+ }
}
// Pin HUN to bottom of expanded QS
@@ -1151,53 +1197,65 @@ public class StackScrollAlgorithm {
ExpandableViewState childViewState = child.getViewState();
float baseZ = ambientState.getBaseZHeight();
- if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible
- && !ambientState.isDozingAndNotPulsing(child)
- && childViewState.getYTranslation() < ambientState.getTopPadding()
- + ambientState.getStackTranslation()) {
-
- if (childrenOnTop != 0.0f) {
- // To elevate the later HUN over previous HUN when multiple HUNs exist
- childrenOnTop++;
+ if (SceneContainerFlag.isEnabled()) {
+ // SceneContainer flags off this logic, and just sets the baseZ because:
+ // - there are no overlapping HUNs anymore, no need for multiplying their shadows
+ // - shadows for HUNs overlapping with the stack are now set from updateHeadsUpStates
+ // - shadows for HUNs overlapping with the shelf are NOT set anymore, because it only
+ // happens on AOD/Pulsing, where they're displayed on a black background so a shadow
+ // wouldn't be visible.
+ childViewState.setZTranslation(baseZ);
+ } else {
+ if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible
+ && !ambientState.isDozingAndNotPulsing(child)
+ && childViewState.getYTranslation() < ambientState.getTopPadding()
+ + ambientState.getStackTranslation()) {
+
+ if (childrenOnTop != 0.0f) {
+ // To elevate the later HUN over previous HUN when multiple HUNs exist
+ childrenOnTop++;
+ } else {
+ // Handles HUN shadow when Shade is opened, and AmbientState.mScrollY > 0
+ // Calculate the HUN's z-value based on its overlapping fraction with QQS Panel.
+ // When scrolling down shade to make HUN back to in-position in Notif Panel,
+ // The overlapping fraction goes to 0, and shadows hides gradually.
+ float overlap = ambientState.getTopPadding()
+ + ambientState.getStackTranslation() - childViewState.getYTranslation();
+ // To prevent over-shadow during HUN entry
+ childrenOnTop += Math.min(
+ 1.0f,
+ overlap / childViewState.height
+ );
+ }
+ childViewState.setZTranslation(baseZ
+ + childrenOnTop * mPinnedZTranslationExtra);
+ } else if (isTopHun) {
+ // In case this is a new view that has never been measured before, we don't want to
+ // elevate if we are currently expanded more than the notification
+ int shelfHeight = ambientState.getShelf() == null ? 0 :
+ ambientState.getShelf().getIntrinsicHeight();
+ float shelfStart = ambientState.getInnerHeight()
+ - shelfHeight + ambientState.getTopPadding()
+ + ambientState.getStackTranslation();
+ float notificationEnd =
+ childViewState.getYTranslation() + child.getIntrinsicHeight()
+ + mPaddingBetweenElements;
+ if (shelfStart > notificationEnd) {
+ // When the notification doesn't overlap with Notification Shelf,
+ // there's no shadow
+ childViewState.setZTranslation(baseZ);
+ } else {
+ // Give shadow to the notification if it overlaps with Notification Shelf
+ float factor = (notificationEnd - shelfStart) / shelfHeight;
+ if (Float.isNaN(factor)) { // Avoid problems when the above is 0/0.
+ factor = 1.0f;
+ }
+ factor = Math.min(factor, 1.0f);
+ childViewState.setZTranslation(baseZ + factor * mPinnedZTranslationExtra);
+ }
} else {
- // Handles HUN shadow when Shade is opened, and AmbientState.mScrollY > 0
- // Calculate the HUN's z-value based on its overlapping fraction with QQS Panel.
- // When scrolling down shade to make HUN back to in-position in Notification Panel,
- // The overlapping fraction goes to 0, and shadows hides gradually.
- float overlap = ambientState.getTopPadding()
- + ambientState.getStackTranslation() - childViewState.getYTranslation();
- // To prevent over-shadow during HUN entry
- childrenOnTop += Math.min(
- 1.0f,
- overlap / childViewState.height
- );
- }
- childViewState.setZTranslation(baseZ
- + childrenOnTop * mPinnedZTranslationExtra);
- } else if (isTopHun) {
- // In case this is a new view that has never been measured before, we don't want to
- // elevate if we are currently expanded more than the notification
- int shelfHeight = ambientState.getShelf() == null ? 0 :
- ambientState.getShelf().getIntrinsicHeight();
- float shelfStart = ambientState.getInnerHeight()
- - shelfHeight + ambientState.getTopPadding()
- + ambientState.getStackTranslation();
- float notificationEnd = childViewState.getYTranslation() + child.getIntrinsicHeight()
- + mPaddingBetweenElements;
- if (shelfStart > notificationEnd) {
- // When the notification doesn't overlap with Notification Shelf, there's no shadow
childViewState.setZTranslation(baseZ);
- } else {
- // Give shadow to the notification if it overlaps with Notification Shelf
- float factor = (notificationEnd - shelfStart) / shelfHeight;
- if (Float.isNaN(factor)) { // Avoid problems when the above is 0/0.
- factor = 1.0f;
- }
- factor = Math.min(factor, 1.0f);
- childViewState.setZTranslation(baseZ + factor * mPinnedZTranslationExtra);
}
- } else {
- childViewState.setZTranslation(baseZ);
}
// While HUN is showing and Shade is closed: headerVisibleAmount stays 0, shadow stays.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index c2622db1cbb1..ad029d7e7ef2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -92,9 +92,12 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
private fun px(@DimenRes id: Int): Float =
testableResources.resources.getDimensionPixelSize(id).toFloat()
- private val bigGap = px(R.dimen.notification_section_divider_height)
- private val smallGap = px(R.dimen.notification_section_divider_height_lockscreen)
+ private val notifSectionDividerGap = px(R.dimen.notification_section_divider_height)
private val scrimPadding = px(R.dimen.notification_side_paddings)
+ private val baseZ by lazy { ambientState.baseZHeight }
+ private val headsUpZ = px(R.dimen.heads_up_pinned_elevation)
+ private val bigGap = notifSectionDividerGap
+ private val smallGap = px(R.dimen.notification_section_divider_height_lockscreen)
@Before
fun setUp() {
@@ -219,6 +222,8 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
// Then: HUN is at the headsUpTop
assertThat(notificationRow.viewState.yTranslation).isEqualTo(headsUpTop)
+ // And: HUN is not elevated
+ assertThat(notificationRow.viewState.zTranslation).isEqualTo(baseZ)
// And: HUN has its full height
assertThat(notificationRow.viewState.height).isEqualTo(intrinsicHeight)
}
@@ -243,6 +248,8 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
// Then: HUN is translated to the headsUpTop
assertThat(notificationRow.viewState.yTranslation).isEqualTo(headsUpTop)
+ // And: HUN is not elevated
+ assertThat(notificationRow.viewState.zTranslation).isEqualTo(baseZ)
// And: HUN is clipped to the available space
// newTranslation = max(150, -25)
// distToReal = 150 - (-25)
@@ -270,6 +277,8 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
// Then: HUN is translated to the headsUpTop
assertThat(notificationRow.viewState.yTranslation).isEqualTo(headsUpTop)
+ // And: HUN fully elevated to baseZ + headsUpZ
+ assertThat(notificationRow.viewState.zTranslation).isEqualTo(baseZ + headsUpZ)
// And: HUN is clipped to its collapsed height
assertThat(notificationRow.viewState.height).isEqualTo(collapsedHeight)
}
@@ -294,11 +303,88 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
// Then: HUN is translated to the headsUpTop
assertThat(notificationRow.viewState.yTranslation).isEqualTo(headsUpTop)
+ // And: HUN is elevated to baseZ + headsUpZ
+ assertThat(notificationRow.viewState.zTranslation).isEqualTo(baseZ + headsUpZ)
// And: HUN maintained its full height
assertThat(notificationRow.viewState.height).isEqualTo(intrinsicHunHeight)
}
@Test
+ @EnableSceneContainer
+ fun updateZTranslationForHunInStack_fullOverlap_hunHasFullElevation() {
+ // Given: the overlap equals to the top content padding
+ val contentTop = 280f
+ val contentTopPadding = 20f
+ val viewState =
+ ExpandableViewState().apply {
+ height = 100
+ yTranslation = 200f
+ }
+
+ // When
+ stackScrollAlgorithm.updateZTranslationForHunInStack(
+ /* scrollingContentTop = */ contentTop,
+ /* scrollingContentTopPadding */ contentTopPadding,
+ /* baseZ = */ 0f,
+ /* viewState = */ viewState,
+ )
+
+ // Then: HUN is fully elevated to baseZ + headsUpZ
+ assertThat(viewState.zTranslation).isEqualTo(headsUpZ)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun updateZTranslationForHunInStack_someOverlap_hunIsPartlyElevated() {
+ // Given: the overlap is bigger than zero, but less than the top content padding
+ val contentTop = 290f
+ val contentTopPadding = 20f
+ val viewState =
+ ExpandableViewState().apply {
+ height = 100
+ yTranslation = 200f
+ }
+
+ // When
+ stackScrollAlgorithm.updateZTranslationForHunInStack(
+ /* scrollingContentTop = */ contentTop,
+ /* scrollingContentTopPadding */ contentTopPadding,
+ /* baseZ = */ 0f,
+ /* viewState = */ viewState,
+ )
+
+ // Then: HUN is partly elevated
+ assertThat(viewState.zTranslation).apply {
+ isGreaterThan(0f)
+ isLessThan(headsUpZ)
+ }
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun updateZTranslationForHunInStack_noOverlap_hunIsNotElevated() {
+ // Given: no overlap between the content and the HUN
+ val contentTop = 300f
+ val contentTopPadding = 20f
+ val viewState =
+ ExpandableViewState().apply {
+ height = 100
+ yTranslation = 200f
+ }
+
+ // When
+ stackScrollAlgorithm.updateZTranslationForHunInStack(
+ /* scrollingContentTop = */ contentTop,
+ /* scrollingContentTopPadding */ contentTopPadding,
+ /* baseZ = */ 0f,
+ /* viewState = */ viewState,
+ )
+
+ // Then: HUN is not elevated
+ assertThat(viewState.zTranslation).isEqualTo(0f)
+ }
+
+ @Test
@DisableSceneContainer
@EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun resetViewStates_defaultHun_showingQS_newHeadsUpAnim_hunTranslatedToMax() {
@@ -971,6 +1057,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
}
@Test
+ @DisableSceneContainer
fun shadeOpened_hunFullyOverlapsQqsPanel_hunShouldHaveFullShadow() {
// Given: shade is opened, yTranslation of HUN is 0,
// the height of HUN equals to the height of QQS Panel,
@@ -996,6 +1083,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
}
@Test
+ @DisableSceneContainer
fun shadeOpened_hunPartiallyOverlapsQQS_hunShouldHavePartialShadow() {
// Given: shade is opened, yTranslation of HUN is greater than 0,
// the height of HUN is equal to the height of QQS Panel,
@@ -1447,6 +1535,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
}
stackScrollAlgorithm.setIsExpanded(true)
+ whenever(notificationRow.headerVisibleAmount).thenReturn(1.0f)
whenever(notificationRow.mustStayOnScreen()).thenReturn(true)
whenever(notificationRow.isHeadsUp).thenReturn(true)
whenever(notificationRow.collapsedHeight).thenReturn(collapsedHeight)