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

Commit 4c66a193 authored by Beverly Tai's avatar Beverly Tai Committed by Automerger Merge Worker
Browse files

Merge "Add face scanning error animation" into tm-d1-dev am: efcb352f am: dd565910

parents a822a285 dd565910
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,