Add dwell-ripple am: 3ad5b8bb1e

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15779694

Change-Id: I19c412766181347cbfb51b41f6732782afa80f66
This commit is contained in:
Beverly
2021-09-11 02:56:55 +00:00
committed by Automerger Merge Worker
5 changed files with 368 additions and 30 deletions

View File

@@ -20,6 +20,7 @@ import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.PointF import android.graphics.PointF
import android.hardware.biometrics.BiometricSourceType import android.hardware.biometrics.BiometricSourceType
import android.util.Log
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.keyguard.KeyguardUpdateMonitorCallback
@@ -38,6 +39,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.ViewController 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
/*** /***
* Controls the ripple effect that shows when authentication is successful. * Controls the ripple effect that shows when authentication is successful.
@@ -54,12 +56,18 @@ class AuthRippleController @Inject constructor(
private val notificationShadeWindowController: NotificationShadeWindowController, private val notificationShadeWindowController: NotificationShadeWindowController,
private val bypassController: KeyguardBypassController, private val bypassController: KeyguardBypassController,
private val biometricUnlockController: BiometricUnlockController, private val biometricUnlockController: BiometricUnlockController,
private val udfpsControllerProvider: Provider<UdfpsController>,
rippleView: AuthRippleView? rippleView: AuthRippleView?
) : ViewController<AuthRippleView>(rippleView) { ) : ViewController<AuthRippleView>(rippleView) {
var fingerprintSensorLocation: PointF? = null var fingerprintSensorLocation: PointF? = null
private var faceSensorLocation: PointF? = null private var faceSensorLocation: PointF? = null
private var circleReveal: LightRevealEffect? = null private var circleReveal: LightRevealEffect? = null
private var udfpsController: UdfpsController? = null
private var dwellScale = 2f
private var expandedDwellScale = 2.5f
private var udfpsRadius: Float = -1f
override fun onInit() { override fun onInit() {
mView.setAlphaInDuration(sysuiContext.resources.getInteger( mView.setAlphaInDuration(sysuiContext.resources.getInteger(
R.integer.auth_ripple_alpha_in_duration).toLong()) R.integer.auth_ripple_alpha_in_duration).toLong())
@@ -67,9 +75,11 @@ class AuthRippleController @Inject constructor(
@VisibleForTesting @VisibleForTesting
public override fun onViewAttached() { public override fun onViewAttached() {
authController.addCallback(authControllerCallback)
updateRippleColor() updateRippleColor()
updateSensorLocation() updateSensorLocation()
authController.addCallback(authControllerCallback) updateUdfpsDependentParams()
udfpsController?.addCallback(udfpsControllerCallback)
configurationController.addCallback(configurationChangedListener) configurationController.addCallback(configurationChangedListener)
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() } commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() }
@@ -77,6 +87,7 @@ class AuthRippleController @Inject constructor(
@VisibleForTesting @VisibleForTesting
public override fun onViewDetached() { public override fun onViewDetached() {
udfpsController?.removeCallback(udfpsControllerCallback)
authController.removeCallback(authControllerCallback) authController.removeCallback(authControllerCallback)
keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback) keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
configurationController.removeCallback(configurationChangedListener) configurationController.removeCallback(configurationChangedListener)
@@ -94,18 +105,18 @@ class AuthRippleController @Inject constructor(
if (biometricSourceType == BiometricSourceType.FINGERPRINT && if (biometricSourceType == BiometricSourceType.FINGERPRINT &&
fingerprintSensorLocation != null) { fingerprintSensorLocation != null) {
mView.setSensorLocation(fingerprintSensorLocation!!) mView.setSensorLocation(fingerprintSensorLocation!!)
showRipple() showUnlockedRipple()
} else if (biometricSourceType == BiometricSourceType.FACE && } else if (biometricSourceType == BiometricSourceType.FACE &&
faceSensorLocation != null) { faceSensorLocation != null) {
if (!bypassController.canBypass()) { if (!bypassController.canBypass()) {
return return
} }
mView.setSensorLocation(faceSensorLocation!!) mView.setSensorLocation(faceSensorLocation!!)
showRipple() showUnlockedRipple()
} }
} }
private fun showRipple() { private fun showUnlockedRipple() {
notificationShadeWindowController.setForcePluginOpen(true, this) notificationShadeWindowController.setForcePluginOpen(true, this)
val biometricUnlockMode = biometricUnlockController.mode val biometricUnlockMode = biometricUnlockController.mode
val useCircleReveal = circleReveal != null && val useCircleReveal = circleReveal != null &&
@@ -117,7 +128,7 @@ class AuthRippleController @Inject constructor(
lightRevealScrim?.revealEffect = circleReveal!! lightRevealScrim?.revealEffect = circleReveal!!
} }
mView.startRipple( mView.startUnlockedRipple(
/* end runnable */ /* end runnable */
Runnable { Runnable {
notificationShadeWindowController.setForcePluginOpen(false, this) notificationShadeWindowController.setForcePluginOpen(false, this)
@@ -152,7 +163,7 @@ class AuthRippleController @Inject constructor(
Utils.getColorAttr(sysuiContext, android.R.attr.colorAccent).defaultColor) Utils.getColorAttr(sysuiContext, android.R.attr.colorAccent).defaultColor)
} }
val keyguardUpdateMonitorCallback = private val keyguardUpdateMonitorCallback =
object : KeyguardUpdateMonitorCallback() { object : KeyguardUpdateMonitorCallback() {
override fun onBiometricAuthenticated( override fun onBiometricAuthenticated(
userId: Int, userId: Int,
@@ -161,9 +172,13 @@ class AuthRippleController @Inject constructor(
) { ) {
showRipple(biometricSourceType) showRipple(biometricSourceType)
} }
override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) {
mView.retractRipple()
}
} }
val configurationChangedListener = private val configurationChangedListener =
object : ConfigurationController.ConfigurationListener { object : ConfigurationController.ConfigurationListener {
override fun onConfigChanged(newConfig: Configuration?) { override fun onConfigChanged(newConfig: Configuration?) {
updateSensorLocation() updateSensorLocation()
@@ -179,14 +194,97 @@ class AuthRippleController @Inject constructor(
} }
} }
private val authControllerCallback = AuthController.Callback { updateSensorLocation() } private val udfpsControllerCallback =
object : UdfpsController.Callback {
override fun onFingerDown() {
if (fingerprintSensorLocation == null) {
Log.e("AuthRipple", "fingerprintSensorLocation=null onFingerDown. " +
"Skip showing dwell ripple")
return
}
mView.setSensorLocation(fingerprintSensorLocation!!)
mView.startDwellRipple(
/* startRadius */ udfpsRadius,
/* endRadius */ udfpsRadius * dwellScale,
/* expandedRadius */ udfpsRadius * expandedDwellScale)
}
override fun onFingerUp() {
mView.retractRipple()
}
}
private val authControllerCallback = AuthController.Callback {
updateSensorLocation()
updateUdfpsDependentParams()
}
private fun updateUdfpsDependentParams() {
authController.udfpsProps?.let {
if (it.size > 0) {
udfpsRadius = it[0].sensorRadius.toFloat()
udfpsController = udfpsControllerProvider.get()
if (mView.isAttachedToWindow) {
udfpsController?.addCallback(udfpsControllerCallback)
}
}
}
}
inner class AuthRippleCommand : Command { inner class AuthRippleCommand : Command {
fun printDwellInfo(pw: PrintWriter) {
pw.println("dwell ripple: " +
"\n\tsensorLocation=$fingerprintSensorLocation" +
"\n\tdwellScale=$dwellScale" +
"\n\tdwellAlpha=${mView.dwellAlpha}, " +
"duration=${mView.dwellAlphaDuration}" +
"\n\tdwellExpand=$expandedDwellScale" +
"\n\t(crash systemui to reset to default)")
}
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)
} else { } else {
when (args[0]) { when (args[0]) {
"dwellScale" -> {
if (args.size > 1 && args[1].toFloatOrNull() != null) {
dwellScale = args[1].toFloat()
printDwellInfo(pw)
} else {
pw.println("expected float argument <dwellScale>")
}
}
"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" -> {
pw.println("fingerprint ripple sensorLocation=$fingerprintSensorLocation") pw.println("fingerprint ripple sensorLocation=$fingerprintSensorLocation")
showRipple(BiometricSourceType.FINGERPRINT) showRipple(BiometricSourceType.FINGERPRINT)
@@ -205,7 +303,7 @@ class AuthRippleController @Inject constructor(
pw.println("custom ripple sensorLocation=" + args[1].toFloat() + ", " + pw.println("custom ripple sensorLocation=" + args[1].toFloat() + ", " +
args[2].toFloat()) args[2].toFloat())
mView.setSensorLocation(PointF(args[1].toFloat(), args[2].toFloat())) mView.setSensorLocation(PointF(args[1].toFloat(), args[2].toFloat()))
showRipple() showUnlockedRipple()
} }
else -> invalidCommand(pw) else -> invalidCommand(pw)
} }
@@ -215,6 +313,9 @@ 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(" 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

@@ -26,7 +26,9 @@ import android.graphics.PointF
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.view.animation.PathInterpolator import android.view.animation.PathInterpolator
import com.android.internal.R.attr.interpolator
import com.android.internal.graphics.ColorUtils import com.android.internal.graphics.ColorUtils
import com.android.systemui.animation.Interpolators
import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.charging.RippleShader import com.android.systemui.statusbar.charging.RippleShader
@@ -34,15 +36,28 @@ private const val RIPPLE_ANIMATION_DURATION: Long = 1533
private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
/** /**
* Expanding ripple effect on the transition from biometric authentication success to showing * Expanding ripple effect
* - startUnlockedRipple for the transition from biometric authentication success to showing
* launcher. * launcher.
* - startDwellRipple for the ripple expansion out when the user has their finger down on the UDFPS
* sensor area
* - retractRipple for the ripple animation inwards to signal a failure
*/ */
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 dwellPulseDuration = 200L
var dwellAlphaDuration = dwellPulseDuration
private val dwellExpandDuration = 1200L - dwellPulseDuration
private val retractDuration = 400L
var dwellAlpha: Float = .5f
private var alphaInDuration: Long = 0 private var alphaInDuration: Long = 0
private var rippleInProgress: Boolean = false private var unlockedRippleInProgress: Boolean = false
private val rippleShader = RippleShader() private val rippleShader = RippleShader()
private val ripplePaint = Paint() private val ripplePaint = Paint()
private var radius: Float = 0.0f private var retractAnimator: Animator? = null
private var dwellPulseOutAnimator: Animator? = null
private var radius: Float = 0f
set(value) { set(value) {
rippleShader.radius = value rippleShader.radius = value
field = value field = value
@@ -63,21 +78,182 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
fun setSensorLocation(location: PointF) { fun setSensorLocation(location: PointF) {
origin = location origin = location
radius = maxOf(location.x, location.y, width - location.x, height - location.y) radius = maxOf(location.x, location.y, width - location.x, height - location.y).toFloat()
.toFloat()
} }
fun setAlphaInDuration(duration: Long) { fun setAlphaInDuration(duration: Long) {
alphaInDuration = duration alphaInDuration = duration
} }
fun startRipple(onAnimationEnd: Runnable?, lightReveal: LightRevealScrim?) { /**
if (rippleInProgress) { * Animate ripple inwards back to radius 0
*/
fun retractRipple() {
if (retractAnimator?.isRunning == true) {
return // let the animation finish
}
if (dwellPulseOutAnimator?.isRunning == true) {
val retractRippleAnimator = ValueAnimator.ofFloat(rippleShader.progress, 0f)
.apply {
interpolator = retractInterpolator
duration = retractDuration
addUpdateListener { animator ->
val now = animator.currentPlayTime
rippleShader.progress = animator.animatedValue as Float
rippleShader.time = now.toFloat()
invalidate()
}
}
val retractAlphaAnimator = ValueAnimator.ofInt(255, 0).apply {
interpolator = Interpolators.LINEAR
duration = retractDuration
addUpdateListener { animator ->
rippleShader.color = ColorUtils.setAlphaComponent(
rippleShader.color,
animator.animatedValue as Int
)
invalidate()
}
}
retractAnimator = AnimatorSet().apply {
playTogether(retractRippleAnimator, retractAlphaAnimator)
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
dwellPulseOutAnimator?.cancel()
rippleShader.shouldFadeOutRipple = false
visibility = VISIBLE
}
override fun onAnimationEnd(animation: Animator?) {
visibility = GONE
resetRippleAlpha()
}
})
start()
}
}
}
/**
* Ripple that moves animates from an outer ripple ring of
* startRadius => endRadius => expandedRadius
*/
fun startDwellRipple(startRadius: Float, endRadius: Float, expandedRadius: Float) {
if (unlockedRippleInProgress || dwellPulseOutAnimator?.isRunning == true) {
return
}
// we divide by 4 because the desired startRadius and endRadius is for the ripple's outer
// ring see RippleShader
val startDwellProgress = startRadius / radius / 4f
val endInitialDwellProgress = endRadius / radius / 4f
val endExpandDwellProgress = expandedRadius / radius / 4f
val pulseOutEndAlpha = (255 * dwellAlpha).toInt()
val expandDwellEndAlpha = kotlin.math.min((255 * (dwellAlpha + .25f)).toInt(), 255)
val dwellPulseOutRippleAnimator = ValueAnimator.ofFloat(startDwellProgress,
endInitialDwellProgress).apply {
interpolator = Interpolators.LINEAR_OUT_SLOW_IN
duration = dwellPulseDuration
addUpdateListener { animator ->
val now = animator.currentPlayTime
rippleShader.progress = animator.animatedValue as Float
rippleShader.time = now.toFloat()
invalidate()
}
}
val dwellPulseOutAlphaAnimator = ValueAnimator.ofInt(0, pulseOutEndAlpha).apply {
interpolator = Interpolators.LINEAR
duration = 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
val expandDwellRippleAnimator = ValueAnimator.ofFloat(endInitialDwellProgress,
endExpandDwellProgress).apply {
interpolator = Interpolators.LINEAR_OUT_SLOW_IN
duration = dwellExpandDuration
addUpdateListener { animator ->
val now = animator.currentPlayTime
rippleShader.progress = animator.animatedValue as Float
rippleShader.time = now.toFloat()
invalidate()
}
}
val expandDwellAlphaAnimator = ValueAnimator.ofInt(pulseOutEndAlpha, expandDwellEndAlpha)
.apply {
interpolator = Interpolators.LINEAR
duration = 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 {
playSequentially(
initialDwellPulseOutAnimator,
expandDwellAnimator
)
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
retractAnimator?.cancel()
rippleShader.shouldFadeOutRipple = false
visibility = VISIBLE
}
override fun onAnimationEnd(animation: Animator?) {
visibility = GONE
resetRippleAlpha()
}
})
start()
}
}
/**
* Ripple that bursts outwards from the position of the sensor to the edges of the screen
*/
fun startUnlockedRipple(onAnimationEnd: Runnable?, lightReveal: LightRevealScrim?) {
if (unlockedRippleInProgress) {
return // Ignore if ripple effect is already playing return // Ignore if ripple effect is already playing
} }
val rippleAnimator = ValueAnimator.ofFloat(0f, 1f).apply { var rippleStart = 0f
interpolator = PathInterpolator(0f, 0f, .2f, 1f) 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
duration = RIPPLE_ANIMATION_DURATION duration = RIPPLE_ANIMATION_DURATION
addUpdateListener { animator -> addUpdateListener { animator ->
val now = animator.currentPlayTime val now = animator.currentPlayTime
@@ -97,7 +273,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 = alphaInDuration duration = alphaDuration
addUpdateListener { animator -> addUpdateListener { animator ->
rippleShader.color = ColorUtils.setAlphaComponent( rippleShader.color = ColorUtils.setAlphaComponent(
rippleShader.color, rippleShader.color,
@@ -115,13 +291,14 @@ 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?) {
rippleInProgress = true unlockedRippleInProgress = true
rippleShader.shouldFadeOutRipple = true
visibility = VISIBLE visibility = VISIBLE
} }
override fun onAnimationEnd(animation: Animator?) { override fun onAnimationEnd(animation: Animator?) {
onAnimationEnd?.run() onAnimationEnd?.run()
rippleInProgress = false unlockedRippleInProgress = false
visibility = GONE visibility = GONE
} }
}) })
@@ -129,8 +306,16 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
animatorSet.start() animatorSet.start()
} }
fun resetRippleAlpha() {
rippleShader.color = ColorUtils.setAlphaComponent(
rippleShader.color,
255
)
}
fun setColor(color: Int) { fun setColor(color: Int) {
rippleShader.color = color rippleShader.color = color
resetRippleAlpha()
} }
override fun onDraw(canvas: Canvas?) { override fun onDraw(canvas: Canvas?) {

View File

@@ -77,7 +77,9 @@ import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution; import com.android.systemui.util.concurrency.Execution;
import com.android.systemui.util.time.SystemClock; import com.android.systemui.util.time.SystemClock;
import java.util.HashSet;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import javax.inject.Inject; import javax.inject.Inject;
@@ -158,6 +160,7 @@ public class UdfpsController implements DozeReceiver {
private Runnable mAodInterruptRunnable; private Runnable mAodInterruptRunnable;
private boolean mOnFingerDown; private boolean mOnFingerDown;
private boolean mAttemptedToDismissKeyguard; private boolean mAttemptedToDismissKeyguard;
private Set<Callback> mCallbacks = new HashSet<>();
@VisibleForTesting @VisibleForTesting
public static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES = public static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES =
@@ -876,6 +879,20 @@ public class UdfpsController implements DozeReceiver {
} }
} }
/**
* Add a callback for fingerUp and fingerDown events
*/
public void addCallback(Callback cb) {
mCallbacks.add(cb);
}
/**
* Remove callback
*/
public void removeCallback(Callback cb) {
mCallbacks.remove(cb);
}
/** /**
* Cancel updfs scan affordances - ability to hide the HbmSurfaceView (white circle) before * Cancel updfs scan affordances - ability to hide the HbmSurfaceView (white circle) before
* user explicitly lifts their finger. Generally, this should be called whenever udfps fails * user explicitly lifts their finger. Generally, this should be called whenever udfps fails
@@ -930,6 +947,10 @@ public class UdfpsController implements DozeReceiver {
mFingerprintManager.onUiReady(mSensorProps.sensorId); mFingerprintManager.onUiReady(mSensorProps.sensorId);
Trace.endAsyncSection("UdfpsController.e2e.startIllumination", 0); Trace.endAsyncSection("UdfpsController.e2e.startIllumination", 0);
}); });
for (Callback cb : mCallbacks) {
cb.onFingerDown();
}
} }
private void onFingerUp() { private void onFingerUp() {
@@ -942,6 +963,9 @@ public class UdfpsController implements DozeReceiver {
} }
if (mOnFingerDown) { if (mOnFingerDown) {
mFingerprintManager.onPointerUp(mSensorProps.sensorId); mFingerprintManager.onPointerUp(mSensorProps.sensorId);
for (Callback cb : mCallbacks) {
cb.onFingerUp();
}
} }
mOnFingerDown = false; mOnFingerDown = false;
if (mView.isIlluminationRequested()) { if (mView.isIlluminationRequested()) {
@@ -962,4 +986,19 @@ public class UdfpsController implements DozeReceiver {
mView.setOnTouchListener(mOnTouchListener); mView.setOnTouchListener(mOnTouchListener);
} }
} }
/**
* Callback for fingerUp and fingerDown events.
*/
public interface Callback {
/**
* Called onFingerUp events. Will only be called if the finger was previously down.
*/
void onFingerUp();
/**
* Called onFingerDown events.
*/
void onFingerDown();
}
} }

