Add dwell-ripple am: 3ad5b8bb1e am: aacc8ecd12
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15779694 Change-Id: I21c8844e191a5586ea24ff3fd28099ae099ec5cd
This commit is contained in:
@@ -20,6 +20,7 @@ import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.PointF
|
||||
import android.hardware.biometrics.BiometricSourceType
|
||||
import android.util.Log
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.keyguard.KeyguardUpdateMonitorCallback
|
||||
@@ -38,6 +39,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController
|
||||
import com.android.systemui.util.ViewController
|
||||
import java.io.PrintWriter
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
/***
|
||||
* Controls the ripple effect that shows when authentication is successful.
|
||||
@@ -54,12 +56,18 @@ class AuthRippleController @Inject constructor(
|
||||
private val notificationShadeWindowController: NotificationShadeWindowController,
|
||||
private val bypassController: KeyguardBypassController,
|
||||
private val biometricUnlockController: BiometricUnlockController,
|
||||
private val udfpsControllerProvider: Provider<UdfpsController>,
|
||||
rippleView: AuthRippleView?
|
||||
) : ViewController<AuthRippleView>(rippleView) {
|
||||
var fingerprintSensorLocation: PointF? = null
|
||||
private var faceSensorLocation: PointF? = 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() {
|
||||
mView.setAlphaInDuration(sysuiContext.resources.getInteger(
|
||||
R.integer.auth_ripple_alpha_in_duration).toLong())
|
||||
@@ -67,9 +75,11 @@ class AuthRippleController @Inject constructor(
|
||||
|
||||
@VisibleForTesting
|
||||
public override fun onViewAttached() {
|
||||
authController.addCallback(authControllerCallback)
|
||||
updateRippleColor()
|
||||
updateSensorLocation()
|
||||
authController.addCallback(authControllerCallback)
|
||||
updateUdfpsDependentParams()
|
||||
udfpsController?.addCallback(udfpsControllerCallback)
|
||||
configurationController.addCallback(configurationChangedListener)
|
||||
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
|
||||
commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() }
|
||||
@@ -77,6 +87,7 @@ class AuthRippleController @Inject constructor(
|
||||
|
||||
@VisibleForTesting
|
||||
public override fun onViewDetached() {
|
||||
udfpsController?.removeCallback(udfpsControllerCallback)
|
||||
authController.removeCallback(authControllerCallback)
|
||||
keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
|
||||
configurationController.removeCallback(configurationChangedListener)
|
||||
@@ -94,18 +105,18 @@ class AuthRippleController @Inject constructor(
|
||||
if (biometricSourceType == BiometricSourceType.FINGERPRINT &&
|
||||
fingerprintSensorLocation != null) {
|
||||
mView.setSensorLocation(fingerprintSensorLocation!!)
|
||||
showRipple()
|
||||
showUnlockedRipple()
|
||||
} else if (biometricSourceType == BiometricSourceType.FACE &&
|
||||
faceSensorLocation != null) {
|
||||
if (!bypassController.canBypass()) {
|
||||
return
|
||||
}
|
||||
mView.setSensorLocation(faceSensorLocation!!)
|
||||
showRipple()
|
||||
showUnlockedRipple()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showRipple() {
|
||||
private fun showUnlockedRipple() {
|
||||
notificationShadeWindowController.setForcePluginOpen(true, this)
|
||||
val biometricUnlockMode = biometricUnlockController.mode
|
||||
val useCircleReveal = circleReveal != null &&
|
||||
@@ -117,7 +128,7 @@ class AuthRippleController @Inject constructor(
|
||||
lightRevealScrim?.revealEffect = circleReveal!!
|
||||
}
|
||||
|
||||
mView.startRipple(
|
||||
mView.startUnlockedRipple(
|
||||
/* end runnable */
|
||||
Runnable {
|
||||
notificationShadeWindowController.setForcePluginOpen(false, this)
|
||||
@@ -152,7 +163,7 @@ class AuthRippleController @Inject constructor(
|
||||
Utils.getColorAttr(sysuiContext, android.R.attr.colorAccent).defaultColor)
|
||||
}
|
||||
|
||||
val keyguardUpdateMonitorCallback =
|
||||
private val keyguardUpdateMonitorCallback =
|
||||
object : KeyguardUpdateMonitorCallback() {
|
||||
override fun onBiometricAuthenticated(
|
||||
userId: Int,
|
||||
@@ -161,9 +172,13 @@ class AuthRippleController @Inject constructor(
|
||||
) {
|
||||
showRipple(biometricSourceType)
|
||||
}
|
||||
|
||||
override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) {
|
||||
mView.retractRipple()
|
||||
}
|
||||
}
|
||||
|
||||
val configurationChangedListener =
|
||||
private val configurationChangedListener =
|
||||
object : ConfigurationController.ConfigurationListener {
|
||||
override fun onConfigChanged(newConfig: Configuration?) {
|
||||
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 {
|
||||
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>) {
|
||||
if (args.isEmpty()) {
|
||||
invalidCommand(pw)
|
||||
} else {
|
||||
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" -> {
|
||||
pw.println("fingerprint ripple sensorLocation=$fingerprintSensorLocation")
|
||||
showRipple(BiometricSourceType.FINGERPRINT)
|
||||
@@ -205,7 +303,7 @@ class AuthRippleController @Inject constructor(
|
||||
pw.println("custom ripple sensorLocation=" + args[1].toFloat() + ", " +
|
||||
args[2].toFloat())
|
||||
mView.setSensorLocation(PointF(args[1].toFloat(), args[2].toFloat()))
|
||||
showRipple()
|
||||
showUnlockedRipple()
|
||||
}
|
||||
else -> invalidCommand(pw)
|
||||
}
|
||||
@@ -215,6 +313,9 @@ class AuthRippleController @Inject constructor(
|
||||
override fun help(pw: PrintWriter) {
|
||||
pw.println("Usage: adb shell cmd statusbar auth-ripple <command>")
|
||||
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(" face")
|
||||
pw.println(" custom <x-location: int> <y-location: int>")
|
||||
|
||||
@@ -26,7 +26,9 @@ import android.graphics.PointF
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.animation.PathInterpolator
|
||||
import com.android.internal.R.attr.interpolator
|
||||
import com.android.internal.graphics.ColorUtils
|
||||
import com.android.systemui.animation.Interpolators
|
||||
import com.android.systemui.statusbar.LightRevealScrim
|
||||
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
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* - 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) {
|
||||
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 rippleInProgress: Boolean = false
|
||||
private var unlockedRippleInProgress: Boolean = false
|
||||
private val rippleShader = RippleShader()
|
||||
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) {
|
||||
rippleShader.radius = value
|
||||
field = value
|
||||
@@ -63,21 +78,182 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
|
||||
|
||||
fun setSensorLocation(location: PointF) {
|
||||
origin = location
|
||||
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 setAlphaInDuration(duration: Long) {
|
||||
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
|
||||
}
|
||||
|
||||
val rippleAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
|
||||
interpolator = PathInterpolator(0f, 0f, .2f, 1f)
|
||||
var rippleStart = 0f
|
||||
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
|
||||
addUpdateListener { animator ->
|
||||
val now = animator.currentPlayTime
|
||||
@@ -97,7 +273,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
|
||||
}
|
||||
|
||||
val alphaInAnimator = ValueAnimator.ofInt(0, 255).apply {
|
||||
duration = alphaInDuration
|
||||
duration = alphaDuration
|
||||
addUpdateListener { animator ->
|
||||
rippleShader.color = ColorUtils.setAlphaComponent(
|
||||
rippleShader.color,
|
||||
@@ -115,13 +291,14 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
|
||||
)
|
||||
addListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationStart(animation: Animator?) {
|
||||
rippleInProgress = true
|
||||
unlockedRippleInProgress = true
|
||||
rippleShader.shouldFadeOutRipple = true
|
||||
visibility = VISIBLE
|
||||
}
|
||||
|
||||
override fun onAnimationEnd(animation: Animator?) {
|
||||
onAnimationEnd?.run()
|
||||
rippleInProgress = false
|
||||
unlockedRippleInProgress = false
|
||||
visibility = GONE
|
||||
}
|
||||
})
|
||||
@@ -129,8 +306,16 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
|
||||
animatorSet.start()
|
||||
}
|
||||
|
||||
fun resetRippleAlpha() {
|
||||
rippleShader.color = ColorUtils.setAlphaComponent(
|
||||
rippleShader.color,
|
||||
255
|
||||
)
|
||||
}
|
||||
|
||||
fun setColor(color: Int) {
|
||||
rippleShader.color = color
|
||||
resetRippleAlpha()
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas?) {
|
||||
|
||||
@@ -77,7 +77,9 @@ import com.android.systemui.util.concurrency.DelayableExecutor;
|
||||
import com.android.systemui.util.concurrency.Execution;
|
||||
import com.android.systemui.util.time.SystemClock;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -158,6 +160,7 @@ public class UdfpsController implements DozeReceiver {
|
||||
private Runnable mAodInterruptRunnable;
|
||||
private boolean mOnFingerDown;
|
||||
private boolean mAttemptedToDismissKeyguard;
|
||||
private Set<Callback> mCallbacks = new HashSet<>();
|
||||
|
||||
@VisibleForTesting
|
||||
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
|
||||
* 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);
|
||||
Trace.endAsyncSection("UdfpsController.e2e.startIllumination", 0);
|
||||
});
|
||||
|
||||
for (Callback cb : mCallbacks) {
|
||||
cb.onFingerDown();
|
||||
}
|
||||
}
|
||||
|
||||
private void onFingerUp() {
|
||||
@@ -942,6 +963,9 @@ public class UdfpsController implements DozeReceiver {
|
||||
}
|
||||
if (mOnFingerDown) {
|
||||
mFingerprintManager.onPointerUp(mSensorProps.sensorId);
|
||||
for (Callback cb : mCallbacks) {
|
||||
cb.onFingerUp();
|
||||
}
|
||||
}
|
||||
mOnFingerDown = false;
|
||||
if (mView.isIlluminationRequested()) {
|
||||
@@ -962,4 +986,19 @@ public class UdfpsController implements DozeReceiver {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,8 +147,12 @@ class RippleShader internal constructor() : RuntimeShader(SHADER, false) {
|
||||
|
||||
val fadeIn = subProgress(0f, 0.1f, value)
|
||||
val fadeOutNoise = subProgress(0.4f, 1f, value)
|
||||
val fadeOutRipple = subProgress(0.3f, 1f, value)
|
||||
val fadeCircle = subProgress(0f, 0.2f, value)
|
||||
var fadeOutRipple = 0f
|
||||
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_fadeCircle", 1 - fadeCircle)
|
||||
setUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
|
||||
@@ -200,4 +204,6 @@ class RippleShader internal constructor() : RuntimeShader(SHADER, false) {
|
||||
field = value
|
||||
setUniform("in_pixelDensity", value)
|
||||
}
|
||||
|
||||
var shouldFadeOutRipple: Boolean = true
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ import org.mockito.Mockito.reset
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
import javax.inject.Provider
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
class AuthRippleControllerTest : SysuiTestCase() {
|
||||
@@ -55,10 +57,14 @@ class AuthRippleControllerTest : SysuiTestCase() {
|
||||
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
|
||||
@Mock private lateinit var bypassController: KeyguardBypassController
|
||||
@Mock private lateinit var biometricUnlockController: BiometricUnlockController
|
||||
@Mock private lateinit var udfpsControllerProvider: Provider<UdfpsController>
|
||||
@Mock private lateinit var udfpsController: UdfpsController
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
`when`(udfpsControllerProvider.get()).thenReturn(udfpsController)
|
||||
|
||||
controller = AuthRippleController(
|
||||
statusBar,
|
||||
context,
|
||||
@@ -69,6 +75,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
|
||||
notificationShadeWindowController,
|
||||
bypassController,
|
||||
biometricUnlockController,
|
||||
udfpsControllerProvider,
|
||||
rippleView
|
||||
)
|
||||
controller.init()
|
||||
@@ -93,7 +100,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
|
||||
|
||||
// THEN update sensor location and show ripple
|
||||
verify(rippleView).setSensorLocation(fpsLocation)
|
||||
verify(rippleView).startRipple(any(), any())
|
||||
verify(rippleView).startUnlockedRipple(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -114,7 +121,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
|
||||
false /* isStrongBiometric */)
|
||||
|
||||
// THEN no ripple
|
||||
verify(rippleView, never()).startRipple(any(), any())
|
||||
verify(rippleView, never()).startUnlockedRipple(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -135,7 +142,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
|
||||
false /* isStrongBiometric */)
|
||||
|
||||
// THEN no ripple
|
||||
verify(rippleView, never()).startRipple(any(), any())
|
||||
verify(rippleView, never()).startUnlockedRipple(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -159,7 +166,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
|
||||
|
||||
// THEN show ripple
|
||||
verify(rippleView).setSensorLocation(faceLocation)
|
||||
verify(rippleView).startRipple(any(), any())
|
||||
verify(rippleView).startUnlockedRipple(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -179,7 +186,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
|
||||
false /* isStrongBiometric */)
|
||||
|
||||
// THEN no ripple
|
||||
verify(rippleView, never()).startRipple(any(), any())
|
||||
verify(rippleView, never()).startUnlockedRipple(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -194,7 +201,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
|
||||
0 /* userId */,
|
||||
BiometricSourceType.FACE /* type */,
|
||||
false /* isStrongBiometric */)
|
||||
verify(rippleView, never()).startRipple(any(), any())
|
||||
verify(rippleView, never()).startUnlockedRipple(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -209,7 +216,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
|
||||
0 /* userId */,
|
||||
BiometricSourceType.FINGERPRINT /* type */,
|
||||
false /* isStrongBiometric */)
|
||||
verify(rippleView, never()).startRipple(any(), any())
|
||||
verify(rippleView, never()).startUnlockedRipple(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user