Merge "Transitioning media on lockscreen with a fade" into sc-dev

This commit is contained in:
Selim Cinek
2021-05-26 16:10:07 +00:00
committed by Android (Google) Code Review
9 changed files with 426 additions and 98 deletions

View File

@@ -22,6 +22,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:clipChildren="false" android:clipChildren="false"
android:clipToPadding="false" android:clipToPadding="false"
android:forceHasOverlappingRendering="false"
android:theme="@style/MediaPlayer"> android:theme="@style/MediaPlayer">
<com.android.systemui.media.MediaScrollView <com.android.systemui.media.MediaScrollView
android:id="@+id/media_carousel_scroller" android:id="@+id/media_carousel_scroller"

View File

@@ -1418,10 +1418,6 @@
<dimen name="media_output_dialog_icon_corner_radius">16dp</dimen> <dimen name="media_output_dialog_icon_corner_radius">16dp</dimen>
<dimen name="media_output_dialog_title_anim_y_delta">12.5dp</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 <!-- Distance that the full shade transition takes in order for qs to fully transition to the
shade --> shade -->
<dimen name="lockscreen_shade_qs_transition_distance">200dp</dimen> <dimen name="lockscreen_shade_qs_transition_distance">200dp</dimen>
@@ -1430,13 +1426,16 @@
the shade (in alpha) --> the shade (in alpha) -->
<dimen name="lockscreen_shade_scrim_transition_distance">80dp</dimen> <dimen name="lockscreen_shade_scrim_transition_distance">80dp</dimen>
<!-- Extra inset for the notifications when accounting for media during the lockscreen to <!-- Distance that the full shade transition takes in order for media to fully transition to
shade transition to compensate for the disappearing media --> the shade -->
<dimen name="lockscreen_shade_transition_extra_media_inset">-48dp</dimen> <dimen name="lockscreen_shade_media_transition_distance">140dp</dimen>
<!-- Maximum overshoot for the topPadding of notifications when transitioning to the full <!-- Maximum overshoot for the topPadding of notifications when transitioning to the full
shade --> 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_widget_radius">28dp</dimen>
<dimen name="people_space_image_radius">20dp</dimen> <dimen name="people_space_image_radius">20dp</dimen>

View File