View File

@@ -147,8 +147,12 @@ class RippleShader internal constructor() : RuntimeShader(SHADER, false) {
val fadeIn = subProgress(0f, 0.1f, value) val fadeIn = subProgress(0f, 0.1f, value)
val fadeOutNoise = subProgress(0.4f, 1f, value) val fadeOutNoise = subProgress(0.4f, 1f, value)
val fadeOutRipple = subProgress(0.3f, 1f, value) var fadeOutRipple = 0f
val fadeCircle = subProgress(0f, 0.2f, value) var fadeCircle = 0f
if (shouldFadeOutRipple) {
fadeCircle = subProgress(0f, 0.2f, value)
fadeOutRipple = subProgress(0.3f, 1f, value)
}
setUniform("in_fadeSparkle", Math.min(fadeIn, 1 - fadeOutNoise)) setUniform("in_fadeSparkle", Math.min(fadeIn, 1 - fadeOutNoise))
setUniform("in_fadeCircle", 1 - fadeCircle) setUniform("in_fadeCircle", 1 - fadeCircle)
setUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple)) setUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
@@ -200,4 +204,6 @@ class RippleShader internal constructor() : RuntimeShader(SHADER, false) {
field = value field = value
setUniform("in_pixelDensity", value) setUniform("in_pixelDensity", value)
} }
var shouldFadeOutRipple: Boolean = true
} }

