Merge "Retry face auth whenever there is a hardware error" into udc-dev
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 5f6098b..05ab01b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -140,8 +140,10 @@
     private var authCancellationSignal: CancellationSignal? = null
     private var detectCancellationSignal: CancellationSignal? = null
     private var faceAcquiredInfoIgnoreList: Set<Int>
+    private var retryCount = 0
 
     private var cancelNotReceivedHandlerJob: Job? = null
+    private var halErrorRetryJob: Job? = null
 
     private val _authenticationStatus: MutableStateFlow<AuthenticationStatus?> =
         MutableStateFlow(null)
@@ -228,6 +230,8 @@
             .onEach { goingAwayOrUserSwitchingInProgress ->
                 if (goingAwayOrUserSwitchingInProgress) {
                     _isAuthenticated.value = false
+                    retryCount = 0
+                    halErrorRetryJob?.cancel()
                 }
             }
             .launchIn(applicationScope)
@@ -385,14 +389,11 @@
                 _authenticationStatus.value = errorStatus
                 _isAuthenticated.value = false
                 if (errorStatus.isCancellationError()) {
-                    cancelNotReceivedHandlerJob?.cancel()
-                    applicationScope.launch {
-                        faceAuthLogger.launchingQueuedFaceAuthRequest(
-                            faceAuthRequestedWhileCancellation
-                        )
-                        faceAuthRequestedWhileCancellation?.let { authenticate(it) }
-                        faceAuthRequestedWhileCancellation = null
-                    }
+                    handleFaceCancellationError()
+                }
+                if (errorStatus.isHardwareError()) {
+                    faceAuthLogger.hardwareError(errorStatus)
+                    handleFaceHardwareError()
                 }
                 faceAuthLogger.authenticationError(
                     errorCode,
@@ -418,6 +419,35 @@
             }
         }
 
+    private fun handleFaceCancellationError() {
+        cancelNotReceivedHandlerJob?.cancel()
+        applicationScope.launch {
+            faceAuthRequestedWhileCancellation?.let {
+                faceAuthLogger.launchingQueuedFaceAuthRequest(it)
+                authenticate(it)
+            }
+            faceAuthRequestedWhileCancellation = null
+        }
+    }
+
+    private fun handleFaceHardwareError() {
+        if (retryCount < HAL_ERROR_RETRY_MAX) {
+            retryCount++
+            halErrorRetryJob?.cancel()
+            halErrorRetryJob =
+                applicationScope.launch {
+                    delay(HAL_ERROR_RETRY_TIMEOUT)
+                    if (retryCount < HAL_ERROR_RETRY_MAX) {
+                        faceAuthLogger.attemptingRetryAfterHardwareError(retryCount)
+                        authenticate(
+                            FaceAuthUiEvent.FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE,
+                            fallbackToDetection = false
+                        )
+                    }
+                }
+        }
+    }
+
     private fun onFaceAuthRequestCompleted() {
         cancellationInProgress = false
         _isAuthRunning.value = false
@@ -558,6 +588,12 @@
          * cancelled.
          */
         const val DEFAULT_CANCEL_SIGNAL_TIMEOUT = 3000L
+
+        /** Number of allowed retries whenever there is a face hardware error */
+        const val HAL_ERROR_RETRY_MAX = 20
+
+        /** Timeout before retries whenever there is a HAL error. */
+        const val HAL_ERROR_RETRY_TIMEOUT = 500L // ms
     }
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
index eded9c1..c8bd958 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
@@ -50,6 +50,11 @@
      * was cancelled before it completed.
      */
     fun isCancellationError() = msgId == FaceManager.FACE_ERROR_CANCELED
+
+    /** Method that checks if [msgId] is a hardware error. */
+    fun isHardwareError() =
+        msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE ||
+            msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS
 }
 
 /** Face detection success message. */
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index 7f6e4a9..efd3ad6 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -4,6 +4,7 @@
 import android.hardware.face.FaceSensorPropertiesInternal
 import com.android.keyguard.FaceAuthUiEvent
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.log.dagger.FaceAuthLog
 import com.android.systemui.plugins.log.LogBuffer
@@ -239,4 +240,25 @@
             { "Requesting face auth for trigger: $str1" }
         )
     }
+
+    fun hardwareError(errorStatus: ErrorAuthenticationStatus) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = "${errorStatus.msg}"
+                int1 = errorStatus.msgId
+            },
+            { "Received face hardware error: $str1 , code: $int1" }
+        )
+    }
+
+    fun attemptingRetryAfterHardwareError(retryCount: Int) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            { int1 = retryCount },
+            { "Attempting face auth again because of HW error: retry attempt $int1" }
+        )
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index fc75d47..a76d03b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -21,6 +21,7 @@
 import android.content.pm.UserInfo
 import android.content.pm.UserInfo.FLAG_PRIMARY
 import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_CANCELED
+import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE
 import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT
 import android.hardware.biometrics.ComponentInfoInternal
 import android.hardware.face.FaceAuthenticateOptions
@@ -824,6 +825,26 @@
             verify(faceManager).scheduleWatchdog()
         }
 
+    @Test
+    fun retryFaceIfThereIsAHardwareError() =
+        testScope.runTest {
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue()
+
+            triggerFaceAuth(fallbackToDetect = false)
+            clearInvocations(faceManager)
+
+            authenticationCallback.value.onAuthenticationError(
+                FACE_ERROR_HW_UNAVAILABLE,
+                "HW unavailable"
+            )
+
+            advanceTimeBy(DeviceEntryFaceAuthRepositoryImpl.HAL_ERROR_RETRY_TIMEOUT)
+            runCurrent()
+
+            faceAuthenticateIsCalled()
+        }
+
     private suspend fun TestScope.testGatingCheckForFaceAuth(gatingCheckModifier: () -> Unit) {
         initCollectors()
         allPreconditionsToRunFaceAuthAreTrue()