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

Commit e87d7509 authored by Yining Liu's avatar Yining Liu Committed by Android (Google) Code Review
Browse files

Merge "Add drop shadow for HUNs when shade is opened" into tm-qpr-dev

parents 2a4339d0 f3a31070
Loading
Loading
Loading
Loading
+56 −26
Original line number Diff line number Diff line
@@ -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;
@@ -504,6 +505,7 @@ public class StackScrollAlgorithm {
    }

    // TODO(b/172289889) polish shade open from HUN

    /**
     * Populates the {@link ExpandableViewState} for a single child.
     *
@@ -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;
+193 −6
Original line number Diff line number Diff line
@@ -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()
@@ -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