Update dwell ripple parameters

Targeted effect:
* Immediate visual response on touch down at
  sufficiently visible radius for variable finger sizes.
* Then decelerate radius expansion to convey tension / elicit
  force and constrain at limited max radius past first stage.
* Bump values on non-AOD surface to improve visibility with
  more variable backgrounds.

Test: `adb shell cmd statusbar auth-ripple dwell` expands ripple (but
doesn't animate the retract)
Test: manual, atest SystemUITests
Bug: 198864174
Change-Id: I1fb2e456e89a824b1b749150e3d82c6bc3223f47
This commit is contained in:
Beverly
2021-09-14 16:27:36 -04:00
committed by Beverly Tai
parent aaba1884bb
commit bc9015e209
3 changed files with 65 additions and 58 deletions

View File

@@ -40,6 +40,7 @@ import com.android.systemui.util.ViewController
import java.io.PrintWriter import java.io.PrintWriter
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Provider import javax.inject.Provider
import com.android.systemui.plugins.statusbar.StatusBarStateController
/*** /***
* Controls the ripple effect that shows when authentication is successful. * Controls the ripple effect that shows when authentication is successful.
@@ -57,6 +58,7 @@ class AuthRippleController @Inject constructor(
private val bypassController: KeyguardBypassController, private val bypassController: KeyguardBypassController,
private val biometricUnlockController: BiometricUnlockController, private val biometricUnlockController: BiometricUnlockController,
private val udfpsControllerProvider: Provider<UdfpsController>, private val udfpsControllerProvider: Provider<UdfpsController>,
private val statusBarStateController: StatusBarStateController,
rippleView: AuthRippleView? rippleView: AuthRippleView?
) : ViewController<AuthRippleView>(rippleView) { ) : ViewController<AuthRippleView>(rippleView) {
var fingerprintSensorLocation: PointF? = null var fingerprintSensorLocation: PointF? = null
@@ -64,8 +66,11 @@ 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 dwellScale = 2f
private var expandedDwellScale = 2.5f 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() {
@@ -163,6 +168,22 @@ class AuthRippleController @Inject constructor(
Utils.getColorAttr(sysuiContext, android.R.attr.colorAccent).defaultColor) Utils.getColorAttr(sysuiContext, android.R.attr.colorAccent).defaultColor)
} }
private fun showDwellRipple() {
if (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 =
object : KeyguardUpdateMonitorCallback() { object : KeyguardUpdateMonitorCallback() {
override fun onBiometricAuthenticated( override fun onBiometricAuthenticated(
@@ -204,10 +225,7 @@ class AuthRippleController @Inject constructor(
} }
mView.setSensorLocation(fingerprintSensorLocation!!) mView.setSensorLocation(fingerprintSensorLocation!!)
mView.startDwellRipple( showDwellRipple()
/* startRadius */ udfpsRadius,
/* endRadius */ udfpsRadius * dwellScale,
/* expandedRadius */ udfpsRadius * expandedDwellScale)
} }
override fun onFingerUp() { override fun onFingerUp() {
@@ -234,14 +252,18 @@ class AuthRippleController @Inject constructor(
} }
inner class AuthRippleCommand : Command { inner class AuthRippleCommand : Command {
fun printDwellInfo(pw: PrintWriter) { fun printLockScreenDwellInfo(pw: PrintWriter) {
pw.println("dwell ripple: " + pw.println("lock screen dwell ripple: " +
"\n\tsensorLocation=$fingerprintSensorLocation" + "\n\tsensorLocation=$fingerprintSensorLocation" +
"\n\tdwellScale=$dwellScale" + "\n\tdwellScale=$dwellScale" +
"\n\tdwellAlpha=${mView.dwellAlpha}, " + "\n\tdwellExpand=$expandedDwellScale")
"duration=${mView.dwellAlphaDuration}" + }
"\n\tdwellExpand=$expandedDwellScale" +
"\n\t(crash systemui to reset to default)") 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>) {
@@ -249,40 +271,12 @@ class AuthRippleController @Inject constructor(
invalidCommand(pw) invalidCommand(pw)
} else { } else {
when (args[0]) { when (args[0]) {
"dwellScale" -> { "dwell" -> {
if (args.size > 1 && args[1].toFloatOrNull() != null) { showDwellRipple()
dwellScale = args[1].toFloat() if (statusBarStateController.isDozing) {
printDwellInfo(pw) printAodDwellInfo(pw)
} else { } else {
pw.println("expected float argument <dwellScale>") printLockScreenDwellInfo(pw)
}
}
"dwellAlpha" -> {
if (args.size > 2 && args[1].toFloatOrNull() != null &&
args[2].toLongOrNull() != null) {
mView.dwellAlpha = args[1].toFloat()
if (args[2].toFloat() > 200L) {
pw.println("alpha animation duration must be less than 200ms.")
}
mView.dwellAlphaDuration = kotlin.math.min(args[2].toLong(), 200L)
printDwellInfo(pw)
} else {
pw.println("expected two float arguments:" +
" <dwellAlpha> <dwellAlphaDuration>")
}
}
"dwellExpand" -> {
if (args.size > 1 && args[1].toFloatOrNull() != null) {
val expandedScale = args[1].toFloat()
if (expandedScale <= dwellScale) {
pw.println("invalid expandedScale. must be greater than " +
"dwellScale=$dwellScale, but given $expandedScale")
} else {
expandedDwellScale = expandedScale
}
printDwellInfo(pw)
} else {
pw.println("expected float argument <expandedScale>")
} }
} }
"fingerprint" -> { "fingerprint" -> {
@@ -313,9 +307,7 @@ class AuthRippleController @Inject constructor(
override fun help(pw: PrintWriter) { override fun help(pw: PrintWriter) {
pw.println("Usage: adb shell cmd statusbar auth-ripple <command>") pw.println("Usage: adb shell cmd statusbar auth-ripple <command>")
pw.println("Available commands:") pw.println("Available commands:")
pw.println(" dwellScale <200ms_scale: float>") pw.println(" dwell")
pw.println(" dwellAlpha <alpha: float> <duration : long>")
pw.println(" dwellExpand <expanded_scale: float>")
pw.println(" fingerprint") pw.println(" fingerprint")
pw.println(" face") pw.println(" face")
pw.println(" custom <x-location: int> <y-location: int>") pw.println(" custom <x-location: int> <y-location: int>")

View File

@@ -45,12 +45,18 @@ 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 = 200L
var dwellAlphaDuration = dwellPulseDuration
private val dwellExpandDuration = 1200L - dwellPulseDuration
private val retractDuration = 400L
var dwellAlpha: Float = .5f private val dwellPulseDuration = 50L
private val dwellAlphaDuration = dwellPulseDuration
private val dwellAlpha: Float = 1f
private val dwellExpandDuration = 1200L - dwellPulseDuration
private val aodDwellPulseDuration = 50L
private var aodDwellAlphaDuration = aodDwellPulseDuration
private var aodDwellAlpha: Float = .8f
private var aodDwellExpandDuration = 1200L - aodDwellPulseDuration
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 rippleShader = RippleShader() private val rippleShader = RippleShader()
@@ -142,7 +148,12 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
* Ripple that moves animates from an outer ripple ring of * Ripple that moves animates from an outer ripple ring of
* startRadius => endRadius => expandedRadius * startRadius => endRadius => expandedRadius
*/ */
fun startDwellRipple(startRadius: Float, endRadius: Float, expandedRadius: Float) { fun startDwellRipple(
startRadius: Float,
endRadius: Float,
expandedRadius: Float,
isDozing: Boolean
) {
if (unlockedRippleInProgress || dwellPulseOutAnimator?.isRunning == true) { if (unlockedRippleInProgress || dwellPulseOutAnimator?.isRunning == true) {
return return
} }
@@ -153,12 +164,13 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
val endInitialDwellProgress = endRadius / radius / 4f val endInitialDwellProgress = endRadius / radius / 4f
val endExpandDwellProgress = expandedRadius / radius / 4f val endExpandDwellProgress = expandedRadius / radius / 4f
val pulseOutEndAlpha = (255 * dwellAlpha).toInt() val alpha = if (isDozing) aodDwellAlpha else dwellAlpha
val expandDwellEndAlpha = kotlin.math.min((255 * (dwellAlpha + .25f)).toInt(), 255) val pulseOutEndAlpha = (255 * alpha).toInt()
val expandDwellEndAlpha = kotlin.math.min((255 * (alpha + .25f)).toInt(), 255)
val dwellPulseOutRippleAnimator = ValueAnimator.ofFloat(startDwellProgress, val dwellPulseOutRippleAnimator = ValueAnimator.ofFloat(startDwellProgress,
endInitialDwellProgress).apply { endInitialDwellProgress).apply {
interpolator = Interpolators.LINEAR_OUT_SLOW_IN interpolator = Interpolators.LINEAR_OUT_SLOW_IN
duration = dwellPulseDuration duration = if (isDozing) aodDwellPulseDuration else dwellPulseDuration
addUpdateListener { animator -> addUpdateListener { animator ->
val now = animator.currentPlayTime val now = animator.currentPlayTime
rippleShader.progress = animator.animatedValue as Float rippleShader.progress = animator.animatedValue as Float
@@ -170,7 +182,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
val dwellPulseOutAlphaAnimator = ValueAnimator.ofInt(0, pulseOutEndAlpha).apply { val dwellPulseOutAlphaAnimator = ValueAnimator.ofInt(0, pulseOutEndAlpha).apply {
interpolator = Interpolators.LINEAR interpolator = Interpolators.LINEAR
duration = dwellAlphaDuration duration = if (isDozing) aodDwellAlphaDuration else dwellAlphaDuration
addUpdateListener { animator -> addUpdateListener { animator ->
rippleShader.color = ColorUtils.setAlphaComponent( rippleShader.color = ColorUtils.setAlphaComponent(
rippleShader.color, rippleShader.color,
@@ -184,7 +196,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
val expandDwellRippleAnimator = ValueAnimator.ofFloat(endInitialDwellProgress, val expandDwellRippleAnimator = ValueAnimator.ofFloat(endInitialDwellProgress,
endExpandDwellProgress).apply { endExpandDwellProgress).apply {
interpolator = Interpolators.LINEAR_OUT_SLOW_IN interpolator = Interpolators.LINEAR_OUT_SLOW_IN
duration = dwellExpandDuration duration = if (isDozing) aodDwellExpandDuration else dwellExpandDuration
addUpdateListener { animator -> addUpdateListener { animator ->
val now = animator.currentPlayTime val now = animator.currentPlayTime
rippleShader.progress = animator.animatedValue as Float rippleShader.progress = animator.animatedValue as Float
@@ -197,7 +209,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
val expandDwellAlphaAnimator = ValueAnimator.ofInt(pulseOutEndAlpha, expandDwellEndAlpha) val expandDwellAlphaAnimator = ValueAnimator.ofInt(pulseOutEndAlpha, expandDwellEndAlpha)
.apply { .apply {
interpolator = Interpolators.LINEAR interpolator = Interpolators.LINEAR
duration = dwellExpandDuration duration = if (isDozing) aodDwellExpandDuration else dwellExpandDuration
addUpdateListener { animator -> addUpdateListener { animator ->
rippleShader.color = ColorUtils.setAlphaComponent( rippleShader.color = ColorUtils.setAlphaComponent(
rippleShader.color, rippleShader.color,

View File

@@ -23,6 +23,7 @@ import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.BiometricUnlockController
@@ -59,6 +60,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
@Mock private lateinit var biometricUnlockController: BiometricUnlockController @Mock private lateinit var biometricUnlockController: BiometricUnlockController
@Mock private lateinit var udfpsControllerProvider: Provider<UdfpsController> @Mock private lateinit var udfpsControllerProvider: Provider<UdfpsController>
@Mock private lateinit var udfpsController: UdfpsController @Mock private lateinit var udfpsController: UdfpsController
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Before @Before
fun setUp() { fun setUp() {
@@ -76,6 +78,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
bypassController, bypassController,
biometricUnlockController, biometricUnlockController,
udfpsControllerProvider, udfpsControllerProvider,
statusBarStateController,
rippleView rippleView
) )
controller.init() controller.init()