Merge "Fix widgets in recommendation card in UMO are clipped" into tm-qpr-dev

This commit is contained in:
Sherry Zhou
2023-01-11 18:33:49 +00:00
committed by Android (Google) Code Review
4 changed files with 177 additions and 128 deletions

View File

@@ -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. */

View File

@@ -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,41 +301,108 @@ 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
}
}
RecommendationViewHolder.mediaContainersIds.forEach { id ->
squishedViewState.widgetStates.get(id)?.let { state ->
state.alpha = calculateAlpha(squishFraction, MEDIACONTAINERS_DELAY, DURATION)
}
}
RecommendationViewHolder.mediaTitlesAndSubtitlesIds.forEach { id ->
squishedViewState.widgetStates.get(id)?.let { state ->
state.alpha = calculateAlpha(squishFraction, MEDIATITLES_DELAY, DURATION)
}
squishedViewState.widgetStates.get(id)?.let { state -> state.height = squishedHeight }
}
// 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 ->
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,
startPosition / nonsquishedHeight,
endPosition / nonsquishedHeight
)
}
}
return groupTop // used for the widget group above this group
}
/**
* Obtain a new viewState for a given media state. This usually returns a cached state, but if
* it's not available, it will recreate one by measuring, which may be expensive.
@@ -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

View File

@@ -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")

View File

@@ -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
)
)
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)
val containerSquishMiddle =
TRANSFORM_BEZIER.getInterpolation(
(MEDIACONTAINERS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
)
mediaViewController.squishViewState(mockViewState, containerSquishMiddle)
// 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 }
}
}