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

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/18914635

Change-Id: I9a9925fdce81f3612762b3468ea66ecb457841c9
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Beverly Tai
2022-06-23 23:02:19 +00:00
committed by Automerger Merge Worker
3 changed files with 161 additions and 44 deletions

View File

@@ -27,12 +27,15 @@ import android.graphics.Matrix
import android.graphics.Paint import android.graphics.Paint
import android.graphics.Path import android.graphics.Path
import android.graphics.RectF import android.graphics.RectF
import android.hardware.biometrics.BiometricSourceType
import android.view.View import android.view.View
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.Utils import com.android.settingslib.Utils
import com.android.systemui.animation.Interpolators import com.android.systemui.animation.Interpolators
import com.android.systemui.plugins.statusbar.StatusBarStateController 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 * 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, context: Context,
pos: Int, pos: Int,
val statusBarStateController: StatusBarStateController, val statusBarStateController: StatusBarStateController,
val keyguardUpdateMonitor: KeyguardUpdateMonitor val keyguardUpdateMonitor: KeyguardUpdateMonitor,
val mainExecutor: Executor
) : ScreenDecorations.DisplayCutoutView(context, pos) { ) : ScreenDecorations.DisplayCutoutView(context, pos) {
private var showScanningAnim = false private var showScanningAnim = false
private val rimPaint = Paint() private val rimPaint = Paint()
@@ -54,11 +58,26 @@ class FaceScanningOverlay(
com.android.systemui.R.attr.wallpaperTextColorAccent) com.android.systemui.R.attr.wallpaperTextColorAccent)
private var cameraProtectionAnimator: ValueAnimator? = null private var cameraProtectionAnimator: ValueAnimator? = null
var hideOverlayRunnable: Runnable? = null var hideOverlayRunnable: Runnable? = null
var faceAuthSucceeded = false
init { init {
visibility = View.INVISIBLE // only show this view when face scanning is happening 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) { override fun setColor(color: Int) {
cameraProtectionColor = color cameraProtectionColor = color
invalidate() invalidate()
@@ -108,7 +127,6 @@ class FaceScanningOverlay(
if (showScanningAnimNow == showScanningAnim) { if (showScanningAnimNow == showScanningAnim) {
return return
} }
showScanningAnim = showScanningAnimNow showScanningAnim = showScanningAnimNow
updateProtectionBoundingPath() updateProtectionBoundingPath()
// Delay the relayout until the end of the animation when hiding, // Delay the relayout until the end of the animation when hiding,
@@ -120,13 +138,20 @@ class FaceScanningOverlay(
cameraProtectionAnimator?.cancel() cameraProtectionAnimator?.cancel()
cameraProtectionAnimator = ValueAnimator.ofFloat(cameraProtectionProgress, cameraProtectionAnimator = ValueAnimator.ofFloat(cameraProtectionProgress,
if (showScanningAnimNow) 1.0f else HIDDEN_CAMERA_PROTECTION_SCALE).apply { if (showScanningAnimNow) SHOW_CAMERA_PROTECTION_SCALE
startDelay = if (showScanningAnim) 0 else PULSE_DISAPPEAR_DURATION else HIDDEN_CAMERA_PROTECTION_SCALE).apply {
duration = if (showScanningAnim) PULSE_APPEAR_DURATION else startDelay =
CAMERA_PROTECTION_DISAPPEAR_DURATION if (showScanningAnim) 0
interpolator = if (showScanningAnim) Interpolators.STANDARD else else if (faceAuthSucceeded) PULSE_SUCCESS_DISAPPEAR_DURATION
Interpolators.EMPHASIZED 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 { addUpdateListener(ValueAnimator.AnimatorUpdateListener {
animation: ValueAnimator -> animation: ValueAnimator ->
cameraProtectionProgress = animation.animatedValue as Float cameraProtectionProgress = animation.animatedValue as Float
@@ -143,47 +168,73 @@ class FaceScanningOverlay(
} }
} }
}) })
start()
} }
rimAnimator?.cancel() rimAnimator?.cancel()
rimAnimator = AnimatorSet().apply { 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) { if (showScanningAnim) {
// appear and then pulse in/out val rimAppearAnimator = ValueAnimator.ofFloat(SHOW_CAMERA_PROTECTION_SCALE,
playSequentially(rimAppearOrDisappearAnimator, 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(), createPulseAnimator(), createPulseAnimator(),
createPulseAnimator(), createPulseAnimator()) createPulseAnimator(), createPulseAnimator())
} else { } else {
val opacityAnimator = ValueAnimator.ofInt(255, 0).apply { val rimDisappearAnimator = ValueAnimator.ofFloat(
duration = PULSE_DISAPPEAR_DURATION rimProgress,
interpolator = Interpolators.LINEAR 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 { addUpdateListener(ValueAnimator.AnimatorUpdateListener {
animation: ValueAnimator -> animation: ValueAnimator ->
rimPaint.alpha = animation.animatedValue as Int rimProgress = animation.animatedValue as Float
invalidate() invalidate()
}) })
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
rimProgress = HIDDEN_RIM_SCALE
invalidate()
}
})
} }
addListener(object : AnimatorListenerAdapter() { if (faceAuthSucceeded) {
override fun onAnimationEnd(animation: Animator) { val successOpacityAnimator = ValueAnimator.ofInt(255, 0).apply {
rimProgress = HIDDEN_RIM_SCALE duration = PULSE_SUCCESS_DISAPPEAR_DURATION
rimPaint.alpha = 255 interpolator = Interpolators.LINEAR
invalidate() addUpdateListener(ValueAnimator.AnimatorUpdateListener {
animation: ValueAnimator ->
rimPaint.alpha = animation.animatedValue as Int
invalidate()
})
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
rimPaint.alpha = 255
invalidate()
}
})
} }
}) val rimSuccessAnimator = AnimatorSet()
rimSuccessAnimator.playTogether(rimDisappearAnimator, successOpacityAnimator)
// disappear playTogether(rimSuccessAnimator, cameraProtectionAnimator)
playTogether(rimAppearOrDisappearAnimator, opacityAnimator) } else {
playTogether(rimDisappearAnimator, cameraProtectionAnimator)
}
} }
addListener(object : AnimatorListenerAdapter() { 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 { companion object {
private const val HIDDEN_RIM_SCALE = HIDDEN_CAMERA_PROTECTION_SCALE 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_INWARDS = 500L
private const val PULSE_DURATION_OUTWARDS = 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_SUCCESS_DISAPPEAR_DURATION = 400L
private const val PULSE_RADIUS_IN = 1.15f private const val CAMERA_PROTECTION_SUCCESS_DISAPPEAR_DURATION = 500L // without start delay
private const val PULSE_RADIUS_OUT = 1.25f
private const val PULSE_ERROR_DISAPPEAR_DURATION = 200L
private const val CAMERA_PROTECTION_ERROR_DISAPPEAR_DURATION = 300L // without start delay
} }
} }

