summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Selim Cinek <cinek@google.com> 2021-05-26 16:10:07 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2021-05-26 16:10:07 +0000
commit7ddf76bedc8c5b7bac0a9d68b779590382875a6f (patch)
treec64c170a6f1422acc84c60645dccfec729c39ad3
parentf7e815dd30292b9370f44201253abbd064f59332 (diff)
parent67563f5ce01fb0ad6a409caed0180e14d6d4620b (diff)
Merge "Transitioning media on lockscreen with a fade" into sc-dev
-rw-r--r--packages/SystemUI/res/layout/media_carousel.xml1
-rw-r--r--packages/SystemUI/res/values/dimens.xml15
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt327
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java49
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt77
9 files changed, 426 insertions, 98 deletions
diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml
index 87acfd088939..52132e881c43 100644
--- a/packages/SystemUI/res/layout/media_carousel.xml
+++ b/packages/SystemUI/res/layout/media_carousel.xml
@@ -22,6 +22,7 @@
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
+ android:forceHasOverlappingRendering="false"
android:theme="@style/MediaPlayer">
<com.android.systemui.media.MediaScrollView
android:id="@+id/media_carousel_scroller"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b2ab5f782d71..5c1e9355b650 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1418,10 +1418,6 @@
<dimen name="media_output_dialog_icon_corner_radius">16dp</dimen>
<dimen name="media_output_dialog_title_anim_y_delta">12.5dp</dimen>
- <!-- Delay after which the media will start transitioning to the full shade on
- the lockscreen -->
- <dimen name="lockscreen_shade_media_transition_start_delay">40dp</dimen>
-
<!-- Distance that the full shade transition takes in order for qs to fully transition to the
shade -->
<dimen name="lockscreen_shade_qs_transition_distance">200dp</dimen>
@@ -1430,13 +1426,16 @@
the shade (in alpha) -->
<dimen name="lockscreen_shade_scrim_transition_distance">80dp</dimen>
- <!-- Extra inset for the notifications when accounting for media during the lockscreen to
- shade transition to compensate for the disappearing media -->
- <dimen name="lockscreen_shade_transition_extra_media_inset">-48dp</dimen>
+ <!-- Distance that the full shade transition takes in order for media to fully transition to
+ the shade -->
+ <dimen name="lockscreen_shade_media_transition_distance">140dp</dimen>
<!-- Maximum overshoot for the topPadding of notifications when transitioning to the full
shade -->
- <dimen name="lockscreen_shade_max_top_overshoot">32dp</dimen>
+ <dimen name="lockscreen_shade_notification_movement">24dp</dimen>
+
+ <!-- Maximum overshoot for the pulse expansion -->
+ <dimen name="pulse_expansion_max_top_overshoot">16dp</dimen>
<dimen name="people_space_widget_radius">28dp</dimen>
<dimen name="people_space_image_radius">20dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 73dfe5e68d9a..075bc700cfa0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -26,21 +26,23 @@ import android.util.MathUtils
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroupOverlay
+import androidx.annotation.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.animation.UniqueObjectHostView
import javax.inject.Inject
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
/**
* Similarly to isShown but also excludes views that have 0 alpha
@@ -80,6 +82,7 @@ class MediaHierarchyManager @Inject constructor(
wakefulnessLifecycle: WakefulnessLifecycle,
private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager
) {
+
/**
* The root overlay of the hierarchy. This is where the media notification is attached to
* whenever the view is transitioning from one host to another. It also make sure that the
@@ -90,6 +93,30 @@ class MediaHierarchyManager @Inject constructor(
private var rootView: View? = null
private var currentBounds = Rect()
private var animationStartBounds: Rect = Rect()
+
+ /**
+ * The cross fade progress at the start of the animation. 0.5f means it's just switching between
+ * the start and the end location and the content is fully faded, while 0.75f means that we're
+ * halfway faded in again in the target state.
+ */
+ private var animationStartCrossFadeProgress = 0.0f
+
+ /**
+ * The starting alpha of the animation
+ */
+ private var animationStartAlpha = 0.0f
+
+ /**
+ * The starting location of the cross fade if an animation is running right now.
+ */
+ @MediaLocation
+ private var crossFadeAnimationStartLocation = -1
+
+ /**
+ * The end location of the cross fade if an animation is running right now.
+ */
+ @MediaLocation
+ private var crossFadeAnimationEndLocation = -1
private var targetBounds: Rect = Rect()
private val mediaFrame
get() = mediaCarouselController.mediaFrame
@@ -98,9 +125,22 @@ class MediaHierarchyManager @Inject constructor(
interpolator = Interpolators.FAST_OUT_SLOW_IN
addUpdateListener {
updateTargetState()
- interpolateBounds(animationStartBounds, targetBounds, animatedFraction,
+ val currentAlpha: Float
+ var boundsProgress = animatedFraction
+ if (isCrossFadeAnimatorRunning) {
+ animationCrossFadeProgress = MathUtils.lerp(animationStartCrossFadeProgress, 1.0f,
+ animatedFraction)
+ // When crossfading, let's keep the bounds at the right location during fading
+ boundsProgress = if (animationCrossFadeProgress < 0.5f) 0.0f else 1.0f
+ currentAlpha = calculateAlphaFromCrossFade(animationCrossFadeProgress,
+ instantlyShowAtEnd = false)
+ } else {
+ // If we're not crossfading, let's interpolate from the start alpha to 1.0f
+ currentAlpha = MathUtils.lerp(animationStartAlpha, 1.0f, animatedFraction)
+ }
+ interpolateBounds(animationStartBounds, targetBounds, boundsProgress,
result = currentBounds)
- applyState(currentBounds)
+ applyState(currentBounds, currentAlpha)
}
addListener(object : AnimatorListenerAdapter() {
private var cancelled: Boolean = false
@@ -112,6 +152,7 @@ class MediaHierarchyManager @Inject constructor(
}
override fun onAnimationEnd(animation: Animator?) {
+ isCrossFadeAnimatorRunning = false
if (!cancelled) {
applyTargetStateIfNotAnimating()
}
@@ -192,11 +233,6 @@ class MediaHierarchyManager @Inject constructor(
private var distanceForFullShadeTransition = 0
/**
- * Delay after which the media will start transitioning to the full shade on the lockscreen.
- */
- private var fullShadeTransitionDelay = 0
-
- /**
* The amount of progress we are currently in if we're transitioning to the full shade.
* 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
* shade.
@@ -207,18 +243,33 @@ class MediaHierarchyManager @Inject constructor(
return
}
field = value
- if (bypassController.bypassEnabled) {
+ if (bypassController.bypassEnabled || statusbarState != StatusBarState.KEYGUARD) {
+ // No need to do all the calculations / updates below if we're not on the lockscreen
+ // or if we're bypassing.
return
}
- updateDesiredLocation()
+ updateDesiredLocation(forceNoAnimation = isCurrentlyFading())
if (value >= 0) {
updateTargetState()
+ // Setting the alpha directly, as the below call will use it to update the alpha
+ carouselAlpha = calculateAlphaFromCrossFade(field, instantlyShowAtEnd = true)
applyTargetStateIfNotAnimating()
}
}
+ /**
+ * Is there currently a cross-fade animation running driven by an animator?
+ */
+ private var isCrossFadeAnimatorRunning = false
+
+ /**
+ * Are we currently transitionioning from the lockscreen to the full shade
+ * [StatusBarState.SHADE_LOCKED] or [StatusBarState.SHADE]. Once the user has dragged down and
+ * the transition starts, this will no longer return true.
+ */
private val isTransitioningToFullShade: Boolean
- get() = fullShadeTransitionProgress != 0f && !bypassController.bypassEnabled
+ get() = fullShadeTransitionProgress != 0f && !bypassController.bypassEnabled &&
+ statusbarState == StatusBarState.KEYGUARD
/**
* Set the amount of pixels we have currently dragged down if we're transitioning to the full
@@ -227,14 +278,8 @@ class MediaHierarchyManager @Inject constructor(
fun setTransitionToFullShadeAmount(value: Float) {
// If we're transitioning starting on the shade_locked, we don't want any delay and rather
// have it aligned with the rest of the animation
- val delay = if (statusbarState == StatusBarState.KEYGUARD) {
- fullShadeTransitionDelay
- } else {
- 0
- }
- val progress = MathUtils.saturate((value - delay) /
- (distanceForFullShadeTransition - delay))
- fullShadeTransitionProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(progress)
+ val progress = MathUtils.saturate(value / distanceForFullShadeTransition)
+ fullShadeTransitionProgress = progress
}
/**
@@ -296,6 +341,49 @@ class MediaHierarchyManager @Inject constructor(
}
}
+ /**
+ * The current cross fade progress. 0.5f means it's just switching
+ * between the start and the end location and the content is fully faded, while 0.75f means
+ * that we're halfway faded in again in the target state.
+ * This is only valid while [isCrossFadeAnimatorRunning] is true.
+ */
+ private var animationCrossFadeProgress = 1.0f
+
+ /**
+ * The current carousel Alpha.
+ */
+ private var carouselAlpha: Float = 1.0f
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ CrossFadeHelper.fadeIn(mediaFrame, value)
+ }
+
+ /**
+ * Calculate the alpha of the view when given a cross-fade progress.
+ *
+ * @param crossFadeProgress The current cross fade progress. 0.5f means it's just switching
+ * between the start and the end location and the content is fully faded, while 0.75f means
+ * that we're halfway faded in again in the target state.
+ *
+ * @param instantlyShowAtEnd should the view be instantly shown at the end. This is needed
+ * to avoid fadinging in when the target was hidden anyway.
+ */
+ private fun calculateAlphaFromCrossFade(
+ crossFadeProgress: Float,
+ instantlyShowAtEnd: Boolean
+ ): Float {
+ if (crossFadeProgress <= 0.5f) {
+ return 1.0f - crossFadeProgress / 0.5f
+ } else if (instantlyShowAtEnd) {
+ return 1.0f
+ } else {
+ return (crossFadeProgress - 0.5f) / 0.5f
+ }
+ }
+
init {
updateConfiguration()
configurationController.addCallback(object : ConfigurationController.ConfigurationListener {
@@ -375,9 +463,7 @@ class MediaHierarchyManager @Inject constructor(
private fun updateConfiguration() {
distanceForFullShadeTransition = context.resources.getDimensionPixelSize(
- R.dimen.lockscreen_shade_qs_transition_distance)
- fullShadeTransitionDelay = context.resources.getDimensionPixelSize(
- R.dimen.lockscreen_shade_media_transition_start_delay)
+ R.dimen.lockscreen_shade_media_transition_distance)
}
/**
@@ -449,8 +535,13 @@ class MediaHierarchyManager @Inject constructor(
shouldAnimateTransition(desiredLocation, previousLocation)
val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation)
val host = getHost(desiredLocation)
- mediaCarouselController.onDesiredLocationChanged(desiredLocation, host, animate,
- animDuration, delay)
+ val willFade = calculateTransformationType() == TRANSFORMATION_TYPE_FADE
+ if (!willFade || isCurrentlyInGuidedTransformation() || !animate) {
+ // if we're fading, we want the desired location / measurement only to change
+ // once fully faded. This is happening in the host attachment
+ mediaCarouselController.onDesiredLocationChanged(desiredLocation, host,
+ animate, animDuration, delay)
+ }
performTransitionToNewLocation(isNewView, animate)
}
}
@@ -470,6 +561,8 @@ class MediaHierarchyManager @Inject constructor(
if (isCurrentlyInGuidedTransformation()) {
applyTargetStateIfNotAnimating()
} else if (animate) {
+ val wasCrossFading = isCrossFadeAnimatorRunning
+ val previewsCrossFadeProgress = animationCrossFadeProgress
animator.cancel()
if (currentAttachmentLocation != previousLocation ||
!previousHost.hostView.isAttachedToWindow) {
@@ -482,6 +575,42 @@ class MediaHierarchyManager @Inject constructor(
// be outdated
animationStartBounds.set(previousHost.currentBounds)
}
+ val transformationType = calculateTransformationType()
+ var needsCrossFade = transformationType == TRANSFORMATION_TYPE_FADE
+ var crossFadeStartProgress = 0.0f
+ // The alpha is only relevant when not cross fading
+ var newCrossFadeStartLocation = previousLocation
+ if (wasCrossFading) {
+ if (currentAttachmentLocation == crossFadeAnimationEndLocation) {
+ if (needsCrossFade) {
+ // We were previously crossFading and we've already reached
+ // the end view, Let's start crossfading from the same position there
+ crossFadeStartProgress = 1.0f - previewsCrossFadeProgress
+ }
+ // Otherwise let's fade in from the current alpha, but not cross fade
+ } else {
+ // We haven't reached the previous location yet, let's still cross fade from
+ // where we were.
+ newCrossFadeStartLocation = crossFadeAnimationStartLocation
+ if (newCrossFadeStartLocation == desiredLocation) {
+ // we're crossFading back to where we were, let's start at the end position
+ crossFadeStartProgress = 1.0f - previewsCrossFadeProgress
+ } else {
+ // Let's start from where we are right now
+ crossFadeStartProgress = previewsCrossFadeProgress
+ // We need to force cross fading as we haven't reached the end location yet
+ needsCrossFade = true
+ }
+ }
+ } else if (needsCrossFade) {
+ // let's not flicker and start with the same alpha
+ crossFadeStartProgress = (1.0f - carouselAlpha) / 2.0f
+ }
+ isCrossFadeAnimatorRunning = needsCrossFade
+ crossFadeAnimationStartLocation = newCrossFadeStartLocation
+ crossFadeAnimationEndLocation = desiredLocation
+ animationStartAlpha = carouselAlpha
+ animationStartCrossFadeProgress = crossFadeStartProgress
adjustAnimatorForTransition(desiredLocation, previousLocation)
if (!animationPending) {
rootView?.let {
@@ -518,6 +647,17 @@ class MediaHierarchyManager @Inject constructor(
// non-trivial reattaching logic happening that will make the view not-shown earlier
return true
}
+
+ if (statusbarState == StatusBarState.KEYGUARD) {
+ if (currentLocation == LOCATION_LOCKSCREEN &&
+ previousLocation == LOCATION_QS ||
+ (currentLocation == LOCATION_QS &&
+ previousLocation == LOCATION_LOCKSCREEN)) {
+ // We're always fading from lockscreen to keyguard in situations where the player
+ // is already fully hidden
+ return false
+ }
+ }
return mediaFrame.isShownNotFaded || animator.isRunning || animationPending
}
@@ -538,7 +678,7 @@ class MediaHierarchyManager @Inject constructor(
keyguardStateController.isKeyguardFadingAway) {
delay = keyguardStateController.keyguardFadingAwayDelay
}
- animDuration = StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE.toLong()
+ animDuration = (StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE / 2f).toLong()
} else if (previousLocation == LOCATION_QQS && desiredLocation == LOCATION_LOCKSCREEN) {
animDuration = StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR.toLong()
}
@@ -550,7 +690,7 @@ class MediaHierarchyManager @Inject constructor(
// Let's immediately apply the target state (which is interpolated) if there is
// no animation running. Otherwise the animation update will already update
// the location
- applyState(targetBounds)
+ applyState(targetBounds, carouselAlpha)
}
}
@@ -558,7 +698,7 @@ class MediaHierarchyManager @Inject constructor(
* Updates the bounds that the view wants to be in at the end of the animation.
*/
private fun updateTargetState() {
- if (isCurrentlyInGuidedTransformation()) {
+ if (isCurrentlyInGuidedTransformation() && !isCurrentlyFading()) {
val progress = getTransformationProgress()
var endHost = getHost(desiredLocation)!!
var starthost = getHost(previousLocation)!!
@@ -606,12 +746,33 @@ class MediaHierarchyManager @Inject constructor(
}
/**
+ * Calculate the transformation type for the current animation
+ */
+ @VisibleForTesting
+ @TransformationType
+ fun calculateTransformationType(): Int {
+ if (isTransitioningToFullShade) {
+ return TRANSFORMATION_TYPE_FADE
+ }
+ if (previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QS ||
+ previousLocation == LOCATION_QS && desiredLocation == LOCATION_LOCKSCREEN) {
+ // animating between ls and qs should fade, as QS is clipped.
+ return TRANSFORMATION_TYPE_FADE
+ }
+ if (previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QQS) {
+ // animating between ls and qqs should fade when dragging down via e.g. expand button
+ return TRANSFORMATION_TYPE_FADE
+ }
+ return TRANSFORMATION_TYPE_TRANSITION
+ }
+
+ /**
* @return the current transformation progress if we're in a guided transformation and -1
* otherwise
*/
private fun getTransformationProgress(): Float {
val progress = getQSTransformationProgress()
- if (progress >= 0) {
+ if (statusbarState != StatusBarState.KEYGUARD && progress >= 0) {
return progress
}
if (isTransitioningToFullShade) {
@@ -643,19 +804,20 @@ class MediaHierarchyManager @Inject constructor(
private fun cancelAnimationAndApplyDesiredState() {
animator.cancel()
getHost(desiredLocation)?.let {
- applyState(it.currentBounds, immediately = true)
+ applyState(it.currentBounds, alpha = 1.0f, immediately = true)
}
}
/**
* Apply the current state to the view, updating it's bounds and desired state
*/
- private fun applyState(bounds: Rect, immediately: Boolean = false) {
+ private fun applyState(bounds: Rect, alpha: Float, immediately: Boolean = false) {
currentBounds.set(bounds)
- val currentlyInGuidedTransformation = isCurrentlyInGuidedTransformation()
- val startLocation = if (currentlyInGuidedTransformation) previousLocation else -1
- val progress = if (currentlyInGuidedTransformation) getTransformationProgress() else 1.0f
- val endLocation = desiredLocation
+ carouselAlpha = if (isCurrentlyFading()) alpha else 1.0f
+ val onlyUseEndState = !isCurrentlyInGuidedTransformation() || isCurrentlyFading()
+ val startLocation = if (onlyUseEndState) -1 else previousLocation
+ val progress = if (onlyUseEndState) 1.0f else getTransformationProgress()
+ val endLocation = resolveLocationForFading()
mediaCarouselController.setCurrentState(startLocation, endLocation, progress, immediately)
updateHostAttachment()
if (currentAttachmentLocation == IN_OVERLAY) {
@@ -668,8 +830,19 @@ class MediaHierarchyManager @Inject constructor(
}
private fun updateHostAttachment() {
- val inOverlay = isTransitionRunning() && rootOverlay != null
- val newLocation = if (inOverlay) IN_OVERLAY else desiredLocation
+ var newLocation = resolveLocationForFading()
+ var canUseOverlay = !isCurrentlyFading()
+ if (isCrossFadeAnimatorRunning) {
+ if (getHost(newLocation)?.visible == true &&
+ getHost(newLocation)?.hostView?.isShown == false &&
+ newLocation != desiredLocation) {
+ // We're crossfading but the view is already hidden. Let's move to the overlay
+ // instead. This happens when animating to the full shade using a button click.
+ canUseOverlay = true
+ }
+ }
+ val inOverlay = isTransitionRunning() && rootOverlay != null && canUseOverlay
+ newLocation = if (inOverlay) IN_OVERLAY else newLocation
if (currentAttachmentLocation != newLocation) {
currentAttachmentLocation = newLocation
@@ -677,10 +850,10 @@ class MediaHierarchyManager @Inject constructor(
(mediaFrame.parent as ViewGroup?)?.removeView(mediaFrame)
// Add it to the new one
- val targetHost = getHost(desiredLocation)!!.hostView
if (inOverlay) {
rootOverlay!!.add(mediaFrame)
} else {
+ val targetHost = getHost(newLocation)!!.hostView
// When adding back to the host, let's make sure to reset the bounds.
// Usually adding the view will trigger a layout that does this automatically,
// but we sometimes suppress this.
@@ -693,7 +866,37 @@ class MediaHierarchyManager @Inject constructor(
left + currentBounds.width(),
top + currentBounds.height())
}
+ if (isCrossFadeAnimatorRunning) {
+ // When cross-fading with an animation, we only notify the media carousel of the
+ // location change, once the view is reattached to the new place and not immediately
+ // when the desired location changes. This callback will update the measurement
+ // of the carousel, only once we've faded out at the old location and then reattach
+ // to fade it in at the new location.
+ mediaCarouselController.onDesiredLocationChanged(
+ newLocation,
+ getHost(newLocation),
+ animate = false
+ )
+ }
+ }
+ }
+
+ /**
+ * Calculate the location when cross fading between locations. While fading out,
+ * the content should remain in the previous location, while after the switch it should
+ * be at the desired location.
+ */
+ private fun resolveLocationForFading(): Int {
+ if (isCrossFadeAnimatorRunning) {
+ // When animating between two hosts with a fade, let's keep ourselves in the old
+ // location for the first half, and then switch over to the end location
+ if (animationCrossFadeProgress > 0.5 || previousLocation == -1) {
+ return crossFadeAnimationEndLocation
+ } else {
+ return crossFadeAnimationStartLocation
+ }
}
+ return desiredLocation
}
private fun isTransitionRunning(): Boolean {
@@ -708,29 +911,29 @@ class MediaHierarchyManager @Inject constructor(
return desiredLocation
}
val onLockscreen = (!bypassController.bypassEnabled &&
- (statusbarState == StatusBarState.KEYGUARD ||
- statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER))
+ (statusbarState == StatusBarState.KEYGUARD ||
+ statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER))
val allowedOnLockscreen = notifLockscreenUserManager.shouldShowLockscreenNotifications()
val location = when {
qsExpansion > 0.0f && !onLockscreen -> LOCATION_QS
qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
- onLockscreen && isTransitioningToFullShade -> LOCATION_QQS
+ onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
onLockscreen && allowedOnLockscreen -> LOCATION_LOCKSCREEN
else -> LOCATION_QQS
}
// When we're on lock screen and the player is not active, we should keep it in QS.
// Otherwise it will try to animate a transition that doesn't make sense.
if (location == LOCATION_LOCKSCREEN && getHost(location)?.visible != true &&
- !statusBarStateController.isDozing) {
+ !statusBarStateController.isDozing) {
return LOCATION_QS
}
if (location == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QS &&
- collapsingShadeFromQS) {
+ collapsingShadeFromQS) {
// When collapsing on the lockscreen, we want to remain in QS
return LOCATION_QS
}
if (location != LOCATION_LOCKSCREEN && desiredLocation == LOCATION_LOCKSCREEN &&
- !fullyAwake) {
+ !fullyAwake) {
// When unlocking from dozing / while waking up, the media shouldn't be transitioning
// in an animated way. Let's keep it in the lockscreen until we're fully awake and
// reattach it without an animation
@@ -740,6 +943,26 @@ class MediaHierarchyManager @Inject constructor(
}
/**
+ * Are we currently transforming to the full shade and already in QQS
+ */
+ private fun isTransformingToFullShadeAndInQQS(): Boolean {
+ if (!isTransitioningToFullShade) {
+ return false
+ }
+ return fullShadeTransitionProgress > 0.5f
+ }
+
+ /**
+ * Is the current transformationType fading
+ */
+ private fun isCurrentlyFading(): Boolean {
+ if (isTransitioningToFullShade) {
+ return true
+ }
+ return isCrossFadeAnimatorRunning
+ }
+
+ /**
* Returns true when the media card could be visible to the user if existed.
*/
private fun isVisibleToUser(): Boolean {
@@ -789,9 +1012,27 @@ class MediaHierarchyManager @Inject constructor(
* Attached at the root of the hierarchy in an overlay
*/
const val IN_OVERLAY = -1000
+
+ /**
+ * The default transformation type where the hosts transform into each other using a direct
+ * transition
+ */
+ const val TRANSFORMATION_TYPE_TRANSITION = 0
+
+ /**
+ * A transformation type where content fades from one place to another instead of
+ * transitioning
+ */
+ const val TRANSFORMATION_TYPE_FADE = 1
}
}
+@IntDef(prefix = ["TRANSFORMATION_TYPE_"], value = [
+ MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION,
+ MediaHierarchyManager.TRANSFORMATION_TYPE_FADE])
+@Retention(AnnotationRetention.SOURCE)
+private annotation class TransformationType
+
@IntDef(prefix = ["LOCATION_"], value = [MediaHierarchyManager.LOCATION_QS,
MediaHierarchyManager.LOCATION_QQS, MediaHierarchyManager.LOCATION_LOCKSCREEN])
@Retention(AnnotationRetention.SOURCE)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 34c654c9135d..1c5fa439d0ee 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -95,7 +95,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private boolean mLastKeyguardAndExpanded;
/**
* The last received state from the controller. This should not be used directly to check if
- * we're on keyguard but use {@link #isKeyguardShowing()} instead since that is more accurate
+ * we're on keyguard but use {@link #isKeyguardState()} instead since that is more accurate
* during state transitions which often call into us.
*/
private int mState;
@@ -326,7 +326,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|| mHeaderAnimating;
mQSPanelController.setExpanded(mQsExpanded);
mQSDetail.setExpanded(mQsExpanded);
- boolean keyguardShowing = isKeyguardShowing();
+ boolean keyguardShowing = isKeyguardState();
mHeader.setVisibility((mQsExpanded || !keyguardShowing || mHeaderAnimating
|| mShowCollapsedOnKeyguard)
? View.VISIBLE
@@ -344,7 +344,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
!mQsDisabled && expandVisually ? View.VISIBLE : View.INVISIBLE);
}
- private boolean isKeyguardShowing() {
+ private boolean isKeyguardState() {
// We want the freshest state here since otherwise we'll have some weirdness if earlier
// listeners trigger updates
return mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
@@ -366,7 +366,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
if (mQSAnimator != null) {
mQSAnimator.setShowCollapsedOnKeyguard(showCollapsed);
}
- if (!showCollapsed && isKeyguardShowing()) {
+ if (!showCollapsed && isKeyguardState()) {
setQsExpansion(mLastQSExpansion, 0);
}
}
@@ -457,7 +457,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mContainer.setExpansion(expansion);
final float translationScaleY = (mTranslateWhileExpanding
? 1 : QSAnimator.SHORT_PARALLAX_AMOUNT) * (expansion - 1);
- boolean onKeyguardAndExpanded = isKeyguardShowing() && !mShowCollapsedOnKeyguard;
+ boolean onKeyguardAndExpanded = isKeyguardState() && !mShowCollapsedOnKeyguard;
if (!mHeaderAnimating && !headerWillBeAnimating()) {
getView().setTranslationY(
onKeyguardAndExpanded
@@ -531,7 +531,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
// The Media can be scrolled off screen by default, let's offset it
float expandedMediaPosition = absoluteBottomPosition - mQSPanelScrollView.getScrollY()
+ mQSPanelScrollView.getScrollRange();
- // The expanded media host should never move below the laid out position
pinToBottom(expandedMediaPosition, mQsMediaHost, true /* expanded */);
// The expanded media host should never move above the laid out position
pinToBottom(absoluteBottomPosition, mQqsMediaHost, false /* expanded */);
@@ -540,7 +539,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private void pinToBottom(float absoluteBottomPosition, MediaHost mediaHost, boolean expanded) {
View hostView = mediaHost.getHostView();
- if (mLastQSExpansion > 0) {
+ // on keyguard we cross-fade to expanded, so no need to pin it.
+ if (mLastQSExpansion > 0 && !isKeyguardState()) {
float targetPosition = absoluteBottomPosition - getTotalBottomMargin(hostView)
- hostView.getHeight();
float currentPosition = mediaHost.getCurrentBounds().top
@@ -573,7 +573,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private boolean headerWillBeAnimating() {
return mState == StatusBarState.KEYGUARD && mShowCollapsedOnKeyguard
- && !isKeyguardShowing();
+ && !isKeyguardState();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 21d8164ba491..94edbd092a4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -5145,8 +5145,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
/**
- * Sets the extra top inset for the full shade transition. This is needed to compensate for
- * media transitioning to quick settings
+ * Sets the extra top inset for the full shade transition. This moves notifications down
+ * during the drag down.
*/
public void setExtraTopInsetForFullShadeTransition(float inset) {
mExtraTopInsetForFullShadeTransition = inset;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index d23a309ad1e9..4432f5463802 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -185,8 +185,17 @@ public class NotificationStackScrollLayoutController {
private ColorExtractor.OnColorsChangedListener mOnColorsChangedListener;
+ /**
+ * The total distance in pixels that the full shade transition takes to transition entirely to
+ * the full shade.
+ */
private int mTotalDistanceForFullShadeTransition;
- private int mTotalExtraMediaInsetFullShadeTransition;
+
+ /**
+ * The amount of movement the notifications do when transitioning to the full shade before
+ * reaching the overstrech
+ */
+ private int mNotificationDragDownMovement;
@VisibleForTesting
final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
@@ -255,8 +264,8 @@ public class NotificationStackScrollLayoutController {
};
private void updateResources() {
- mTotalExtraMediaInsetFullShadeTransition = mResources.getDimensionPixelSize(
- R.dimen.lockscreen_shade_transition_extra_media_inset);
+ mNotificationDragDownMovement = mResources.getDimensionPixelSize(
+ R.dimen.lockscreen_shade_notification_movement);
mTotalDistanceForFullShadeTransition = mResources.getDimensionPixelSize(
R.dimen.lockscreen_shade_qs_transition_distance);
}
@@ -1410,15 +1419,13 @@ public class NotificationStackScrollLayoutController {
* shade. 0.0f means we're not transitioning yet.
*/
public void setTransitionToFullShadeAmount(float amount) {
- float extraTopInset;
- MediaHeaderView view = mKeyguardMediaController.getSinglePaneContainer();
- if (view == null || view.getHeight() == 0
- || mStatusBarStateController.getState() != KEYGUARD) {
- extraTopInset = 0;
- } else {
- extraTopInset = MathUtils.saturate(amount / mTotalDistanceForFullShadeTransition);
- extraTopInset = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(extraTopInset);
- extraTopInset = extraTopInset * mTotalExtraMediaInsetFullShadeTransition;
+ float extraTopInset = 0.0f;
+ if (mStatusBarStateController.getState() == KEYGUARD) {
+ float overallProgress = MathUtils.saturate(amount / mView.getHeight());
+ float transitionProgress = Interpolators.getOvershootInterpolation(overallProgress,
+ 0.6f,
+ (float) mTotalDistanceForFullShadeTransition / (float) mView.getHeight());
+ extraTopInset = transitionProgress * mNotificationDragDownMovement;
}
mView.setExtraTopInsetForFullShadeTransition(extraTopInset);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index f4710f49524d..7c2723d724ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -325,7 +325,9 @@ public class KeyguardClockPositionAlgorithm {
*/
private float getClockAlpha(int y) {
float alphaKeyguard = Math.max(0, y / Math.max(1f, getClockY(1f, mDarkAmount)));
- alphaKeyguard *= (1f - mQsExpansion);
+ float qsAlphaFactor = MathUtils.saturate(mQsExpansion / 0.3f);
+ qsAlphaFactor = 1f - qsAlphaFactor;
+ alphaKeyguard *= qsAlphaFactor;
alphaKeyguard = Interpolators.ACCELERATE.getInterpolation(alphaKeyguard);
return MathUtils.lerp(alphaKeyguard, 1f, mDarkAmount);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index def9092e4171..74071439b0d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -510,6 +510,13 @@ public class NotificationPanelViewController extends PanelViewController {
private float mSectionPadding;
/**
+ * The padding between the start of notifications and the qs boundary on the lockscreen.
+ * On lockscreen, notifications aren't inset this extra amount, but we still want the
+ * qs boundary to be padded.
+ */
+ private int mLockscreenNotificationQSPadding;
+
+ /**
* The amount of progress we are currently in if we're transitioning to the full shade.
* 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
* shade. This value can also go beyond 1.1 when we're overshooting!
@@ -531,7 +538,7 @@ public class NotificationPanelViewController extends PanelViewController {
/**
* The maximum overshoot allowed for the top padding for the full shade transition
*/
- private int mMaxOverscrollAmountForDragDown;
+ private int mMaxOverscrollAmountForPulse;
/**
* Should we animate the next bounds update
@@ -823,7 +830,7 @@ public class NotificationPanelViewController extends PanelViewController {
amount -> {
float progress = amount / mView.getHeight();
float overstretch = Interpolators.getOvershootInterpolation(progress,
- (float) mMaxOverscrollAmountForDragDown / mView.getHeight(),
+ (float) mMaxOverscrollAmountForPulse / mView.getHeight(),
0.2f);
setOverStrechAmount(overstretch);
});
@@ -886,14 +893,16 @@ public class NotificationPanelViewController extends PanelViewController {
R.dimen.heads_up_status_bar_padding);
mDistanceForQSFullShadeTransition = mResources.getDimensionPixelSize(
R.dimen.lockscreen_shade_qs_transition_distance);
- mMaxOverscrollAmountForDragDown = mResources.getDimensionPixelSize(
- R.dimen.lockscreen_shade_max_top_overshoot);
+ mMaxOverscrollAmountForPulse = mResources.getDimensionPixelSize(
+ R.dimen.pulse_expansion_max_top_overshoot);
mScrimCornerRadius = mResources.getDimensionPixelSize(
R.dimen.notification_scrim_corner_radius);
mScreenCornerRadius = mResources.getDimensionPixelSize(
com.android.internal.R.dimen.rounded_corner_radius);
mNotificationScrimPadding = mResources.getDimensionPixelSize(
R.dimen.notification_side_paddings);
+ mLockscreenNotificationQSPadding = mResources.getDimensionPixelSize(
+ R.dimen.notification_side_paddings);
}
private void updateViewControllers(KeyguardStatusView keyguardStatusView,
@@ -2380,7 +2389,6 @@ public class NotificationPanelViewController extends PanelViewController {
public void setTransitionToFullShadeAmount(float pxAmount, boolean animate, long delay) {
mAnimateNextNotificationBounds = animate && !mShouldUseSplitNotificationShade;
mNotificationBoundsAnimationDelay = delay;
- float progress = MathUtils.saturate(pxAmount / mView.getHeight());
float endPosition = 0;
if (pxAmount > 0.0f) {
@@ -2388,29 +2396,28 @@ public class NotificationPanelViewController extends PanelViewController {
&& !mMediaDataManager.hasActiveMedia()) {
// No notifications are visible, let's animate to the height of qs instead
if (mQs != null) {
- // Let's interpolate to the header height
- endPosition = mQs.getHeader().getHeight();
+ // Let's interpolate to the header height instead of the top padding,
+ // because the toppadding is way too low because of the large clock.
+ // we still want to take into account the edgePosition though as that nicely
+ // overshoots in the stackscroller
+ endPosition = getQSEdgePosition()
+ - mNotificationStackScrollLayoutController.getTopPadding()
+ + mQs.getHeader().getHeight();
}
} else {
// Interpolating to the new bottom edge position!
- endPosition = getQSEdgePosition() - mOverStretchAmount;
-
- // If we have media, we need to put the boundary below it, as the media header
- // still uses the space during the transition.
- endPosition +=
- mNotificationStackScrollLayoutController.getFullShadeTransitionInset();
+ endPosition = getQSEdgePosition()
+ + mNotificationStackScrollLayoutController.getFullShadeTransitionInset();
+ if (isOnKeyguard()) {
+ endPosition -= mLockscreenNotificationQSPadding;
+ }
}
}
// Calculate the overshoot amount such that we're reaching the target after our desired
// distance, but only reach it fully once we drag a full shade length.
- float transitionProgress = 0;
- if (endPosition != 0 && progress != 0) {
- transitionProgress = Interpolators.getOvershootInterpolation(progress,
- mMaxOverscrollAmountForDragDown / endPosition,
- (float) mDistanceForQSFullShadeTransition / (float) mView.getHeight());
- }
- mTransitioningToFullShadeProgress = transitionProgress;
+ mTransitioningToFullShadeProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+ MathUtils.saturate(pxAmount / mDistanceForQSFullShadeTransition));
int position = (int) MathUtils.lerp((float) 0, endPosition,
mTransitioningToFullShadeProgress);
@@ -2418,8 +2425,6 @@ public class NotificationPanelViewController extends PanelViewController {
// we want at least 1 pixel otherwise the panel won't be clipped
position = Math.max(1, position);
}
- float overStretchAmount = Math.max(position - endPosition, 0.0f);
- setOverStrechAmount(overStretchAmount);
mTransitionToFullShadeQSPosition = position;
updateQsExpansion();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
index c6aef4a18373..bf87a4a59c49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
@@ -20,6 +20,7 @@ import android.graphics.Rect
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.ViewGroup
+import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
@@ -33,6 +34,7 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.animation.UniqueObjectHostView
+import junit.framework.Assert
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Rule
@@ -65,8 +67,6 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
@Mock
private lateinit var bypassController: KeyguardBypassController
@Mock
- private lateinit var mediaFrame: ViewGroup
- @Mock
private lateinit var keyguardStateController: KeyguardStateController
@Mock
private lateinit var statusBarStateController: SysuiStatusBarStateController
@@ -90,9 +90,11 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
@Rule
val mockito = MockitoJUnit.rule()
private lateinit var mediaHiearchyManager: MediaHierarchyManager
+ private lateinit var mediaFrame: ViewGroup
@Before
fun setup() {
+ mediaFrame = FrameLayout(context)
`when`(mediaCarouselController.mediaFrame).thenReturn(mediaFrame)
mediaHiearchyManager = MediaHierarchyManager(
context,
@@ -112,6 +114,9 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
`when`(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
`when`(mediaCarouselController.mediaCarouselScrollHandler)
.thenReturn(mediaCarouselScrollHandler)
+ val observer = wakefullnessObserver.value
+ assertNotNull("lifecycle observer wasn't registered", observer)
+ observer.onFinishedWakingUp()
// We'll use the viewmanager to verify a few calls below, let's reset this.
clearInvocations(mediaCarouselController)
}
@@ -120,6 +125,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
`when`(host.location).thenReturn(location)
`when`(host.currentBounds).thenReturn(Rect())
`when`(host.hostView).thenReturn(UniqueObjectHostView(context))
+ `when`(host.visible).thenReturn(true)
mediaHiearchyManager.register(host)
}
@@ -160,6 +166,73 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
}
@Test
+ fun testGoingToFullShade() {
+ // Let's set it onto Lock screen
+ `when`(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ `when`(notificationLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(
+ true)
+ statusBarCallback.value.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD)
+ clearInvocations(mediaCarouselController)
+
+ // Let's transition all the way to full shade
+ mediaHiearchyManager.setTransitionToFullShadeAmount(100000f)
+ verify(mediaCarouselController).onDesiredLocationChanged(
+ eq(MediaHierarchyManager.LOCATION_QQS),
+ any(MediaHostState::class.java),
+ eq(false),
+ anyLong(),
+ anyLong())
+ clearInvocations(mediaCarouselController)
+
+ // Let's go back to the lock screen
+ mediaHiearchyManager.setTransitionToFullShadeAmount(0.0f)
+ verify(mediaCarouselController).onDesiredLocationChanged(
+ eq(MediaHierarchyManager.LOCATION_LOCKSCREEN),
+ any(MediaHostState::class.java),
+ eq(false),
+ anyLong(),
+ anyLong())
+
+ // Let's make sure alpha is set
+ mediaHiearchyManager.setTransitionToFullShadeAmount(2.0f)
+ Assert.assertTrue("alpha should not be 1.0f when cross fading", mediaFrame.alpha != 1.0f)
+ }
+
+ @Test
+ fun testTransformationOnLockScreenIsFading() {
+ // Let's set it onto Lock screen
+ `when`(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ `when`(notificationLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(
+ true)
+ statusBarCallback.value.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD)
+ clearInvocations(mediaCarouselController)
+
+ // Let's transition from lockscreen to qs
+ mediaHiearchyManager.qsExpansion = 1.0f
+ val transformType = mediaHiearchyManager.calculateTransformationType()
+ Assert.assertTrue("media isn't transforming to qs with a fade",
+ transformType == MediaHierarchyManager.TRANSFORMATION_TYPE_FADE)
+ }
+
+ @Test
+ fun testTransformationOnLockScreenToQQSisFading() {
+ // Let's set it onto Lock screen
+ `when`(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ `when`(notificationLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(
+ true)
+ statusBarCallback.value.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD)
+ clearInvocations(mediaCarouselController)
+
+ // Let's transition from lockscreen to qs
+ `when`(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
+ statusBarCallback.value.onStatePreChange(StatusBarState.KEYGUARD,
+ StatusBarState.SHADE_LOCKED)
+ val transformType = mediaHiearchyManager.calculateTransformationType()
+ Assert.assertTrue("media isn't transforming to qqswith a fade",
+ transformType == MediaHierarchyManager.TRANSFORMATION_TYPE_FADE)
+ }
+
+ @Test
fun testCloseGutsRelayToCarousel() {
mediaHiearchyManager.closeGuts()