Add BiometricMessageInteractor for fingerprint msgs
BiometricMessageInteractor filters fingerprint error, help and failure messages. Test: atest BiometricMessageInteractorTest Bug: 288308594 Change-Id: Ic18303ab489672796de35feedae2f3f24bd8551c
This commit is contained in:
@@ -28,7 +28,7 @@ import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
|
||||
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
|
||||
import com.android.systemui.keyguard.shared.model.FailedFingerprintAuthenticationStatus
|
||||
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
|
||||
import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
|
||||
import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
|
||||
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
|
||||
@@ -214,7 +214,7 @@ constructor(
|
||||
) {
|
||||
sendUpdateIfFingerprint(
|
||||
biometricSourceType,
|
||||
FailedFingerprintAuthenticationStatus,
|
||||
FailFingerprintAuthenticationStatus,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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.keyguard.domain.interactor
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.hardware.biometrics.BiometricSourceType
|
||||
import android.hardware.biometrics.BiometricSourceType.FINGERPRINT
|
||||
import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED
|
||||
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
|
||||
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
|
||||
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
|
||||
import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
|
||||
import com.android.systemui.keyguard.util.IndicationHelper
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.filterNot
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
/**
|
||||
* BiometricMessage business logic. Filters biometric error/acquired/fail/success events for
|
||||
* authentication events that should never surface a message to the user at the current device
|
||||
* state.
|
||||
*/
|
||||
@ExperimentalCoroutinesApi
|
||||
@SysUISingleton
|
||||
class BiometricMessageInteractor
|
||||
@Inject
|
||||
constructor(
|
||||
@Main private val resources: Resources,
|
||||
private val fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
|
||||
private val fingerprintPropertyRepository: FingerprintPropertyRepository,
|
||||
private val indicationHelper: IndicationHelper,
|
||||
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
|
||||
) {
|
||||
val fingerprintErrorMessage: Flow<BiometricMessage> =
|
||||
fingerprintAuthRepository.authenticationStatus
|
||||
.filter {
|
||||
it is ErrorFingerprintAuthenticationStatus &&
|
||||
!indicationHelper.shouldSuppressErrorMsg(FINGERPRINT, it.msgId)
|
||||
}
|
||||
.map {
|
||||
val errorStatus = it as ErrorFingerprintAuthenticationStatus
|
||||
BiometricMessage(
|
||||
FINGERPRINT,
|
||||
BiometricMessageType.ERROR,
|
||||
errorStatus.msgId,
|
||||
errorStatus.msg,
|
||||
)
|
||||
}
|
||||
|
||||
val fingerprintHelpMessage: Flow<BiometricMessage> =
|
||||
fingerprintAuthRepository.authenticationStatus
|
||||
.filter { it is HelpFingerprintAuthenticationStatus }
|
||||
.filterNot { isPrimaryAuthRequired() }
|
||||
.map {
|
||||
val helpStatus = it as HelpFingerprintAuthenticationStatus
|
||||
BiometricMessage(
|
||||
FINGERPRINT,
|
||||
BiometricMessageType.HELP,
|
||||
helpStatus.msgId,
|
||||
helpStatus.msg,
|
||||
)
|
||||
}
|
||||
|
||||
val fingerprintFailMessage: Flow<BiometricMessage> =
|
||||
isUdfps().flatMapLatest { isUdfps ->
|
||||
fingerprintAuthRepository.authenticationStatus
|
||||
.filter { it is FailFingerprintAuthenticationStatus }
|
||||
.filterNot { isPrimaryAuthRequired() }
|
||||
.map {
|
||||
BiometricMessage(
|
||||
FINGERPRINT,
|
||||
BiometricMessageType.FAIL,
|
||||
BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
|
||||
if (isUdfps) {
|
||||
resources.getString(
|
||||
com.android.internal.R.string.fingerprint_udfps_error_not_match
|
||||
)
|
||||
} else {
|
||||
resources.getString(
|
||||
com.android.internal.R.string.fingerprint_error_not_match
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isUdfps() =
|
||||
fingerprintPropertyRepository.sensorType.map {
|
||||
it == FingerprintSensorType.UDFPS_OPTICAL ||
|
||||
it == FingerprintSensorType.UDFPS_ULTRASONIC
|
||||
}
|
||||
|
||||
private fun isPrimaryAuthRequired(): Boolean {
|
||||
// Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
|
||||
// as long as primary auth, i.e. PIN/pattern/password, is required), so it's ok to
|
||||
// pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
|
||||
// check of whether non-strong biometric is allowed since strong biometrics can still be
|
||||
// used.
|
||||
return !keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
|
||||
}
|
||||
}
|
||||
|
||||
data class BiometricMessage(
|
||||
val source: BiometricSourceType,
|
||||
val type: BiometricMessageType,
|
||||
val id: Int,
|
||||
val message: String?,
|
||||
)
|
||||
|
||||
enum class BiometricMessageType {
|
||||
HELP,
|
||||
ERROR,
|
||||
FAIL,
|
||||
}
|
||||
@@ -41,7 +41,7 @@ data class AcquiredFingerprintAuthenticationStatus(val acquiredInfo: Int) :
|
||||
FingerprintAuthenticationStatus()
|
||||
|
||||
/** Fingerprint authentication failed message. */
|
||||
object FailedFingerprintAuthenticationStatus : FingerprintAuthenticationStatus()
|
||||
object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus()
|
||||
|
||||
/** Fingerprint authentication error message */
|
||||
data class ErrorFingerprintAuthenticationStatus(
|
||||
|
||||
@@ -28,7 +28,7 @@ import com.android.systemui.biometrics.AuthController
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
|
||||
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
|
||||
import com.android.systemui.keyguard.shared.model.FailedFingerprintAuthenticationStatus
|
||||
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
|
||||
import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
|
||||
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
@@ -210,7 +210,7 @@ class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() {
|
||||
)
|
||||
|
||||
assertThat(authenticationStatus)
|
||||
.isInstanceOf(FailedFingerprintAuthenticationStatus::class.java)
|
||||
.isInstanceOf(FailFingerprintAuthenticationStatus::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* 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.keyguard.domain.interactor
|
||||
|
||||
import android.hardware.biometrics.BiometricSourceType.FINGERPRINT
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
|
||||
import com.android.systemui.biometrics.shared.model.SensorStrength
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
|
||||
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
|
||||
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
|
||||
import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
|
||||
import com.android.systemui.keyguard.util.IndicationHelper
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentMatchers.anyBoolean
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class BiometricMessageInteractorTest : SysuiTestCase() {
|
||||
|
||||
private lateinit var underTest: BiometricMessageInteractor
|
||||
private lateinit var testScope: TestScope
|
||||
private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
|
||||
private lateinit var fingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository
|
||||
|
||||
@Mock private lateinit var indicationHelper: IndicationHelper
|
||||
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
testScope = TestScope()
|
||||
fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
|
||||
fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
|
||||
underTest =
|
||||
BiometricMessageInteractor(
|
||||
mContext.resources,
|
||||
fingerprintAuthRepository,
|
||||
fingerprintPropertyRepository,
|
||||
indicationHelper,
|
||||
keyguardUpdateMonitor,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fingerprintErrorMessage() =
|
||||
testScope.runTest {
|
||||
val fingerprintErrorMessage by collectLastValue(underTest.fingerprintErrorMessage)
|
||||
|
||||
// GIVEN FINGERPRINT_ERROR_HW_UNAVAILABLE should NOT be suppressed
|
||||
whenever(
|
||||
indicationHelper.shouldSuppressErrorMsg(
|
||||
FINGERPRINT,
|
||||
FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
|
||||
)
|
||||
)
|
||||
.thenReturn(false)
|
||||
|
||||
// WHEN authentication status error is FINGERPRINT_ERROR_HW_UNAVAILABLE
|
||||
fingerprintAuthRepository.setAuthenticationStatus(
|
||||
ErrorFingerprintAuthenticationStatus(
|
||||
msgId = FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE,
|
||||
msg = "test"
|
||||
)
|
||||
)
|
||||
|
||||
// THEN fingerprintErrorMessage is updated
|
||||
assertThat(fingerprintErrorMessage?.source).isEqualTo(FINGERPRINT)
|
||||
assertThat(fingerprintErrorMessage?.type).isEqualTo(BiometricMessageType.ERROR)
|
||||
assertThat(fingerprintErrorMessage?.id)
|
||||
.isEqualTo(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE)
|
||||
assertThat(fingerprintErrorMessage?.message).isEqualTo("test")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fingerprintErrorMessage_suppressedError() =
|
||||
testScope.runTest {
|
||||
val fingerprintErrorMessage by collectLastValue(underTest.fingerprintErrorMessage)
|
||||
|
||||
// GIVEN FINGERPRINT_ERROR_HW_UNAVAILABLE should be suppressed
|
||||
whenever(
|
||||
indicationHelper.shouldSuppressErrorMsg(
|
||||
FINGERPRINT,
|
||||
FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
|
||||
)
|
||||
)
|
||||
.thenReturn(true)
|
||||
|
||||
// WHEN authentication status error is FINGERPRINT_ERROR_HW_UNAVAILABLE
|
||||
fingerprintAuthRepository.setAuthenticationStatus(
|
||||
ErrorFingerprintAuthenticationStatus(
|
||||
msgId = FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE,
|
||||
msg = "test"
|
||||
)
|
||||
)
|
||||
|
||||
// THEN fingerprintErrorMessage isn't update - it's still null
|
||||
assertThat(fingerprintErrorMessage).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fingerprintHelpMessage() =
|
||||
testScope.runTest {
|
||||
val fingerprintHelpMessage by collectLastValue(underTest.fingerprintHelpMessage)
|
||||
|
||||
// GIVEN primary auth is NOT required
|
||||
whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
|
||||
.thenReturn(true)
|
||||
|
||||
// WHEN authentication status help is FINGERPRINT_ACQUIRED_IMAGER_DIRTY
|
||||
fingerprintAuthRepository.setAuthenticationStatus(
|
||||
HelpFingerprintAuthenticationStatus(
|
||||
msgId = FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY,
|
||||
msg = "test"
|
||||
)
|
||||
)
|
||||
|
||||
// THEN fingerprintHelpMessage is updated
|
||||
assertThat(fingerprintHelpMessage?.source).isEqualTo(FINGERPRINT)
|
||||
assertThat(fingerprintHelpMessage?.type).isEqualTo(BiometricMessageType.HELP)
|
||||
assertThat(fingerprintHelpMessage?.id)
|
||||
.isEqualTo(FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY)
|
||||
assertThat(fingerprintHelpMessage?.message).isEqualTo("test")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fingerprintHelpMessage_primaryAuthRequired() =
|
||||
testScope.runTest {
|
||||
val fingerprintHelpMessage by collectLastValue(underTest.fingerprintHelpMessage)
|
||||
|
||||
// GIVEN primary auth is required
|
||||
whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
|
||||
.thenReturn(false)
|
||||
|
||||
// WHEN authentication status help is FINGERPRINT_ACQUIRED_IMAGER_DIRTY
|
||||
fingerprintAuthRepository.setAuthenticationStatus(
|
||||
HelpFingerprintAuthenticationStatus(
|
||||
msgId = FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY,
|
||||
msg = "test"
|
||||
)
|
||||
)
|
||||
|
||||
// THEN fingerprintHelpMessage isn't update - it's still null
|
||||
assertThat(fingerprintHelpMessage).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fingerprintFailMessage_nonUdfps() =
|
||||
testScope.runTest {
|
||||
val fingerprintFailMessage by collectLastValue(underTest.fingerprintFailMessage)
|
||||
|
||||
// GIVEN primary auth is NOT required
|
||||
whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
|
||||
.thenReturn(true)
|
||||
|
||||
// GIVEN rear fingerprint (not UDFPS)
|
||||
fingerprintPropertyRepository.setProperties(
|
||||
0,
|
||||
SensorStrength.STRONG,
|
||||
FingerprintSensorType.REAR,
|
||||
mapOf()
|
||||
)
|
||||
|
||||
// WHEN authentication status fail
|
||||
fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
|
||||
|
||||
// THEN fingerprintFailMessage is updated
|
||||
assertThat(fingerprintFailMessage?.source).isEqualTo(FINGERPRINT)
|
||||
assertThat(fingerprintFailMessage?.type).isEqualTo(BiometricMessageType.FAIL)
|
||||
assertThat(fingerprintFailMessage?.id)
|
||||
.isEqualTo(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED)
|
||||
assertThat(fingerprintFailMessage?.message)
|
||||
.isEqualTo(
|
||||
mContext.resources.getString(
|
||||
com.android.internal.R.string.fingerprint_error_not_match
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fingerprintFailMessage_udfps() =
|
||||
testScope.runTest {
|
||||
val fingerprintFailMessage by collectLastValue(underTest.fingerprintFailMessage)
|
||||
|
||||
// GIVEN primary auth is NOT required
|
||||
whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
|
||||
.thenReturn(true)
|
||||
|
||||
// GIVEN UDFPS
|
||||
fingerprintPropertyRepository.setProperties(
|
||||
0,
|
||||
SensorStrength.STRONG,
|
||||
FingerprintSensorType.UDFPS_OPTICAL,
|
||||
mapOf()
|
||||
)
|
||||
|
||||
// WHEN authentication status fail
|
||||
fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
|
||||
|
||||
// THEN fingerprintFailMessage is updated to udfps message
|
||||
assertThat(fingerprintFailMessage?.source).isEqualTo(FINGERPRINT)
|
||||
assertThat(fingerprintFailMessage?.type).isEqualTo(BiometricMessageType.FAIL)
|
||||
assertThat(fingerprintFailMessage?.id)
|
||||
.isEqualTo(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED)
|
||||
assertThat(fingerprintFailMessage?.message)
|
||||
.isEqualTo(
|
||||
mContext.resources.getString(
|
||||
com.android.internal.R.string.fingerprint_udfps_error_not_match
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fingerprintFailedMessage_primaryAuthRequired() =
|
||||
testScope.runTest {
|
||||
val fingerprintFailedMessage by collectLastValue(underTest.fingerprintFailMessage)
|
||||
|
||||
// GIVEN primary auth is required
|
||||
whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
|
||||
.thenReturn(false)
|
||||
|
||||
// WHEN authentication status fail
|
||||
fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
|
||||
|
||||
// THEN fingerprintFailedMessage isn't update - it's still null
|
||||
assertThat(fingerprintFailedMessage).isNull()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user