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,