Loading packages/SystemUI/res/layout/media_carousel.xml +1 −0 Original line number Diff line number Diff line Loading @@ -48,5 +48,6 @@ android:layout_height="48dp" android:layout_marginBottom="4dp" android:tint="@color/media_primary_text" android:forceHasOverlappingRendering="false" /> </FrameLayout> packages/SystemUI/res/layout/media_view.xml +1 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ android:clipChildren="false" android:clipToPadding="false" android:gravity="center_horizontal|fill_vertical" android:forceHasOverlappingRendering="false" android:background="@drawable/qs_media_background"> <!-- As per Material Design on Biderectionality, this is forced to LTR in code --> Loading packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +28 −2 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ import android.content.Intent import android.content.res.Configuration import android.graphics.Color import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS import android.util.MathUtils import android.view.LayoutInflater import android.view.View import android.view.ViewGroup Loading Loading @@ -328,9 +329,32 @@ class MediaCarouselController @Inject constructor( updatePlayerToState(mediaPlayer, immediately) } maybeResetSettingsCog() updatePageIndicatorAlpha() } } private 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 var alpha = 1.0f if (!endIsVisible || !startIsVisible) { var progress = currentTransitionProgress if (!endIsVisible) { progress = 1.0f - progress } // Let's fade in quickly at the end where the view is visible progress = MathUtils.constrain( MathUtils.map(0.95f, 1.0f, 0.0f, 1.0f, progress), 0.0f, 1.0f) alpha = MathUtils.lerp(startAlpha, endAlpha, progress) } pageIndicator.alpha = alpha } private fun updatePageIndicatorLocation() { // Update the location of the page indicator, carousel clipping val translationX = if (isRtl) { Loading @@ -352,8 +376,10 @@ class MediaCarouselController @Inject constructor( var height = 0 for (mediaPlayer in mediaPlayers.values) { val controller = mediaPlayer.mediaViewController width = Math.max(width, controller.currentWidth) height = Math.max(height, controller.currentHeight) // When transitioning the view to gone, the view gets smaller, but the translation // Doesn't, let's add the translation width = Math.max(width, controller.currentWidth + controller.translationX.toInt()) height = Math.max(height, controller.currentHeight + controller.translationY.toInt()) } if (width != currentCarouselWidth || height != currentCarouselHeight) { currentCarouselWidth = width Loading packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +23 −11 Original line number Diff line number Diff line Loading @@ -329,7 +329,7 @@ class MediaHierarchyManager @Inject constructor( applyTargetStateIfNotAnimating() } else if (animate) { animator.cancel() if (currentAttachmentLocation == IN_OVERLAY || if (currentAttachmentLocation != previousLocation || !previousHost.hostView.isAttachedToWindow) { // Let's animate to the new position, starting from the current position // We also go in here in case the view was detached, since the bounds wouldn't Loading @@ -341,11 +341,13 @@ class MediaHierarchyManager @Inject constructor( animationStartBounds.set(previousHost.currentBounds) } adjustAnimatorForTransition(desiredLocation, previousLocation) if (!animationPending) { rootView?.let { // Let's delay the animation start until we finished laying out animationPending = true it.postOnAnimation(startAnimation) } } } else { cancelAnimationAndApplyDesiredState() } Loading Loading @@ -403,15 +405,23 @@ class MediaHierarchyManager @Inject constructor( } /** * Updates the state that the view wants to be in at the end of the animation. * Updates the bounds that the view wants to be in at the end of the animation. */ private fun updateTargetState() { if (isCurrentlyInGuidedTransformation()) { val progress = getTransformationProgress() val currentHost = getHost(desiredLocation)!! val previousHost = getHost(previousLocation)!! val newBounds = currentHost.currentBounds val previousBounds = previousHost.currentBounds var endHost = getHost(desiredLocation)!! var starthost = getHost(previousLocation)!! // If either of the hosts are invisible, let's keep them at the other host location to // have a nicer disappear animation. Otherwise the currentBounds of the state might // be undefined if (!endHost.visible) { endHost = starthost } else if (!starthost.visible) { starthost = endHost } val newBounds = endHost.currentBounds val previousBounds = starthost.currentBounds targetBounds = interpolateBounds(previousBounds, newBounds, progress) } else { val bounds = getHost(desiredLocation)?.currentBounds ?: return Loading Loading @@ -462,9 +472,11 @@ class MediaHierarchyManager @Inject constructor( val previousHost = getHost(previousLocation) if (currentHost?.location == LOCATION_QS) { if (previousHost?.location == LOCATION_QQS) { if (previousHost.visible || statusbarState != StatusBarState.KEYGUARD) { return qsExpansion } } } return -1.0f } Loading packages/SystemUI/src/com/android/systemui/media/MediaHost.kt +30 −29 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ import android.graphics.Rect import android.util.ArraySet import android.view.View import android.view.View.OnAttachStateChangeListener import com.android.systemui.util.animation.DisappearParameters import com.android.systemui.util.animation.MeasurementInput import com.android.systemui.util.animation.MeasurementOutput import com.android.systemui.util.animation.UniqueObjectHostView Loading Loading @@ -128,8 +129,6 @@ class MediaHost @Inject constructor( } class MediaHostStateHolder @Inject constructor() : MediaHostState { private var gonePivot: PointF = PointF() override var measurementInput: MeasurementInput? = null set(value) { if (value?.equals(field) != true) { Loading Loading @@ -172,16 +171,19 @@ class MediaHost @Inject constructor( changedListener?.invoke() } override fun getPivotX(): Float = gonePivot.x override fun getPivotY(): Float = gonePivot.y override fun setGonePivot(x: Float, y: Float) { if (gonePivot.equals(x, y)) { override var disappearParameters: DisappearParameters = DisappearParameters() set(value) { val newHash = value.hashCode() if (lastDisappearHash.equals(newHash)) { return } gonePivot.set(x, y) field = value lastDisappearHash = newHash changedListener?.invoke() } private var lastDisappearHash = disappearParameters.hashCode() /** * A listener for all changes. This won't be copied over when invoking [copy] */ Loading @@ -196,7 +198,7 @@ class MediaHost @Inject constructor( mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia mediaHostState.measurementInput = measurementInput?.copy() mediaHostState.visible = visible mediaHostState.gonePivot.set(gonePivot) mediaHostState.disappearParameters = disappearParameters.deepCopy() mediaHostState.falsingProtectionNeeded = falsingProtectionNeeded return mediaHostState } Loading @@ -220,7 +222,7 @@ class MediaHost @Inject constructor( if (falsingProtectionNeeded != other.falsingProtectionNeeded) { return false } if (!gonePivot.equals(other.getPivotX(), other.getPivotY())) { if (!disappearParameters.equals(other.disappearParameters)) { return false } return true Loading @@ -232,12 +234,23 @@ class MediaHost @Inject constructor( result = 31 * result + falsingProtectionNeeded.hashCode() result = 31 * result + showsOnlyActiveMedia.hashCode() result = 31 * result + if (visible) 1 else 2 result = 31 * result + gonePivot.hashCode() result = 31 * result + disappearParameters.hashCode() return result } } } /** * A description of a media host state that describes the behavior whenever the media carousel * is hosted. The HostState notifies the media players of changes to their properties, who * in turn will create view states from it. * When adding a new property to this, make sure to update the listener and notify them * about the changes. * In case you need to have a different rendering based on the state, you can add a new * constraintState to the [MediaViewController]. Otherwise, similar host states will resolve * to the same viewstate, a behavior that is described in [CacheKey]. Make sure to only update * that key if the underlying view needs to have a different measurement. */ interface MediaHostState { /** Loading Loading @@ -268,23 +281,11 @@ interface MediaHostState { var falsingProtectionNeeded: Boolean /** * Sets the pivot point when clipping the height or width. * Clipping happens when animating visibility when we're visible in QS but not on QQS, * for example. */ fun setGonePivot(x: Float, y: Float) /** * x position of pivot, from 0 to 1 * @see [setGonePivot] */ fun getPivotX(): Float /** * y position of pivot, from 0 to 1 * @see [setGonePivot] * The parameters how the view disappears from this location when going to a host that's not * visible. If modified, make sure to set this value again on the host to ensure the values * are propagated */ fun getPivotY(): Float var disappearParameters: DisappearParameters /** * Get a copy of this view state, deepcopying all appropriate members Loading Loading
packages/SystemUI/res/layout/media_carousel.xml +1 −0 Original line number Diff line number Diff line Loading @@ -48,5 +48,6 @@ android:layout_height="48dp" android:layout_marginBottom="4dp" android:tint="@color/media_primary_text" android:forceHasOverlappingRendering="false" /> </FrameLayout>
packages/SystemUI/res/layout/media_view.xml +1 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ android:clipChildren="false" android:clipToPadding="false" android:gravity="center_horizontal|fill_vertical" android:forceHasOverlappingRendering="false" android:background="@drawable/qs_media_background"> <!-- As per Material Design on Biderectionality, this is forced to LTR in code --> Loading
packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +28 −2 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ import android.content.Intent import android.content.res.Configuration import android.graphics.Color import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS import android.util.MathUtils import android.view.LayoutInflater import android.view.View import android.view.ViewGroup Loading Loading @@ -328,9 +329,32 @@ class MediaCarouselController @Inject constructor( updatePlayerToState(mediaPlayer, immediately) } maybeResetSettingsCog() updatePageIndicatorAlpha() } } private 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 var alpha = 1.0f if (!endIsVisible || !startIsVisible) { var progress = currentTransitionProgress if (!endIsVisible) { progress = 1.0f - progress } // Let's fade in quickly at the end where the view is visible progress = MathUtils.constrain( MathUtils.map(0.95f, 1.0f, 0.0f, 1.0f, progress), 0.0f, 1.0f) alpha = MathUtils.lerp(startAlpha, endAlpha, progress) } pageIndicator.alpha = alpha } private fun updatePageIndicatorLocation() { // Update the location of the page indicator, carousel clipping val translationX = if (isRtl) { Loading @@ -352,8 +376,10 @@ class MediaCarouselController @Inject constructor( var height = 0 for (mediaPlayer in mediaPlayers.values) { val controller = mediaPlayer.mediaViewController width = Math.max(width, controller.currentWidth) height = Math.max(height, controller.currentHeight) // When transitioning the view to gone, the view gets smaller, but the translation // Doesn't, let's add the translation width = Math.max(width, controller.currentWidth + controller.translationX.toInt()) height = Math.max(height, controller.currentHeight + controller.translationY.toInt()) } if (width != currentCarouselWidth || height != currentCarouselHeight) { currentCarouselWidth = width Loading
packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +23 −11 Original line number Diff line number Diff line Loading @@ -329,7 +329,7 @@ class MediaHierarchyManager @Inject constructor( applyTargetStateIfNotAnimating() } else if (animate) { animator.cancel() if (currentAttachmentLocation == IN_OVERLAY || if (currentAttachmentLocation != previousLocation || !previousHost.hostView.isAttachedToWindow) { // Let's animate to the new position, starting from the current position // We also go in here in case the view was detached, since the bounds wouldn't Loading @@ -341,11 +341,13 @@ class MediaHierarchyManager @Inject constructor( animationStartBounds.set(previousHost.currentBounds) } adjustAnimatorForTransition(desiredLocation, previousLocation) if (!animationPending) { rootView?.let { // Let's delay the animation start until we finished laying out animationPending = true it.postOnAnimation(startAnimation) } } } else { cancelAnimationAndApplyDesiredState() } Loading Loading @@ -403,15 +405,23 @@ class MediaHierarchyManager @Inject constructor( } /** * Updates the state that the view wants to be in at the end of the animation. * Updates the bounds that the view wants to be in at the end of the animation. */ private fun updateTargetState() { if (isCurrentlyInGuidedTransformation()) { val progress = getTransformationProgress() val currentHost = getHost(desiredLocation)!! val previousHost = getHost(previousLocation)!! val newBounds = currentHost.currentBounds val previousBounds = previousHost.currentBounds var endHost = getHost(desiredLocation)!! var starthost = getHost(previousLocation)!! // If either of the hosts are invisible, let's keep them at the other host location to // have a nicer disappear animation. Otherwise the currentBounds of the state might // be undefined if (!endHost.visible) { endHost = starthost } else if (!starthost.visible) { starthost = endHost } val newBounds = endHost.currentBounds val previousBounds = starthost.currentBounds targetBounds = interpolateBounds(previousBounds, newBounds, progress) } else { val bounds = getHost(desiredLocation)?.currentBounds ?: return Loading Loading @@ -462,9 +472,11 @@ class MediaHierarchyManager @Inject constructor( val previousHost = getHost(previousLocation) if (currentHost?.location == LOCATION_QS) { if (previousHost?.location == LOCATION_QQS) { if (previousHost.visible || statusbarState != StatusBarState.KEYGUARD) { return qsExpansion } } } return -1.0f } Loading
packages/SystemUI/src/com/android/systemui/media/MediaHost.kt +30 −29 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ import android.graphics.Rect import android.util.ArraySet import android.view.View import android.view.View.OnAttachStateChangeListener import com.android.systemui.util.animation.DisappearParameters import com.android.systemui.util.animation.MeasurementInput import com.android.systemui.util.animation.MeasurementOutput import com.android.systemui.util.animation.UniqueObjectHostView Loading Loading @@ -128,8 +129,6 @@ class MediaHost @Inject constructor( } class MediaHostStateHolder @Inject constructor() : MediaHostState { private var gonePivot: PointF = PointF() override var measurementInput: MeasurementInput? = null set(value) { if (value?.equals(field) != true) { Loading Loading @@ -172,16 +171,19 @@ class MediaHost @Inject constructor( changedListener?.invoke() } override fun getPivotX(): Float = gonePivot.x override fun getPivotY(): Float = gonePivot.y override fun setGonePivot(x: Float, y: Float) { if (gonePivot.equals(x, y)) { override var disappearParameters: DisappearParameters = DisappearParameters() set(value) { val newHash = value.hashCode() if (lastDisappearHash.equals(newHash)) { return } gonePivot.set(x, y) field = value lastDisappearHash = newHash changedListener?.invoke() } private var lastDisappearHash = disappearParameters.hashCode() /** * A listener for all changes. This won't be copied over when invoking [copy] */ Loading @@ -196,7 +198,7 @@ class MediaHost @Inject constructor( mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia mediaHostState.measurementInput = measurementInput?.copy() mediaHostState.visible = visible mediaHostState.gonePivot.set(gonePivot) mediaHostState.disappearParameters = disappearParameters.deepCopy() mediaHostState.falsingProtectionNeeded = falsingProtectionNeeded return mediaHostState } Loading @@ -220,7 +222,7 @@ class MediaHost @Inject constructor( if (falsingProtectionNeeded != other.falsingProtectionNeeded) { return false } if (!gonePivot.equals(other.getPivotX(), other.getPivotY())) { if (!disappearParameters.equals(other.disappearParameters)) { return false } return true Loading @@ -232,12 +234,23 @@ class MediaHost @Inject constructor( result = 31 * result + falsingProtectionNeeded.hashCode() result = 31 * result + showsOnlyActiveMedia.hashCode() result = 31 * result + if (visible) 1 else 2 result = 31 * result + gonePivot.hashCode() result = 31 * result + disappearParameters.hashCode() return result } } } /** * A description of a media host state that describes the behavior whenever the media carousel * is hosted. The HostState notifies the media players of changes to their properties, who * in turn will create view states from it. * When adding a new property to this, make sure to update the listener and notify them * about the changes. * In case you need to have a different rendering based on the state, you can add a new * constraintState to the [MediaViewController]. Otherwise, similar host states will resolve * to the same viewstate, a behavior that is described in [CacheKey]. Make sure to only update * that key if the underlying view needs to have a different measurement. */ interface MediaHostState { /** Loading Loading @@ -268,23 +281,11 @@ interface MediaHostState { var falsingProtectionNeeded: Boolean /** * Sets the pivot point when clipping the height or width. * Clipping happens when animating visibility when we're visible in QS but not on QQS, * for example. */ fun setGonePivot(x: Float, y: Float) /** * x position of pivot, from 0 to 1 * @see [setGonePivot] */ fun getPivotX(): Float /** * y position of pivot, from 0 to 1 * @see [setGonePivot] * The parameters how the view disappears from this location when going to a host that's not * visible. If modified, make sure to set this value again on the host to ensure the values * are propagated */ fun getPivotY(): Float var disappearParameters: DisappearParameters /** * Get a copy of this view state, deepcopying all appropriate members Loading