Merge "Updated blur interpolation for notification shade" into rvc-dev am: 9b35a417e5 am: 0ae8ae528a

Change-Id: I5c0c6db3a9ec93b8243846ab4dcc0e15730f7faa
This commit is contained in:
Lucas Dupin
2020-04-30 20:49:30 +00:00
committed by Automerger Merge Worker
3 changed files with 198 additions and 10 deletions

View File

@@ -38,8 +38,8 @@ open class BlurUtils @Inject constructor(
@Main private val resources: Resources, @Main private val resources: Resources,
dumpManager: DumpManager dumpManager: DumpManager
) : Dumpable { ) : Dumpable {
private val minBlurRadius = resources.getDimensionPixelSize(R.dimen.min_window_blur_radius) val minBlurRadius = resources.getDimensionPixelSize(R.dimen.min_window_blur_radius)
private val maxBlurRadius = resources.getDimensionPixelSize(R.dimen.max_window_blur_radius) val maxBlurRadius = resources.getDimensionPixelSize(R.dimen.max_window_blur_radius)
private val blurSupportedSysProp = SystemProperties private val blurSupportedSysProp = SystemProperties
.getBoolean("ro.surface_flinger.supports_background_blur", false) .getBoolean("ro.surface_flinger.supports_background_blur", false)
private val blurDisabledSysProp = SystemProperties private val blurDisabledSysProp = SystemProperties

View File

@@ -20,7 +20,9 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.app.WallpaperManager import android.app.WallpaperManager
import android.os.SystemClock
import android.util.Log import android.util.Log
import android.util.MathUtils
import android.view.Choreographer import android.view.Choreographer
import android.view.View import android.view.View
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
@@ -44,6 +46,7 @@ import java.io.PrintWriter
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import kotlin.math.max import kotlin.math.max
import kotlin.math.sign
/** /**
* Controller responsible for statusbar window blur. * Controller responsible for statusbar window blur.
@@ -61,6 +64,11 @@ class NotificationShadeDepthController @Inject constructor(
) : PanelExpansionListener, Dumpable { ) : PanelExpansionListener, Dumpable {
companion object { companion object {
private const val WAKE_UP_ANIMATION_ENABLED = true 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" private const val TAG = "DepthController"
} }
@@ -71,8 +79,19 @@ class NotificationShadeDepthController @Inject constructor(
private var updateScheduled: Boolean = false private var updateScheduled: Boolean = false
private var shadeExpansion = 0f private var shadeExpansion = 0f
private var ignoreShadeBlurUntilHidden: Boolean = false 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 @VisibleForTesting
var shadeSpring = DepthAnimation() var shadeSpring = DepthAnimation()
var shadeAnimation = DepthAnimation()
@VisibleForTesting @VisibleForTesting
var globalActionsSpring = DepthAnimation() var globalActionsSpring = DepthAnimation()
var showingHomeControls: Boolean = false var showingHomeControls: Boolean = false
@@ -98,12 +117,15 @@ class NotificationShadeDepthController @Inject constructor(
return return
} }
if (shadeSpring.radius == 0) { if (shadeSpring.radius == 0 && shadeAnimation.radius == 0) {
return return
} }
ignoreShadeBlurUntilHidden = true ignoreShadeBlurUntilHidden = true
shadeSpring.animateTo(0) shadeSpring.animateTo(0)
shadeSpring.finishIfRunning() shadeSpring.finishIfRunning()
shadeAnimation.animateTo(0)
shadeAnimation.finishIfRunning()
} }
/** /**
@@ -132,8 +154,11 @@ class NotificationShadeDepthController @Inject constructor(
@VisibleForTesting @VisibleForTesting
val updateBlurCallback = Choreographer.FrameCallback { val updateBlurCallback = Choreographer.FrameCallback {
updateScheduled = false updateScheduled = false
val normalizedBlurRadius = MathUtils.constrain(shadeAnimation.radius,
var shadeRadius = max(shadeSpring.radius, wakeAndUnlockBlurRadius).toFloat() 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 shadeRadius *= 1f - brightnessMirrorSpring.ratio
val launchProgress = notificationLaunchAnimationParams?.linearProgress ?: 0f val launchProgress = notificationLaunchAnimationParams?.linearProgress ?: 0f
shadeRadius *= (1f - launchProgress) * (1f - launchProgress) shadeRadius *= (1f - launchProgress) * (1f - launchProgress)
@@ -208,12 +233,15 @@ class NotificationShadeDepthController @Inject constructor(
private val statusBarStateCallback = object : StatusBarStateController.StateListener { private val statusBarStateCallback = object : StatusBarStateController.StateListener {
override fun onStateChanged(newState: Int) { override fun onStateChanged(newState: Int) {
updateShadeAnimationBlur(
shadeExpansion, prevTracking, prevShadeVelocity, prevShadeDirection)
updateShadeBlur() updateShadeBlur()
} }
override fun onDozingChanged(isDozing: Boolean) { override fun onDozingChanged(isDozing: Boolean) {
if (isDozing) { if (isDozing) {
shadeSpring.finishIfRunning() shadeSpring.finishIfRunning()
shadeAnimation.finishIfRunning()
globalActionsSpring.finishIfRunning() globalActionsSpring.finishIfRunning()
brightnessMirrorSpring.finishIfRunning() brightnessMirrorSpring.finishIfRunning()
} }
@@ -234,24 +262,110 @@ class NotificationShadeDepthController @Inject constructor(
// Stop blur effect when scrims is opaque to avoid unnecessary GPU composition. // Stop blur effect when scrims is opaque to avoid unnecessary GPU composition.
visibility -> scrimsVisible = visibility == ScrimController.OPAQUE visibility -> scrimsVisible = visibility == ScrimController.OPAQUE
} }
shadeAnimation.setStiffness(SpringForce.STIFFNESS_LOW)
shadeAnimation.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)
} }
/** /**
* Update blurs when pulling down the shade * Update blurs when pulling down the shade
*/ */
override fun onPanelExpansionChanged(expansion: Float, tracking: Boolean) { override fun onPanelExpansionChanged(expansion: Float, tracking: Boolean) {
if (expansion == shadeExpansion) { val timestamp = SystemClock.elapsedRealtimeNanos()
if (shadeExpansion == expansion && prevTracking == tracking) {
prevTimestamp = timestamp
return 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 shadeExpansion = expansion
prevTracking = tracking
prevTimestamp = timestamp
updateShadeBlur() 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() { private fun updateShadeBlur() {
var newBlur = 0 var newBlur = 0
val state = statusBarStateController.state if (isOnKeyguardNotDismissing()) {
if ((state == StatusBarState.SHADE || state == StatusBarState.SHADE_LOCKED) &&
!keyguardStateController.isKeyguardFadingAway) {
newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion) newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion)
} }
shadeSpring.animateTo(newBlur) shadeSpring.animateTo(newBlur)
@@ -266,6 +380,12 @@ class NotificationShadeDepthController @Inject constructor(
choreographer.postFrameCallback(updateBlurCallback) 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?) { fun updateGlobalDialogVisibility(visibility: Float, dialogView: View?) {
globalActionsSpring.animateTo(blurUtils.blurRadiusOfRatio(visibility), dialogView) globalActionsSpring.animateTo(blurUtils.blurRadiusOfRatio(visibility), dialogView)
} }
@@ -275,6 +395,7 @@ class NotificationShadeDepthController @Inject constructor(
it.println("StatusBarWindowBlurController:") it.println("StatusBarWindowBlurController:")
it.increaseIndent() it.increaseIndent()
it.println("shadeRadius: ${shadeSpring.radius}") it.println("shadeRadius: ${shadeSpring.radius}")
it.println("shadeAnimation: ${shadeAnimation.radius}")
it.println("globalActionsRadius: ${globalActionsSpring.radius}") it.println("globalActionsRadius: ${globalActionsSpring.radius}")
it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}") it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}")
it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius") it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius")
@@ -311,7 +432,7 @@ class NotificationShadeDepthController @Inject constructor(
private var view: View? = null private var view: View? = null
private var springAnimation = SpringAnimation(this, object : private var springAnimation = SpringAnimation(this, object :
FloatPropertyCompat<DepthAnimation>("blurRadius") { FloatPropertyCompat<DepthAnimation>("blurRadius") {
override fun setValue(rect: DepthAnimation?, value: Float) { override fun setValue(rect: DepthAnimation?, value: Float) {
radius = value.toInt() radius = value.toInt()
scheduleUpdate(view) scheduleUpdate(view)
@@ -343,5 +464,17 @@ class NotificationShadeDepthController @Inject constructor(
springAnimation.skipToEnd() 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)
}
} }
} }

View File

@@ -64,6 +64,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
@Mock private lateinit var root: View @Mock private lateinit var root: View
@Mock private lateinit var viewRootImpl: ViewRootImpl @Mock private lateinit var viewRootImpl: ViewRootImpl
@Mock private lateinit var shadeSpring: NotificationShadeDepthController.DepthAnimation @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 globalActionsSpring: NotificationShadeDepthController.DepthAnimation
@Mock private lateinit var brightnessSpring: NotificationShadeDepthController.DepthAnimation @Mock private lateinit var brightnessSpring: NotificationShadeDepthController.DepthAnimation
@JvmField @Rule val mockitoRule = MockitoJUnit.rule() @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
@@ -80,11 +81,15 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
`when`(blurUtils.blurRadiusOfRatio(anyFloat())).then { answer -> `when`(blurUtils.blurRadiusOfRatio(anyFloat())).then { answer ->
(answer.arguments[0] as Float * maxBlur).toInt() (answer.arguments[0] as Float * maxBlur).toInt()
} }
`when`(blurUtils.minBlurRadius).thenReturn(0)
`when`(blurUtils.maxBlurRadius).thenReturn(maxBlur)
notificationShadeDepthController = NotificationShadeDepthController( notificationShadeDepthController = NotificationShadeDepthController(
statusBarStateController, blurUtils, biometricUnlockController, statusBarStateController, blurUtils, biometricUnlockController,
keyguardStateController, choreographer, wallpaperManager, keyguardStateController, choreographer, wallpaperManager,
notificationShadeWindowController, dumpManager) notificationShadeWindowController, dumpManager)
notificationShadeDepthController.shadeSpring = shadeSpring notificationShadeDepthController.shadeSpring = shadeSpring
notificationShadeDepthController.shadeAnimation = shadeAnimation
notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring
notificationShadeDepthController.globalActionsSpring = globalActionsSpring notificationShadeDepthController.globalActionsSpring = globalActionsSpring
notificationShadeDepthController.root = root notificationShadeDepthController.root = root
@@ -104,16 +109,61 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
notificationShadeDepthController.onPanelExpansionChanged(1f /* expansion */, notificationShadeDepthController.onPanelExpansionChanged(1f /* expansion */,
false /* tracking */) false /* tracking */)
verify(shadeSpring).animateTo(eq(maxBlur), any()) 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 @Test
fun onStateChanged_reevalutesBlurs_ifSameRadiusAndNewState() { fun onStateChanged_reevalutesBlurs_ifSameRadiusAndNewState() {
onPanelExpansionChanged_apliesBlur_ifShade() onPanelExpansionChanged_apliesBlur_ifShade()
clearInvocations(shadeSpring) clearInvocations(shadeSpring)
clearInvocations(shadeAnimation)
statusBarState = StatusBarState.KEYGUARD statusBarState = StatusBarState.KEYGUARD
statusBarStateListener.onStateChanged(statusBarState) statusBarStateListener.onStateChanged(statusBarState)
verify(shadeSpring).animateTo(eq(0), any()) verify(shadeSpring).animateTo(eq(0), any())
verify(shadeAnimation).animateTo(eq(0), any())
} }
@Test @Test
@@ -147,6 +197,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
@Test @Test
fun updateBlurCallback_setsBlur_whenExpanded() { fun updateBlurCallback_setsBlur_whenExpanded() {
`when`(shadeSpring.radius).thenReturn(maxBlur) `when`(shadeSpring.radius).thenReturn(maxBlur)
`when`(shadeAnimation.radius).thenReturn(maxBlur)
notificationShadeDepthController.updateBlurCallback.doFrame(0) notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(blurUtils).applyBlur(any(), eq(maxBlur)) verify(blurUtils).applyBlur(any(), eq(maxBlur))
} }
@@ -154,6 +205,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
@Test @Test
fun updateBlurCallback_appLaunchAnimation_overridesZoom() { fun updateBlurCallback_appLaunchAnimation_overridesZoom() {
`when`(shadeSpring.radius).thenReturn(maxBlur) `when`(shadeSpring.radius).thenReturn(maxBlur)
`when`(shadeAnimation.radius).thenReturn(maxBlur)
val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters() val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters()
animProgress.linearProgress = 1f animProgress.linearProgress = 1f
notificationShadeDepthController.notificationLaunchAnimationParams = animProgress notificationShadeDepthController.notificationLaunchAnimationParams = animProgress
@@ -187,6 +239,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
`when`(brightnessSpring.ratio).thenReturn(1f) `when`(brightnessSpring.ratio).thenReturn(1f)
// And shade is blurred // And shade is blurred
`when`(shadeSpring.radius).thenReturn(maxBlur) `when`(shadeSpring.radius).thenReturn(maxBlur)
`when`(shadeAnimation.radius).thenReturn(maxBlur)
notificationShadeDepthController.updateBlurCallback.doFrame(0) notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(notificationShadeWindowController).setBackgroundBlurRadius(0) verify(notificationShadeWindowController).setBackgroundBlurRadius(0)
@@ -207,8 +260,10 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters() val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters()
animProgress.linearProgress = 0.5f animProgress.linearProgress = 0.5f
`when`(shadeSpring.radius).thenReturn(0) `when`(shadeSpring.radius).thenReturn(0)
`when`(shadeAnimation.radius).thenReturn(0)
notificationShadeDepthController.notificationLaunchAnimationParams = animProgress notificationShadeDepthController.notificationLaunchAnimationParams = animProgress
verify(shadeSpring, never()).animateTo(anyInt(), any()) verify(shadeSpring, never()).animateTo(anyInt(), any())
verify(shadeAnimation, never()).animateTo(anyInt(), any())
} }
private fun <T : Any> safeEq(value: T): T { private fun <T : Any> safeEq(value: T): T {