Merge "Make UMO squishy and add motion to widgets only for split shade" into tm-qpr-dev
This commit is contained in:
@@ -16,6 +16,6 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||||
<solid android:color="@android:color/white" />
|
<solid android:color="@android:color/transparent" />
|
||||||
<corners android:radius="@dimen/qs_media_album_radius" />
|
<corners android:radius="@dimen/qs_media_album_radius" />
|
||||||
</shape>
|
</shape>
|
||||||
@@ -11,6 +11,7 @@ import android.util.MathUtils
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.view.animation.PathInterpolator
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import com.android.internal.logging.InstanceId
|
import com.android.internal.logging.InstanceId
|
||||||
@@ -95,7 +96,8 @@ class MediaCarouselController @Inject constructor(
|
|||||||
* finished
|
* finished
|
||||||
*/
|
*/
|
||||||
@MediaLocation
|
@MediaLocation
|
||||||
private var currentEndLocation: Int = -1
|
@VisibleForTesting
|
||||||
|
var currentEndLocation: Int = -1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ending location of the view where it ends when all animations and transitions have
|
* The ending location of the view where it ends when all animations and transitions have
|
||||||
@@ -126,7 +128,8 @@ class MediaCarouselController @Inject constructor(
|
|||||||
lateinit var settingsButton: View
|
lateinit var settingsButton: View
|
||||||
private set
|
private set
|
||||||
private val mediaContent: ViewGroup
|
private val mediaContent: ViewGroup
|
||||||
private val pageIndicator: PageIndicator
|
@VisibleForTesting
|
||||||
|
val pageIndicator: PageIndicator
|
||||||
private val visualStabilityCallback: OnReorderingAllowedListener
|
private val visualStabilityCallback: OnReorderingAllowedListener
|
||||||
private var needsReordering: Boolean = false
|
private var needsReordering: Boolean = false
|
||||||
private var keysNeedRemoval = mutableSetOf<String>()
|
private var keysNeedRemoval = mutableSetOf<String>()
|
||||||
@@ -149,6 +152,27 @@ class MediaCarouselController @Inject 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val configListener = object : ConfigurationController.ConfigurationListener {
|
private val configListener = object : ConfigurationController.ConfigurationListener {
|
||||||
override fun onDensityOrFontScaleChanged() {
|
override fun onDensityOrFontScaleChanged() {
|
||||||
// System font changes should only happen when UMO is offscreen or a flicker may occur
|
// System font changes should only happen when UMO is offscreen or a flicker may occur
|
||||||
@@ -633,12 +657,17 @@ class MediaCarouselController @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updatePageIndicatorAlpha() {
|
@VisibleForTesting
|
||||||
|
fun updatePageIndicatorAlpha() {
|
||||||
val hostStates = mediaHostStatesManager.mediaHostStates
|
val hostStates = mediaHostStatesManager.mediaHostStates
|
||||||
val endIsVisible = hostStates[currentEndLocation]?.visible ?: false
|
val endIsVisible = hostStates[currentEndLocation]?.visible ?: false
|
||||||
val startIsVisible = hostStates[currentStartLocation]?.visible ?: false
|
val startIsVisible = hostStates[currentStartLocation]?.visible ?: false
|
||||||
val startAlpha = if (startIsVisible) 1.0f else 0.0f
|
val startAlpha = if (startIsVisible) 1.0f else 0.0f
|
||||||
val endAlpha = if (endIsVisible) 1.0f else 0.0f
|
// when squishing in split shade, only use endState, which keeps changing
|
||||||
|
// to provide squishFraction
|
||||||
|
val squishFraction = hostStates[currentEndLocation]?.squishFraction ?: 1.0F
|
||||||
|
val endAlpha = (if (endIsVisible) 1.0f else 0.0f) *
|
||||||
|
calculateAlpha(squishFraction, PAGINATION_DELAY, DURATION)
|
||||||
var alpha = 1.0f
|
var alpha = 1.0f
|
||||||
if (!endIsVisible || !startIsVisible) {
|
if (!endIsVisible || !startIsVisible) {
|
||||||
var progress = currentTransitionProgress
|
var progress = currentTransitionProgress
|
||||||
@@ -687,6 +716,7 @@ class MediaCarouselController @Inject constructor(
|
|||||||
mediaCarouselScrollHandler.setCarouselBounds(
|
mediaCarouselScrollHandler.setCarouselBounds(
|
||||||
currentCarouselWidth, currentCarouselHeight)
|
currentCarouselWidth, currentCarouselHeight)
|
||||||
updatePageIndicatorLocation()
|
updatePageIndicatorLocation()
|
||||||
|
updatePageIndicatorAlpha()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -203,6 +203,14 @@ class MediaHost constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override var squishFraction: Float = 1.0f
|
||||||
|
set(value) {
|
||||||
|
if (!value.equals(field)) {
|
||||||
|
field = value
|
||||||
|
changedListener?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override var showsOnlyActiveMedia: Boolean = false
|
override var showsOnlyActiveMedia: Boolean = false
|
||||||
set(value) {
|
set(value) {
|
||||||
if (!value.equals(field)) {
|
if (!value.equals(field)) {
|
||||||
@@ -253,6 +261,7 @@ class MediaHost constructor(
|
|||||||
override fun copy(): MediaHostState {
|
override fun copy(): MediaHostState {
|
||||||
val mediaHostState = MediaHostStateHolder()
|
val mediaHostState = MediaHostStateHolder()
|
||||||
mediaHostState.expansion = expansion
|
mediaHostState.expansion = expansion
|
||||||
|
mediaHostState.squishFraction = squishFraction
|
||||||
mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia
|
mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia
|
||||||
mediaHostState.measurementInput = measurementInput?.copy()
|
mediaHostState.measurementInput = measurementInput?.copy()
|
||||||
mediaHostState.visible = visible
|
mediaHostState.visible = visible
|
||||||
@@ -271,6 +280,9 @@ class MediaHost constructor(
|
|||||||
if (expansion != other.expansion) {
|
if (expansion != other.expansion) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if (squishFraction != other.squishFraction) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if (showsOnlyActiveMedia != other.showsOnlyActiveMedia) {
|
if (showsOnlyActiveMedia != other.showsOnlyActiveMedia) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -289,6 +301,7 @@ class MediaHost constructor(
|
|||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = measurementInput?.hashCode() ?: 0
|
var result = measurementInput?.hashCode() ?: 0
|
||||||
result = 31 * result + expansion.hashCode()
|
result = 31 * result + expansion.hashCode()
|
||||||
|
result = 31 * result + squishFraction.hashCode()
|
||||||
result = 31 * result + falsingProtectionNeeded.hashCode()
|
result = 31 * result + falsingProtectionNeeded.hashCode()
|
||||||
result = 31 * result + showsOnlyActiveMedia.hashCode()
|
result = 31 * result + showsOnlyActiveMedia.hashCode()
|
||||||
result = 31 * result + if (visible) 1 else 2
|
result = 31 * result + if (visible) 1 else 2
|
||||||
@@ -328,6 +341,11 @@ interface MediaHostState {
|
|||||||
*/
|
*/
|
||||||
var expansion: Float
|
var expansion: Float
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fraction of the height animation.
|
||||||
|
*/
|
||||||
|
var squishFraction: Float
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is this host only showing active media or is it showing all of them including resumption?
|
* Is this host only showing active media or is it showing all of them including resumption?
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -18,8 +18,15 @@ package com.android.systemui.media
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.constraintlayout.widget.ConstraintSet
|
import androidx.constraintlayout.widget.ConstraintSet
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.media.MediaCarouselController.Companion.CONTROLS_DELAY
|
||||||
|
import com.android.systemui.media.MediaCarouselController.Companion.DETAILS_DELAY
|
||||||
|
import com.android.systemui.media.MediaCarouselController.Companion.DURATION
|
||||||
|
import com.android.systemui.media.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY
|
||||||
|
import com.android.systemui.media.MediaCarouselController.Companion.MEDIATITLES_DELAY
|
||||||
|
import com.android.systemui.media.MediaCarouselController.Companion.calculateAlpha
|
||||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||||
import com.android.systemui.util.animation.MeasurementOutput
|
import com.android.systemui.util.animation.MeasurementOutput
|
||||||
import com.android.systemui.util.animation.TransitionLayout
|
import com.android.systemui.util.animation.TransitionLayout
|
||||||
@@ -50,6 +57,24 @@ class MediaViewController @Inject constructor(
|
|||||||
companion object {
|
companion object {
|
||||||
@JvmField
|
@JvmField
|
||||||
val GUTS_ANIMATION_DURATION = 500L
|
val GUTS_ANIMATION_DURATION = 500L
|
||||||
|
val controlIds = setOf(
|
||||||
|
R.id.media_progress_bar,
|
||||||
|
R.id.actionNext,
|
||||||
|
R.id.actionPrev,
|
||||||
|
R.id.action0,
|
||||||
|
R.id.action1,
|
||||||
|
R.id.action2,
|
||||||
|
R.id.action3,
|
||||||
|
R.id.action4,
|
||||||
|
R.id.media_scrubbing_elapsed_time,
|
||||||
|
R.id.media_scrubbing_total_time
|
||||||
|
)
|
||||||
|
|
||||||
|
val detailIds = setOf(
|
||||||
|
R.id.header_title,
|
||||||
|
R.id.header_artist,
|
||||||
|
R.id.actionPlayPause,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,6 +82,7 @@ class MediaViewController @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
lateinit var sizeChangedListener: () -> Unit
|
lateinit var sizeChangedListener: () -> Unit
|
||||||
private var firstRefresh: Boolean = true
|
private var firstRefresh: Boolean = true
|
||||||
|
@VisibleForTesting
|
||||||
private var transitionLayout: TransitionLayout? = null
|
private var transitionLayout: TransitionLayout? = null
|
||||||
private val layoutController = TransitionLayoutController()
|
private val layoutController = TransitionLayoutController()
|
||||||
private var animationDelay: Long = 0
|
private var animationDelay: Long = 0
|
||||||
@@ -278,11 +304,48 @@ class MediaViewController @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply squishFraction to a copy of viewState such that the cached version is untouched.
|
||||||
|
*/
|
||||||
|
internal fun squishViewState(
|
||||||
|
viewState: TransitionViewState,
|
||||||
|
squishFraction: Float
|
||||||
|
): TransitionViewState {
|
||||||
|
val squishedViewState = viewState.copy()
|
||||||
|
squishedViewState.height = (squishedViewState.height * squishFraction).toInt()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return squishedViewState
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtain a new viewState for a given media state. This usually returns a cached state, but if
|
* 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.
|
* it's not available, it will recreate one by measuring, which may be expensive.
|
||||||
*/
|
*/
|
||||||
private fun obtainViewState(state: MediaHostState?): TransitionViewState? {
|
@VisibleForTesting
|
||||||
|
fun obtainViewState(state: MediaHostState?): TransitionViewState? {
|
||||||
if (state == null || state.measurementInput == null) {
|
if (state == null || state.measurementInput == null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -291,41 +354,46 @@ class MediaViewController @Inject constructor(
|
|||||||
val viewState = viewStates[cacheKey]
|
val viewState = viewStates[cacheKey]
|
||||||
if (viewState != null) {
|
if (viewState != null) {
|
||||||
// we already have cached this measurement, let's continue
|
// we already have cached this measurement, let's continue
|
||||||
|
if (state.squishFraction <= 1f) {
|
||||||
|
return squishViewState(viewState, state.squishFraction)
|
||||||
|
}
|
||||||
return viewState
|
return viewState
|
||||||
}
|
}
|
||||||
// Copy the key since this might call recursively into it and we're using tmpKey
|
// Copy the key since this might call recursively into it and we're using tmpKey
|
||||||
cacheKey = cacheKey.copy()
|
cacheKey = cacheKey.copy()
|
||||||
val result: TransitionViewState?
|
val result: TransitionViewState?
|
||||||
|
|
||||||
if (transitionLayout != null) {
|
if (transitionLayout == null) {
|
||||||
// Let's create a new measurement
|
return null
|
||||||
if (state.expansion == 0.0f || state.expansion == 1.0f) {
|
}
|
||||||
result = transitionLayout!!.calculateViewState(
|
// Let's create a new measurement
|
||||||
state.measurementInput!!,
|
if (state.expansion == 0.0f || state.expansion == 1.0f) {
|
||||||
constraintSetForExpansion(state.expansion),
|
result = transitionLayout!!.calculateViewState(
|
||||||
TransitionViewState())
|
state.measurementInput!!,
|
||||||
|
constraintSetForExpansion(state.expansion),
|
||||||
|
TransitionViewState())
|
||||||
|
|
||||||
setGutsViewState(result)
|
setGutsViewState(result)
|
||||||
// We don't want to cache interpolated or null states as this could quickly fill up
|
// We don't want to cache interpolated or null states as this could quickly fill up
|
||||||
// our cache. We only cache the start and the end states since the interpolation
|
// our cache. We only cache the start and the end states since the interpolation
|
||||||
// is cheap
|
// is cheap
|
||||||
viewStates[cacheKey] = result
|
viewStates[cacheKey] = result
|
||||||
} else {
|
|
||||||
// This is an interpolated state
|
|
||||||
val startState = state.copy().also { it.expansion = 0.0f }
|
|
||||||
|
|
||||||
// Given that we have a measurement and a view, let's get (guaranteed) viewstates
|
|
||||||
// from the start and end state and interpolate them
|
|
||||||
val startViewState = obtainViewState(startState) as TransitionViewState
|
|
||||||
val endState = state.copy().also { it.expansion = 1.0f }
|
|
||||||
val endViewState = obtainViewState(endState) as TransitionViewState
|
|
||||||
result = layoutController.getInterpolatedState(
|
|
||||||
startViewState,
|
|
||||||
endViewState,
|
|
||||||
state.expansion)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
result = null
|
// This is an interpolated state
|
||||||
|
val startState = state.copy().also { it.expansion = 0.0f }
|
||||||
|
|
||||||
|
// Given that we have a measurement and a view, let's get (guaranteed) viewstates
|
||||||
|
// from the start and end state and interpolate them
|
||||||
|
val startViewState = obtainViewState(startState) as TransitionViewState
|
||||||
|
val endState = state.copy().also { it.expansion = 1.0f }
|
||||||
|
val endViewState = obtainViewState(endState) as TransitionViewState
|
||||||
|
result = layoutController.getInterpolatedState(
|
||||||
|
startViewState,
|
||||||
|
endViewState,
|
||||||
|
state.expansion)
|
||||||
|
}
|
||||||
|
if (state.squishFraction <= 1f) {
|
||||||
|
return squishViewState(result, state.squishFraction)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,5 +106,20 @@ class RecommendationViewHolder private constructor(itemView: View) {
|
|||||||
R.id.media_subtitle2,
|
R.id.media_subtitle2,
|
||||||
R.id.media_subtitle3
|
R.id.media_subtitle3
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val mediaTitlesAndSubtitlesIds = setOf(
|
||||||
|
R.id.media_title1,
|
||||||
|
R.id.media_title2,
|
||||||
|
R.id.media_title3,
|
||||||
|
R.id.media_subtitle1,
|
||||||
|
R.id.media_subtitle2,
|
||||||
|
R.id.media_subtitle3
|
||||||
|
)
|
||||||
|
|
||||||
|
val mediaContainersIds = setOf(
|
||||||
|
R.id.media_cover1_container,
|
||||||
|
R.id.media_cover2_container,
|
||||||
|
R.id.media_cover3_container
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -691,6 +691,15 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
|||||||
if (mQSAnimator != null) {
|
if (mQSAnimator != null) {
|
||||||
mQSAnimator.setPosition(expansion);
|
mQSAnimator.setPosition(expansion);
|
||||||
}
|
}
|
||||||
|
if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD
|
||||||
|
|| mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
|
||||||
|
// At beginning, state is 0 and will apply wrong squishiness to MediaHost in lockscreen
|
||||||
|
// and media player expect no change by squishiness in lock screen shade
|
||||||
|
mQsMediaHost.setSquishFraction(1.0F);
|
||||||
|
} else {
|
||||||
|
mQsMediaHost.setSquishFraction(mSquishinessFraction);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setAlphaAnimationProgress(float progress) {
|
private void setAlphaAnimationProgress(float progress) {
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ import com.android.systemui.SysuiTestCase
|
|||||||
import com.android.systemui.classifier.FalsingCollector
|
import com.android.systemui.classifier.FalsingCollector
|
||||||
import com.android.systemui.dagger.qualifiers.Main
|
import com.android.systemui.dagger.qualifiers.Main
|
||||||
import com.android.systemui.dump.DumpManager
|
import com.android.systemui.dump.DumpManager
|
||||||
|
import com.android.systemui.media.MediaCarouselController.Companion.ANIMATION_BASE_DURATION
|
||||||
|
import com.android.systemui.media.MediaCarouselController.Companion.DURATION
|
||||||
|
import com.android.systemui.media.MediaCarouselController.Companion.PAGINATION_DELAY
|
||||||
|
import com.android.systemui.media.MediaCarouselController.Companion.TRANSFORM_BEZIER
|
||||||
|
import com.android.systemui.media.MediaHierarchyManager.Companion.LOCATION_QS
|
||||||
import com.android.systemui.plugins.ActivityStarter
|
import com.android.systemui.plugins.ActivityStarter
|
||||||
import com.android.systemui.plugins.FalsingManager
|
import com.android.systemui.plugins.FalsingManager
|
||||||
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
|
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
|
||||||
@@ -398,4 +403,24 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
|||||||
// added to the end because it was active less recently.
|
// added to the end because it was active less recently.
|
||||||
assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
|
assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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)
|
||||||
|
whenever(mediaHostStatesManager.mediaHostStates)
|
||||||
|
.thenReturn(mutableMapOf(LOCATION_QS to mediaHostState))
|
||||||
|
whenever(mediaHostState.visible).thenReturn(true)
|
||||||
|
mediaCarouselController.currentEndLocation = LOCATION_QS
|
||||||
|
whenever(mediaHostState.squishFraction).thenReturn(paginationSquishMiddle)
|
||||||
|
mediaCarouselController.updatePageIndicatorAlpha()
|
||||||
|
assertEquals(mediaCarouselController.pageIndicator.alpha, 0.5F, delta)
|
||||||
|
|
||||||
|
whenever(mediaHostState.squishFraction).thenReturn(paginationSquishEnd)
|
||||||
|
mediaCarouselController.updatePageIndicatorAlpha()
|
||||||
|
assertEquals(mediaCarouselController.pageIndicator.alpha, 1.0F, delta)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.systemui.media
|
||||||
|
|
||||||
|
import android.testing.AndroidTestingRunner
|
||||||
|
import android.testing.TestableLooper
|
||||||
|
import android.view.View
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.media.MediaCarouselController.Companion.ANIMATION_BASE_DURATION
|
||||||
|
import com.android.systemui.media.MediaCarouselController.Companion.CONTROLS_DELAY
|
||||||
|
import com.android.systemui.media.MediaCarouselController.Companion.DETAILS_DELAY
|
||||||
|
import com.android.systemui.media.MediaCarouselController.Companion.DURATION
|
||||||
|
import com.android.systemui.media.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY
|
||||||
|
import com.android.systemui.media.MediaCarouselController.Companion.MEDIATITLES_DELAY
|
||||||
|
import com.android.systemui.media.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
|
||||||
|
import com.android.systemui.util.animation.WidgetState
|
||||||
|
import junit.framework.Assert.assertTrue
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.ArgumentMatchers.floatThat
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.verify
|
||||||
|
import org.mockito.Mockito.`when` as whenever
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@TestableLooper.RunWithLooper(setAsMainLooper = true)
|
||||||
|
@RunWith(AndroidTestingRunner::class)
|
||||||
|
class MediaViewControllerTest : SysuiTestCase() {
|
||||||
|
private val mediaHostStateHolder = MediaHost.MediaHostStateHolder()
|
||||||
|
private val mediaHostStatesManager = MediaHostStatesManager()
|
||||||
|
private val configurationController =
|
||||||
|
com.android.systemui.statusbar.phone.ConfigurationControllerImpl(context)
|
||||||
|
private var player = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0)
|
||||||
|
private var recommendation = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0)
|
||||||
|
@Mock lateinit var logger: MediaViewLogger
|
||||||
|
@Mock private lateinit var mockViewState: TransitionViewState
|
||||||
|
@Mock private lateinit var mockCopiedState: TransitionViewState
|
||||||
|
@Mock private lateinit var detailWidgetState: WidgetState
|
||||||
|
@Mock private lateinit var controlWidgetState: WidgetState
|
||||||
|
@Mock private lateinit var mediaTitleWidgetState: WidgetState
|
||||||
|
@Mock private lateinit var mediaContainerWidgetState: WidgetState
|
||||||
|
|
||||||
|
val delta = 0.0001F
|
||||||
|
|
||||||
|
private lateinit var mediaViewController: MediaViewController
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
mediaViewController =
|
||||||
|
MediaViewController(context, configurationController, mediaHostStatesManager, logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testObtainViewState_applySquishFraction_toPlayerTransitionViewState_height() {
|
||||||
|
mediaViewController.attach(player, MediaViewController.TYPE.PLAYER)
|
||||||
|
player.measureState = TransitionViewState().apply { this.height = 100 }
|
||||||
|
mediaHostStateHolder.expansion = 1f
|
||||||
|
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
|
||||||
|
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
|
||||||
|
mediaHostStateHolder.measurementInput =
|
||||||
|
MeasurementInput(widthMeasureSpec, heightMeasureSpec)
|
||||||
|
|
||||||
|
// Test no squish
|
||||||
|
mediaHostStateHolder.squishFraction = 1f
|
||||||
|
assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 100)
|
||||||
|
|
||||||
|
// Test half squish
|
||||||
|
mediaHostStateHolder.squishFraction = 0.5f
|
||||||
|
assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 50)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testObtainViewState_applySquishFraction_toRecommendationTransitionViewState_height() {
|
||||||
|
mediaViewController.attach(recommendation, MediaViewController.TYPE.RECOMMENDATION)
|
||||||
|
recommendation.measureState = TransitionViewState().apply { this.height = 100 }
|
||||||
|
mediaHostStateHolder.expansion = 1f
|
||||||
|
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
|
||||||
|
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
|
||||||
|
mediaHostStateHolder.measurementInput =
|
||||||
|
MeasurementInput(widthMeasureSpec, heightMeasureSpec)
|
||||||
|
|
||||||
|
// Test no squish
|
||||||
|
mediaHostStateHolder.squishFraction = 1f
|
||||||
|
assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 100)
|
||||||
|
|
||||||
|
// Test half squish
|
||||||
|
mediaHostStateHolder.squishFraction = 0.5f
|
||||||
|
assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 50)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_forMediaPlayer() {
|
||||||
|
whenever(mockViewState.copy()).thenReturn(mockCopiedState)
|
||||||
|
whenever(mockCopiedState.widgetStates)
|
||||||
|
.thenReturn(
|
||||||
|
mutableMapOf(
|
||||||
|
R.id.media_progress_bar to controlWidgetState,
|
||||||
|
R.id.header_artist to detailWidgetState
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val detailSquishMiddle =
|
||||||
|
TRANSFORM_BEZIER.getInterpolation(
|
||||||
|
(DETAILS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
|
||||||
|
)
|
||||||
|
mediaViewController.squishViewState(mockViewState, detailSquishMiddle)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_forRecommendation() {
|
||||||
|
whenever(mockViewState.copy()).thenReturn(mockCopiedState)
|
||||||
|
whenever(mockCopiedState.widgetStates)
|
||||||
|
.thenReturn(
|
||||||
|
mutableMapOf(
|
||||||
|
R.id.media_title1 to mediaTitleWidgetState,
|
||||||
|
R.id.media_cover1_container to mediaContainerWidgetState
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val containerSquishMiddle =
|
||||||
|
TRANSFORM_BEZIER.getInterpolation(
|
||||||
|
(MEDIACONTAINERS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
|
||||||
|
)
|
||||||
|
mediaViewController.squishViewState(mockViewState, containerSquishMiddle)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user