@@ -26,21 +26,23 @@ import android.util.MathUtils
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewGroupOverlay import android.view.ViewGroupOverlay
import androidx.annotation.VisibleForTesting
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.animation.Interpolators import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.stack.StackStateAnimator import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.phone.KeyguardBypassController 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.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.animation.UniqueObjectHostView import com.android.systemui.util.animation.UniqueObjectHostView
import javax.inject.Inject import javax.inject.Inject
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
/** /**
* Similarly to isShown but also excludes views that have 0 alpha * Similarly to isShown but also excludes views that have 0 alpha
@@ -80,6 +82,7 @@ class MediaHierarchyManager @Inject constructor(
wakefulnessLifecycle: WakefulnessLifecycle, wakefulnessLifecycle: WakefulnessLifecycle,
private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager
) { ) {
/** /**
* The root overlay of the hierarchy. This is where the media notification is attached to * 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 * 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 rootView: View? = null
private var currentBounds = Rect() private var currentBounds = Rect()
private var animationStartBounds: Rect = 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 var targetBounds: Rect = Rect()
private val mediaFrame private val mediaFrame
get() = mediaCarouselController.mediaFrame get() = mediaCarouselController.mediaFrame
@@ -98,9 +125,22 @@ class MediaHierarchyManager @Inject constructor(
interpolator = Interpolators.FAST_OUT_SLOW_IN interpolator = Interpolators.FAST_OUT_SLOW_IN
addUpdateListener { addUpdateListener {
updateTargetState() 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) result = currentBounds)
applyState(currentBounds) applyState(currentBounds, currentAlpha)
} }
addListener(object : AnimatorListenerAdapter() { addListener(object : AnimatorListenerAdapter() {
private var cancelled: Boolean = false private var cancelled: Boolean = false
@@ -112,6 +152,7 @@ class MediaHierarchyManager @Inject constructor(
} }
override fun onAnimationEnd(animation: Animator?) { override fun onAnimationEnd(animation: Animator?) {
isCrossFadeAnimatorRunning = false
if (!cancelled) { if (!cancelled) {
applyTargetStateIfNotAnimating() applyTargetStateIfNotAnimating()
} }
@@ -191,11 +232,6 @@ class MediaHierarchyManager @Inject constructor(
*/ */
private var distanceForFullShadeTransition = 0 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. * 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 * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
@@ -207,18 +243,33 @@ class MediaHierarchyManager @Inject constructor(
return return
} }
field = value 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 return
} }
updateDesiredLocation() updateDesiredLocation(forceNoAnimation = isCurrentlyFading())
if (value >= 0) { if (value >= 0) {
updateTargetState() updateTargetState()
// Setting the alpha directly, as the below call will use it to update the alpha
carouselAlpha = calculateAlphaFromCrossFade(field, instantlyShowAtEnd = true)
applyTargetStateIfNotAnimating() 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 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 * 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) { fun setTransitionToFullShadeAmount(value: Float) {
// If we're transitioning starting on the shade_locked, we don't want any delay and rather // 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 // have it aligned with the rest of the animation
val delay = if (statusbarState == StatusBarState.KEYGUARD) { val progress = MathUtils.saturate(value / distanceForFullShadeTransition)
fullShadeTransitionDelay fullShadeTransitionProgress = progress
} else {
0
}
val progress = MathUtils.saturate((value - delay) /
(distanceForFullShadeTransition - delay))
fullShadeTransitionProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(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 { init {
updateConfiguration() updateConfiguration()
configurationController.addCallback(object : ConfigurationController.ConfigurationListener { configurationController.addCallback(object : ConfigurationController.ConfigurationListener {
@@ -375,9 +463,7 @@ class MediaHierarchyManager @Inject constructor(
private fun updateConfiguration() { private fun updateConfiguration() {
distanceForFullShadeTransition = context.resources.getDimensionPixelSize( distanceForFullShadeTransition = context.resources.getDimensionPixelSize(
R.dimen.lockscreen_shade_qs_transition_distance) R.dimen.lockscreen_shade_media_transition_distance)
fullShadeTransitionDelay = context.resources.getDimensionPixelSize(
R.dimen.lockscreen_shade_media_transition_start_delay)
} }
/** /**
@@ -449,8 +535,13 @@ class MediaHierarchyManager @Inject constructor(
shouldAnimateTransition(desiredLocation, previousLocation) shouldAnimateTransition(desiredLocation, previousLocation)
val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation) val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation)
val host = getHost(desiredLocation) val host = getHost(desiredLocation)
mediaCarouselController.onDesiredLocationChanged(desiredLocation, host, animate, val willFade = calculateTransformationType() == TRANSFORMATION_TYPE_FADE
animDuration, delay) 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) performTransitionToNewLocation(isNewView, animate)
} }
} }
@@ -470,6 +561,8 @@ class MediaHierarchyManager @Inject constructor(
if (isCurrentlyInGuidedTransformation()) { if (isCurrentlyInGuidedTransformation()) {
applyTargetStateIfNotAnimating() applyTargetStateIfNotAnimating()
} else if (animate) { } else if (animate) {
val wasCrossFading = isCrossFadeAnimatorRunning
val previewsCrossFadeProgress = animationCrossFadeProgress
animator.cancel() animator.cancel()
if (currentAttachmentLocation != previousLocation || if (currentAttachmentLocation != previousLocation ||
!previousHost.hostView.isAttachedToWindow) { !previousHost.hostView.isAttachedToWindow) {
@@ -482,6 +575,42 @@ class MediaHierarchyManager @Inject constructor(
// be outdated // be outdated
animationStartBounds.set(previousHost.currentBounds) 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) adjustAnimatorForTransition(desiredLocation, previousLocation)
if (!animationPending) { if (!animationPending) {
rootView?.let { rootView?.let {
@@ -518,6 +647,17 @@ class MediaHierarchyManager @Inject constructor(
// non-trivial reattaching logic happening that will make the view not-shown earlier // non-trivial reattaching logic happening that will make the view not-shown earlier
return true 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 return mediaFrame.isShownNotFaded || animator.isRunning || animationPending
} }
@@ -538,7 +678,7 @@ class MediaHierarchyManager @Inject constructor(
keyguardStateController.isKeyguardFadingAway) { keyguardStateController.isKeyguardFadingAway) {
delay = keyguardStateController.keyguardFadingAwayDelay 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) { } else if (previousLocation == LOCATION_QQS && desiredLocation == LOCATION_LOCKSCREEN) {
animDuration = StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR.toLong() 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 // Let's immediately apply the target state (which is interpolated) if there is
// no animation running. Otherwise the animation update will already update // no animation running. Otherwise the animation update will already update
// the location // 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. * Updates the bounds that the view wants to be in at the end of the animation.
*/ */
private fun updateTargetState() { private fun updateTargetState() {
if (isCurrentlyInGuidedTransformation()) { if (isCurrentlyInGuidedTransformation() && !isCurrentlyFading()) {
val progress = getTransformationProgress() val progress = getTransformationProgress()
var endHost = getHost(desiredLocation)!! var endHost = getHost(desiredLocation)!!
var starthost = getHost(previousLocation)!! var starthost = getHost(previousLocation)!!
@@ -605,13 +745,34 @@ class MediaHierarchyManager @Inject constructor(
return getTransformationProgress() >= 0 return getTransformationProgress() >= 0
} }
/**
* 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 * @return the current transformation progress if we're in a guided transformation and -1
* otherwise * otherwise
*/ */
private fun getTransformationProgress(): Float { private fun getTransformationProgress(): Float {
val progress = getQSTransformationProgress() val progress = getQSTransformationProgress()
if (progress >= 0) { if (statusbarState != StatusBarState.KEYGUARD && progress >= 0) {
return progress return progress
} }
if (isTransitioningToFullShade) { if (isTransitioningToFullShade) {
@@ -643,19 +804,20 @@ class MediaHierarchyManager @Inject constructor(
private fun cancelAnimationAndApplyDesiredState() { private fun cancelAnimationAndApplyDesiredState() {
animator.cancel() animator.cancel()
getHost(desiredLocation)?.let { 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 * 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) currentBounds.set(bounds)
val currentlyInGuidedTransformation = isCurrentlyInGuidedTransformation() carouselAlpha = if (isCurrentlyFading()) alpha else 1.0f
val startLocation = if (currentlyInGuidedTransformation) previousLocation else -1 val onlyUseEndState = !isCurrentlyInGuidedTransformation() || isCurrentlyFading()
val progress = if (currentlyInGuidedTransformation) getTransformationProgress() else 1.0f val startLocation = if (onlyUseEndState) -1 else previousLocation
val endLocation = desiredLocation val progress = if (onlyUseEndState) 1.0f else getTransformationProgress()
val endLocation = resolveLocationForFading()
mediaCarouselController.setCurrentState(startLocation, endLocation, progress, immediately) mediaCarouselController.setCurrentState(startLocation, endLocation, progress, immediately)
updateHostAttachment() updateHostAttachment()
if (currentAttachmentLocation == IN_OVERLAY) { if (currentAttachmentLocation == IN_OVERLAY) {
@@ -668,8 +830,19 @@ class MediaHierarchyManager @Inject constructor(
} }
private fun updateHostAttachment() { private fun updateHostAttachment() {
val inOverlay = isTransitionRunning() && rootOverlay != null var newLocation = resolveLocationForFading()
val newLocation = if (inOverlay) IN_OVERLAY else desiredLocation 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) { if (currentAttachmentLocation != newLocation) {
currentAttachmentLocation = newLocation currentAttachmentLocation = newLocation
@@ -677,10 +850,10 @@ class MediaHierarchyManager @Inject constructor(
(mediaFrame.parent as ViewGroup?)?.removeView(mediaFrame) (mediaFrame.parent as ViewGroup?)?.removeView(mediaFrame)
// Add it to the new one // Add it to the new one
val targetHost = getHost(desiredLocation)!!.hostView
if (inOverlay) { if (inOverlay) {
rootOverlay!!.add(mediaFrame) rootOverlay!!.add(mediaFrame)
} else { } else {
val targetHost = getHost(newLocation)!!.hostView
// When adding back to the host, let's make sure to reset the bounds. // 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, // Usually adding the view will trigger a layout that does this automatically,
// but we sometimes suppress this. // but we sometimes suppress this.
@@ -693,9 +866,39 @@ class MediaHierarchyManager @Inject constructor(
left + currentBounds.width(), left + currentBounds.width(),
top + currentBounds.height()) 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 { private fun isTransitionRunning(): Boolean {
return isCurrentlyInGuidedTransformation() && getTransformationProgress() != 1.0f || return isCurrentlyInGuidedTransformation() && getTransformationProgress() != 1.0f ||
animator.isRunning || animationPending animator.isRunning || animationPending
@@ -708,29 +911,29 @@ class MediaHierarchyManager @Inject constructor(
return desiredLocation return desiredLocation
} }
val onLockscreen = (!bypassController.bypassEnabled && val onLockscreen = (!bypassController.bypassEnabled &&
(statusbarState == StatusBarState.KEYGUARD || (statusbarState == StatusBarState.KEYGUARD ||
statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER)) statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER))
val allowedOnLockscreen = notifLockscreenUserManager.shouldShowLockscreenNotifications() val allowedOnLockscreen = notifLockscreenUserManager.shouldShowLockscreenNotifications()
val location = when { val location = when {
qsExpansion > 0.0f && !onLockscreen -> LOCATION_QS qsExpansion > 0.0f && !onLockscreen -> LOCATION_QS
qsExpansion > 0.4f && onLockscreen -> LOCATION_QS qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
onLockscreen && isTransitioningToFullShade -> LOCATION_QQS onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
onLockscreen && allowedOnLockscreen -> LOCATION_LOCKSCREEN onLockscreen && allowedOnLockscreen -> LOCATION_LOCKSCREEN
else -> LOCATION_QQS else -> LOCATION_QQS
} }
// When we're on lock screen and the player is not active, we should keep it in QS. // 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. // Otherwise it will try to animate a transition that doesn't make sense.
if (location == LOCATION_LOCKSCREEN && getHost(location)?.visible != true && if (location == LOCATION_LOCKSCREEN && getHost(location)?.visible != true &&
!statusBarStateController.isDozing) { !statusBarStateController.isDozing) {
return LOCATION_QS return LOCATION_QS
} }
if (location == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QS && if (location == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QS &&
collapsingShadeFromQS) { collapsingShadeFromQS) {
// When collapsing on the lockscreen, we want to remain in QS // When collapsing on the lockscreen, we want to remain in QS
return LOCATION_QS return LOCATION_QS
} }
if (location != LOCATION_LOCKSCREEN && desiredLocation == LOCATION_LOCKSCREEN && if (location != LOCATION_LOCKSCREEN && desiredLocation == LOCATION_LOCKSCREEN &&
!fullyAwake) { !fullyAwake) {
// When unlocking from dozing / while waking up, the media shouldn't be transitioning // 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 // in an animated way. Let's keep it in the lockscreen until we're fully awake and
// reattach it without an animation // reattach it without an animation
@@ -739,6 +942,26 @@ class MediaHierarchyManager @Inject constructor(
return location return location
} }
/**
* 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. * Returns true when the media card could be visible to the user if existed.
*/ */
@@ -789,9 +1012,27 @@ class MediaHierarchyManager @Inject constructor(
* Attached at the root of the hierarchy in an overlay * Attached at the root of the hierarchy in an overlay
*/ */
const val IN_OVERLAY = -1000 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, @IntDef(prefix = ["LOCATION_"], value = [MediaHierarchyManager.LOCATION_QS,
MediaHierarchyManager.LOCATION_QQS, MediaHierarchyManager.LOCATION_LOCKSCREEN]) MediaHierarchyManager.LOCATION_QQS, MediaHierarchyManager.LOCATION_LOCKSCREEN])
@Retention(AnnotationRetention.SOURCE) @Retention(AnnotationRetention.SOURCE)

View File

@@ -95,7 +95,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private boolean mLastKeyguardAndExpanded; private boolean mLastKeyguardAndExpanded;
/** /**
* The last received state from the controller. This should not be used directly to check if * 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. * during state transitions which often call into us.
*/ */
private int mState; private int mState;
@@ -326,7 +326,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|| mHeaderAnimating; || mHeaderAnimating;
mQSPanelController.setExpanded(mQsExpanded); mQSPanelController.setExpanded(mQsExpanded);
mQSDetail.setExpanded(mQsExpanded); mQSDetail.setExpanded(mQsExpanded);
boolean keyguardShowing = isKeyguardShowing(); boolean keyguardShowing = isKeyguardState();
mHeader.setVisibility((mQsExpanded || !keyguardShowing || mHeaderAnimating mHeader.setVisibility((mQsExpanded || !keyguardShowing || mHeaderAnimating
|| mShowCollapsedOnKeyguard) || mShowCollapsedOnKeyguard)
? View.VISIBLE ? View.VISIBLE
@@ -344,7 +344,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
!mQsDisabled && expandVisually ? View.VISIBLE : View.INVISIBLE); !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 // We want the freshest state here since otherwise we'll have some weirdness if earlier
// listeners trigger updates // listeners trigger updates
return mStatusBarStateController.getState() == StatusBarState.KEYGUARD; return mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
@@ -366,7 +366,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
if (mQSAnimator != null) { if (mQSAnimator != null) {
mQSAnimator.setShowCollapsedOnKeyguard(showCollapsed); mQSAnimator.setShowCollapsedOnKeyguard(showCollapsed);
} }
if (!showCollapsed && isKeyguardShowing()) { if (!showCollapsed && isKeyguardState()) {
setQsExpansion(mLastQSExpansion, 0); setQsExpansion(mLastQSExpansion, 0);
} }
} }
@@ -457,7 +457,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mContainer.setExpansion(expansion); mContainer.setExpansion(expansion);
final float translationScaleY = (mTranslateWhileExpanding final float translationScaleY = (mTranslateWhileExpanding
? 1 : QSAnimator.SHORT_PARALLAX_AMOUNT) * (expansion - 1); ? 1 : QSAnimator.SHORT_PARALLAX_AMOUNT) * (expansion - 1);
boolean onKeyguardAndExpanded = isKeyguardShowing() && !mShowCollapsedOnKeyguard; boolean onKeyguardAndExpanded = isKeyguardState() && !mShowCollapsedOnKeyguard;
if (!mHeaderAnimating && !headerWillBeAnimating()) { if (!mHeaderAnimating && !headerWillBeAnimating()) {
getView().setTranslationY( getView().setTranslationY(
onKeyguardAndExpanded 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 // The Media can be scrolled off screen by default, let's offset it
float expandedMediaPosition = absoluteBottomPosition - mQSPanelScrollView.getScrollY() float expandedMediaPosition = absoluteBottomPosition - mQSPanelScrollView.getScrollY()
+ mQSPanelScrollView.getScrollRange(); + mQSPanelScrollView.getScrollRange();
// The expanded media host should never move below the laid out position
pinToBottom(expandedMediaPosition, mQsMediaHost, true /* expanded */); pinToBottom(expandedMediaPosition, mQsMediaHost, true /* expanded */);
// The expanded media host should never move above the laid out position // The expanded media host should never move above the laid out position
pinToBottom(absoluteBottomPosition, mQqsMediaHost, false /* expanded */); 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) { private void pinToBottom(float absoluteBottomPosition, MediaHost mediaHost, boolean expanded) {
View hostView = mediaHost.getHostView(); 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) float targetPosition = absoluteBottomPosition - getTotalBottomMargin(hostView)
- hostView.getHeight(); - hostView.getHeight();
float currentPosition = mediaHost.getCurrentBounds().top float currentPosition = mediaHost.getCurrentBounds().top
@@ -573,7 +573,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private boolean headerWillBeAnimating() { private boolean headerWillBeAnimating() {
return mState == StatusBarState.KEYGUARD && mShowCollapsedOnKeyguard return mState == StatusBarState.KEYGUARD && mShowCollapsedOnKeyguard
&& !isKeyguardShowing(); && !isKeyguardState();
} }
@Override @Override

View File

@@ -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 * Sets the extra top inset for the full shade transition. This moves notifications down
* media transitioning to quick settings * during the drag down.
*/ */
public void setExtraTopInsetForFullShadeTransition(float inset) { public void setExtraTopInsetForFullShadeTransition(float inset) {
mExtraTopInsetForFullShadeTransition = inset; mExtraTopInsetForFullShadeTransition = inset;

View File

@@ -185,8 +185,17 @@ public class NotificationStackScrollLayoutController {
private ColorExtractor.OnColorsChangedListener mOnColorsChangedListener; 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 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 @VisibleForTesting
final View.OnAttachStateChangeListener mOnAttachStateChangeListener = final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
@@ -255,8 +264,8 @@ public class NotificationStackScrollLayoutController {
}; };
private void updateResources() { private void updateResources() {
mTotalExtraMediaInsetFullShadeTransition = mResources.getDimensionPixelSize( mNotificationDragDownMovement = mResources.getDimensionPixelSize(
R.dimen.lockscreen_shade_transition_extra_media_inset); R.dimen.lockscreen_shade_notification_movement);
mTotalDistanceForFullShadeTransition = mResources.getDimensionPixelSize( mTotalDistanceForFullShadeTransition = mResources.getDimensionPixelSize(
R.dimen.lockscreen_shade_qs_transition_distance); R.dimen.lockscreen_shade_qs_transition_distance);
} }
@@ -1410,15 +1419,13 @@ public class NotificationStackScrollLayoutController {
* shade. 0.0f means we're not transitioning yet. * shade. 0.0f means we're not transitioning yet.
*/ */
public void setTransitionToFullShadeAmount(float amount) { public void setTransitionToFullShadeAmount(float amount) {
float extraTopInset; float extraTopInset = 0.0f;
MediaHeaderView view = mKeyguardMediaController.getSinglePaneContainer(); if (mStatusBarStateController.getState() == KEYGUARD) {
if (view == null || view.getHeight() == 0 float overallProgress = MathUtils.saturate(amount / mView.getHeight());
|| mStatusBarStateController.getState() != KEYGUARD) { float transitionProgress = Interpolators.getOvershootInterpolation(overallProgress,
extraTopInset = 0; 0.6f,
} else { (float) mTotalDistanceForFullShadeTransition / (float) mView.getHeight());
extraTopInset = MathUtils.saturate(amount / mTotalDistanceForFullShadeTransition); extraTopInset = transitionProgress * mNotificationDragDownMovement;
extraTopInset = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(extraTopInset);
extraTopInset = extraTopInset * mTotalExtraMediaInsetFullShadeTransition;
} }
mView.setExtraTopInsetForFullShadeTransition(extraTopInset); mView.setExtraTopInsetForFullShadeTransition(extraTopInset);
} }

View File

@@ -325,7 +325,9 @@ public class KeyguardClockPositionAlgorithm {
*/ */
private float getClockAlpha(int y) { private float getClockAlpha(int y) {
float alphaKeyguard = Math.max(0, y / Math.max(1f, getClockY(1f, mDarkAmount))); 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); alphaKeyguard = Interpolators.ACCELERATE.getInterpolation(alphaKeyguard);
return MathUtils.lerp(alphaKeyguard, 1f, mDarkAmount); return MathUtils.lerp(alphaKeyguard, 1f, mDarkAmount);
} }

View File

@@ -509,6 +509,13 @@ public class NotificationPanelViewController extends PanelViewController {
private Runnable mExpandAfterLayoutRunnable; private Runnable mExpandAfterLayoutRunnable;
private float mSectionPadding; 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. * 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 * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
@@ -531,7 +538,7 @@ public class NotificationPanelViewController extends PanelViewController {
/** /**
* The maximum overshoot allowed for the top padding for the full shade transition * 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 * Should we animate the next bounds update
@@ -823,7 +830,7 @@ public class NotificationPanelViewController extends PanelViewController {
amount -> { amount -> {
float progress = amount / mView.getHeight(); float progress = amount / mView.getHeight();
float overstretch = Interpolators.getOvershootInterpolation(progress, float overstretch = Interpolators.getOvershootInterpolation(progress,
(float) mMaxOverscrollAmountForDragDown / mView.getHeight(), (float) mMaxOverscrollAmountForPulse / mView.getHeight(),
0.2f); 0.2f);
setOverStrechAmount(overstretch); setOverStrechAmount(overstretch);
}); });
@@ -886,14 +893,16 @@ public class NotificationPanelViewController extends PanelViewController {
R.dimen.heads_up_status_bar_padding); R.dimen.heads_up_status_bar_padding);
mDistanceForQSFullShadeTransition = mResources.getDimensionPixelSize( mDistanceForQSFullShadeTransition = mResources.getDimensionPixelSize(
R.dimen.lockscreen_shade_qs_transition_distance); R.dimen.lockscreen_shade_qs_transition_distance);
mMaxOverscrollAmountForDragDown = mResources.getDimensionPixelSize( mMaxOverscrollAmountForPulse = mResources.getDimensionPixelSize(
R.dimen.lockscreen_shade_max_top_overshoot); R.dimen.pulse_expansion_max_top_overshoot);
mScrimCornerRadius = mResources.getDimensionPixelSize( mScrimCornerRadius = mResources.getDimensionPixelSize(
R.dimen.notification_scrim_corner_radius); R.dimen.notification_scrim_corner_radius);
mScreenCornerRadius = mResources.getDimensionPixelSize( mScreenCornerRadius = mResources.getDimensionPixelSize(
com.android.internal.R.dimen.rounded_corner_radius); com.android.internal.R.dimen.rounded_corner_radius);
mNotificationScrimPadding = mResources.getDimensionPixelSize( mNotificationScrimPadding = mResources.getDimensionPixelSize(
R.dimen.notification_side_paddings); R.dimen.notification_side_paddings);
mLockscreenNotificationQSPadding = mResources.getDimensionPixelSize(
R.dimen.notification_side_paddings);
} }
private void updateViewControllers(KeyguardStatusView keyguardStatusView, private void updateViewControllers(KeyguardStatusView keyguardStatusView,
@@ -2380,7 +2389,6 @@ public class NotificationPanelViewController extends PanelViewController {
public void setTransitionToFullShadeAmount(float pxAmount, boolean animate, long delay) { public void setTransitionToFullShadeAmount(float pxAmount, boolean animate, long delay) {
mAnimateNextNotificationBounds = animate && !mShouldUseSplitNotificationShade; mAnimateNextNotificationBounds = animate && !mShouldUseSplitNotificationShade;
mNotificationBoundsAnimationDelay = delay; mNotificationBoundsAnimationDelay = delay;
float progress = MathUtils.saturate(pxAmount / mView.getHeight());
float endPosition = 0; float endPosition = 0;
if (pxAmount > 0.0f) { if (pxAmount > 0.0f) {
@@ -2388,29 +2396,28 @@ public class NotificationPanelViewController extends PanelViewController {
&& !mMediaDataManager.hasActiveMedia()) { && !mMediaDataManager.hasActiveMedia()) {
// No notifications are visible, let's animate to the height of qs instead // No notifications are visible, let's animate to the height of qs instead
if (mQs != null) { if (mQs != null) {
// Let's interpolate to the header height // Let's interpolate to the header height instead of the top padding,
endPosition = mQs.getHeader().getHeight(); // 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 { } else {
// Interpolating to the new bottom edge position! // Interpolating to the new bottom edge position!
endPosition = getQSEdgePosition() - mOverStretchAmount; endPosition = getQSEdgePosition()
+ mNotificationStackScrollLayoutController.getFullShadeTransitionInset();
// If we have media, we need to put the boundary below it, as the media header if (isOnKeyguard()) {
// still uses the space during the transition. endPosition -= mLockscreenNotificationQSPadding;
endPosition += }
mNotificationStackScrollLayoutController.getFullShadeTransitionInset();
} }
} }
// Calculate the overshoot amount such that we're reaching the target after our desired // 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. // distance, but only reach it fully once we drag a full shade length.
float transitionProgress = 0; mTransitioningToFullShadeProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
if (endPosition != 0 && progress != 0) { MathUtils.saturate(pxAmount / mDistanceForQSFullShadeTransition));
transitionProgress = Interpolators.getOvershootInterpolation(progress,
mMaxOverscrollAmountForDragDown / endPosition,
(float) mDistanceForQSFullShadeTransition / (float) mView.getHeight());
}
mTransitioningToFullShadeProgress = transitionProgress;
int position = (int) MathUtils.lerp((float) 0, endPosition, int position = (int) MathUtils.lerp((float) 0, endPosition,
mTransitioningToFullShadeProgress); mTransitioningToFullShadeProgress);
@@ -2418,8 +2425,6 @@ public class NotificationPanelViewController extends PanelViewController {
// we want at least 1 pixel otherwise the panel won't be clipped // we want at least 1 pixel otherwise the panel won't be clipped
position = Math.max(1, position); position = Math.max(1, position);
} }
float overStretchAmount = Math.max(position - endPosition, 0.0f);
setOverStrechAmount(overStretchAmount);
mTransitionToFullShadeQSPosition = position; mTransitionToFullShadeQSPosition = position;
updateQsExpansion(); updateQsExpansion();
} }

View File

@@ -20,6 +20,7 @@ import android.graphics.Rect
import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner
import android.testing.TestableLooper import android.testing.TestableLooper
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq 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.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.animation.UniqueObjectHostView import com.android.systemui.util.animation.UniqueObjectHostView
import junit.framework.Assert
import org.junit.Assert.assertNotNull import org.junit.Assert.assertNotNull
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
@@ -65,8 +67,6 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
@Mock @Mock
private lateinit var bypassController: KeyguardBypassController private lateinit var bypassController: KeyguardBypassController
@Mock @Mock
private lateinit var mediaFrame: ViewGroup
@Mock
private lateinit var keyguardStateController: KeyguardStateController private lateinit var keyguardStateController: KeyguardStateController
@Mock @Mock
private lateinit var statusBarStateController: SysuiStatusBarStateController private lateinit var statusBarStateController: SysuiStatusBarStateController
@@ -90,9 +90,11 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
@Rule @Rule
val mockito = MockitoJUnit.rule() val mockito = MockitoJUnit.rule()
private lateinit var mediaHiearchyManager: MediaHierarchyManager private lateinit var mediaHiearchyManager: MediaHierarchyManager
private lateinit var mediaFrame: ViewGroup
@Before @Before
fun setup() { fun setup() {
mediaFrame = FrameLayout(context)
`when`(mediaCarouselController.mediaFrame).thenReturn(mediaFrame) `when`(mediaCarouselController.mediaFrame).thenReturn(mediaFrame)
mediaHiearchyManager = MediaHierarchyManager( mediaHiearchyManager = MediaHierarchyManager(
context, context,
@@ -112,6 +114,9 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
`when`(statusBarStateController.state).thenReturn(StatusBarState.SHADE) `when`(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
`when`(mediaCarouselController.mediaCarouselScrollHandler) `when`(mediaCarouselController.mediaCarouselScrollHandler)
.thenReturn(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. // We'll use the viewmanager to verify a few calls below, let's reset this.
clearInvocations(mediaCarouselController) clearInvocations(mediaCarouselController)
} }
@@ -120,6 +125,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
`when`(host.location).thenReturn(location) `when`(host.location).thenReturn(location)
`when`(host.currentBounds).thenReturn(Rect()) `when`(host.currentBounds).thenReturn(Rect())
`when`(host.hostView).thenReturn(UniqueObjectHostView(context)) `when`(host.hostView).thenReturn(UniqueObjectHostView(context))
`when`(host.visible).thenReturn(true)
mediaHiearchyManager.register(host) mediaHiearchyManager.register(host)
} }
@@ -159,6 +165,73 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong()) any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
} }
@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 @Test
fun testCloseGutsRelayToCarousel() { fun testCloseGutsRelayToCarousel() {
mediaHiearchyManager.closeGuts() mediaHiearchyManager.closeGuts()