Loading packages/SystemUI/multivalentTests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt +1 −1 Original line number Diff line number Diff line Loading @@ -115,7 +115,7 @@ class FaceScanningProviderFactoryTest : SysuiTestCase() { @Test fun shouldShowFaceScanningAnimationIfKeyguardFaceDetectionIsShowing() { whenever(keyguardUpdateMonitor.isFaceEnabledAndEnrolled).thenReturn(true) whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(true) whenever(keyguardUpdateMonitor.isFaceAuthOrDetectionRunning).thenReturn(true) assertThat(underTest.shouldShowFaceScanningAnim()).isTrue() } Loading packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +23 −2 Original line number Diff line number Diff line Loading @@ -737,7 +737,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false) assertThat(canFaceAuthRun()).isFalse() underTest.requestAuthenticate( FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED, fallbackToDetection = true, ) faceAuthenticateIsNotCalled() Loading @@ -758,7 +758,28 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { keyguardRepository.setKeyguardDismissible(true) assertThat(canFaceAuthRun()).isFalse() underTest.requestAuthenticate( FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED, fallbackToDetection = true, ) faceAuthenticateIsNotCalled() faceDetectIsCalled() } @Test fun authenticateFallbacksToDetectionWhenFaceIsLockedOut() = testScope.runTest { whenever(faceManager.sensorPropertiesInternal) .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true))) whenever(bypassController.bypassEnabled).thenReturn(true) underTest = createDeviceEntryFaceAuthRepositoryImpl() initCollectors() allPreconditionsToRunFaceAuthAreTrue() underTest.setLockedOut(true) assertThat(canFaceAuthRun()).isFalse() underTest.requestAuthenticate( FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED, fallbackToDetection = true, ) faceAuthenticateIsNotCalled() Loading packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt +1 −2 Original line number Diff line number Diff line Loading @@ -190,10 +190,9 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() { } @Test fun whenFaceIsLockedOutAndBypass_DetectRuns() = fun whenFaceIsLockedOutAndBypass_runningAuthRequestNotNull() = kosmos.runTest { underTest.start() val authenticationStatus = collectLastValue(underTest.authenticationStatus) faceAuthRepository.setLockedOut(true) fakeDeviceEntryFaceAuthRepository.isBypassEnabled.value = true Loading packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +2 −2 Original line number Diff line number Diff line Loading @@ -1294,7 +1294,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, CoreSt for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { cb.onBiometricRunningStateChanged(isFaceDetectionRunning(), cb.onBiometricRunningStateChanged(isFaceAuthOrDetectionRunning(), FACE); } } Loading @@ -1308,7 +1308,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, CoreSt * @deprecated This is being migrated to use modern architecture. */ @Deprecated public boolean isFaceDetectionRunning() { public boolean isFaceAuthOrDetectionRunning() { return getFaceAuthInteractor() != null && (getFaceAuthInteractor().isAuthRunning() || getFaceAuthInteractor().isDetectRunning()); Loading packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt +114 −98 Original line number Diff line number Diff line Loading @@ -87,7 +87,7 @@ class FaceScanningOverlay( override fun enableShowProtection(isCameraActive: Boolean) { val scanningAnimationRequiredWhenCameraActive = keyguardUpdateMonitor.isFaceDetectionRunning || authController.isShowing || mDebug keyguardUpdateMonitor.isFaceAuthOrDetectionRunning || authController.isShowing || mDebug val faceAuthSucceeded = keyguardUpdateMonitor.isFaceAuthenticated val showScanningAnimationNow = scanningAnimationRequiredWhenCameraActive && isCameraActive if (showScanningAnimationNow == showScanningAnim) { Loading @@ -95,11 +95,12 @@ class FaceScanningOverlay( } logger.cameraProtectionShownOrHidden( showScanningAnimationNow, keyguardUpdateMonitor.isFaceDetectionRunning, keyguardUpdateMonitor.isFaceAuthOrDetectionRunning, authController.isShowing, faceAuthSucceeded, isCameraActive, showScanningAnim) showScanningAnim, ) showScanningAnim = showScanningAnimationNow updateProtectionBoundingPath() // Delay the relayout until the end of the animation when hiding, Loading @@ -115,7 +116,8 @@ class FaceScanningOverlay( } rimAnimator?.cancel() rimAnimator = faceScanningRimAnimator( rimAnimator = faceScanningRimAnimator( faceAuthSucceeded, if (Flags.faceScanningAnimationNpeFix()) { cameraProtectionAnimator(faceAuthSucceeded) Loading @@ -128,23 +130,28 @@ class FaceScanningOverlay( private fun faceScanningRimAnimator( faceAuthSucceeded: Boolean, cameraProtectAnimator: ValueAnimator? cameraProtectAnimator: ValueAnimator?, ): AnimatorSet { return if (showScanningAnim) { createFaceScanningRimAnimator(cameraProtectAnimator) } else if (faceAuthSucceeded) { } else { if (faceAuthSucceeded) { createFaceSuccessRimAnimator(cameraProtectAnimator) } else { createFaceNotSuccessRimAnimator(cameraProtectAnimator) }.apply { addListener(object : AnimatorListenerAdapter() { } .apply { addListener( object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { rimAnimator = null if (!showScanningAnim) { requestLayout() } } }) } ) } } } Loading @@ -152,8 +159,9 @@ class FaceScanningOverlay( return ValueAnimator.ofFloat( cameraProtectionProgress, if (showScanningAnim) SHOW_CAMERA_PROTECTION_SCALE else HIDDEN_CAMERA_PROTECTION_SCALE ).apply { else HIDDEN_CAMERA_PROTECTION_SCALE, ) .apply { startDelay = if (showScanningAnim) 0 else if (faceAuthSucceeded) PULSE_SUCCESS_DISAPPEAR_DURATION Loading @@ -167,7 +175,8 @@ class FaceScanningOverlay( else if (faceAuthSucceeded) Interpolators.STANDARD else Interpolators.STANDARD_DECELERATE addUpdateListener(this@FaceScanningOverlay::updateCameraProtectionProgress) addListener(object : AnimatorListenerAdapter() { addListener( object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { if (!Flags.faceScanningAnimationNpeFix()) { cameraProtectionAnimator = null Loading @@ -176,7 +185,8 @@ class FaceScanningOverlay( hide() } } }) } ) } } Loading @@ -202,21 +212,25 @@ class FaceScanningOverlay( rimRect.left.toInt(), rimRect.top.toInt(), rimRect.right.toInt(), rimRect.bottom.toInt()) rimRect.bottom.toInt(), ) val measuredWidth = resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0) val measuredHeight = resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0) logger.boundingRect(rimRect, "onMeasure: Face scanning animation") logger.boundingRect(mBoundingRect, "onMeasure: Display cutout view bounding rect") logger.boundingRect(mTotalBounds, "onMeasure: TotalBounds") logger.onMeasureDimensions(widthMeasureSpec, logger.onMeasureDimensions( widthMeasureSpec, heightMeasureSpec, measuredWidth, measuredHeight) measuredHeight, ) setMeasuredDimension(measuredWidth, measuredHeight) } else { setMeasuredDimension( resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0), resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0)) resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0), ) } } Loading @@ -225,10 +239,11 @@ class FaceScanningOverlay( scalePath(rimPath, rimProgress) rimPaint.style = Paint.Style.FILL val rimPaintAlpha = rimPaint.alpha rimPaint.color = ColorUtils.blendARGB( rimPaint.color = ColorUtils.blendARGB( faceScanningAnimColor, Color.WHITE, statusBarStateController.dozeAmount statusBarStateController.dozeAmount, ) rimPaint.alpha = rimPaintAlpha canvas.drawPath(rimPath, rimPaint) Loading @@ -248,22 +263,22 @@ class FaceScanningOverlay( createRimDisappearAnimator( PULSE_RADIUS_SUCCESS, PULSE_SUCCESS_DISAPPEAR_DURATION, Interpolators.STANDARD_DECELERATE Interpolators.STANDARD_DECELERATE, ), createSuccessOpacityAnimator(), ) return AnimatorSet().apply { playTogether(rimSuccessAnimator, cameraProtectAnimator) } return AnimatorSet().apply { playTogether(rimSuccessAnimator, cameraProtectAnimator) } } private fun createFaceNotSuccessRimAnimator(cameraProtectAnimator: ValueAnimator?): AnimatorSet { private fun createFaceNotSuccessRimAnimator( cameraProtectAnimator: ValueAnimator? ): AnimatorSet { return AnimatorSet().apply { playTogether( createRimDisappearAnimator( SHOW_CAMERA_PROTECTION_SCALE, PULSE_ERROR_DISAPPEAR_DURATION, Interpolators.STANDARD Interpolators.STANDARD, ), cameraProtectAnimator, ) Loading @@ -273,18 +288,20 @@ class FaceScanningOverlay( private fun createRimDisappearAnimator( endValue: Float, animDuration: Long, timeInterpolator: TimeInterpolator timeInterpolator: TimeInterpolator, ): ValueAnimator { return ValueAnimator.ofFloat(rimProgress, endValue).apply { duration = animDuration interpolator = timeInterpolator addUpdateListener(this@FaceScanningOverlay::updateRimProgress) addListener(object : AnimatorListenerAdapter() { addListener( object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { rimProgress = HIDDEN_RIM_SCALE invalidate() } }) } ) } } Loading @@ -293,29 +310,25 @@ class FaceScanningOverlay( duration = PULSE_SUCCESS_DISAPPEAR_DURATION interpolator = Interpolators.LINEAR addUpdateListener(this@FaceScanningOverlay::updateRimAlpha) addListener(object : AnimatorListenerAdapter() { addListener( object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { rimPaint.alpha = 255 invalidate() } }) } ) } } private fun createFaceScanningRimAnimator(cameraProtectAnimator: ValueAnimator?): AnimatorSet { return AnimatorSet().apply { playSequentially( cameraProtectAnimator, createRimAppearAnimator(), ) playSequentially(cameraProtectAnimator, createRimAppearAnimator()) } } private fun createRimAppearAnimator(): ValueAnimator { return ValueAnimator.ofFloat( SHOW_CAMERA_PROTECTION_SCALE, PULSE_RADIUS_OUT ).apply { return ValueAnimator.ofFloat(SHOW_CAMERA_PROTECTION_SCALE, PULSE_RADIUS_OUT).apply { duration = PULSE_APPEAR_DURATION interpolator = Interpolators.STANDARD_DECELERATE addUpdateListener(this@FaceScanningOverlay::updateRimProgress) Loading Loading @@ -361,12 +374,15 @@ class FaceScanningOverlay( private const val CAMERA_PROTECTION_ERROR_DISAPPEAR_DURATION = 300L // without start delay private fun scalePath(path: Path, scalingFactor: Float) { val scaleMatrix = Matrix().apply { val scaleMatrix = Matrix().apply { val boundingRectangle = RectF() path.computeBounds(boundingRectangle, true) setScale( scalingFactor, scalingFactor, boundingRectangle.centerX(), boundingRectangle.centerY() scalingFactor, scalingFactor, boundingRectangle.centerX(), boundingRectangle.centerY(), ) } path.transform(scaleMatrix) Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt +1 −1 Original line number Diff line number Diff line Loading @@ -115,7 +115,7 @@ class FaceScanningProviderFactoryTest : SysuiTestCase() { @Test fun shouldShowFaceScanningAnimationIfKeyguardFaceDetectionIsShowing() { whenever(keyguardUpdateMonitor.isFaceEnabledAndEnrolled).thenReturn(true) whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(true) whenever(keyguardUpdateMonitor.isFaceAuthOrDetectionRunning).thenReturn(true) assertThat(underTest.shouldShowFaceScanningAnim()).isTrue() } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +23 −2 Original line number Diff line number Diff line Loading @@ -737,7 +737,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false) assertThat(canFaceAuthRun()).isFalse() underTest.requestAuthenticate( FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED, fallbackToDetection = true, ) faceAuthenticateIsNotCalled() Loading @@ -758,7 +758,28 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { keyguardRepository.setKeyguardDismissible(true) assertThat(canFaceAuthRun()).isFalse() underTest.requestAuthenticate( FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED, fallbackToDetection = true, ) faceAuthenticateIsNotCalled() faceDetectIsCalled() } @Test fun authenticateFallbacksToDetectionWhenFaceIsLockedOut() = testScope.runTest { whenever(faceManager.sensorPropertiesInternal) .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true))) whenever(bypassController.bypassEnabled).thenReturn(true) underTest = createDeviceEntryFaceAuthRepositoryImpl() initCollectors() allPreconditionsToRunFaceAuthAreTrue() underTest.setLockedOut(true) assertThat(canFaceAuthRun()).isFalse() underTest.requestAuthenticate( FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED, fallbackToDetection = true, ) faceAuthenticateIsNotCalled() Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt +1 −2 Original line number Diff line number Diff line Loading @@ -190,10 +190,9 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() { } @Test fun whenFaceIsLockedOutAndBypass_DetectRuns() = fun whenFaceIsLockedOutAndBypass_runningAuthRequestNotNull() = kosmos.runTest { underTest.start() val authenticationStatus = collectLastValue(underTest.authenticationStatus) faceAuthRepository.setLockedOut(true) fakeDeviceEntryFaceAuthRepository.isBypassEnabled.value = true Loading
packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +2 −2 Original line number Diff line number Diff line Loading @@ -1294,7 +1294,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, CoreSt for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { cb.onBiometricRunningStateChanged(isFaceDetectionRunning(), cb.onBiometricRunningStateChanged(isFaceAuthOrDetectionRunning(), FACE); } } Loading @@ -1308,7 +1308,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, CoreSt * @deprecated This is being migrated to use modern architecture. */ @Deprecated public boolean isFaceDetectionRunning() { public boolean isFaceAuthOrDetectionRunning() { return getFaceAuthInteractor() != null && (getFaceAuthInteractor().isAuthRunning() || getFaceAuthInteractor().isDetectRunning()); Loading
packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt +114 −98 Original line number Diff line number Diff line Loading @@ -87,7 +87,7 @@ class FaceScanningOverlay( override fun enableShowProtection(isCameraActive: Boolean) { val scanningAnimationRequiredWhenCameraActive = keyguardUpdateMonitor.isFaceDetectionRunning || authController.isShowing || mDebug keyguardUpdateMonitor.isFaceAuthOrDetectionRunning || authController.isShowing || mDebug val faceAuthSucceeded = keyguardUpdateMonitor.isFaceAuthenticated val showScanningAnimationNow = scanningAnimationRequiredWhenCameraActive && isCameraActive if (showScanningAnimationNow == showScanningAnim) { Loading @@ -95,11 +95,12 @@ class FaceScanningOverlay( } logger.cameraProtectionShownOrHidden( showScanningAnimationNow, keyguardUpdateMonitor.isFaceDetectionRunning, keyguardUpdateMonitor.isFaceAuthOrDetectionRunning, authController.isShowing, faceAuthSucceeded, isCameraActive, showScanningAnim) showScanningAnim, ) showScanningAnim = showScanningAnimationNow updateProtectionBoundingPath() // Delay the relayout until the end of the animation when hiding, Loading @@ -115,7 +116,8 @@ class FaceScanningOverlay( } rimAnimator?.cancel() rimAnimator = faceScanningRimAnimator( rimAnimator = faceScanningRimAnimator( faceAuthSucceeded, if (Flags.faceScanningAnimationNpeFix()) { cameraProtectionAnimator(faceAuthSucceeded) Loading @@ -128,23 +130,28 @@ class FaceScanningOverlay( private fun faceScanningRimAnimator( faceAuthSucceeded: Boolean, cameraProtectAnimator: ValueAnimator? cameraProtectAnimator: ValueAnimator?, ): AnimatorSet { return if (showScanningAnim) { createFaceScanningRimAnimator(cameraProtectAnimator) } else if (faceAuthSucceeded) { } else { if (faceAuthSucceeded) { createFaceSuccessRimAnimator(cameraProtectAnimator) } else { createFaceNotSuccessRimAnimator(cameraProtectAnimator) }.apply { addListener(object : AnimatorListenerAdapter() { } .apply { addListener( object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { rimAnimator = null if (!showScanningAnim) { requestLayout() } } }) } ) } } } Loading @@ -152,8 +159,9 @@ class FaceScanningOverlay( return ValueAnimator.ofFloat( cameraProtectionProgress, if (showScanningAnim) SHOW_CAMERA_PROTECTION_SCALE else HIDDEN_CAMERA_PROTECTION_SCALE ).apply { else HIDDEN_CAMERA_PROTECTION_SCALE, ) .apply { startDelay = if (showScanningAnim) 0 else if (faceAuthSucceeded) PULSE_SUCCESS_DISAPPEAR_DURATION Loading @@ -167,7 +175,8 @@ class FaceScanningOverlay( else if (faceAuthSucceeded) Interpolators.STANDARD else Interpolators.STANDARD_DECELERATE addUpdateListener(this@FaceScanningOverlay::updateCameraProtectionProgress) addListener(object : AnimatorListenerAdapter() { addListener( object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { if (!Flags.faceScanningAnimationNpeFix()) { cameraProtectionAnimator = null Loading @@ -176,7 +185,8 @@ class FaceScanningOverlay( hide() } } }) } ) } } Loading @@ -202,21 +212,25 @@ class FaceScanningOverlay( rimRect.left.toInt(), rimRect.top.toInt(), rimRect.right.toInt(), rimRect.bottom.toInt()) rimRect.bottom.toInt(), ) val measuredWidth = resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0) val measuredHeight = resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0) logger.boundingRect(rimRect, "onMeasure: Face scanning animation") logger.boundingRect(mBoundingRect, "onMeasure: Display cutout view bounding rect") logger.boundingRect(mTotalBounds, "onMeasure: TotalBounds") logger.onMeasureDimensions(widthMeasureSpec, logger.onMeasureDimensions( widthMeasureSpec, heightMeasureSpec, measuredWidth, measuredHeight) measuredHeight, ) setMeasuredDimension(measuredWidth, measuredHeight) } else { setMeasuredDimension( resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0), resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0)) resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0), ) } } Loading @@ -225,10 +239,11 @@ class FaceScanningOverlay( scalePath(rimPath, rimProgress) rimPaint.style = Paint.Style.FILL val rimPaintAlpha = rimPaint.alpha rimPaint.color = ColorUtils.blendARGB( rimPaint.color = ColorUtils.blendARGB( faceScanningAnimColor, Color.WHITE, statusBarStateController.dozeAmount statusBarStateController.dozeAmount, ) rimPaint.alpha = rimPaintAlpha canvas.drawPath(rimPath, rimPaint) Loading @@ -248,22 +263,22 @@ class FaceScanningOverlay( createRimDisappearAnimator( PULSE_RADIUS_SUCCESS, PULSE_SUCCESS_DISAPPEAR_DURATION, Interpolators.STANDARD_DECELERATE Interpolators.STANDARD_DECELERATE, ), createSuccessOpacityAnimator(), ) return AnimatorSet().apply { playTogether(rimSuccessAnimator, cameraProtectAnimator) } return AnimatorSet().apply { playTogether(rimSuccessAnimator, cameraProtectAnimator) } } private fun createFaceNotSuccessRimAnimator(cameraProtectAnimator: ValueAnimator?): AnimatorSet { private fun createFaceNotSuccessRimAnimator( cameraProtectAnimator: ValueAnimator? ): AnimatorSet { return AnimatorSet().apply { playTogether( createRimDisappearAnimator( SHOW_CAMERA_PROTECTION_SCALE, PULSE_ERROR_DISAPPEAR_DURATION, Interpolators.STANDARD Interpolators.STANDARD, ), cameraProtectAnimator, ) Loading @@ -273,18 +288,20 @@ class FaceScanningOverlay( private fun createRimDisappearAnimator( endValue: Float, animDuration: Long, timeInterpolator: TimeInterpolator timeInterpolator: TimeInterpolator, ): ValueAnimator { return ValueAnimator.ofFloat(rimProgress, endValue).apply { duration = animDuration interpolator = timeInterpolator addUpdateListener(this@FaceScanningOverlay::updateRimProgress) addListener(object : AnimatorListenerAdapter() { addListener( object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { rimProgress = HIDDEN_RIM_SCALE invalidate() } }) } ) } } Loading @@ -293,29 +310,25 @@ class FaceScanningOverlay( duration = PULSE_SUCCESS_DISAPPEAR_DURATION interpolator = Interpolators.LINEAR addUpdateListener(this@FaceScanningOverlay::updateRimAlpha) addListener(object : AnimatorListenerAdapter() { addListener( object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { rimPaint.alpha = 255 invalidate() } }) } ) } } private fun createFaceScanningRimAnimator(cameraProtectAnimator: ValueAnimator?): AnimatorSet { return AnimatorSet().apply { playSequentially( cameraProtectAnimator, createRimAppearAnimator(), ) playSequentially(cameraProtectAnimator, createRimAppearAnimator()) } } private fun createRimAppearAnimator(): ValueAnimator { return ValueAnimator.ofFloat( SHOW_CAMERA_PROTECTION_SCALE, PULSE_RADIUS_OUT ).apply { return ValueAnimator.ofFloat(SHOW_CAMERA_PROTECTION_SCALE, PULSE_RADIUS_OUT).apply { duration = PULSE_APPEAR_DURATION interpolator = Interpolators.STANDARD_DECELERATE addUpdateListener(this@FaceScanningOverlay::updateRimProgress) Loading Loading @@ -361,12 +374,15 @@ class FaceScanningOverlay( private const val CAMERA_PROTECTION_ERROR_DISAPPEAR_DURATION = 300L // without start delay private fun scalePath(path: Path, scalingFactor: Float) { val scaleMatrix = Matrix().apply { val scaleMatrix = Matrix().apply { val boundingRectangle = RectF() path.computeBounds(boundingRectangle, true) setScale( scalingFactor, scalingFactor, boundingRectangle.centerX(), boundingRectangle.centerY() scalingFactor, scalingFactor, boundingRectangle.centerX(), boundingRectangle.centerY(), ) } path.transform(scaleMatrix) Loading