Loading packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt +148 −40 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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() Loading @@ -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() Loading Loading @@ -108,7 +127,6 @@ class FaceScanningOverlay( if (showScanningAnimNow == showScanningAnim) { return } showScanningAnim = showScanningAnimNow updateProtectionBoundingPath() // Delay the relayout until the end of the animation when hiding, Loading @@ -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 Loading @@ -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() { Loading Loading @@ -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 } } packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt +11 −3 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -82,7 +85,9 @@ class FaceScanningProviderFactory @Inject constructor( bound.baseOnRotation0(displayInfo.rotation), authController, statusBarStateController, keyguardUpdateMonitor) keyguardUpdateMonitor, mainExecutor ) ) } } Loading @@ -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 Loading @@ -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 { Loading packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +2 −1 Original line number Diff line number Diff line Loading @@ -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, Loading Loading
packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt +148 −40 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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() Loading @@ -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() Loading Loading @@ -108,7 +127,6 @@ class FaceScanningOverlay( if (showScanningAnimNow == showScanningAnim) { return } showScanningAnim = showScanningAnimNow updateProtectionBoundingPath() // Delay the relayout until the end of the animation when hiding, Loading @@ -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 Loading @@ -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() { Loading Loading @@ -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 } }
packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt +11 −3 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -82,7 +85,9 @@ class FaceScanningProviderFactory @Inject constructor( bound.baseOnRotation0(displayInfo.rotation), authController, statusBarStateController, keyguardUpdateMonitor) keyguardUpdateMonitor, mainExecutor ) ) } } Loading @@ -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 Loading @@ -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 { Loading
packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +2 −1 Original line number Diff line number Diff line Loading @@ -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, Loading