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

Commit 73607f90 authored by Beverly's avatar Beverly
Browse files

Add face scanning error animation

On face error or authentication fail, immedilately
retract the face scanning animation. On success,
the rim will expand outwards and decrease in opacity.

Update size of face scanning animation so that it's more
subtle.

Test: manually authenticate with face with bypass
enabled and disabled, check error animation animate away
inwards and success animation is an opacity fade outwards
Bug: 185128224

Change-Id: Id595e60cf13adb35e22eb7249ffd20b22eda827e
parent 0592820f
Loading
Loading
Loading
Loading
+148 −40
Original line number Diff line number Diff line
@@ -27,12 +27,15 @@ import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RectF
import android.hardware.biometrics.BiometricSourceType
import android.view.View
import androidx.core.graphics.ColorUtils
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.Utils
import com.android.systemui.animation.Interpolators
import com.android.systemui.plugins.statusbar.StatusBarStateController
import java.util.concurrent.Executor

/**
 * When the face is enrolled, we use this view to show the face scanning animation and the camera
@@ -42,7 +45,8 @@ class FaceScanningOverlay(
    context: Context,
    pos: Int,
    val statusBarStateController: StatusBarStateController,
    val keyguardUpdateMonitor: KeyguardUpdateMonitor
    val keyguardUpdateMonitor: KeyguardUpdateMonitor,
    val mainExecutor: Executor
) : ScreenDecorations.DisplayCutoutView(context, pos) {
    private var showScanningAnim = false
    private val rimPaint = Paint()
@@ -54,11 +58,26 @@ class FaceScanningOverlay(
            com.android.systemui.R.attr.wallpaperTextColorAccent)
    private var cameraProtectionAnimator: ValueAnimator? = null
    var hideOverlayRunnable: Runnable? = null
    var faceAuthSucceeded = false

    init {
        visibility = View.INVISIBLE // only show this view when face scanning is happening
    }

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        mainExecutor.execute {
            keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
        }
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        mainExecutor.execute {
            keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
        }
    }

    override fun setColor(color: Int) {
        cameraProtectionColor = color
        invalidate()
@@ -108,7 +127,6 @@ class FaceScanningOverlay(
        if (showScanningAnimNow == showScanningAnim) {
            return
        }

        showScanningAnim = showScanningAnimNow
        updateProtectionBoundingPath()
        // Delay the relayout until the end of the animation when hiding,
@@ -120,13 +138,20 @@ class FaceScanningOverlay(

        cameraProtectionAnimator?.cancel()
        cameraProtectionAnimator = ValueAnimator.ofFloat(cameraProtectionProgress,
                if (showScanningAnimNow) 1.0f else HIDDEN_CAMERA_PROTECTION_SCALE).apply {
            startDelay = if (showScanningAnim) 0 else PULSE_DISAPPEAR_DURATION
            duration = if (showScanningAnim) PULSE_APPEAR_DURATION else
                CAMERA_PROTECTION_DISAPPEAR_DURATION
            interpolator = if (showScanningAnim) Interpolators.STANDARD else
                Interpolators.EMPHASIZED

                if (showScanningAnimNow) SHOW_CAMERA_PROTECTION_SCALE
                else HIDDEN_CAMERA_PROTECTION_SCALE).apply {
            startDelay =
                    if (showScanningAnim) 0
                    else if (faceAuthSucceeded) PULSE_SUCCESS_DISAPPEAR_DURATION
                    else PULSE_ERROR_DISAPPEAR_DURATION
            duration =
                    if (showScanningAnim) CAMERA_PROTECTION_APPEAR_DURATION
                    else if (faceAuthSucceeded) CAMERA_PROTECTION_SUCCESS_DISAPPEAR_DURATION
                    else CAMERA_PROTECTION_ERROR_DISAPPEAR_DURATION
            interpolator =
                    if (showScanningAnim) Interpolators.STANDARD_ACCELERATE
                    else if (faceAuthSucceeded) Interpolators.STANDARD
                    else Interpolators.STANDARD_DECELERATE
            addUpdateListener(ValueAnimator.AnimatorUpdateListener {
                animation: ValueAnimator ->
                cameraProtectionProgress = animation.animatedValue as Float
@@ -143,47 +168,73 @@ class FaceScanningOverlay(
                    }
                }
            })
            start()
        }

        rimAnimator?.cancel()
        rimAnimator = AnimatorSet().apply {
            val rimAppearOrDisappearAnimator = ValueAnimator.ofFloat(rimProgress,
                    if (showScanningAnim) PULSE_RADIUS_OUT else (PULSE_RADIUS_IN * 1.15f)).apply {
                duration = if (showScanningAnim) PULSE_APPEAR_DURATION else PULSE_DISAPPEAR_DURATION
                interpolator = Interpolators.STANDARD
            if (showScanningAnim) {
                val rimAppearAnimator = ValueAnimator.ofFloat(SHOW_CAMERA_PROTECTION_SCALE,
                        PULSE_RADIUS_OUT).apply {
                    duration = PULSE_APPEAR_DURATION
                    interpolator = Interpolators.STANDARD_DECELERATE
                    addUpdateListener(ValueAnimator.AnimatorUpdateListener {
                        animation: ValueAnimator ->
                        rimProgress = animation.animatedValue as Float
                        invalidate()
                    })
                }
            if (showScanningAnim) {
                // appear and then pulse in/out
                playSequentially(rimAppearOrDisappearAnimator,

                // animate in camera protection, rim, and then pulse in/out
                playSequentially(cameraProtectionAnimator, rimAppearAnimator,
                        createPulseAnimator(), createPulseAnimator(),
                        createPulseAnimator(), createPulseAnimator(),
                        createPulseAnimator(), createPulseAnimator())
            } else {
                val opacityAnimator = ValueAnimator.ofInt(255, 0).apply {
                    duration = PULSE_DISAPPEAR_DURATION
                val rimDisappearAnimator = ValueAnimator.ofFloat(
                        rimProgress,
                        if (faceAuthSucceeded) PULSE_RADIUS_SUCCESS
                        else SHOW_CAMERA_PROTECTION_SCALE
                ).apply {
                    duration =
                            if (faceAuthSucceeded) PULSE_SUCCESS_DISAPPEAR_DURATION
                            else PULSE_ERROR_DISAPPEAR_DURATION
                    interpolator =
                            if (faceAuthSucceeded) Interpolators.STANDARD_DECELERATE
                            else Interpolators.STANDARD
                    addUpdateListener(ValueAnimator.AnimatorUpdateListener {
                        animation: ValueAnimator ->
                        rimProgress = animation.animatedValue as Float
                        invalidate()
                    })
                    addListener(object : AnimatorListenerAdapter() {
                        override fun onAnimationEnd(animation: Animator) {
                            rimProgress = HIDDEN_RIM_SCALE
                            invalidate()
                        }
                    })
                }
                if (faceAuthSucceeded) {
                    val successOpacityAnimator = ValueAnimator.ofInt(255, 0).apply {
                        duration = PULSE_SUCCESS_DISAPPEAR_DURATION
                        interpolator = Interpolators.LINEAR
                        addUpdateListener(ValueAnimator.AnimatorUpdateListener {
                            animation: ValueAnimator ->
                            rimPaint.alpha = animation.animatedValue as Int
                            invalidate()
                        })
                }
                        addListener(object : AnimatorListenerAdapter() {
                            override fun onAnimationEnd(animation: Animator) {
                        rimProgress = HIDDEN_RIM_SCALE
                                rimPaint.alpha = 255
                                invalidate()
                            }
                        })

                // disappear
                playTogether(rimAppearOrDisappearAnimator, opacityAnimator)
                    }
                    val rimSuccessAnimator = AnimatorSet()
                    rimSuccessAnimator.playTogether(rimDisappearAnimator, successOpacityAnimator)
                    playTogether(rimSuccessAnimator, cameraProtectionAnimator)
                } else {
                    playTogether(rimDisappearAnimator, cameraProtectionAnimator)
                }
            }

            addListener(object : AnimatorListenerAdapter() {
@@ -253,15 +304,72 @@ class FaceScanningOverlay(
        }
    }

    private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
        override fun onBiometricAuthenticated(
            userId: Int,
            biometricSourceType: BiometricSourceType?,
            isStrongBiometric: Boolean
        ) {
            if (biometricSourceType == BiometricSourceType.FACE) {
                post {
                    faceAuthSucceeded = true
                    enableShowProtection(true)
                }
            }
        }

        override fun onBiometricAcquired(
            biometricSourceType: BiometricSourceType?,
            acquireInfo: Int
        ) {
            if (biometricSourceType == BiometricSourceType.FACE) {
                post {
                    faceAuthSucceeded = false // reset
                }
            }
        }

        override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) {
            if (biometricSourceType == BiometricSourceType.FACE) {
                post {
                    faceAuthSucceeded = false
                    enableShowProtection(false)
                }
            }
        }

        override fun onBiometricError(
            msgId: Int,
            errString: String?,
            biometricSourceType: BiometricSourceType?
        ) {
            if (biometricSourceType == BiometricSourceType.FACE) {
                post {
                    faceAuthSucceeded = false
                    enableShowProtection(false)
                }
            }
        }
    }

    companion object {
        private const val HIDDEN_RIM_SCALE = HIDDEN_CAMERA_PROTECTION_SCALE
        private const val SHOW_CAMERA_PROTECTION_SCALE = 1f

        private const val PULSE_RADIUS_IN = 1.1f
        private const val PULSE_RADIUS_OUT = 1.125f
        private const val PULSE_RADIUS_SUCCESS = 1.25f

        private const val CAMERA_PROTECTION_APPEAR_DURATION = 250L
        private const val PULSE_APPEAR_DURATION = 250L // without start delay

        private const val PULSE_APPEAR_DURATION = 350L
        private const val PULSE_DURATION_INWARDS = 500L
        private const val PULSE_DURATION_OUTWARDS = 500L
        private const val PULSE_DISAPPEAR_DURATION = 850L
        private const val CAMERA_PROTECTION_DISAPPEAR_DURATION = 700L // excluding start delay
        private const val PULSE_RADIUS_IN = 1.15f
        private const val PULSE_RADIUS_OUT = 1.25f

        private const val PULSE_SUCCESS_DISAPPEAR_DURATION = 400L
        private const val CAMERA_PROTECTION_SUCCESS_DISAPPEAR_DURATION = 500L // without start delay

        private const val PULSE_ERROR_DISAPPEAR_DURATION = 200L
        private const val CAMERA_PROTECTION_ERROR_DISAPPEAR_DURATION = 300L // without start delay
    }
}
+11 −3
Original line number Diff line number Diff line
@@ -32,10 +32,12 @@ import android.widget.FrameLayout
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.FaceScanningOverlay
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.plugins.statusbar.StatusBarStateController
import java.util.concurrent.Executor
import javax.inject.Inject

@SysUISingleton
@@ -44,6 +46,7 @@ class FaceScanningProviderFactory @Inject constructor(
    private val context: Context,
    private val statusBarStateController: StatusBarStateController,
    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
    @Main private val mainExecutor: Executor,
    private val featureFlags: FeatureFlags
) : DecorProviderFactory() {
    private val display = context.display
@@ -82,7 +85,9 @@ class FaceScanningProviderFactory @Inject constructor(
                                        bound.baseOnRotation0(displayInfo.rotation),
                                        authController,
                                        statusBarStateController,
                                        keyguardUpdateMonitor)
                                        keyguardUpdateMonitor,
                                        mainExecutor
                                )
                        )
                    }
                }
@@ -102,7 +107,8 @@ class FaceScanningOverlayProviderImpl(
    override val alignedBound: Int,
    private val authController: AuthController,
    private val statusBarStateController: StatusBarStateController,
    private val keyguardUpdateMonitor: KeyguardUpdateMonitor
    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
    private val mainExecutor: Executor
) : BoundDecorProvider() {
    override val viewId: Int = com.android.systemui.R.id.face_scanning_anim

@@ -127,7 +133,9 @@ class FaceScanningOverlayProviderImpl(
                context,
                alignedBound,
                statusBarStateController,
                keyguardUpdateMonitor)
                keyguardUpdateMonitor,
                mainExecutor
        )
        view.id = viewId
        FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT).let {
+2 −1
Original line number Diff line number Diff line
@@ -210,7 +210,8 @@ public class ScreenDecorationsTest extends SysuiTestCase {
                BOUNDS_POSITION_TOP,
                mAuthController,
                mStatusBarStateController,
                mKeyguardUpdateMonitor));
                mKeyguardUpdateMonitor,
                mExecutor));

        mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings,
                mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController,