From 73607f90773f0dba85cca7d4f5091eddbc4efd81 Mon Sep 17 00:00:00 2001 From: Beverly Date: Wed, 15 Jun 2022 19:07:55 +0000 Subject: [PATCH] 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 --- .../android/systemui/FaceScanningOverlay.kt | 188 ++++++++++++++---- .../decor/FaceScanningProviderFactory.kt | 14 +- .../systemui/ScreenDecorationsTest.java | 3 +- 3 files changed, 161 insertions(+), 44 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt index 61b1b66338e87..c5955860aebf5 100644 --- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt @@ -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 - addUpdateListener(ValueAnimator.AnimatorUpdateListener { - animation: ValueAnimator -> - rimProgress = animation.animatedValue as Float - invalidate() - }) - } if (showScanningAnim) { - // appear and then pulse in/out - playSequentially(rimAppearOrDisappearAnimator, + 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() + }) + } + + // 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 - interpolator = Interpolators.LINEAR + 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 -> - rimPaint.alpha = animation.animatedValue as Int + rimProgress = animation.animatedValue as Float invalidate() }) + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + rimProgress = HIDDEN_RIM_SCALE + invalidate() + } + }) } - addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - rimProgress = HIDDEN_RIM_SCALE - rimPaint.alpha = 255 - 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) { + 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 } } diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt index adc0096eed377..81d3d6caebd79 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt @@ -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 { diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index a35efa995ddd1..90609fa2772f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -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,