Prioritize fingerprint errors for coex.

Bug: 287011195
Test: atest PromptViewModelTest
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:f9a99625e4ff4b657b72604265a9f7c85e4100c1)
Merged-In: Ie148ef7fb74ba4546f2c871592a3ef4c2bb8dae6
Change-Id: Ie148ef7fb74ba4546f2c871592a3ef4c2bb8dae6
This commit is contained in:
Joe Bolinger
2023-07-06 00:30:10 +00:00
committed by Cherrypicker Worker
parent 3e869b8f69
commit b829ba97bc
7 changed files with 108 additions and 45 deletions

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.systemui.biometrics.domain.model
package com.android.systemui.biometrics.shared.model
import android.hardware.biometrics.BiometricAuthenticator

View File

@@ -46,9 +46,9 @@ import com.android.systemui.biometrics.AuthIconController
import com.android.systemui.biometrics.AuthPanelController
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.domain.model.BiometricModalities
import com.android.systemui.biometrics.domain.model.BiometricModality
import com.android.systemui.biometrics.domain.model.asBiometricModality
import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.PromptKind
import com.android.systemui.biometrics.shared.model.asBiometricModality
import com.android.systemui.biometrics.ui.BiometricPromptLayout
import com.android.systemui.biometrics.ui.viewmodel.FingerprintStartMode
import com.android.systemui.biometrics.ui.viewmodel.PromptMessage
@@ -396,7 +396,6 @@ private class Spaghetti(
private var lifecycleScope: CoroutineScope? = null
private var modalities: BiometricModalities = BiometricModalities()
private var faceFailedAtLeastOnce = false
private var legacyCallback: Callback? = null
override var legacyIconController: AuthIconController? = null
@@ -476,19 +475,15 @@ private class Spaghetti(
viewModel.ensureFingerprintHasStarted(isDelayed = true)
applicationScope.launch {
val suppress =
modalities.hasFaceAndFingerprint &&
(failedModality == BiometricModality.Face) &&
faceFailedAtLeastOnce
if (failedModality == BiometricModality.Face) {
faceFailedAtLeastOnce = true
}
viewModel.showTemporaryError(
failureReason,
messageAfterError = modalities.asDefaultHelpMessage(applicationContext),
authenticateAfterError = modalities.hasFingerprint,
suppressIfErrorShowing = suppress,
suppressIf = { currentMessage ->
modalities.hasFaceAndFingerprint &&
failedModality == BiometricModality.Face &&
currentMessage.isError
},
failedModality = failedModality,
)
}
@@ -501,11 +496,10 @@ private class Spaghetti(
}
applicationScope.launch {
val suppress =
modalities.hasFaceAndFingerprint && (errorModality == BiometricModality.Face)
viewModel.showTemporaryError(
error,
suppressIfErrorShowing = suppress,
messageAfterError = modalities.asDefaultHelpMessage(applicationContext),
authenticateAfterError = modalities.hasFingerprint,
)
delay(BiometricPrompt.HIDE_DIALOG_DELAY.toLong())
legacyCallback?.onAction(Callback.ACTION_ERROR)
@@ -522,6 +516,7 @@ private class Spaghetti(
viewModel.showTemporaryError(
help,
messageAfterError = modalities.asDefaultHelpMessage(applicationContext),
authenticateAfterError = modalities.hasFingerprint,
hapticFeedback = false,
)
}

View File

@@ -16,7 +16,7 @@
package com.android.systemui.biometrics.ui.viewmodel
import com.android.systemui.biometrics.domain.model.BiometricModality
import com.android.systemui.biometrics.shared.model.BiometricModality
/**
* The authenticated state with the [authenticatedModality] (when [isAuthenticated]) with an

View File

@@ -33,9 +33,9 @@ sealed interface PromptMessage {
else -> ""
}
/** If this is an [Error] or [Help] message. */
val isErrorOrHelp: Boolean
get() = this is Error || this is Help
/** If this is an [Error]. */
val isError: Boolean
get() = this is Error
/** An error message. */
data class Error(val errorMessage: String) : PromptMessage

View File

@@ -20,7 +20,7 @@ import android.util.Log
import com.android.systemui.biometrics.AuthBiometricView
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.model.BiometricModalities
import com.android.systemui.biometrics.domain.model.BiometricModality
import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.PromptKind
import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
@@ -210,35 +210,33 @@ constructor(
* Show a temporary error [message] associated with an optional [failedModality] and play
* [hapticFeedback].
*
* An optional [messageAfterError] will be shown via [showAuthenticating] when
* [authenticateAfterError] is set (or via [showHelp] when not set) after the error is
* dismissed.
* The [messageAfterError] will be shown via [showAuthenticating] when [authenticateAfterError]
* 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 [suppressIfErrorShowing] is
* set and an error message is already showing.
* The error is ignored if the user has already authenticated or if [suppressIf] is true given
* the currently showing [PromptMessage].
*/
suspend fun showTemporaryError(
message: String,
messageAfterError: String,
authenticateAfterError: Boolean,
suppressIf: (PromptMessage) -> Boolean = { false },
hapticFeedback: Boolean = true,
messageAfterError: String = "",
authenticateAfterError: Boolean = false,
suppressIfErrorShowing: Boolean = false,
failedModality: BiometricModality = BiometricModality.None,
) = coroutineScope {
if (_isAuthenticated.value.isAuthenticated) {
return@coroutineScope
}
if (_message.value.isErrorOrHelp && suppressIfErrorShowing) {
if (_isAuthenticated.value.isNotAuthenticated) {
_canTryAgainNow.value = supportsRetry(failedModality)
}
_canTryAgainNow.value = supportsRetry(failedModality)
if (suppressIf(_message.value)) {
return@coroutineScope
}
_isAuthenticating.value = false
_isAuthenticated.value = PromptAuthState(false)
_forceMediumSize.value = true
_canTryAgainNow.value = supportsRetry(failedModality)
_message.value = PromptMessage.Error(message)
_legacyState.value = AuthBiometricView.STATE_ERROR

View File

@@ -18,7 +18,7 @@ package com.android.systemui.biometrics.ui.viewmodel
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.domain.model.BiometricModality
import com.android.systemui.biometrics.shared.model.BiometricModality
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith

View File

@@ -27,11 +27,12 @@ import com.android.systemui.biometrics.data.repository.FakePromptRepository
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
import com.android.systemui.biometrics.domain.model.BiometricModalities
import com.android.systemui.biometrics.domain.model.BiometricModality
import com.android.systemui.biometrics.extractAuthenticatorTypes
import com.android.systemui.biometrics.faceSensorPropertiesInternal
import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
@@ -48,7 +49,6 @@ import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
@@ -204,7 +204,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
@Test
fun plays_haptic_on_errors() = runGenericTest {
viewModel.showTemporaryError("so sad", hapticFeedback = true)
viewModel.showTemporaryError(
"so sad",
messageAfterError = "",
authenticateAfterError = false,
hapticFeedback = true,
)
verify(vibrator).vibrateAuthError(any())
verify(vibrator, never()).vibrateAuthSuccess(any())
@@ -212,7 +217,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
@Test
fun plays_haptic_on_errors_unless_skipped() = runGenericTest {
viewModel.showTemporaryError("still sad", hapticFeedback = false)
viewModel.showTemporaryError(
"still sad",
messageAfterError = "",
authenticateAfterError = false,
hapticFeedback = false,
)
verify(vibrator, never()).vibrateAuthError(any())
verify(vibrator, never()).vibrateAuthSuccess(any())
@@ -287,7 +297,13 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
assertThat(canTryAgain).isFalse()
}
val errorJob = launch { viewModel.showTemporaryError("error") }
val errorJob = launch {
viewModel.showTemporaryError(
"error",
messageAfterError = "",
authenticateAfterError = false,
)
}
verifyNoError()
errorJob.join()
verifyNoError()
@@ -306,12 +322,66 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
assertThat(messageIsShowing).isTrue()
}
// @Test
fun `suppress errors`() = runGenericTest {
val errorMessage = "woot"
val message by collectLastValue(viewModel.message)
@Test
fun suppress_temporary_error() = runGenericTest {
val messages by collectValues(viewModel.message)
val errorJob = launch { viewModel.showTemporaryError(errorMessage) }
for (error in listOf("never", "see", "me")) {
launch {
viewModel.showTemporaryError(
error,
messageAfterError = "or me",
authenticateAfterError = false,
suppressIf = { _ -> true },
)
}
}
testScheduler.advanceUntilIdle()
assertThat(messages).containsExactly(PromptMessage.Empty)
}
@Test
fun suppress_temporary_error_when_already_showing_when_requested() =
suppress_temporary_error_when_already_showing(suppress = true)
@Test
fun do_not_suppress_temporary_error_when_already_showing_when_not_requested() =
suppress_temporary_error_when_already_showing(suppress = false)
private fun suppress_temporary_error_when_already_showing(suppress: Boolean) = runGenericTest {
val errors = listOf("woot", "oh yeah", "nope")
val afterSuffix = "(after)"
val expectedErrorMessage = if (suppress) errors.first() else errors.last()
val messages by collectValues(viewModel.message)
for (error in errors) {
launch {
viewModel.showTemporaryError(
error,
messageAfterError = "$error $afterSuffix",
authenticateAfterError = false,
suppressIf = { currentMessage -> suppress && currentMessage.isError },
)
}
}
testScheduler.runCurrent()
assertThat(messages)
.containsExactly(
PromptMessage.Empty,
PromptMessage.Error(expectedErrorMessage),
)
.inOrder()
testScheduler.advanceUntilIdle()
assertThat(messages)
.containsExactly(
PromptMessage.Empty,
PromptMessage.Error(expectedErrorMessage),
PromptMessage.Help("$expectedErrorMessage $afterSuffix"),
)
.inOrder()
}
@Test