View File

@@ -32,10 +32,12 @@ import android.widget.FrameLayout
import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.FaceScanningOverlay import com.android.systemui.FaceScanningOverlay
import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags import com.android.systemui.flags.Flags
import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.plugins.statusbar.StatusBarStateController
import java.util.concurrent.Executor
import javax.inject.Inject import javax.inject.Inject
@SysUISingleton @SysUISingleton
@@ -44,6 +46,7 @@ class FaceScanningProviderFactory @Inject constructor(
private val context: Context, private val context: Context,
private val statusBarStateController: StatusBarStateController, private val statusBarStateController: StatusBarStateController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@Main private val mainExecutor: Executor,
private val featureFlags: FeatureFlags private val featureFlags: FeatureFlags
) : DecorProviderFactory() { ) : DecorProviderFactory() {
private val display = context.display private val display = context.display
@@ -82,7 +85,9 @@ class FaceScanningProviderFactory @Inject constructor(
bound.baseOnRotation0(displayInfo.rotation), bound.baseOnRotation0(displayInfo.rotation),
authController, authController,
statusBarStateController, statusBarStateController,
keyguardUpdateMonitor) keyguardUpdateMonitor,
mainExecutor
)
) )
} }
} }
@@ -102,7 +107,8 @@ class FaceScanningOverlayProviderImpl(
override val alignedBound: Int, override val alignedBound: Int,
private val authController: AuthController, private val authController: AuthController,
private val statusBarStateController: StatusBarStateController, private val statusBarStateController: StatusBarStateController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val mainExecutor: Executor
) : BoundDecorProvider() { ) : BoundDecorProvider() {
override val viewId: Int = com.android.systemui.R.id.face_scanning_anim override val viewId: Int = com.android.systemui.R.id.face_scanning_anim
@@ -127,7 +133,9 @@ class FaceScanningOverlayProviderImpl(
context, context,
alignedBound, alignedBound,
statusBarStateController, statusBarStateController,
keyguardUpdateMonitor) keyguardUpdateMonitor,
mainExecutor
)
view.id = viewId view.id = viewId
FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT).let { ViewGroup.LayoutParams.MATCH_PARENT).let {

View File

@@ -210,7 +210,8 @@ public class ScreenDecorationsTest extends SysuiTestCase {
BOUNDS_POSITION_TOP, BOUNDS_POSITION_TOP,
mAuthController, mAuthController,
mStatusBarStateController, mStatusBarStateController,
mKeyguardUpdateMonitor)); mKeyguardUpdateMonitor,
mExecutor));
mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings, mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings,
mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController, mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController,