Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ce56e3ff authored by András Kurucz's avatar András Kurucz
Browse files

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
parent 878b61b0
Loading
Loading
Loading
Loading
+14 −8
Original line number Diff line number Diff line
@@ -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;
            }
        }
+96 −14
Original line number Diff line number Diff line
@@ -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,