Fix missing HUN Outro animations

The disappear animation was broken, because we were setting some wrong
values on the HUN animating away:
 - a bottom clipping, which made it to immediately disappear at the start of the animation
 - an yTranslation, which made it to jump around on split shade
 - a wrong height, which made it to flicker during the animation (note: the height flickering only happened if there were other HUNs moving into the shade in the meantime -> the avalanche is the typical use case)

Fixes: b/282624825
Test: atest StackScrollAlgorithmTest
Test: atest create HUNs and observe the animations

Change-Id: If0c1e4f439a3011b5ea499edc2128aa417279ec8
This commit is contained in:
András Kurucz
2023-05-14 16:59:17 +00:00
parent 878b61b063
commit ce56e3ff55
2 changed files with 110 additions and 22 deletions

View File

@@ -315,7 +315,8 @@ public class StackScrollAlgorithm {
float newNotificationEnd = newYTranslation + newHeight;
boolean isHeadsUp = (child instanceof ExpandableNotificationRow) && child.isPinned();
if (mClipNotificationScrollToTop
&& ((isHeadsUp && !firstHeadsUp) || child.isHeadsUpAnimatingAway())
&& !firstHeadsUp
&& (isHeadsUp || child.isHeadsUpAnimatingAway())
&& newNotificationEnd > firstHeadsUpEnd
&& !ambientState.isShadeExpanded()) {
// The bottom of this view is peeking out from under the previous view.
@@ -619,13 +620,12 @@ public class StackScrollAlgorithm {
updateViewWithShelf(view, viewState, shelfStart);
}
}
// Avoid pulsing notification flicker during AOD to LS
// A pulsing notification is already expanded, no need to expand it again with animation
if (ambientState.isPulsingRow(view)) {
expansionFraction = 1.0f;
viewState.height = getMaxAllowedChildHeight(view);
if (!view.isPinned() && !view.isHeadsUpAnimatingAway()
&& !ambientState.isPulsingRow(view)) {
// The expansion fraction should not affect HUNs or pulsing notifications.
viewState.height *= expansionFraction;
}
// Clip height of view right before shelf.
viewState.height = (int) (getMaxAllowedChildHeight(view) * expansionFraction);
}
algorithmState.mCurrentYPosition +=
@@ -785,6 +785,8 @@ public class StackScrollAlgorithm {
}
}
if (row.isPinned()) {
// Make sure row yTranslation is at maximum the HUN yTranslation,
// which accounts for AmbientState.stackTopMargin in split-shade.
childState.setYTranslation(
Math.max(childState.getYTranslation(), headsUpTranslation));
childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
@@ -809,7 +811,11 @@ public class StackScrollAlgorithm {
}
}
if (row.isHeadsUpAnimatingAway()) {
childState.setYTranslation(Math.max(childState.getYTranslation(), mHeadsUpInset));
// Make sure row yTranslation is at maximum the HUN yTranslation,
// which accounts for AmbientState.stackTopMargin in split-shade.
childState.setYTranslation(
Math.max(childState.getYTranslation(), headsUpTranslation));
// keep it visible for the animation
childState.hidden = false;
}
}

View File

@@ -35,7 +35,6 @@ import org.mockito.Mockito.`when` as whenever
@SmallTest
class StackScrollAlgorithmTest : SysuiTestCase() {
@JvmField @Rule
var expect: Expect = Expect.create()
@@ -82,26 +81,58 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
fun resetViewStates_defaultHun_yTranslationIsInset() {
whenever(notificationRow.isPinned).thenReturn(true)
whenever(notificationRow.isHeadsUp).thenReturn(true)
stackScrollAlgorithm.resetViewStates(ambientState, 0)
assertThat(notificationRow.viewState.yTranslation)
.isEqualTo(stackScrollAlgorithm.mHeadsUpInset)
resetViewStates_hunYTranslationIsInset()
}
@Test
fun resetViewStates_stackMargin_changesHunYTranslation() {
fun resetViewStates_defaultHunWithStackMargin_changesHunYTranslation() {
whenever(notificationRow.isPinned).thenReturn(true)
whenever(notificationRow.isHeadsUp).thenReturn(true)
val minHeadsUpTranslation = context.resources
.getDimensionPixelSize(R.dimen.notification_side_paddings)
resetViewStates_stackMargin_changesHunYTranslation()
}
// split shade case with top margin introduced by shade's status bar
ambientState.stackTopMargin = 100
stackScrollAlgorithm.resetViewStates(ambientState, 0)
@Test
fun resetViewStates_hunAnimatingAway_yTranslationIsInset() {
whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
resetViewStates_hunYTranslationIsInset()
}
// top margin presence should decrease heads up translation up to minHeadsUpTranslation
assertThat(notificationRow.viewState.yTranslation).isEqualTo(minHeadsUpTranslation)
@Test
fun resetViewStates_hunAnimatingAway_StackMarginChangesHunYTranslation() {
whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
resetViewStates_stackMargin_changesHunYTranslation()
}
@Test
fun resetViewStates_hunAnimatingAway_bottomNotClipped() {
whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
assertThat(notificationRow.viewState.clipBottomAmount).isEqualTo(0)
}
@Test
fun resetViewStates_hunsOverlapping_bottomHunClipped() {
val topHun = mockExpandableNotificationRow()
val bottomHun = mockExpandableNotificationRow()
whenever(topHun.isHeadsUp).thenReturn(true)
whenever(topHun.isPinned).thenReturn(true)
whenever(bottomHun.isHeadsUp).thenReturn(true)
whenever(bottomHun.isPinned).thenReturn(true)
resetViewStates_hunsOverlapping_bottomHunClipped(topHun, bottomHun)
}
@Test
fun resetViewStates_hunsOverlappingAndBottomHunAnimatingAway_bottomHunClipped() {
val topHun = mockExpandableNotificationRow()
val bottomHun = mockExpandableNotificationRow()
whenever(topHun.isHeadsUp).thenReturn(true)
whenever(topHun.isPinned).thenReturn(true)
whenever(bottomHun.isHeadsUpAnimatingAway).thenReturn(true)
resetViewStates_hunsOverlapping_bottomHunClipped(topHun, bottomHun)
}
@Test
@@ -855,6 +886,57 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
ambientState.stackHeight = ambientState.stackEndHeight * fraction
}
private fun resetViewStates_hunYTranslationIsInset() {
stackScrollAlgorithm.resetViewStates(ambientState, 0)
assertThat(notificationRow.viewState.yTranslation)
.isEqualTo(stackScrollAlgorithm.mHeadsUpInset)
}
private fun resetViewStates_stackMargin_changesHunYTranslation() {
val stackTopMargin = 50
val headsUpTranslationY = stackScrollAlgorithm.mHeadsUpInset - stackTopMargin
// we need the shelf to mock the real-life behaviour of StackScrollAlgorithm#updateChild
ambientState.shelf = notificationShelf
// split shade case with top margin introduced by shade's status bar
ambientState.stackTopMargin = stackTopMargin
stackScrollAlgorithm.resetViewStates(ambientState, 0)
// heads up translation should be decreased by the top margin
assertThat(notificationRow.viewState.yTranslation).isEqualTo(headsUpTranslationY)
}
private fun resetViewStates_hunsOverlapping_bottomHunClipped(
topHun: ExpandableNotificationRow,
bottomHun: ExpandableNotificationRow
) {
val topHunHeight = mContext.resources.getDimensionPixelSize(
R.dimen.notification_content_min_height)
val bottomHunHeight = mContext.resources.getDimensionPixelSize(
R.dimen.notification_max_heads_up_height)
whenever(topHun.intrinsicHeight).thenReturn(topHunHeight)
whenever(bottomHun.intrinsicHeight).thenReturn(bottomHunHeight)
// we need the shelf to mock the real-life behaviour of StackScrollAlgorithm#updateChild
ambientState.shelf = notificationShelf
// add two overlapping HUNs
hostView.removeAllViews()
hostView.addView(topHun)
hostView.addView(bottomHun)
stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
// the height shouldn't change
assertThat(topHun.viewState.height).isEqualTo(topHunHeight)
assertThat(bottomHun.viewState.height).isEqualTo(bottomHunHeight)
// the HUN at the bottom should be clipped
assertThat(topHun.viewState.clipBottomAmount).isEqualTo(0)
assertThat(bottomHun.viewState.clipBottomAmount).isEqualTo(bottomHunHeight - topHunHeight)
}
private fun resetViewStates_expansionChanging_notificationAlphaUpdated(
expansionFraction: Float,
expectedAlpha: Float,