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

Commit 2ab27eb5 authored by Yining Liu's avatar Yining Liu Committed by Automerger Merge Worker
Browse files

Merge "Fix Notification clipping flicker during AOD=>LS" into udc-dev am: 51a5c2fd am: f355a188

parents 645f1544 f355a188
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -93,6 +93,12 @@ public class AmbientState implements Dumpable {
    private boolean mAppearing;
    private float mPulseHeight = MAX_PULSE_HEIGHT;

    /**
     * The ExpandableNotificationRow that is pulsing, or the one that was pulsing
     * when the device started to transition from AOD to LockScreen.
     */
    private ExpandableNotificationRow mPulsingRow;

    /** Fraction of lockscreen to shade animation (on lockscreen swipe down). */
    private float mFractionToShade;

@@ -564,6 +570,19 @@ public class AmbientState implements Dumpable {
        return mPulsing && entry.isAlerting();
    }

    public void setPulsingRow(ExpandableNotificationRow row) {
        mPulsingRow = row;
    }

    /**
     * @param row The row to check
     * @return true if row is the pulsing row when the device started to transition from AOD to lock
     * screen
     */
    public boolean isPulsingRow(ExpandableView row) {
        return mPulsingRow == row;
    }

    public boolean isPanelTracking() {
        return mPanelTracking;
    }
+22 −2
Original line number Diff line number Diff line
@@ -548,7 +548,7 @@ public class StackScrollAlgorithm {
        ExpandableViewState viewState = view.getViewState();
        viewState.location = ExpandableViewState.LOCATION_UNKNOWN;

        final float expansionFraction = getExpansionFractionWithoutShelf(
        float expansionFraction = getExpansionFractionWithoutShelf(
                algorithmState, ambientState);

        // Add gap between sections.
@@ -619,6 +619,11 @@ 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;
            }
            // Clip height of view right before shelf.
            viewState.height = (int) (getMaxAllowedChildHeight(view) * expansionFraction);
        }
@@ -700,9 +705,11 @@ public class StackScrollAlgorithm {
                && !(child instanceof FooterView);
    }

    private void updatePulsingStates(StackScrollAlgorithmState algorithmState,
    @VisibleForTesting
    void updatePulsingStates(StackScrollAlgorithmState algorithmState,
                                     AmbientState ambientState) {
        int childCount = algorithmState.visibleChildren.size();
        ExpandableNotificationRow pulsingRow = null;
        for (int i = 0; i < childCount; i++) {
            View child = algorithmState.visibleChildren.get(i);
            if (!(child instanceof ExpandableNotificationRow)) {
@@ -714,6 +721,19 @@ public class StackScrollAlgorithm {
            }
            ExpandableViewState viewState = row.getViewState();
            viewState.hidden = false;
            pulsingRow = row;
        }

        // Set AmbientState#pulsingRow to the current pulsing row when on AOD.
        // Set AmbientState#pulsingRow=null when on lockscreen, since AmbientState#pulsingRow
        // is only used for skipping the unfurl animation for (the notification that was already
        // showing at full height on AOD) during the AOD=>lockscreen transition, where
        // dozeAmount=[1f, 0f). We also need to reset the pulsingRow once it is no longer used
        // because it will interfere with future unfurling animations - for example, during the
        // LS=>AOD animation, the pulsingRow may stay at full height when it should squish with the
        // rest of the stack.
        if (ambientState.getDozeAmount() == 0.0f || ambientState.getDozeAmount() == 1.0f) {
            ambientState.setPulsingRow(pulsingRow);
        }
    }

+111 −0
Original line number Diff line number Diff line
@@ -716,6 +716,94 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
                .isLessThan(px(R.dimen.heads_up_pinned_elevation))
    }

    @Test
    fun aodToLockScreen_hasPulsingNotification_pulsingNotificationRowDoesNotChange() {
        // Given: Before AOD to LockScreen, there was a pulsing notification
        val pulsingNotificationView = createPulsingViewMock()
        val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
        algorithmState.visibleChildren.add(pulsingNotificationView)
        ambientState.setPulsingRow(pulsingNotificationView)

        // When: during AOD to LockScreen, any dozeAmount between (0, 1.0) is equivalent as a middle
        // stage; here we use 0.5 for testing.
        // stackScrollAlgorithm.updatePulsingStates is called
        ambientState.dozeAmount = 0.5f
        stackScrollAlgorithm.updatePulsingStates(algorithmState, ambientState)

        // Then: ambientState.pulsingRow should still be pulsingNotificationView
        assertTrue(ambientState.isPulsingRow(pulsingNotificationView))
    }

    @Test
    fun deviceOnAod_hasPulsingNotification_recordPulsingNotificationRow() {
        // Given: Device is on AOD, there is a pulsing notification
        // ambientState.pulsingRow is null before stackScrollAlgorithm.updatePulsingStates
        ambientState.dozeAmount = 1.0f
        val pulsingNotificationView = createPulsingViewMock()
        val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
        algorithmState.visibleChildren.add(pulsingNotificationView)
        ambientState.setPulsingRow(null)

        // When: stackScrollAlgorithm.updatePulsingStates is called
        stackScrollAlgorithm.updatePulsingStates(algorithmState, ambientState)

        // Then: ambientState.pulsingRow should record the pulsingNotificationView
        assertTrue(ambientState.isPulsingRow(pulsingNotificationView))
    }

    @Test
    fun deviceOnLockScreen_hasPulsingNotificationBefore_clearPulsingNotificationRowRecord() {
        // Given: Device finished AOD to LockScreen, there was a pulsing notification, and
        // ambientState.pulsingRow was not null before AOD to LockScreen
        // pulsingNotificationView.showingPulsing() returns false since the device is on LockScreen
        ambientState.dozeAmount = 0.0f
        val pulsingNotificationView = createPulsingViewMock()
        whenever(pulsingNotificationView.showingPulsing()).thenReturn(false)
        val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
        algorithmState.visibleChildren.add(pulsingNotificationView)
        ambientState.setPulsingRow(pulsingNotificationView)

        // When: stackScrollAlgorithm.updatePulsingStates is called
        stackScrollAlgorithm.updatePulsingStates(algorithmState, ambientState)

        // Then: ambientState.pulsingRow should be null
        assertTrue(ambientState.isPulsingRow(null))
    }

    @Test
    fun aodToLockScreen_hasPulsingNotification_pulsingNotificationRowShowAtFullHeight() {
        // Given: Before AOD to LockScreen, there was a pulsing notification
        val pulsingNotificationView = createPulsingViewMock()
        val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
        algorithmState.visibleChildren.add(pulsingNotificationView)
        ambientState.setPulsingRow(pulsingNotificationView)

        // When: during AOD to LockScreen, any dozeAmount between (0, 1.0) is equivalent as a middle
        // stage; here we use 0.5 for testing. The expansionFraction is also 0.5.
        // stackScrollAlgorithm.resetViewStates is called.
        ambientState.dozeAmount = 0.5f
        setExpansionFractionWithoutShelfDuringAodToLockScreen(
                ambientState,
                algorithmState,
                fraction = 0.5f
        )
        stackScrollAlgorithm.resetViewStates(ambientState, 0)

        // Then: pulsingNotificationView should show at full height
        assertEquals(
                stackScrollAlgorithm.getMaxAllowedChildHeight(pulsingNotificationView),
                pulsingNotificationView.viewState.height
        )

        // After: reset dozeAmount and expansionFraction
        ambientState.dozeAmount = 0f
        setExpansionFractionWithoutShelfDuringAodToLockScreen(
                ambientState,
                algorithmState,
                fraction = 1f
        )
    }

    private fun createHunViewMock(
            isShadeOpen: Boolean,
            fullyVisible: Boolean,
@@ -744,6 +832,29 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
                headsUpIsVisible = fullyVisible
            }

    private fun createPulsingViewMock(
    ) =
            mock<ExpandableNotificationRow>().apply {
                whenever(this.viewState).thenReturn(ExpandableViewState())
                whenever(this.showingPulsing()).thenReturn(true)
            }

    private fun setExpansionFractionWithoutShelfDuringAodToLockScreen(
            ambientState: AmbientState,
            algorithmState: StackScrollAlgorithm.StackScrollAlgorithmState,
            fraction: Float
    ) {
        // showingShelf: false
        algorithmState.firstViewInShelf = null
        // scrimPadding: 0, because device is on lock screen
        ambientState.setStatusBarState(StatusBarState.KEYGUARD)
        ambientState.dozeAmount = 0.0f
        // set stackEndHeight and stackHeight
        // ExpansionFractionWithoutShelf == stackHeight / stackEndHeight
        ambientState.stackEndHeight = 100f
        ambientState.stackHeight = ambientState.stackEndHeight * fraction
    }

    private fun resetViewStates_expansionChanging_notificationAlphaUpdated(
            expansionFraction: Float,
            expectedAlpha: Float,