Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f26e310e authored by Beverly's avatar Beverly Committed by Automerger Merge Worker
Browse files

Add dwell-ripple am: 3ad5b8bb

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

Change-Id: Id5df04e25b20ed1c4bd2a6431e63dd5f98c99c7a
parents b3c724aa 3ad5b8bb
Loading
Loading
Loading
Loading
+110 −9
Original line number Diff line number Diff line
@@ -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>")
+197 −12
Original line number Diff line number Diff line
@@ -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?) {
+39 −0
Original line number Diff line number Diff line
@@ -77,7 +77,9 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;

import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

import javax.inject.Inject;

@@ -157,6 +159,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 =
@@ -863,6 +866,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
@@ -917,6 +934,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() {
@@ -929,6 +950,9 @@ public class UdfpsController implements DozeReceiver {
        }
        if (mOnFingerDown) {
            mFingerprintManager.onPointerUp(mSensorProps.sensorId);
            for (Callback cb : mCallbacks) {
                cb.onFingerUp();
            }
        }
        mOnFingerDown = false;
        if (mView.isIlluminationRequested()) {
@@ -949,4 +973,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();
    }
}
+8 −2
Original line number Diff line number Diff line
@@ -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
}
+14 −7

File changed.

Preview size limit exceeded, changes collapsed.