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

Commit 066471a3 authored by Sherry Zhou's avatar Sherry Zhou
Browse files

Fix widgets in recommendation card in UMO are clipped

Bug: 263418717
Test: MediaViewControllerTest MediaCarouselControllerTest
Change-Id: Ie2134ef82568035b89abf7193cbd2e6faf5d907d
parent 4987fc3a
Loading
Loading
Loading
Loading
+24 −23
Original line number Diff line number Diff line
@@ -94,7 +94,7 @@ constructor(
    private var currentCarouselWidth: Int = 0

    /** The current height of the carousel */
    private var currentCarouselHeight: Int = 0
    @VisibleForTesting var currentCarouselHeight: Int = 0

    /** Are we currently showing only active players */
    private var currentlyShowingOnlyActive: Boolean = false
@@ -128,14 +128,14 @@ constructor(
    /** The measured height of the carousel */
    private var carouselMeasureHeight: Int = 0
    private var desiredHostState: MediaHostState? = null
    private val mediaCarousel: MediaScrollView
    @VisibleForTesting var mediaCarousel: MediaScrollView
    val mediaCarouselScrollHandler: MediaCarouselScrollHandler
    val mediaFrame: ViewGroup
    @VisibleForTesting
    lateinit var settingsButton: View
        private set
    private val mediaContent: ViewGroup
    @VisibleForTesting val pageIndicator: PageIndicator
    @VisibleForTesting var pageIndicator: PageIndicator
    private val visualStabilityCallback: OnReorderingAllowedListener
    private var needsReordering: Boolean = false
    private var keysNeedRemoval = mutableSetOf<String>()
@@ -160,25 +160,20 @@ constructor(
        }

    companion object {
        const val ANIMATION_BASE_DURATION = 2200f
        const val DURATION = 167f
        const val DETAILS_DELAY = 1067f
        const val CONTROLS_DELAY = 1400f
        const val PAGINATION_DELAY = 1900f
        const val MEDIATITLES_DELAY = 1000f
        const val MEDIACONTAINERS_DELAY = 967f
        val TRANSFORM_BEZIER = PathInterpolator(0.68F, 0F, 0F, 1F)
        val REVERSE_BEZIER = PathInterpolator(0F, 0.68F, 1F, 0F)

        fun calculateAlpha(squishinessFraction: Float, delay: Float, duration: Float): Float {
            val transformStartFraction = delay / ANIMATION_BASE_DURATION
            val transformDurationFraction = duration / ANIMATION_BASE_DURATION
            val squishinessToTime = REVERSE_BEZIER.getInterpolation(squishinessFraction)
            return MathUtils.constrain(
                (squishinessToTime - transformStartFraction) / transformDurationFraction,

        fun calculateAlpha(
            squishinessFraction: Float,
            startPosition: Float,
            endPosition: Float
        ): Float {
            val transformFraction =
                MathUtils.constrain(
                    (squishinessFraction - startPosition) / (endPosition - startPosition),
                    0F,
                    1F
                )
            return TRANSFORM_BEZIER.getInterpolation(transformFraction)
        }
    }

@@ -800,7 +795,12 @@ constructor(
        val squishFraction = hostStates[currentEndLocation]?.squishFraction ?: 1.0F
        val endAlpha =
            (if (endIsVisible) 1.0f else 0.0f) *
                calculateAlpha(squishFraction, PAGINATION_DELAY, DURATION)
                calculateAlpha(
                    squishFraction,
                    (pageIndicator.translationY + pageIndicator.height) /
                        mediaCarousel.measuredHeight,
                    1F
                )
        var alpha = 1.0f
        if (!endIsVisible || !startIsVisible) {
            var progress = currentTransitionProgress
@@ -826,7 +826,8 @@ constructor(
        pageIndicator.translationX = translationX + mediaCarouselScrollHandler.contentTranslation
        val layoutParams = pageIndicator.layoutParams as ViewGroup.MarginLayoutParams
        pageIndicator.translationY =
            (currentCarouselHeight - pageIndicator.height - layoutParams.bottomMargin).toFloat()
            (mediaCarousel.measuredHeight - pageIndicator.height - layoutParams.bottomMargin)
                .toFloat()
    }

    /** Update the dimension of this carousel. */
+96 −30
Original line number Diff line number Diff line
@@ -24,11 +24,6 @@ import com.android.systemui.R
import com.android.systemui.media.controls.models.GutsViewHolder
import com.android.systemui.media.controls.models.player.MediaViewHolder
import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.CONTROLS_DELAY
import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DETAILS_DELAY
import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DURATION
import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY
import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIATITLES_DELAY
import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.calculateAlpha
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.animation.MeasurementOutput
@@ -36,6 +31,8 @@ import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.animation.TransitionLayoutController
import com.android.systemui.util.animation.TransitionViewState
import com.android.systemui.util.traceSection
import java.lang.Float.max
import java.lang.Float.min
import javax.inject.Inject

/**
@@ -304,39 +301,106 @@ constructor(
        val squishedViewState = viewState.copy()
        val squishedHeight = (squishedViewState.measureHeight * squishFraction).toInt()
        squishedViewState.height = squishedHeight
        controlIds.forEach { id ->
            squishedViewState.widgetStates.get(id)?.let { state ->
                state.alpha = calculateAlpha(squishFraction, CONTROLS_DELAY, DURATION)
            }
        }

        detailIds.forEach { id ->
            squishedViewState.widgetStates.get(id)?.let { state ->
                state.alpha = calculateAlpha(squishFraction, DETAILS_DELAY, DURATION)
            }
        }

        // We are not overriding the squishedViewStates height but only the children to avoid
        // them remeasuring the whole view. Instead it just remains as the original size
        backgroundIds.forEach { id ->
            squishedViewState.widgetStates.get(id)?.let { state ->
                state.height = squishedHeight
            }
            squishedViewState.widgetStates.get(id)?.let { state -> state.height = squishedHeight }
        }

        RecommendationViewHolder.mediaContainersIds.forEach { id ->
            squishedViewState.widgetStates.get(id)?.let { state ->
                state.alpha = calculateAlpha(squishFraction, MEDIACONTAINERS_DELAY, DURATION)
            }
        // media player
        val controlsTop =
            calculateWidgetGroupAlphaForSquishiness(
                controlIds,
                squishedViewState.measureHeight.toFloat(),
                squishedViewState,
                squishFraction
            )
        calculateWidgetGroupAlphaForSquishiness(
            detailIds,
            controlsTop,
            squishedViewState,
            squishFraction
        )
        // recommendation card
        val titlesTop =
            calculateWidgetGroupAlphaForSquishiness(
                RecommendationViewHolder.mediaTitlesAndSubtitlesIds,
                squishedViewState.measureHeight.toFloat(),
                squishedViewState,
                squishFraction
            )
        calculateWidgetGroupAlphaForSquishiness(
            RecommendationViewHolder.mediaContainersIds,
            titlesTop,
            squishedViewState,
            squishFraction
        )
        return squishedViewState
    }

        RecommendationViewHolder.mediaTitlesAndSubtitlesIds.forEach { id ->
    /**
     * This function is to make each widget in UMO disappear before being clipped by squished UMO
     *
     * The general rule is that widgets in UMO has been divided into several groups, and widgets in
     * one group have the same alpha during squishing It will change from alpha 0.0 when the visible
     * bottom of UMO reach the bottom of this group It will change to alpha 1.0 when the visible
     * bottom of UMO reach the top of the group below e.g.Album title, artist title and play-pause
     * button will change alpha together.
     * ```
     *     And their alpha becomes 1.0 when the visible bottom of UMO reach the top of controls,
     *     including progress bar, next button, previous button
     * ```
     * widgetGroupIds: a group of widgets have same state during UMO is squished,
     * ```
     *     e.g. Album title, artist title and play-pause button
     * ```
     * groupEndPosition: the height of UMO, when the height reaches this value,
     * ```
     *     widgets in this group should have 1.0 as alpha
     *     e.g., the group of album title, artist title and play-pause button will become fully
     *         visible when the height of UMO reaches the top of controls group
     *         (progress bar, previous button and next button)
     * ```
     * squishedViewState: hold the widgetState of each widget, which will be modified
     * squishFraction: the squishFraction of UMO
     */
    private fun calculateWidgetGroupAlphaForSquishiness(
        widgetGroupIds: Set<Int>,
        groupEndPosition: Float,
        squishedViewState: TransitionViewState,
        squishFraction: Float
    ): Float {
        val nonsquishedHeight = squishedViewState.measureHeight
        var groupTop = squishedViewState.measureHeight.toFloat()
        var groupBottom = 0F
        widgetGroupIds.forEach { id ->
            squishedViewState.widgetStates.get(id)?.let { state ->
                groupTop = min(groupTop, state.y)
                groupBottom = max(groupBottom, state.y + state.height)
            }
        }
        // startPosition means to the height of squished UMO where the widget alpha should start
        // changing from 0.0
        // generally, it equals to the bottom of widgets, so that we can meet the requirement that
        // widget should not go beyond the bounds of background
        // endPosition means to the height of squished UMO where the widget alpha should finish
        // changing alpha to 1.0
        var startPosition = groupBottom
        val endPosition = groupEndPosition
        if (startPosition == endPosition) {
            startPosition = (endPosition - 0.2 * (groupBottom - groupTop)).toFloat()
        }
        widgetGroupIds.forEach { id ->
            squishedViewState.widgetStates.get(id)?.let { state ->
                state.alpha = calculateAlpha(squishFraction, MEDIATITLES_DELAY, DURATION)
                state.alpha =
                    calculateAlpha(
                        squishFraction,
                        startPosition / nonsquishedHeight,
                        endPosition / nonsquishedHeight
                    )
            }
        }

        return squishedViewState
        return groupTop // used for the widget group above this group
    }

    /**
@@ -544,11 +608,13 @@ constructor(
        overrideSize?.let {
            // To be safe we're using a maximum here. The override size should always be set
            // properly though.
            if (result.measureHeight != it.measuredHeight
                    || result.measureWidth != it.measuredWidth) {
            if (
                result.measureHeight != it.measuredHeight || result.measureWidth != it.measuredWidth
            ) {
                result.measureHeight = Math.max(it.measuredHeight, result.measureHeight)
                result.measureWidth = Math.max(it.measuredWidth, result.measureWidth)
                // The measureHeight and the shown height should both be set to the overridden height
                // The measureHeight and the shown height should both be set to the overridden
                // height
                result.height = result.measureHeight
                result.width = result.measureWidth
                // Make sure all background views are also resized such that their size is correct
+14 −16
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.media.controls.ui
import android.app.PendingIntent
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.util.MathUtils.abs
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
@@ -30,14 +31,11 @@ import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.media.controls.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.ANIMATION_BASE_DURATION
import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DURATION
import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.PAGINATION_DELAY
import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.TRANSFORM_BEZIER
import com.android.systemui.media.controls.ui.MediaHierarchyManager.Companion.LOCATION_QS
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PageIndicator
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -55,6 +53,7 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.floatThat
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
@@ -85,6 +84,8 @@ class MediaCarouselControllerTest : SysuiTestCase() {
    @Mock lateinit var debugLogger: MediaCarouselControllerLogger
    @Mock lateinit var mediaViewController: MediaViewController
    @Mock lateinit var smartspaceMediaData: SmartspaceMediaData
    @Mock lateinit var mediaCarousel: MediaScrollView
    @Mock lateinit var pageIndicator: PageIndicator
    @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
    @Captor lateinit var visualStabilityCallback: ArgumentCaptor<OnReorderingAllowedListener>

@@ -642,24 +643,21 @@ class MediaCarouselControllerTest : SysuiTestCase() {
    @Test
    fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() {
        val delta = 0.0001F
        val paginationSquishMiddle =
            TRANSFORM_BEZIER.getInterpolation(
                (PAGINATION_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
            )
        val paginationSquishEnd =
            TRANSFORM_BEZIER.getInterpolation(
                (PAGINATION_DELAY + DURATION) / ANIMATION_BASE_DURATION
            )
        mediaCarouselController.mediaCarousel = mediaCarousel
        mediaCarouselController.pageIndicator = pageIndicator
        whenever(mediaCarousel.measuredHeight).thenReturn(100)
        whenever(pageIndicator.translationY).thenReturn(80F)
        whenever(pageIndicator.height).thenReturn(10)
        whenever(mediaHostStatesManager.mediaHostStates)
            .thenReturn(mutableMapOf(LOCATION_QS to mediaHostState))
        whenever(mediaHostState.visible).thenReturn(true)
        mediaCarouselController.currentEndLocation = LOCATION_QS
        whenever(mediaHostState.squishFraction).thenReturn(paginationSquishMiddle)
        whenever(mediaHostState.squishFraction).thenReturn(0.938F)
        mediaCarouselController.updatePageIndicatorAlpha()
        assertEquals(mediaCarouselController.pageIndicator.alpha, 0.5F, delta)
        verify(pageIndicator).alpha = floatThat { abs(it - 0.5F) < delta }

        whenever(mediaHostState.squishFraction).thenReturn(paginationSquishEnd)
        whenever(mediaHostState.squishFraction).thenReturn(1.0F)
        mediaCarouselController.updatePageIndicatorAlpha()
        assertEquals(mediaCarouselController.pageIndicator.alpha, 1.0F, delta)
        verify(pageIndicator).alpha = floatThat { abs(it - 1.0F) < delta }
    }
}
+40 −56
Original line number Diff line number Diff line
@@ -22,13 +22,6 @@ import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.ANIMATION_BASE_DURATION
import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.CONTROLS_DELAY
import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DETAILS_DELAY
import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DURATION
import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY
import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIATITLES_DELAY
import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.TRANSFORM_BEZIER
import com.android.systemui.util.animation.MeasurementInput
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.animation.TransitionViewState
@@ -60,9 +53,10 @@ class MediaViewControllerTest : SysuiTestCase() {
    @Mock private lateinit var controlWidgetState: WidgetState
    @Mock private lateinit var bgWidgetState: WidgetState
    @Mock private lateinit var mediaTitleWidgetState: WidgetState
    @Mock private lateinit var mediaSubTitleWidgetState: WidgetState
    @Mock private lateinit var mediaContainerWidgetState: WidgetState

    val delta = 0.0001F
    val delta = 0.1F

    private lateinit var mediaViewController: MediaViewController

@@ -76,7 +70,8 @@ class MediaViewControllerTest : SysuiTestCase() {
    @Test
    fun testObtainViewState_applySquishFraction_toPlayerTransitionViewState_height() {
        mediaViewController.attach(player, MediaViewController.TYPE.PLAYER)
        player.measureState = TransitionViewState().apply {
        player.measureState =
            TransitionViewState().apply {
                this.height = 100
                this.measureHeight = 100
            }
@@ -128,29 +123,21 @@ class MediaViewControllerTest : SysuiTestCase() {
                    R.id.header_artist to detailWidgetState
                )
            )

        val detailSquishMiddle =
            TRANSFORM_BEZIER.getInterpolation(
                (DETAILS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
            )
        mediaViewController.squishViewState(mockViewState, detailSquishMiddle)
        whenever(mockCopiedState.measureHeight).thenReturn(200)
        // detail widgets occupy [90, 100]
        whenever(detailWidgetState.y).thenReturn(90F)
        whenever(detailWidgetState.height).thenReturn(10)
        // control widgets occupy [150, 170]
        whenever(controlWidgetState.y).thenReturn(150F)
        whenever(controlWidgetState.height).thenReturn(20)
        // in current beizer, when the progress reach 0.38, the result will be 0.5
        mediaViewController.squishViewState(mockViewState, 119F / 200F)
        verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }

        val detailSquishEnd =
            TRANSFORM_BEZIER.getInterpolation((DETAILS_DELAY + DURATION) / ANIMATION_BASE_DURATION)
        mediaViewController.squishViewState(mockViewState, detailSquishEnd)
        mediaViewController.squishViewState(mockViewState, 150F / 200F)
        verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }

        val controlSquishMiddle =
            TRANSFORM_BEZIER.getInterpolation(
                (CONTROLS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
            )
        mediaViewController.squishViewState(mockViewState, controlSquishMiddle)
        mediaViewController.squishViewState(mockViewState, 181.4F / 200F)
        verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }

        val controlSquishEnd =
            TRANSFORM_BEZIER.getInterpolation((CONTROLS_DELAY + DURATION) / ANIMATION_BASE_DURATION)
        mediaViewController.squishViewState(mockViewState, controlSquishEnd)
        mediaViewController.squishViewState(mockViewState, 200F / 200F)
        verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
    }

@@ -161,36 +148,33 @@ class MediaViewControllerTest : SysuiTestCase() {
            .thenReturn(
                mutableMapOf(
                    R.id.media_title1 to mediaTitleWidgetState,
                    R.id.media_subtitle1 to mediaSubTitleWidgetState,
                    R.id.media_cover1_container to mediaContainerWidgetState
                )
            )

        val containerSquishMiddle =
            TRANSFORM_BEZIER.getInterpolation(
                (MEDIACONTAINERS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
            )
        mediaViewController.squishViewState(mockViewState, containerSquishMiddle)
        whenever(mockCopiedState.measureHeight).thenReturn(360)
        // media container widgets occupy [20, 300]
        whenever(mediaContainerWidgetState.y).thenReturn(20F)
        whenever(mediaContainerWidgetState.height).thenReturn(280)
        // media title widgets occupy [320, 330]
        whenever(mediaTitleWidgetState.y).thenReturn(320F)
        whenever(mediaTitleWidgetState.height).thenReturn(10)
        // media subtitle widgets occupy [340, 350]
        whenever(mediaSubTitleWidgetState.y).thenReturn(340F)
        whenever(mediaSubTitleWidgetState.height).thenReturn(10)

        // in current beizer, when the progress reach 0.38, the result will be 0.5
        mediaViewController.squishViewState(mockViewState, 307.6F / 360F)
        verify(mediaContainerWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }

        val containerSquishEnd =
            TRANSFORM_BEZIER.getInterpolation(
                (MEDIACONTAINERS_DELAY + DURATION) / ANIMATION_BASE_DURATION
            )
        mediaViewController.squishViewState(mockViewState, containerSquishEnd)
        mediaViewController.squishViewState(mockViewState, 320F / 360F)
        verify(mediaContainerWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }

        val titleSquishMiddle =
            TRANSFORM_BEZIER.getInterpolation(
                (MEDIATITLES_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
            )
        mediaViewController.squishViewState(mockViewState, titleSquishMiddle)
        // media title and media subtitle are in same widget group, should be calculate together and
        // have same alpha
        mediaViewController.squishViewState(mockViewState, 353.8F / 360F)
        verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }

        val titleSquishEnd =
            TRANSFORM_BEZIER.getInterpolation(
                (MEDIATITLES_DELAY + DURATION) / ANIMATION_BASE_DURATION
            )
        mediaViewController.squishViewState(mockViewState, titleSquishEnd)
        verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
        mediaViewController.squishViewState(mockViewState, 360F / 360F)
        verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
        verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
    }
}