diff options
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) |