Merge "Update dwell ripple animation" into sc-v2-dev

This commit is contained in:
Beverly Tai
2021-12-21 14:09:37 +00:00
committed by Android (Google) Code Review
4 changed files with 107 additions and 147 deletions

View File

@@ -80,11 +80,6 @@ class AuthRippleController @Inject constructor(
private var circleReveal: LightRevealEffect? = null private var circleReveal: LightRevealEffect? = null
private var udfpsController: UdfpsController? = null private var udfpsController: UdfpsController? = null
private var dwellScale = 2f
private var expandedDwellScale = 2.5f
private var aodDwellScale = 1.9f
private var aodExpandedDwellScale = 2.3f
private var udfpsRadius: Float = -1f private var udfpsRadius: Float = -1f
override fun onInit() { override fun onInit() {
@@ -128,7 +123,7 @@ class AuthRippleController @Inject constructor(
updateSensorLocation() updateSensorLocation()
if (biometricSourceType == BiometricSourceType.FINGERPRINT && if (biometricSourceType == BiometricSourceType.FINGERPRINT &&
fingerprintSensorLocation != null) { fingerprintSensorLocation != null) {
mView.setSensorLocation(fingerprintSensorLocation!!) mView.setFingerprintSensorLocation(fingerprintSensorLocation!!, udfpsRadius)
showUnlockedRipple() showUnlockedRipple()
} else if (biometricSourceType == BiometricSourceType.FACE && } else if (biometricSourceType == BiometricSourceType.FACE &&
faceSensorLocation != null) { faceSensorLocation != null) {
@@ -241,24 +236,12 @@ class AuthRippleController @Inject constructor(
} }
private fun updateRippleColor() { private fun updateRippleColor() {
mView.setColor( mView.setLockScreenColor(Utils.getColorAttrDefaultColor(sysuiContext,
Utils.getColorAttr(sysuiContext, android.R.attr.colorAccent).defaultColor) R.attr.wallpaperTextColorAccent))
} }
private fun showDwellRipple() { private fun showDwellRipple() {
if (statusBarStateController.isDozing) { mView.startDwellRipple(statusBarStateController.isDozing)
mView.startDwellRipple(
/* startRadius */ udfpsRadius,
/* endRadius */ udfpsRadius * aodDwellScale,
/* expandedRadius */ udfpsRadius * aodExpandedDwellScale,
/* isDozing */ true)
} else {
mView.startDwellRipple(
/* startRadius */ udfpsRadius,
/* endRadius */ udfpsRadius * dwellScale,
/* expandedRadius */ udfpsRadius * expandedDwellScale,
/* isDozing */ false)
}
} }
private val keyguardUpdateMonitorCallback = private val keyguardUpdateMonitorCallback =
@@ -295,7 +278,7 @@ class AuthRippleController @Inject constructor(
return return
} }
mView.setSensorLocation(fingerprintSensorLocation!!) mView.setFingerprintSensorLocation(fingerprintSensorLocation!!, udfpsRadius)
showDwellRipple() showDwellRipple()
} }
@@ -307,8 +290,8 @@ class AuthRippleController @Inject constructor(
private val authControllerCallback = private val authControllerCallback =
object : AuthController.Callback { object : AuthController.Callback {
override fun onAllAuthenticatorsRegistered() { override fun onAllAuthenticatorsRegistered() {
updateSensorLocation()
updateUdfpsDependentParams() updateUdfpsDependentParams()
updateSensorLocation()
} }
override fun onEnrollmentsChanged() { override fun onEnrollmentsChanged() {
@@ -329,20 +312,6 @@ class AuthRippleController @Inject constructor(
} }
inner class AuthRippleCommand : Command { inner class AuthRippleCommand : Command {
fun printLockScreenDwellInfo(pw: PrintWriter) {
pw.println("lock screen dwell ripple: " +
"\n\tsensorLocation=$fingerprintSensorLocation" +
"\n\tdwellScale=$dwellScale" +
"\n\tdwellExpand=$expandedDwellScale")
}
fun printAodDwellInfo(pw: PrintWriter) {
pw.println("aod dwell ripple: " +
"\n\tsensorLocation=$fingerprintSensorLocation" +
"\n\tdwellScale=$aodDwellScale" +
"\n\tdwellExpand=$aodExpandedDwellScale")
}
override fun execute(pw: PrintWriter, args: List<String>) { override fun execute(pw: PrintWriter, args: List<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
invalidCommand(pw) invalidCommand(pw)
@@ -350,11 +319,9 @@ class AuthRippleController @Inject constructor(
when (args[0]) { when (args[0]) {
"dwell" -> { "dwell" -> {
showDwellRipple() showDwellRipple()
if (statusBarStateController.isDozing) { pw.println("lock screen dwell ripple: " +
printAodDwellInfo(pw) "\n\tsensorLocation=$fingerprintSensorLocation" +
} else { "\n\tudfpsRadius=$udfpsRadius")
printLockScreenDwellInfo(pw)
}
} }
"fingerprint" -> { "fingerprint" -> {
updateSensorLocation() updateSensorLocation()

View File

@@ -21,6 +21,7 @@ import android.animation.AnimatorSet
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.content.Context import android.content.Context
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint import android.graphics.Paint
import android.graphics.PointF import android.graphics.PointF
import android.util.AttributeSet import android.util.AttributeSet
@@ -28,6 +29,7 @@ import android.view.View
import android.view.animation.PathInterpolator import android.view.animation.PathInterpolator
import com.android.internal.graphics.ColorUtils import com.android.internal.graphics.ColorUtils
import com.android.systemui.animation.Interpolators import com.android.systemui.animation.Interpolators
import com.android.systemui.statusbar.charging.DwellRippleShader
import com.android.systemui.statusbar.charging.RippleShader import com.android.systemui.statusbar.charging.RippleShader
private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
@@ -43,23 +45,32 @@ private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
private val retractInterpolator = PathInterpolator(.05f, .93f, .1f, 1f) private val retractInterpolator = PathInterpolator(.05f, .93f, .1f, 1f)
private val dwellPulseDuration = 50L private val dwellPulseDuration = 100L
private val dwellAlphaDuration = dwellPulseDuration private val dwellExpandDuration = 2000L - dwellPulseDuration
private val dwellAlpha: Float = 1f
private val dwellExpandDuration = 1200L - dwellPulseDuration
private val aodDwellPulseDuration = 50L private var drawDwell: Boolean = false
private var aodDwellAlphaDuration = aodDwellPulseDuration private var drawRipple: Boolean = false
private var aodDwellAlpha: Float = .8f
private var aodDwellExpandDuration = 1200L - aodDwellPulseDuration
private var lockScreenColorVal = Color.WHITE
private val retractDuration = 400L private val retractDuration = 400L
private var alphaInDuration: Long = 0 private var alphaInDuration: Long = 0
private var unlockedRippleInProgress: Boolean = false private var unlockedRippleInProgress: Boolean = false
private val dwellShader = DwellRippleShader()
private val dwellPaint = Paint()
private val rippleShader = RippleShader() private val rippleShader = RippleShader()
private val ripplePaint = Paint() private val ripplePaint = Paint()
private var retractAnimator: Animator? = null private var retractAnimator: Animator? = null
private var dwellPulseOutAnimator: Animator? = null private var dwellPulseOutAnimator: Animator? = null
private var dwellRadius: Float = 0f
set(value) {
dwellShader.maxRadius = value
field = value
}
private var dwellOrigin: PointF = PointF()
set(value) {
dwellShader.origin = value
field = value
}
private var radius: Float = 0f private var radius: Float = 0f
set(value) { set(value) {
rippleShader.radius = value rippleShader.radius = value
@@ -76,6 +87,11 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
rippleShader.progress = 0f rippleShader.progress = 0f
rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
ripplePaint.shader = rippleShader ripplePaint.shader = rippleShader
dwellShader.color = 0xffffffff.toInt() // default color
dwellShader.progress = 0f
dwellShader.distortionStrength = .4f
dwellPaint.shader = dwellShader
visibility = GONE visibility = GONE
} }
@@ -84,6 +100,13 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
radius = maxOf(location.x, location.y, width - location.x, height - location.y).toFloat() radius = maxOf(location.x, location.y, width - location.x, height - location.y).toFloat()
} }
fun setFingerprintSensorLocation(location: PointF, sensorRadius: Float) {
origin = location
radius = maxOf(location.x, location.y, width - location.x, height - location.y).toFloat()
dwellOrigin = location
dwellRadius = sensorRadius * 1.5f
}
fun setAlphaInDuration(duration: Long) { fun setAlphaInDuration(duration: Long) {
alphaInDuration = duration alphaInDuration = duration
} }
@@ -97,14 +120,14 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
} }
if (dwellPulseOutAnimator?.isRunning == true) { if (dwellPulseOutAnimator?.isRunning == true) {
val retractRippleAnimator = ValueAnimator.ofFloat(rippleShader.progress, 0f) val retractRippleAnimator = ValueAnimator.ofFloat(dwellShader.progress, 0f)
.apply { .apply {
interpolator = retractInterpolator interpolator = retractInterpolator
duration = retractDuration duration = retractDuration
addUpdateListener { animator -> addUpdateListener { animator ->
val now = animator.currentPlayTime val now = animator.currentPlayTime
rippleShader.progress = animator.animatedValue as Float dwellShader.progress = animator.animatedValue as Float
rippleShader.time = now.toFloat() dwellShader.time = now.toFloat()
invalidate() invalidate()
} }
@@ -114,8 +137,8 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
interpolator = Interpolators.LINEAR interpolator = Interpolators.LINEAR
duration = retractDuration duration = retractDuration
addUpdateListener { animator -> addUpdateListener { animator ->
rippleShader.color = ColorUtils.setAlphaComponent( dwellShader.color = ColorUtils.setAlphaComponent(
rippleShader.color, dwellShader.color,
animator.animatedValue as Int animator.animatedValue as Int
) )
invalidate() invalidate()
@@ -127,13 +150,12 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
addListener(object : AnimatorListenerAdapter() { addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) { override fun onAnimationStart(animation: Animator?) {
dwellPulseOutAnimator?.cancel() dwellPulseOutAnimator?.cancel()
rippleShader.shouldFadeOutRipple = false drawDwell = true
visibility = VISIBLE
} }
override fun onAnimationEnd(animation: Animator?) { override fun onAnimationEnd(animation: Animator?) {
visibility = GONE drawDwell = false
resetRippleAlpha() resetDwellAlpha()
} }
}) })
start() start()
@@ -142,101 +164,54 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
} }
/** /**
* Ripple that moves animates from an outer ripple ring of * Plays a ripple animation that grows to the dwellRadius with distortion.
* startRadius => endRadius => expandedRadius
*/ */
fun startDwellRipple( fun startDwellRipple(isDozing: Boolean) {
startRadius: Float,
endRadius: Float,
expandedRadius: Float,
isDozing: Boolean
) {
if (unlockedRippleInProgress || dwellPulseOutAnimator?.isRunning == true) { if (unlockedRippleInProgress || dwellPulseOutAnimator?.isRunning == true) {
return return
} }
// we divide by 4 because the desired startRadius and endRadius is for the ripple's outer updateDwellRippleColor(isDozing)
// ring see RippleShader
val startDwellProgress = startRadius / radius / 4f
val endInitialDwellProgress = endRadius / radius / 4f
val endExpandDwellProgress = expandedRadius / radius / 4f
val alpha = if (isDozing) aodDwellAlpha else dwellAlpha val dwellPulseOutRippleAnimator = ValueAnimator.ofFloat(0f, .8f).apply {
val pulseOutEndAlpha = (255 * alpha).toInt() interpolator = Interpolators.LINEAR
val expandDwellEndAlpha = kotlin.math.min((255 * (alpha + .25f)).toInt(), 255) duration = dwellPulseDuration
val dwellPulseOutRippleAnimator = ValueAnimator.ofFloat(startDwellProgress,
endInitialDwellProgress).apply {
interpolator = Interpolators.LINEAR_OUT_SLOW_IN
duration = if (isDozing) aodDwellPulseDuration else dwellPulseDuration
addUpdateListener { animator -> addUpdateListener { animator ->
val now = animator.currentPlayTime val now = animator.currentPlayTime
rippleShader.progress = animator.animatedValue as Float dwellShader.progress = animator.animatedValue as Float
rippleShader.time = now.toFloat() dwellShader.time = now.toFloat()
invalidate() invalidate()
} }
} }
val dwellPulseOutAlphaAnimator = ValueAnimator.ofInt(0, pulseOutEndAlpha).apply {
interpolator = Interpolators.LINEAR
duration = if (isDozing) aodDwellAlphaDuration else dwellAlphaDuration
addUpdateListener { animator ->
rippleShader.color = ColorUtils.setAlphaComponent(
rippleShader.color,
animator.animatedValue as Int
)
invalidate()
}
}
// slowly animate outwards until we receive a call to retractRipple or startUnlockedRipple // slowly animate outwards until we receive a call to retractRipple or startUnlockedRipple
val expandDwellRippleAnimator = ValueAnimator.ofFloat(endInitialDwellProgress, val expandDwellRippleAnimator = ValueAnimator.ofFloat(.8f, 1f).apply {
endExpandDwellProgress).apply {
interpolator = Interpolators.LINEAR_OUT_SLOW_IN interpolator = Interpolators.LINEAR_OUT_SLOW_IN
duration = if (isDozing) aodDwellExpandDuration else dwellExpandDuration duration = dwellExpandDuration
addUpdateListener { animator -> addUpdateListener { animator ->
val now = animator.currentPlayTime val now = animator.currentPlayTime
rippleShader.progress = animator.animatedValue as Float dwellShader.progress = animator.animatedValue as Float
rippleShader.time = now.toFloat() dwellShader.time = now.toFloat()
invalidate() invalidate()
} }
} }
val expandDwellAlphaAnimator = ValueAnimator.ofInt(pulseOutEndAlpha, expandDwellEndAlpha)
.apply {
interpolator = Interpolators.LINEAR
duration = if (isDozing) aodDwellExpandDuration else dwellExpandDuration
addUpdateListener { animator ->
rippleShader.color = ColorUtils.setAlphaComponent(
rippleShader.color,
animator.animatedValue as Int
)
invalidate()
}
}
val initialDwellPulseOutAnimator = AnimatorSet().apply {
playTogether(dwellPulseOutRippleAnimator, dwellPulseOutAlphaAnimator)
}
val expandDwellAnimator = AnimatorSet().apply {
playTogether(expandDwellRippleAnimator, expandDwellAlphaAnimator)
}
dwellPulseOutAnimator = AnimatorSet().apply { dwellPulseOutAnimator = AnimatorSet().apply {
playSequentially( playSequentially(
initialDwellPulseOutAnimator, dwellPulseOutRippleAnimator,
expandDwellAnimator expandDwellRippleAnimator
) )
addListener(object : AnimatorListenerAdapter() { addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) { override fun onAnimationStart(animation: Animator?) {
retractAnimator?.cancel() retractAnimator?.cancel()
rippleShader.shouldFadeOutRipple = false
visibility = VISIBLE visibility = VISIBLE
drawDwell = true
} }
override fun onAnimationEnd(animation: Animator?) { override fun onAnimationEnd(animation: Animator?) {
visibility = GONE drawDwell = false
resetRippleAlpha() resetRippleAlpha()
} }
}) })
@@ -252,16 +227,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
return // Ignore if ripple effect is already playing return // Ignore if ripple effect is already playing
} }
var rippleStart = 0f val rippleAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
var alphaDuration = alphaInDuration
if (dwellPulseOutAnimator?.isRunning == true || retractAnimator?.isRunning == true) {
rippleStart = rippleShader.progress
alphaDuration = 0
dwellPulseOutAnimator?.cancel()
retractAnimator?.cancel()
}
val rippleAnimator = ValueAnimator.ofFloat(rippleStart, 1f).apply {
interpolator = Interpolators.LINEAR_OUT_SLOW_IN interpolator = Interpolators.LINEAR_OUT_SLOW_IN
duration = AuthRippleController.RIPPLE_ANIMATION_DURATION duration = AuthRippleController.RIPPLE_ANIMATION_DURATION
addUpdateListener { animator -> addUpdateListener { animator ->
@@ -274,7 +240,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
} }
val alphaInAnimator = ValueAnimator.ofInt(0, 255).apply { val alphaInAnimator = ValueAnimator.ofInt(0, 255).apply {
duration = alphaDuration duration = alphaInDuration
addUpdateListener { animator -> addUpdateListener { animator ->
rippleShader.color = ColorUtils.setAlphaComponent( rippleShader.color = ColorUtils.setAlphaComponent(
rippleShader.color, rippleShader.color,
@@ -293,12 +259,14 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
override fun onAnimationStart(animation: Animator?) { override fun onAnimationStart(animation: Animator?) {
unlockedRippleInProgress = true unlockedRippleInProgress = true
rippleShader.shouldFadeOutRipple = true rippleShader.shouldFadeOutRipple = true
drawRipple = true
visibility = VISIBLE visibility = VISIBLE
} }
override fun onAnimationEnd(animation: Animator?) { override fun onAnimationEnd(animation: Animator?) {
onAnimationEnd?.run() onAnimationEnd?.run()
unlockedRippleInProgress = false unlockedRippleInProgress = false
drawRipple = false
visibility = GONE visibility = GONE
} }
}) })
@@ -313,17 +281,42 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
) )
} }
fun setColor(color: Int) { fun setLockScreenColor(color: Int) {
rippleShader.color = color lockScreenColorVal = color
rippleShader.color = lockScreenColorVal
resetRippleAlpha() resetRippleAlpha()
} }
fun updateDwellRippleColor(isDozing: Boolean) {
if (isDozing) {
dwellShader.color = Color.WHITE
} else {
dwellShader.color = lockScreenColorVal
}
resetDwellAlpha()
}
fun resetDwellAlpha() {
dwellShader.color = ColorUtils.setAlphaComponent(
dwellShader.color,
255
)
}
override fun onDraw(canvas: Canvas?) { override fun onDraw(canvas: Canvas?) {
// To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover // To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover
// the active effect area. Values here should be kept in sync with the // the active effect area. Values here should be kept in sync with the
// animation implementation in the ripple shader. // animation implementation in the ripple shader.
val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) * if (drawDwell) {
val maskRadius = (1 - (1 - dwellShader.progress) * (1 - dwellShader.progress) *
(1 - dwellShader.progress)) * dwellRadius * 2f
canvas?.drawCircle(dwellOrigin.x, dwellOrigin.y, maskRadius, dwellPaint)
}
if (drawRipple) {
val mask = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
(1 - rippleShader.progress)) * radius * 2f (1 - rippleShader.progress)) * radius * 2f
canvas?.drawCircle(origin.x, origin.y, maskRadius, ripplePaint) canvas?.drawCircle(origin.x, origin.y, mask, ripplePaint)
}
} }
} }

View File

@@ -59,8 +59,8 @@ class DwellRippleShader internal constructor() : RuntimeShader(SHADER, false) {
} }
vec2 distort(vec2 p, float time, float distort_amount_xy, float frequency) { vec2 distort(vec2 p, float time, float distort_amount_xy, float frequency) {
return p + vec2(sin(p.x * frequency + in_phase1), return p + vec2(sin(p.y * frequency + in_phase1),
cos(p.y * frequency * 1.23 + in_phase2)) * distort_amount_xy; cos(p.x * frequency * -1.23 + in_phase2)) * distort_amount_xy;
} }
vec4 ripple(vec2 p, float distort_xy, float frequency) { vec4 ripple(vec2 p, float distort_xy, float frequency) {
@@ -73,11 +73,11 @@ class DwellRippleShader internal constructor() : RuntimeShader(SHADER, false) {
""" """
private const val SHADER_MAIN = """vec4 main(vec2 p) { private const val SHADER_MAIN = """vec4 main(vec2 p) {
vec4 color1 = ripple(p, vec4 color1 = ripple(p,
12 * in_distortion_strength, // distort_xy 34 * in_distortion_strength, // distort_xy
0.012 // frequency 0.012 // frequency
); );
vec4 color2 = ripple(p, vec4 color2 = ripple(p,
17.5 * in_distortion_strength, // distort_xy 49 * in_distortion_strength, // distort_xy
0.018 // frequency 0.018 // frequency
); );
// Alpha blend between two layers. // Alpha blend between two layers.
@@ -128,8 +128,8 @@ class DwellRippleShader internal constructor() : RuntimeShader(SHADER, false) {
set(value) { set(value) {
field = value * 0.001f field = value * 0.001f
setUniform("in_time", field) setUniform("in_time", field)
setUniform("in_phase1", field * 2f + 0.367f) setUniform("in_phase1", field * 3f + 0.367f)
setUniform("in_phase2", field * 5.2f * 1.531f) setUniform("in_phase2", field * 7.2f * 1.531f)
} }
/** /**

View File

@@ -131,7 +131,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
false /* isStrongBiometric */) false /* isStrongBiometric */)
// THEN update sensor location and show ripple // THEN update sensor location and show ripple
verify(rippleView).setSensorLocation(fpsLocation) verify(rippleView).setFingerprintSensorLocation(fpsLocation, -1f)
verify(rippleView).startUnlockedRipple(any()) verify(rippleView).startUnlockedRipple(any())
} }
@@ -292,10 +292,10 @@ class AuthRippleControllerTest : SysuiTestCase() {
reset(rippleView) reset(rippleView)
captor.value.onThemeChanged() captor.value.onThemeChanged()
verify(rippleView).setColor(ArgumentMatchers.anyInt()) verify(rippleView).setLockScreenColor(ArgumentMatchers.anyInt())
reset(rippleView) reset(rippleView)
captor.value.onUiModeChanged() captor.value.onUiModeChanged()
verify(rippleView).setColor(ArgumentMatchers.anyInt()) verify(rippleView).setLockScreenColor(ArgumentMatchers.anyInt())
} }
} }