From 97c7a5cd81177c297c89c352ef61d912c1ad9cbb Mon Sep 17 00:00:00 2001 From: Joe Bolinger Date: Tue, 18 Jul 2023 03:19:14 +0000 Subject: [PATCH] Show only first face error message for coex. Fix: 291126484 Test: atest PromptHistoryImplTest PromptViewModelTest Test: manual (use bp test app, fail coex multiple times, retry face, and verify error message) Change-Id: I0349ec3be6d0d0312c87f02ec51043be2070fd72 --- .../ui/binder/BiometricViewBinder.kt | 4 +- .../biometrics/ui/viewmodel/PromptHistory.kt | 46 +++++++++++ .../ui/viewmodel/PromptViewModel.kt | 9 ++- .../ui/viewmodel/PromptHistoryImplTest.kt | 76 +++++++++++++++++++ .../ui/viewmodel/PromptViewModelTest.kt | 4 +- 5 files changed, 132 insertions(+), 7 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistory.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index e5a4d1a644f14..7ae1443203bba 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -479,10 +479,10 @@ private class Spaghetti( failureReason, messageAfterError = modalities.asDefaultHelpMessage(applicationContext), authenticateAfterError = modalities.hasFingerprint, - suppressIf = { currentMessage -> + suppressIf = { currentMessage, history -> modalities.hasFaceAndFingerprint && failedModality == BiometricModality.Face && - currentMessage.isError + (currentMessage.isError || history.faceFailed) }, failedModality = failedModality, ) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistory.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistory.kt new file mode 100644 index 0000000000000..d002bf02cc04f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistory.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.ui.viewmodel + +import com.android.systemui.biometrics.shared.model.BiometricModality + +/** Contains metadata about key events that have occurred while biometric prompt is showing. */ +interface PromptHistory { + + /** If face authentication has failed at least once. */ + val faceFailed: Boolean + + /** If fingerprint authentication has failed at least once. */ + val fingerprintFailed: Boolean +} + +class PromptHistoryImpl : PromptHistory { + private var failures = mutableSetOf() + + override val faceFailed + get() = failures.contains(BiometricModality.Face) + + override val fingerprintFailed + get() = failures.contains(BiometricModality.Fingerprint) + + /** Record a failure event. */ + fun failure(modality: BiometricModality) { + if (modality != BiometricModality.None) { + failures.add(modality) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index 8a2e4059ee730..dca19c503fd35 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -204,6 +204,7 @@ constructor( } .distinctUntilChanged() + private val history = PromptHistoryImpl() private var messageJob: Job? = null /** @@ -214,13 +215,13 @@ constructor( * is set (or via [showHelp] when not set) after the error is dismissed. * * The error is ignored if the user has already authenticated or if [suppressIf] is true given - * the currently showing [PromptMessage]. + * the currently showing [PromptMessage] and [PromptHistory]. */ suspend fun showTemporaryError( message: String, messageAfterError: String, authenticateAfterError: Boolean, - suppressIf: (PromptMessage) -> Boolean = { false }, + suppressIf: (PromptMessage, PromptHistory) -> Boolean = { _, _ -> false }, hapticFeedback: Boolean = true, failedModality: BiometricModality = BiometricModality.None, ) = coroutineScope { @@ -230,7 +231,9 @@ constructor( _canTryAgainNow.value = supportsRetry(failedModality) - if (suppressIf(_message.value)) { + val suppress = suppressIf(_message.value, history) + history.failure(failedModality) + if (suppress) { return@coroutineScope } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt new file mode 100644 index 0000000000000..f9b590f8e018d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.ui.viewmodel + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.shared.model.BiometricModality +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +class PromptHistoryImplTest : SysuiTestCase() { + + private lateinit var history: PromptHistoryImpl + + @Before + fun setup() { + history = PromptHistoryImpl() + } + + @Test + fun empty() { + assertThat(history.faceFailed).isFalse() + assertThat(history.fingerprintFailed).isFalse() + } + + @Test + fun faceFailed() = + repeat(2) { + history.failure(BiometricModality.None) + history.failure(BiometricModality.Face) + + assertThat(history.faceFailed).isTrue() + assertThat(history.fingerprintFailed).isFalse() + } + + @Test + fun fingerprintFailed() = + repeat(2) { + history.failure(BiometricModality.None) + history.failure(BiometricModality.Fingerprint) + + assertThat(history.faceFailed).isFalse() + assertThat(history.fingerprintFailed).isTrue() + } + + @Test + fun coexFailed() = + repeat(2) { + history.failure(BiometricModality.Face) + history.failure(BiometricModality.Fingerprint) + + assertThat(history.faceFailed).isTrue() + assertThat(history.fingerprintFailed).isTrue() + + history.failure(BiometricModality.None) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index 91140a9b0fc42..40b1f207894a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -335,7 +335,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa error, messageAfterError = "or me", authenticateAfterError = false, - suppressIf = { _ -> true }, + suppressIf = { _, _ -> true }, ) } } @@ -364,7 +364,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa error, messageAfterError = "$error $afterSuffix", authenticateAfterError = false, - suppressIf = { currentMessage -> suppress && currentMessage.isError }, + suppressIf = { currentMessage, _ -> suppress && currentMessage.isError }, ) } }