Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +103 −45 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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, Loading Loading @@ -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 Loading Loading @@ -1151,6 +1197,15 @@ public class StackScrollAlgorithm { ExpandableViewState childViewState = child.getViewState(); float baseZ = ambientState.getBaseZHeight(); 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() Loading @@ -1162,7 +1217,7 @@ public class StackScrollAlgorithm { } 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, // 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(); Loading @@ -1182,10 +1237,12 @@ public class StackScrollAlgorithm { float shelfStart = ambientState.getInnerHeight() - shelfHeight + ambientState.getTopPadding() + ambientState.getStackTranslation(); float notificationEnd = childViewState.getYTranslation() + child.getIntrinsicHeight() float notificationEnd = childViewState.getYTranslation() + child.getIntrinsicHeight() + mPaddingBetweenElements; if (shelfStart > notificationEnd) { // When the notification doesn't overlap with Notification Shelf, there's no shadow // 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 Loading @@ -1199,6 +1256,7 @@ public class StackScrollAlgorithm { } else { childViewState.setZTranslation(baseZ); } } // While HUN is showing and Shade is closed: headerVisibleAmount stays 0, shadow stays. // During HUN-to-Shade (eg. dragging down HUN to open Shade): headerVisibleAmount goes Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +91 −2 Original line number Diff line number Diff line Loading @@ -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() { Loading Loading @@ -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) } Loading @@ -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) Loading Loading @@ -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) } Loading @@ -294,10 +303,87 @@ 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) Loading Loading @@ -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, Loading @@ -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, Loading Loading @@ -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) Loading Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +103 −45 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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, Loading Loading @@ -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 Loading Loading @@ -1151,6 +1197,15 @@ public class StackScrollAlgorithm { ExpandableViewState childViewState = child.getViewState(); float baseZ = ambientState.getBaseZHeight(); 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() Loading @@ -1162,7 +1217,7 @@ public class StackScrollAlgorithm { } 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, // 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(); Loading @@ -1182,10 +1237,12 @@ public class StackScrollAlgorithm { float shelfStart = ambientState.getInnerHeight() - shelfHeight + ambientState.getTopPadding() + ambientState.getStackTranslation(); float notificationEnd = childViewState.getYTranslation() + child.getIntrinsicHeight() float notificationEnd = childViewState.getYTranslation() + child.getIntrinsicHeight() + mPaddingBetweenElements; if (shelfStart > notificationEnd) { // When the notification doesn't overlap with Notification Shelf, there's no shadow // 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 Loading @@ -1199,6 +1256,7 @@ public class StackScrollAlgorithm { } else { childViewState.setZTranslation(baseZ); } } // While HUN is showing and Shade is closed: headerVisibleAmount stays 0, shadow stays. // During HUN-to-Shade (eg. dragging down HUN to open Shade): headerVisibleAmount goes Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +91 −2 Original line number Diff line number Diff line Loading @@ -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() { Loading Loading @@ -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) } Loading @@ -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) Loading Loading @@ -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) } Loading @@ -294,10 +303,87 @@ 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) Loading Loading @@ -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, Loading @@ -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, Loading Loading @@ -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) Loading