Merge "Transitioning media on lockscreen with a fade" into sc-dev
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -191,11 +232,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
|
||||
@@ -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)!!
|
||||
@@ -605,13 +745,34 @@ class MediaHierarchyManager @Inject constructor(
|
||||
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
|
||||
* 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,9 +866,39 @@ 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 {
|
||||
return isCurrentlyInGuidedTransformation() && getTransformationProgress() != 1.0f ||
|
||||
animator.isRunning || animationPending
|
||||
@@ -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
|
||||
@@ -739,6 +942,26 @@ class MediaHierarchyManager @Inject constructor(
|
||||
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.
|
||||
*/
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -509,6 +509,13 @@ public class NotificationPanelViewController extends PanelViewController {
|
||||
private Runnable mExpandAfterLayoutRunnable;
|
||||
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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -159,6 +165,73 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
|
||||
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
|
||||
fun testCloseGutsRelayToCarousel() {
|
||||
mediaHiearchyManager.closeGuts()
|
||||
|
||||
Reference in New Issue
Block a user