View File

@@ -42,6 +42,8 @@ import org.mockito.Mockito.reset
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
import javax.inject.Provider
@SmallTest @SmallTest
@RunWith(AndroidTestingRunner::class) @RunWith(AndroidTestingRunner::class)
class AuthRippleControllerTest : SysuiTestCase() { class AuthRippleControllerTest : SysuiTestCase() {
@@ -55,10 +57,14 @@ class AuthRippleControllerTest : SysuiTestCase() {
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock private lateinit var bypassController: KeyguardBypassController @Mock private lateinit var bypassController: KeyguardBypassController
@Mock private lateinit var biometricUnlockController: BiometricUnlockController @Mock private lateinit var biometricUnlockController: BiometricUnlockController
@Mock private lateinit var udfpsControllerProvider: Provider<UdfpsController>
@Mock private lateinit var udfpsController: UdfpsController
@Before @Before
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
`when`(udfpsControllerProvider.get()).thenReturn(udfpsController)
controller = AuthRippleController( controller = AuthRippleController(
statusBar, statusBar,
context, context,
@@ -69,6 +75,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
notificationShadeWindowController, notificationShadeWindowController,
bypassController, bypassController,
biometricUnlockController, biometricUnlockController,
udfpsControllerProvider,
rippleView rippleView
) )
controller.init() controller.init()
@@ -93,7 +100,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
// THEN update sensor location and show ripple // THEN update sensor location and show ripple
verify(rippleView).setSensorLocation(fpsLocation) verify(rippleView).setSensorLocation(fpsLocation)
verify(rippleView).startRipple(any(), any()) verify(rippleView).startUnlockedRipple(any(), any())
} }
@Test @Test
@@ -114,7 +121,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
false /* isStrongBiometric */) false /* isStrongBiometric */)
// THEN no ripple // THEN no ripple
verify(rippleView, never()).startRipple(any(), any()) verify(rippleView, never()).startUnlockedRipple(any(), any())
} }
@Test @Test
@@ -135,7 +142,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
false /* isStrongBiometric */) false /* isStrongBiometric */)
// THEN no ripple // THEN no ripple
verify(rippleView, never()).startRipple(any(), any()) verify(rippleView, never()).startUnlockedRipple(any(), any())
} }
@Test @Test
@@ -159,7 +166,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
// THEN show ripple // THEN show ripple
verify(rippleView).setSensorLocation(faceLocation) verify(rippleView).setSensorLocation(faceLocation)
verify(rippleView).startRipple(any(), any()) verify(rippleView).startUnlockedRipple(any(), any())
} }
@Test @Test
@@ -179,7 +186,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
false /* isStrongBiometric */) false /* isStrongBiometric */)
// THEN no ripple // THEN no ripple
verify(rippleView, never()).startRipple(any(), any()) verify(rippleView, never()).startUnlockedRipple(any(), any())
} }
@Test @Test
@@ -194,7 +201,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
0 /* userId */, 0 /* userId */,
BiometricSourceType.FACE /* type */, BiometricSourceType.FACE /* type */,
false /* isStrongBiometric */) false /* isStrongBiometric */)
verify(rippleView, never()).startRipple(any(), any()) verify(rippleView, never()).startUnlockedRipple(any(), any())
} }
@Test @Test
@@ -209,7 +216,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
0 /* userId */, 0 /* userId */,
BiometricSourceType.FINGERPRINT /* type */, BiometricSourceType.FINGERPRINT /* type */,
false /* isStrongBiometric */) false /* isStrongBiometric */)
verify(rippleView, never()).startRipple(any(), any()) verify(rippleView, never()).startUnlockedRipple(any(), any())
} }
@Test @Test