Merge "Updated blur interpolation for notification shade" into rvc-dev am: 9b35a417e5
Change-Id: Ic159c75fcfe89c1e2a89d60ea00c916a59d47f24
This commit is contained in:
@@ -38,8 +38,8 @@ open class BlurUtils @Inject constructor(
|
||||
@Main private val resources: Resources,
|
||||
dumpManager: DumpManager
|
||||
) : Dumpable {
|
||||
private val minBlurRadius = resources.getDimensionPixelSize(R.dimen.min_window_blur_radius)
|
||||
private val maxBlurRadius = resources.getDimensionPixelSize(R.dimen.max_window_blur_radius)
|
||||
val minBlurRadius = resources.getDimensionPixelSize(R.dimen.min_window_blur_radius)
|
||||
val maxBlurRadius = resources.getDimensionPixelSize(R.dimen.max_window_blur_radius)
|
||||
private val blurSupportedSysProp = SystemProperties
|
||||
.getBoolean("ro.surface_flinger.supports_background_blur", false)
|
||||
private val blurDisabledSysProp = SystemProperties
|
||||
|
||||
@@ -20,7 +20,9 @@ import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.animation.ValueAnimator
|
||||
import android.app.WallpaperManager
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import android.util.MathUtils
|
||||
import android.view.Choreographer
|
||||
import android.view.View
|
||||
import androidx.annotation.VisibleForTesting
|
||||
@@ -44,6 +46,7 @@ import java.io.PrintWriter
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.math.max
|
||||
import kotlin.math.sign
|
||||
|
||||
/**
|
||||
* Controller responsible for statusbar window blur.
|
||||
@@ -61,6 +64,11 @@ class NotificationShadeDepthController @Inject constructor(
|
||||
) : PanelExpansionListener, Dumpable {
|
||||
companion object {
|
||||
private const val WAKE_UP_ANIMATION_ENABLED = true
|
||||
private const val VELOCITY_SCALE = 100f
|
||||
private const val MAX_VELOCITY = 3000f
|
||||
private const val MIN_VELOCITY = -MAX_VELOCITY
|
||||
private const val INTERACTION_BLUR_FRACTION = 0.4f
|
||||
private const val ANIMATION_BLUR_FRACTION = 1f - INTERACTION_BLUR_FRACTION
|
||||
private const val TAG = "DepthController"
|
||||
}
|
||||
|
||||
@@ -71,8 +79,19 @@ class NotificationShadeDepthController @Inject constructor(
|
||||
private var updateScheduled: Boolean = false
|
||||
private var shadeExpansion = 0f
|
||||
private var ignoreShadeBlurUntilHidden: Boolean = false
|
||||
private var isClosed: Boolean = true
|
||||
private var isOpen: Boolean = false
|
||||
private var isBlurred: Boolean = false
|
||||
|
||||
private var prevTracking: Boolean = false
|
||||
private var prevTimestamp: Long = -1
|
||||
private var prevShadeDirection = 0
|
||||
private var prevShadeVelocity = 0f
|
||||
|
||||
@VisibleForTesting
|
||||
var shadeSpring = DepthAnimation()
|
||||
var shadeAnimation = DepthAnimation()
|
||||
|
||||
@VisibleForTesting
|
||||
var globalActionsSpring = DepthAnimation()
|
||||
var showingHomeControls: Boolean = false
|
||||
@@ -98,12 +117,15 @@ class NotificationShadeDepthController @Inject constructor(
|
||||
return
|
||||
}
|
||||
|
||||
if (shadeSpring.radius == 0) {
|
||||
if (shadeSpring.radius == 0 && shadeAnimation.radius == 0) {
|
||||
return
|
||||
}
|
||||
ignoreShadeBlurUntilHidden = true
|
||||
shadeSpring.animateTo(0)
|
||||
shadeSpring.finishIfRunning()
|
||||
|
||||
shadeAnimation.animateTo(0)
|
||||
shadeAnimation.finishIfRunning()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,8 +154,11 @@ class NotificationShadeDepthController @Inject constructor(
|
||||
@VisibleForTesting
|
||||
val updateBlurCallback = Choreographer.FrameCallback {
|
||||
updateScheduled = false
|
||||
|
||||
var shadeRadius = max(shadeSpring.radius, wakeAndUnlockBlurRadius).toFloat()
|
||||
val normalizedBlurRadius = MathUtils.constrain(shadeAnimation.radius,
|
||||
blurUtils.minBlurRadius, blurUtils.maxBlurRadius)
|
||||
val combinedBlur = (shadeSpring.radius * INTERACTION_BLUR_FRACTION +
|
||||
normalizedBlurRadius * ANIMATION_BLUR_FRACTION).toInt()
|
||||
var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius).toFloat()
|
||||
shadeRadius *= 1f - brightnessMirrorSpring.ratio
|
||||
val launchProgress = notificationLaunchAnimationParams?.linearProgress ?: 0f
|
||||
shadeRadius *= (1f - launchProgress) * (1f - launchProgress)
|
||||
@@ -208,12 +233,15 @@ class NotificationShadeDepthController @Inject constructor(
|
||||
|
||||
private val statusBarStateCallback = object : StatusBarStateController.StateListener {
|
||||
override fun onStateChanged(newState: Int) {
|
||||
updateShadeAnimationBlur(
|
||||
shadeExpansion, prevTracking, prevShadeVelocity, prevShadeDirection)
|
||||
updateShadeBlur()
|
||||
}
|
||||
|
||||
override fun onDozingChanged(isDozing: Boolean) {
|
||||
if (isDozing) {
|
||||
shadeSpring.finishIfRunning()
|
||||
shadeAnimation.finishIfRunning()
|
||||
globalActionsSpring.finishIfRunning()
|
||||
brightnessMirrorSpring.finishIfRunning()
|
||||
}
|
||||
@@ -234,24 +262,110 @@ class NotificationShadeDepthController @Inject constructor(
|
||||
// Stop blur effect when scrims is opaque to avoid unnecessary GPU composition.
|
||||
visibility -> scrimsVisible = visibility == ScrimController.OPAQUE
|
||||
}
|
||||
shadeAnimation.setStiffness(SpringForce.STIFFNESS_LOW)
|
||||
shadeAnimation.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update blurs when pulling down the shade
|
||||
*/
|
||||
override fun onPanelExpansionChanged(expansion: Float, tracking: Boolean) {
|
||||
if (expansion == shadeExpansion) {
|
||||
val timestamp = SystemClock.elapsedRealtimeNanos()
|
||||
|
||||
if (shadeExpansion == expansion && prevTracking == tracking) {
|
||||
prevTimestamp = timestamp
|
||||
return
|
||||
}
|
||||
|
||||
var deltaTime = 1f
|
||||
if (prevTimestamp < 0) {
|
||||
prevTimestamp = timestamp
|
||||
} else {
|
||||
deltaTime = MathUtils.constrain(
|
||||
((timestamp - prevTimestamp) / 1E9).toFloat(), 0.00001f, 1f)
|
||||
}
|
||||
|
||||
val diff = expansion - shadeExpansion
|
||||
val shadeDirection = sign(diff).toInt()
|
||||
val shadeVelocity = MathUtils.constrain(
|
||||
VELOCITY_SCALE * diff / deltaTime, MIN_VELOCITY, MAX_VELOCITY)
|
||||
updateShadeAnimationBlur(expansion, tracking, shadeVelocity, shadeDirection)
|
||||
|
||||
prevShadeDirection = shadeDirection
|
||||
prevShadeVelocity = shadeVelocity
|
||||
shadeExpansion = expansion
|
||||
prevTracking = tracking
|
||||
prevTimestamp = timestamp
|
||||
|
||||
updateShadeBlur()
|
||||
}
|
||||
|
||||
private fun updateShadeAnimationBlur(
|
||||
expansion: Float,
|
||||
tracking: Boolean,
|
||||
velocity: Float,
|
||||
direction: Int
|
||||
) {
|
||||
if (isOnKeyguardNotDismissing()) {
|
||||
if (expansion > 0f) {
|
||||
// Blur view if user starts animating in the shade.
|
||||
if (isClosed) {
|
||||
animateBlur(true, velocity)
|
||||
isClosed = false
|
||||
}
|
||||
|
||||
// If we were blurring out and the user stopped the animation, blur view.
|
||||
if (tracking && !isBlurred) {
|
||||
animateBlur(true, 0f)
|
||||
}
|
||||
|
||||
// If shade is being closed and the user isn't interacting with it, un-blur.
|
||||
if (!tracking && direction < 0 && isBlurred) {
|
||||
animateBlur(false, velocity)
|
||||
}
|
||||
|
||||
if (expansion == 1f) {
|
||||
if (!isOpen) {
|
||||
isOpen = true
|
||||
// If shade is open and view is not blurred, blur.
|
||||
if (!isBlurred) {
|
||||
animateBlur(true, velocity)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isOpen = false
|
||||
}
|
||||
// Automatic animation when the user closes the shade.
|
||||
} else if (!isClosed) {
|
||||
isClosed = true
|
||||
// If shade is closed and view is not blurred, blur.
|
||||
if (isBlurred) {
|
||||
animateBlur(false, velocity)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
animateBlur(false, 0f)
|
||||
isClosed = true
|
||||
isOpen = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun animateBlur(blur: Boolean, velocity: Float) {
|
||||
isBlurred = blur
|
||||
|
||||
val targetBlurNormalized = if (blur && isOnKeyguardNotDismissing()) {
|
||||
1f
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
|
||||
shadeAnimation.setStartVelocity(velocity)
|
||||
shadeAnimation.animateTo(blurUtils.blurRadiusOfRatio(targetBlurNormalized))
|
||||
}
|
||||
|
||||
private fun updateShadeBlur() {
|
||||
var newBlur = 0
|
||||
val state = statusBarStateController.state
|
||||
if ((state == StatusBarState.SHADE || state == StatusBarState.SHADE_LOCKED) &&
|
||||
!keyguardStateController.isKeyguardFadingAway) {
|
||||
if (isOnKeyguardNotDismissing()) {
|
||||
newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion)
|
||||
}
|
||||
shadeSpring.animateTo(newBlur)
|
||||
@@ -266,6 +380,12 @@ class NotificationShadeDepthController @Inject constructor(
|
||||
choreographer.postFrameCallback(updateBlurCallback)
|
||||
}
|
||||
|
||||
private fun isOnKeyguardNotDismissing(): Boolean {
|
||||
val state = statusBarStateController.state
|
||||
return (state == StatusBarState.SHADE || state == StatusBarState.SHADE_LOCKED) &&
|
||||
!keyguardStateController.isKeyguardFadingAway
|
||||
}
|
||||
|
||||
fun updateGlobalDialogVisibility(visibility: Float, dialogView: View?) {
|
||||
globalActionsSpring.animateTo(blurUtils.blurRadiusOfRatio(visibility), dialogView)
|
||||
}
|
||||
@@ -275,6 +395,7 @@ class NotificationShadeDepthController @Inject constructor(
|
||||
it.println("StatusBarWindowBlurController:")
|
||||
it.increaseIndent()
|
||||
it.println("shadeRadius: ${shadeSpring.radius}")
|
||||
it.println("shadeAnimation: ${shadeAnimation.radius}")
|
||||
it.println("globalActionsRadius: ${globalActionsSpring.radius}")
|
||||
it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}")
|
||||
it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius")
|
||||
@@ -311,7 +432,7 @@ class NotificationShadeDepthController @Inject constructor(
|
||||
private var view: View? = null
|
||||
|
||||
private var springAnimation = SpringAnimation(this, object :
|
||||
FloatPropertyCompat<DepthAnimation>("blurRadius") {
|
||||
FloatPropertyCompat<DepthAnimation>("blurRadius") {
|
||||
override fun setValue(rect: DepthAnimation?, value: Float) {
|
||||
radius = value.toInt()
|
||||
scheduleUpdate(view)
|
||||
@@ -343,5 +464,17 @@ class NotificationShadeDepthController @Inject constructor(
|
||||
springAnimation.skipToEnd()
|
||||
}
|
||||
}
|
||||
|
||||
fun setStiffness(stiffness: Float) {
|
||||
springAnimation.spring.stiffness = stiffness
|
||||
}
|
||||
|
||||
fun setDampingRatio(dampingRation: Float) {
|
||||
springAnimation.spring.dampingRatio = dampingRation
|
||||
}
|
||||
|
||||
fun setStartVelocity(velocity: Float) {
|
||||
springAnimation.setStartVelocity(velocity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
|
||||
@Mock private lateinit var root: View
|
||||
@Mock private lateinit var viewRootImpl: ViewRootImpl
|
||||
@Mock private lateinit var shadeSpring: NotificationShadeDepthController.DepthAnimation
|
||||
@Mock private lateinit var shadeAnimation: NotificationShadeDepthController.DepthAnimation
|
||||
@Mock private lateinit var globalActionsSpring: NotificationShadeDepthController.DepthAnimation
|
||||
@Mock private lateinit var brightnessSpring: NotificationShadeDepthController.DepthAnimation
|
||||
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
|
||||
@@ -80,11 +81,15 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
|
||||
`when`(blurUtils.blurRadiusOfRatio(anyFloat())).then { answer ->
|
||||
(answer.arguments[0] as Float * maxBlur).toInt()
|
||||
}
|
||||
`when`(blurUtils.minBlurRadius).thenReturn(0)
|
||||
`when`(blurUtils.maxBlurRadius).thenReturn(maxBlur)
|
||||
|
||||
notificationShadeDepthController = NotificationShadeDepthController(
|
||||
statusBarStateController, blurUtils, biometricUnlockController,
|
||||
keyguardStateController, choreographer, wallpaperManager,
|
||||
notificationShadeWindowController, dumpManager)
|
||||
notificationShadeDepthController.shadeSpring = shadeSpring
|
||||
notificationShadeDepthController.shadeAnimation = shadeAnimation
|
||||
notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring
|
||||
notificationShadeDepthController.globalActionsSpring = globalActionsSpring
|
||||
notificationShadeDepthController.root = root
|
||||
@@ -104,16 +109,61 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
|
||||
notificationShadeDepthController.onPanelExpansionChanged(1f /* expansion */,
|
||||
false /* tracking */)
|
||||
verify(shadeSpring).animateTo(eq(maxBlur), any())
|
||||
verify(shadeAnimation).animateTo(eq(maxBlur), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onPanelExpansionChanged_animatesBlurIn_ifShade() {
|
||||
notificationShadeDepthController.onPanelExpansionChanged(0.01f /* expansion */,
|
||||
false /* tracking */)
|
||||
verify(shadeAnimation).animateTo(eq(maxBlur), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onPanelExpansionChanged_animatesBlurOut_ifShade() {
|
||||
onPanelExpansionChanged_animatesBlurIn_ifShade()
|
||||
clearInvocations(shadeAnimation)
|
||||
notificationShadeDepthController.onPanelExpansionChanged(0f /* expansion */,
|
||||
false /* tracking */)
|
||||
verify(shadeAnimation).animateTo(eq(0), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onPanelExpansionChanged_animatesBlurOut_ifFlick() {
|
||||
onPanelExpansionChanged_apliesBlur_ifShade()
|
||||
clearInvocations(shadeAnimation)
|
||||
notificationShadeDepthController.onPanelExpansionChanged(1f /* expansion */,
|
||||
true /* tracking */)
|
||||
verify(shadeAnimation, never()).animateTo(anyInt(), any())
|
||||
|
||||
notificationShadeDepthController.onPanelExpansionChanged(0.9f /* expansion */,
|
||||
true /* tracking */)
|
||||
verify(shadeAnimation, never()).animateTo(anyInt(), any())
|
||||
|
||||
notificationShadeDepthController.onPanelExpansionChanged(0.8f /* expansion */,
|
||||
false /* tracking */)
|
||||
verify(shadeAnimation).animateTo(eq(0), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onPanelExpansionChanged_animatesBlurIn_ifFlickCancelled() {
|
||||
onPanelExpansionChanged_animatesBlurOut_ifFlick()
|
||||
clearInvocations(shadeAnimation)
|
||||
notificationShadeDepthController.onPanelExpansionChanged(0.6f /* expansion */,
|
||||
true /* tracking */)
|
||||
verify(shadeAnimation).animateTo(eq(maxBlur), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onStateChanged_reevalutesBlurs_ifSameRadiusAndNewState() {
|
||||
onPanelExpansionChanged_apliesBlur_ifShade()
|
||||
clearInvocations(shadeSpring)
|
||||
clearInvocations(shadeAnimation)
|
||||
|
||||
statusBarState = StatusBarState.KEYGUARD
|
||||
statusBarStateListener.onStateChanged(statusBarState)
|
||||
verify(shadeSpring).animateTo(eq(0), any())
|
||||
verify(shadeAnimation).animateTo(eq(0), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -147,6 +197,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun updateBlurCallback_setsBlur_whenExpanded() {
|
||||
`when`(shadeSpring.radius).thenReturn(maxBlur)
|
||||
`when`(shadeAnimation.radius).thenReturn(maxBlur)
|
||||
notificationShadeDepthController.updateBlurCallback.doFrame(0)
|
||||
verify(blurUtils).applyBlur(any(), eq(maxBlur))
|
||||
}
|
||||
@@ -154,6 +205,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun updateBlurCallback_appLaunchAnimation_overridesZoom() {
|
||||
`when`(shadeSpring.radius).thenReturn(maxBlur)
|
||||
`when`(shadeAnimation.radius).thenReturn(maxBlur)
|
||||
val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters()
|
||||
animProgress.linearProgress = 1f
|
||||
notificationShadeDepthController.notificationLaunchAnimationParams = animProgress
|
||||
@@ -187,6 +239,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
|
||||
`when`(brightnessSpring.ratio).thenReturn(1f)
|
||||
// And shade is blurred
|
||||
`when`(shadeSpring.radius).thenReturn(maxBlur)
|
||||
`when`(shadeAnimation.radius).thenReturn(maxBlur)
|
||||
|
||||
notificationShadeDepthController.updateBlurCallback.doFrame(0)
|
||||
verify(notificationShadeWindowController).setBackgroundBlurRadius(0)
|
||||
@@ -207,8 +260,10 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
|
||||
val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters()
|
||||
animProgress.linearProgress = 0.5f
|
||||
`when`(shadeSpring.radius).thenReturn(0)
|
||||
`when`(shadeAnimation.radius).thenReturn(0)
|
||||
notificationShadeDepthController.notificationLaunchAnimationParams = animProgress
|
||||
verify(shadeSpring, never()).animateTo(anyInt(), any())
|
||||
verify(shadeAnimation, never()).animateTo(anyInt(), any())
|
||||
}
|
||||
|
||||
private fun <T : Any> safeEq(value: T): T {
|
||||
|
||||
Reference in New Issue
Block a user