Prioritize fingerprint errors for coex.
Bug: 287011195 Test: atest PromptViewModelTest Change-Id: Ie148ef7fb74ba4546f2c871592a3ef4c2bb8dae6
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user