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

Change-Id: Ic159c75fcfe89c1e2a89d60ea00c916a59d47f24
This commit is contained in:
Lucas Dupin
2020-04-30 20:39:06 +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,
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

View File

@@ -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)
}
}
}

View File

@@ -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 {