diff options
4 files changed, 174 insertions, 125 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt index d5558b27ef1a..e7f7647797cd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt @@ -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, - 0F, - 1F - ) + + fun calculateAlpha( + squishinessFraction: Float, + startPosition: Float, + endPosition: Float + ): Float { + val transformFraction = + MathUtils.constrain( + (squishinessFraction - startPosition) / (endPosition - startPosition), + 0F, + 1F + ) + return TRANSFORM_BEZIER.getInterpolation(transformFraction) } } @@ -813,7 +808,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 @@ -839,7 +839,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. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt index d2c8e15868dc..2ec7be6eaa32 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt @@ -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 /** @@ -305,39 +302,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 -> + // 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 + } + + /** + * 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 -> - state.alpha = calculateAlpha(squishFraction, MEDIACONTAINERS_DELAY, DURATION) + groupTop = min(groupTop, state.y) + groupBottom = max(groupBottom, state.y + state.height) } } - - RecommendationViewHolder.mediaTitlesAndSubtitlesIds.forEach { id -> + // 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 } /** @@ -545,11 +609,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 diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt index 039dd4d92eb4..e4e95e580a7c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt @@ -20,6 +20,7 @@ import android.app.PendingIntent import android.content.res.Configuration 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 @@ -31,14 +32,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 @@ -56,6 +54,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 @@ -86,6 +85,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 configListener: ArgumentCaptor<ConfigurationController.ConfigurationListener> @@ -647,25 +648,22 @@ 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 } } @Ignore("b/253229241") diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt index 35b0eb678441..4ed6d7cf6bd0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt @@ -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,10 +70,11 @@ class MediaViewControllerTest : SysuiTestCase() { @Test fun testObtainViewState_applySquishFraction_toPlayerTransitionViewState_height() { mediaViewController.attach(player, MediaViewController.TYPE.PLAYER) - player.measureState = TransitionViewState().apply { - this.height = 100 - this.measureHeight = 100 - } + player.measureState = + TransitionViewState().apply { + this.height = 100 + this.measureHeight = 100 + } mediaHostStateHolder.expansion = 1f val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) @@ -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 } } } |