diff options
| author | 2022-11-17 17:57:10 +0000 | |
|---|---|---|
| committer | 2022-11-17 17:57:10 +0000 | |
| commit | 7c88119d7f4a9c04b9566f4c971558854117a70c (patch) | |
| tree | d728fd899294f48948abcbac706ec21644ae1c6c | |
| parent | 905e965bf57afcb1e9694ebc1229760daccf2396 (diff) | |
| parent | 23dae8748a3a63600cdcaae8525e5c5196c114a7 (diff) | |
Merge "Add drop shadow for HUNs when shade is opened" into tm-qpr-dev am: e87d75091f am: 23dae8748a
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20468691
Change-Id: I2691fa0da5bfce375d47124e2ea8b8b3339c9a19
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
2 files changed, 249 insertions, 32 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 62f57b8d4068..d8c68780951a 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 @@ -62,7 +62,8 @@ public class StackScrollAlgorithm { private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState(); private boolean mIsExpanded; private boolean mClipNotificationScrollToTop; - @VisibleForTesting float mHeadsUpInset; + @VisibleForTesting + float mHeadsUpInset; private int mPinnedZTranslationExtra; private float mNotificationScrimPadding; private int mMarginBottom; @@ -456,7 +457,7 @@ public class StackScrollAlgorithm { /** * @return Fraction to apply to view height and gap between views. - * Does not include shelf height even if shelf is showing. + * Does not include shelf height even if shelf is showing. */ protected float getExpansionFractionWithoutShelf( StackScrollAlgorithmState algorithmState, @@ -470,7 +471,7 @@ public class StackScrollAlgorithm { && (!ambientState.isBypassEnabled() || !ambientState.isPulseExpanding()) ? 0 : mNotificationScrimPadding; - final float stackHeight = ambientState.getStackHeight() - shelfHeight - scrimPadding; + final float stackHeight = ambientState.getStackHeight() - shelfHeight - scrimPadding; final float stackEndHeight = ambientState.getStackEndHeight() - shelfHeight - scrimPadding; if (stackEndHeight == 0f) { // This should not happen, since even when the shade is empty we show EmptyShadeView @@ -504,13 +505,14 @@ public class StackScrollAlgorithm { } // TODO(b/172289889) polish shade open from HUN + /** * Populates the {@link ExpandableViewState} for a single child. * - * @param i The index of the child in - * {@link StackScrollAlgorithmState#visibleChildren}. - * @param algorithmState The overall output state of the algorithm. - * @param ambientState The input state provided to the algorithm. + * @param i The index of the child in + * {@link StackScrollAlgorithmState#visibleChildren}. + * @param algorithmState The overall output state of the algorithm. + * @param ambientState The input state provided to the algorithm. */ protected void updateChild( int i, @@ -584,8 +586,8 @@ public class StackScrollAlgorithm { final float stackBottom = !ambientState.isShadeExpanded() || ambientState.getDozeAmount() == 1f || bypassPulseNotExpanding - ? ambientState.getInnerHeight() - : ambientState.getStackHeight(); + ? ambientState.getInnerHeight() + : ambientState.getStackHeight(); final float shelfStart = stackBottom - ambientState.getShelf().getIntrinsicHeight() - mPaddingBetweenElements; @@ -621,9 +623,9 @@ public class StackScrollAlgorithm { * Get the gap height needed for before a view * * @param sectionProvider the sectionProvider used to understand the sections - * @param visibleIndex the visible index of this view in the list - * @param child the child asked about - * @param previousChild the child right before it or null if none + * @param visibleIndex the visible index of this view in the list + * @param child the child asked about + * @param previousChild the child right before it or null if none * @return the size of the gap needed or 0 if none is needed */ public float getGapHeightForChild( @@ -657,9 +659,9 @@ public class StackScrollAlgorithm { * Does a given child need a gap, i.e spacing before a view? * * @param sectionProvider the sectionProvider used to understand the sections - * @param visibleIndex the visible index of this view in the list - * @param child the child asked about - * @param previousChild the child right before it or null if none + * @param visibleIndex the visible index of this view in the list + * @param child the child asked about + * @param previousChild the child right before it or null if none * @return if the child needs a gap height */ private boolean childNeedsGapHeight( @@ -862,30 +864,53 @@ public class StackScrollAlgorithm { } } + /** + * Calculate and update the Z positions for a given child. We currently only give shadows to + * HUNs to distinguish a HUN from its surroundings. + * + * @param isTopHun Whether the child is a top HUN. A top HUN means a HUN that shows on the + * vertically top of screen. Top HUNs should have drop shadows + * @param childrenOnTop It is greater than 0 when there's an existing HUN that is elevated + * @return childrenOnTop The decimal part represents the fraction of the elevated HUN's height + * that overlaps with QQS Panel. The integer part represents the count of + * previous HUNs whose Z positions are greater than 0. + */ protected float updateChildZValue(int i, float childrenOnTop, StackScrollAlgorithmState algorithmState, AmbientState ambientState, - boolean shouldElevateHun) { + boolean isTopHun) { ExpandableView child = algorithmState.visibleChildren.get(i); ExpandableViewState childViewState = child.getViewState(); - int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements(); float baseZ = ambientState.getBaseZHeight(); + + // Handles HUN shadow when Shade is opened + if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible && !ambientState.isDozingAndNotPulsing(child) && childViewState.getYTranslation() < ambientState.getTopPadding() + ambientState.getStackTranslation()) { + // 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 over-lapping fraction goes to 0, and shadows hides gradually. if (childrenOnTop != 0.0f) { + // To elevate the later HUN over previous HUN childrenOnTop++; } else { float overlap = ambientState.getTopPadding() + ambientState.getStackTranslation() - childViewState.getYTranslation(); - childrenOnTop += Math.min(1.0f, overlap / childViewState.height); + // To prevent over-shadow during HUN entry + childrenOnTop += Math.min( + 1.0f, + overlap / childViewState.height + ); + MathUtils.saturate(childrenOnTop); } childViewState.setZTranslation(baseZ - + childrenOnTop * zDistanceBetweenElements); - } else if (shouldElevateHun) { + + 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 then the notification + // elevate if we are currently expanded more than the notification int shelfHeight = ambientState.getShelf() == null ? 0 : ambientState.getShelf().getIntrinsicHeight(); float shelfStart = ambientState.getInnerHeight() @@ -894,23 +919,28 @@ public class StackScrollAlgorithm { 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 * zDistanceBetweenElements); + childViewState.setZTranslation(baseZ + factor * mPinnedZTranslationExtra); } } else { childViewState.setZTranslation(baseZ); } - // We need to scrim the notification more from its surrounding content when we are pinned, - // and we therefore elevate it higher. - // We can use the headerVisibleAmount for this, since the value nicely goes from 0 to 1 when - // expanding after which we have a normal elevation again. + // Handles HUN shadow when shade is closed. + // 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 + // gradually from 0 to 1, shadow hides gradually. + // Header visibility is a deprecated concept, we are using headerVisibleAmount only because + // this value nicely goes from 0 to 1 during the HUN-to-Shade process. + childViewState.setZTranslation(childViewState.getZTranslation() + (1.0f - child.getHeaderVisibleAmount()) * mPinnedZTranslationExtra); return childrenOnTop; 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 743e7d69cfc0..4d9db8c28e07 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 @@ -14,6 +14,7 @@ import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager +import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse @@ -31,10 +32,10 @@ class StackScrollAlgorithmTest : SysuiTestCase() { private val hostView = FrameLayout(context) private val stackScrollAlgorithm = StackScrollAlgorithm(context, hostView) - private val notificationRow = mock(ExpandableNotificationRow::class.java) - private val dumpManager = mock(DumpManager::class.java) - private val mStatusBarKeyguardViewManager = mock(StatusBarKeyguardViewManager::class.java) - private val notificationShelf = mock(NotificationShelf::class.java) + private val notificationRow = mock<ExpandableNotificationRow>() + private val dumpManager = mock<DumpManager>() + private val mStatusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>() + private val notificationShelf = mock<NotificationShelf>() private val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply { layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100) } @@ -46,7 +47,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { mStatusBarKeyguardViewManager ) - private val testableResources = mContext.orCreateTestableResources + private val testableResources = mContext.getOrCreateTestableResources() private fun px(@DimenRes id: Int): Float = testableResources.resources.getDimensionPixelSize(id).toFloat() @@ -98,7 +99,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0) val marginBottom = - context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom) + context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom) val fullHeight = ambientState.layoutMaxHeight + marginBottom - ambientState.stackY val centeredY = ambientState.stackY + fullHeight / 2f - emptyShadeView.height / 2f assertThat(emptyShadeView.viewState?.yTranslation).isEqualTo(centeredY) @@ -507,6 +508,192 @@ class StackScrollAlgorithmTest : SysuiTestCase() { assertEquals(1f, currentRoundness) } + @Test + fun shadeOpened_hunFullyOverlapsQqsPanel_hunShouldHaveFullShadow() { + // Given: shade is opened, yTranslation of HUN is 0, + // the height of HUN equals to the height of QQS Panel, + // and HUN fully overlaps with QQS Panel + ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) + + px(R.dimen.qqs_layout_padding_bottom) + val childHunView = createHunViewMock( + isShadeOpen = true, + fullyVisible = false, + headerVisibleAmount = 1f + ) + val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState() + algorithmState.visibleChildren.add(childHunView) + + // When: updateChildZValue() is called for the top HUN + stackScrollAlgorithm.updateChildZValue( + /* i= */ 0, + /* childrenOnTop= */ 0.0f, + /* StackScrollAlgorithmState= */ algorithmState, + /* ambientState= */ ambientState, + /* shouldElevateHun= */ true + ) + + // Then: full shadow would be applied + assertEquals(px(R.dimen.heads_up_pinned_elevation), childHunView.viewState.zTranslation) + } + + @Test + 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, + // and HUN partially overlaps with QQS Panel + ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) + + px(R.dimen.qqs_layout_padding_bottom) + val childHunView = createHunViewMock( + isShadeOpen = true, + fullyVisible = false, + headerVisibleAmount = 1f + ) + // Use half of the HUN's height as overlap + childHunView.viewState.yTranslation = (childHunView.viewState.height + 1 shr 1).toFloat() + val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState() + algorithmState.visibleChildren.add(childHunView) + + // When: updateChildZValue() is called for the top HUN + stackScrollAlgorithm.updateChildZValue( + /* i= */ 0, + /* childrenOnTop= */ 0.0f, + /* StackScrollAlgorithmState= */ algorithmState, + /* ambientState= */ ambientState, + /* shouldElevateHun= */ true + ) + + // Then: HUN should have shadow, but not as full size + assertThat(childHunView.viewState.zTranslation).isGreaterThan(0.0f) + assertThat(childHunView.viewState.zTranslation) + .isLessThan(px(R.dimen.heads_up_pinned_elevation)) + } + + @Test + fun shadeOpened_hunDoesNotOverlapQQS_hunShouldHaveNoShadow() { + // Given: shade is opened, yTranslation of HUN is equal to QQS Panel's height, + // the height of HUN is equal to the height of QQS Panel, + // and HUN doesn't overlap with QQS Panel + ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) + + px(R.dimen.qqs_layout_padding_bottom) + // Mock the height of shade + ambientState.setLayoutMinHeight(1000) + val childHunView = createHunViewMock( + isShadeOpen = true, + fullyVisible = true, + headerVisibleAmount = 1f + ) + // HUN doesn't overlap with QQS Panel + childHunView.viewState.yTranslation = ambientState.topPadding + + ambientState.stackTranslation + val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState() + algorithmState.visibleChildren.add(childHunView) + + // When: updateChildZValue() is called for the top HUN + stackScrollAlgorithm.updateChildZValue( + /* i= */ 0, + /* childrenOnTop= */ 0.0f, + /* StackScrollAlgorithmState= */ algorithmState, + /* ambientState= */ ambientState, + /* shouldElevateHun= */ true + ) + + // Then: HUN should not have shadow + assertEquals(0f, childHunView.viewState.zTranslation) + } + + @Test + fun shadeClosed_hunShouldHaveFullShadow() { + // Given: shade is closed, ambientState.stackTranslation == -ambientState.topPadding, + // the height of HUN is equal to the height of QQS Panel, + ambientState.stackTranslation = -ambientState.topPadding + // Mock the height of shade + ambientState.setLayoutMinHeight(1000) + val childHunView = createHunViewMock( + isShadeOpen = false, + fullyVisible = false, + headerVisibleAmount = 0f + ) + childHunView.viewState.yTranslation = 0f + // Shade is closed, thus childHunView's headerVisibleAmount is 0 + childHunView.headerVisibleAmount = 0f + val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState() + algorithmState.visibleChildren.add(childHunView) + + // When: updateChildZValue() is called for the top HUN + stackScrollAlgorithm.updateChildZValue( + /* i= */ 0, + /* childrenOnTop= */ 0.0f, + /* StackScrollAlgorithmState= */ algorithmState, + /* ambientState= */ ambientState, + /* shouldElevateHun= */ true + ) + + // Then: HUN should have full shadow + assertEquals(px(R.dimen.heads_up_pinned_elevation), childHunView.viewState.zTranslation) + } + + @Test + fun draggingHunToOpenShade_hunShouldHavePartialShadow() { + // Given: shade is closed when HUN pops up, + // now drags down the HUN to open shade + ambientState.stackTranslation = -ambientState.topPadding + // Mock the height of shade + ambientState.setLayoutMinHeight(1000) + val childHunView = createHunViewMock( + isShadeOpen = false, + fullyVisible = false, + headerVisibleAmount = 0.5f + ) + childHunView.viewState.yTranslation = 0f + // Shade is being opened, thus childHunView's headerVisibleAmount is between 0 and 1 + // use 0.5 as headerVisibleAmount here + childHunView.headerVisibleAmount = 0.5f + val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState() + algorithmState.visibleChildren.add(childHunView) + + // When: updateChildZValue() is called for the top HUN + stackScrollAlgorithm.updateChildZValue( + /* i= */ 0, + /* childrenOnTop= */ 0.0f, + /* StackScrollAlgorithmState= */ algorithmState, + /* ambientState= */ ambientState, + /* shouldElevateHun= */ true + ) + + // Then: HUN should have shadow, but not as full size + assertThat(childHunView.viewState.zTranslation).isGreaterThan(0.0f) + assertThat(childHunView.viewState.zTranslation) + .isLessThan(px(R.dimen.heads_up_pinned_elevation)) + } + + private fun createHunViewMock( + isShadeOpen: Boolean, + fullyVisible: Boolean, + headerVisibleAmount: Float + ) = + mock<ExpandableNotificationRow>().apply { + val childViewStateMock = createHunChildViewState(isShadeOpen, fullyVisible) + whenever(this.viewState).thenReturn(childViewStateMock) + + whenever(this.mustStayOnScreen()).thenReturn(true) + whenever(this.headerVisibleAmount).thenReturn(headerVisibleAmount) + } + + + private fun createHunChildViewState(isShadeOpen: Boolean, fullyVisible: Boolean) = + ExpandableViewState().apply { + // Mock the HUN's height with ambientState.topPadding + + // ambientState.stackTranslation + height = (ambientState.topPadding + ambientState.stackTranslation).toInt() + if (isShadeOpen && fullyVisible) { + yTranslation = + ambientState.topPadding + ambientState.stackTranslation + } else { + yTranslation = 0f + } + headsUpIsVisible = fullyVisible + } + private fun resetViewStates_expansionChanging_notificationAlphaUpdated( expansionFraction: Float, expectedAlpha: Float |