Loading packages/SystemUI/res/drawable/bg_smartspace_media_item.xml +1 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,6 @@ --> <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" /> </shape> No newline at end of file packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +34 −4 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ import android.util.MathUtils import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.animation.PathInterpolator import android.widget.LinearLayout import androidx.annotation.VisibleForTesting import com.android.internal.logging.InstanceId Loading Loading @@ -95,7 +96,8 @@ class MediaCarouselController @Inject constructor( * finished */ @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 Loading Loading @@ -126,7 +128,8 @@ class MediaCarouselController @Inject constructor( lateinit var settingsButton: View private set private val mediaContent: ViewGroup private val pageIndicator: PageIndicator @VisibleForTesting val pageIndicator: PageIndicator private val visualStabilityCallback: OnReorderingAllowedListener private var needsReordering: Boolean = false private var keysNeedRemoval = mutableSetOf<String>() Loading @@ -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 { override fun onDensityOrFontScaleChanged() { // System font changes should only happen when UMO is offscreen or a flicker may occur Loading Loading @@ -633,12 +657,17 @@ class MediaCarouselController @Inject constructor( } } private fun updatePageIndicatorAlpha() { @VisibleForTesting fun updatePageIndicatorAlpha() { val hostStates = mediaHostStatesManager.mediaHostStates val endIsVisible = hostStates[currentEndLocation]?.visible ?: false val startIsVisible = hostStates[currentStartLocation]?.visible ?: false 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 if (!endIsVisible || !startIsVisible) { var progress = currentTransitionProgress Loading Loading @@ -687,6 +716,7 @@ class MediaCarouselController @Inject constructor( mediaCarouselScrollHandler.setCarouselBounds( currentCarouselWidth, currentCarouselHeight) updatePageIndicatorLocation() updatePageIndicatorAlpha() } } Loading packages/SystemUI/src/com/android/systemui/media/MediaHost.kt +18 −0 Original line number Diff line number Diff line Loading @@ -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 set(value) { if (!value.equals(field)) { Loading Loading @@ -253,6 +261,7 @@ class MediaHost constructor( override fun copy(): MediaHostState { val mediaHostState = MediaHostStateHolder() mediaHostState.expansion = expansion mediaHostState.squishFraction = squishFraction mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia mediaHostState.measurementInput = measurementInput?.copy() mediaHostState.visible = visible Loading @@ -271,6 +280,9 @@ class MediaHost constructor( if (expansion != other.expansion) { return false } if (squishFraction != other.squishFraction) { return false } if (showsOnlyActiveMedia != other.showsOnlyActiveMedia) { return false } Loading @@ -289,6 +301,7 @@ class MediaHost constructor( override fun hashCode(): Int { var result = measurementInput?.hashCode() ?: 0 result = 31 * result + expansion.hashCode() result = 31 * result + squishFraction.hashCode() result = 31 * result + falsingProtectionNeeded.hashCode() result = 31 * result + showsOnlyActiveMedia.hashCode() result = 31 * result + if (visible) 1 else 2 Loading Loading @@ -328,6 +341,11 @@ interface MediaHostState { */ 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? */ Loading packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt +97 −29 Original line number Diff line number Diff line Loading @@ -18,8 +18,15 @@ package com.android.systemui.media import android.content.Context import android.content.res.Configuration import androidx.annotation.VisibleForTesting import androidx.constraintlayout.widget.ConstraintSet 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.util.animation.MeasurementOutput import com.android.systemui.util.animation.TransitionLayout Loading Loading @@ -50,6 +57,24 @@ class MediaViewController @Inject constructor( companion object { @JvmField 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, ) } /** Loading @@ -57,6 +82,7 @@ class MediaViewController @Inject constructor( */ lateinit var sizeChangedListener: () -> Unit private var firstRefresh: Boolean = true @VisibleForTesting private var transitionLayout: TransitionLayout? = null private val layoutController = TransitionLayoutController() private var animationDelay: Long = 0 Loading Loading @@ -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 * 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) { return null } Loading @@ -291,13 +354,18 @@ class MediaViewController @Inject constructor( val viewState = viewStates[cacheKey] if (viewState != null) { // we already have cached this measurement, let's continue if (state.squishFraction <= 1f) { return squishViewState(viewState, state.squishFraction) } return viewState } // Copy the key since this might call recursively into it and we're using tmpKey cacheKey = cacheKey.copy() val result: TransitionViewState? if (transitionLayout != null) { if (transitionLayout == null) { return null } // Let's create a new measurement if (state.expansion == 0.0f || state.expansion == 1.0f) { result = transitionLayout!!.calculateViewState( Loading @@ -324,8 +392,8 @@ class MediaViewController @Inject constructor( endViewState, state.expansion) } } else { result = null if (state.squishFraction <= 1f) { return squishViewState(result, state.squishFraction) } return result } Loading packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt +15 −0 Original line number Diff line number Diff line Loading @@ -106,5 +106,20 @@ class RecommendationViewHolder private constructor(itemView: View) { R.id.media_subtitle2, 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 ) } } Loading
packages/SystemUI/res/drawable/bg_smartspace_media_item.xml +1 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,6 @@ --> <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" /> </shape> No newline at end of file
packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +34 −4 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ import android.util.MathUtils import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.animation.PathInterpolator import android.widget.LinearLayout import androidx.annotation.VisibleForTesting import com.android.internal.logging.InstanceId Loading Loading @@ -95,7 +96,8 @@ class MediaCarouselController @Inject constructor( * finished */ @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 Loading Loading @@ -126,7 +128,8 @@ class MediaCarouselController @Inject constructor( lateinit var settingsButton: View private set private val mediaContent: ViewGroup private val pageIndicator: PageIndicator @VisibleForTesting val pageIndicator: PageIndicator private val visualStabilityCallback: OnReorderingAllowedListener private var needsReordering: Boolean = false private var keysNeedRemoval = mutableSetOf<String>() Loading @@ -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 { override fun onDensityOrFontScaleChanged() { // System font changes should only happen when UMO is offscreen or a flicker may occur Loading Loading @@ -633,12 +657,17 @@ class MediaCarouselController @Inject constructor( } } private fun updatePageIndicatorAlpha() { @VisibleForTesting fun updatePageIndicatorAlpha() { val hostStates = mediaHostStatesManager.mediaHostStates val endIsVisible = hostStates[currentEndLocation]?.visible ?: false val startIsVisible = hostStates[currentStartLocation]?.visible ?: false 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 if (!endIsVisible || !startIsVisible) { var progress = currentTransitionProgress Loading Loading @@ -687,6 +716,7 @@ class MediaCarouselController @Inject constructor( mediaCarouselScrollHandler.setCarouselBounds( currentCarouselWidth, currentCarouselHeight) updatePageIndicatorLocation() updatePageIndicatorAlpha() } } Loading
packages/SystemUI/src/com/android/systemui/media/MediaHost.kt +18 −0 Original line number Diff line number Diff line Loading @@ -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 set(value) { if (!value.equals(field)) { Loading Loading @@ -253,6 +261,7 @@ class MediaHost constructor( override fun copy(): MediaHostState { val mediaHostState = MediaHostStateHolder() mediaHostState.expansion = expansion mediaHostState.squishFraction = squishFraction mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia mediaHostState.measurementInput = measurementInput?.copy() mediaHostState.visible = visible Loading @@ -271,6 +280,9 @@ class MediaHost constructor( if (expansion != other.expansion) { return false } if (squishFraction != other.squishFraction) { return false } if (showsOnlyActiveMedia != other.showsOnlyActiveMedia) { return false } Loading @@ -289,6 +301,7 @@ class MediaHost constructor( override fun hashCode(): Int { var result = measurementInput?.hashCode() ?: 0 result = 31 * result + expansion.hashCode() result = 31 * result + squishFraction.hashCode() result = 31 * result + falsingProtectionNeeded.hashCode() result = 31 * result + showsOnlyActiveMedia.hashCode() result = 31 * result + if (visible) 1 else 2 Loading Loading @@ -328,6 +341,11 @@ interface MediaHostState { */ 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? */ Loading
packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt +97 −29 Original line number Diff line number Diff line Loading @@ -18,8 +18,15 @@ package com.android.systemui.media import android.content.Context import android.content.res.Configuration import androidx.annotation.VisibleForTesting import androidx.constraintlayout.widget.ConstraintSet 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.util.animation.MeasurementOutput import com.android.systemui.util.animation.TransitionLayout Loading Loading @@ -50,6 +57,24 @@ class MediaViewController @Inject constructor( companion object { @JvmField 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, ) } /** Loading @@ -57,6 +82,7 @@ class MediaViewController @Inject constructor( */ lateinit var sizeChangedListener: () -> Unit private var firstRefresh: Boolean = true @VisibleForTesting private var transitionLayout: TransitionLayout? = null private val layoutController = TransitionLayoutController() private var animationDelay: Long = 0 Loading Loading @@ -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 * 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) { return null } Loading @@ -291,13 +354,18 @@ class MediaViewController @Inject constructor( val viewState = viewStates[cacheKey] if (viewState != null) { // we already have cached this measurement, let's continue if (state.squishFraction <= 1f) { return squishViewState(viewState, state.squishFraction) } return viewState } // Copy the key since this might call recursively into it and we're using tmpKey cacheKey = cacheKey.copy() val result: TransitionViewState? if (transitionLayout != null) { if (transitionLayout == null) { return null } // Let's create a new measurement if (state.expansion == 0.0f || state.expansion == 1.0f) { result = transitionLayout!!.calculateViewState( Loading @@ -324,8 +392,8 @@ class MediaViewController @Inject constructor( endViewState, state.expansion) } } else { result = null if (state.squishFraction <= 1f) { return squishViewState(result, state.squishFraction) } return result } Loading
packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt +15 −0 Original line number Diff line number Diff line Loading @@ -106,5 +106,20 @@ class RecommendationViewHolder private constructor(itemView: View) { R.id.media_subtitle2, 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 ) } }