summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Yining Liu <liuyining@google.com> 2022-11-17 17:57:10 +0000
committer Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> 2022-11-17 17:57:10 +0000
commit7c88119d7f4a9c04b9566f4c971558854117a70c (patch)
treed728fd899294f48948abcbac706ec21644ae1c6c
parent905e965bf57afcb1e9694ebc1229760daccf2396 (diff)
parent23dae8748a3a63600cdcaae8525e5c5196c114a7 (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>
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java82
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt199
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