PM-13382 show contextual message for the level of Biometrics available (#4099)

This commit is contained in:
Dave Severns 2024-10-16 12:10:37 -04:00 committed by GitHub
parent 1446e43c46
commit 62cfd5e746
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 57 additions and 11 deletions

View file

@ -161,7 +161,7 @@ private fun SetupUnlockScreenContent(
Spacer(modifier = Modifier.height(height = 24.dp))
BitwardenUnlockWithBiometricsSwitch(
isBiometricsSupported = biometricsManager.isBiometricsSupported,
biometricSupportStatus = biometricsManager.biometricSupportStatus,
isChecked = state.isUnlockWithBiometricsEnabled || showBiometricsPrompt,
onDisableBiometrics = handler.onDisableBiometrics,
onEnableBiometrics = handler.onEnableBiometrics,

View file

@ -4,29 +4,41 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.manager.biometrics.BiometricSupportStatus
/**
* Displays a switch for enabling or disabling unlock with biometrics functionality.
*
* @param isChecked Indicates that the switch should be checked or not.
* @param isBiometricsSupported Indicates if biometrics is supported and we should display the
* switch.
* @param biometricSupportStatus Indicates what type of biometrics are supported on device.
* @param onDisableBiometrics Callback invoked when the toggle has be turned off.
* @param onEnableBiometrics Callback invoked when the toggle has be turned on.
* @param modifier The [Modifier] to be applied to the switch.
*/
@Composable
fun BitwardenUnlockWithBiometricsSwitch(
isBiometricsSupported: Boolean,
biometricSupportStatus: BiometricSupportStatus,
isChecked: Boolean,
onDisableBiometrics: () -> Unit,
onEnableBiometrics: () -> Unit,
modifier: Modifier = Modifier,
) {
if (!isBiometricsSupported) return
val biometricsDescription: String = when (biometricSupportStatus) {
BiometricSupportStatus.CLASS_3_SUPPORTED -> {
stringResource(R.string.class_3_biometrics_description)
}
BiometricSupportStatus.CLASS_2_SUPPORTED -> {
stringResource(R.string.class_2_biometrics_description)
}
BiometricSupportStatus.NOT_SUPPORTED -> return
}
BitwardenWideSwitch(
modifier = modifier,
label = stringResource(id = R.string.unlock_with, stringResource(id = R.string.biometrics)),
label = stringResource(
id = R.string.unlock_with,
stringResource(id = R.string.biometrics),
),
isChecked = isChecked,
onCheckedChange = { toggled ->
if (toggled) {
@ -35,5 +47,7 @@ fun BitwardenUnlockWithBiometricsSwitch(
onDisableBiometrics()
}
},
enabled = biometricSupportStatus == BiometricSupportStatus.CLASS_3_SUPPORTED,
description = biometricsDescription,
)
}

View file

@ -232,7 +232,7 @@ fun AccountSecurityScreen(
.padding(horizontal = 16.dp),
)
BitwardenUnlockWithBiometricsSwitch(
isBiometricsSupported = biometricsManager.isBiometricsSupported,
biometricSupportStatus = biometricsManager.biometricSupportStatus,
isChecked = state.isUnlockWithBiometricsEnabled || showBiometricsPrompt,
onDisableBiometrics = remember(viewModel) {
{

View file

@ -9,7 +9,8 @@ import javax.crypto.Cipher
@Immutable
interface BiometricsManager {
/**
* Returns `true` if the device supports string biometric authentication, `false` otherwise.
* Returns `true` if the device supports Class 3 (STRONG) biometric authentication, `false`
* otherwise.
*/
val isBiometricsSupported: Boolean
@ -19,6 +20,11 @@ interface BiometricsManager {
*/
val isUserVerificationSupported: Boolean
/**
* Returns the status of biometric support available on the device.
*/
val biometricSupportStatus: BiometricSupportStatus
/**
* Display a prompt for setting up or verifying biometrics.
*/
@ -49,3 +55,12 @@ interface BiometricsManager {
onNotSupported: () -> Unit,
)
}
/**
* Status of biometric support available on the device.
*/
enum class BiometricSupportStatus {
CLASS_3_SUPPORTED,
CLASS_2_SUPPORTED,
NOT_SUPPORTED,
}

View file

@ -22,13 +22,26 @@ class BiometricsManagerImpl(
private val fragmentActivity: FragmentActivity get() = activity as FragmentActivity
override val isBiometricsSupported: Boolean
get() = canAuthenticate(Authenticators.BIOMETRIC_STRONG)
get() = biometricSupportStatus == BiometricSupportStatus.CLASS_3_SUPPORTED
override val isUserVerificationSupported: Boolean
get() = canAuthenticate(
authenticators = Authenticators.BIOMETRIC_STRONG or Authenticators.DEVICE_CREDENTIAL,
)
override val biometricSupportStatus: BiometricSupportStatus
get() = when {
canAuthenticate(Authenticators.BIOMETRIC_STRONG) -> {
BiometricSupportStatus.CLASS_3_SUPPORTED
}
canAuthenticate(Authenticators.BIOMETRIC_WEAK) -> {
BiometricSupportStatus.CLASS_2_SUPPORTED
}
else -> BiometricSupportStatus.NOT_SUPPORTED
}
override fun promptBiometrics(
onSuccess: (cipher: Cipher?) -> Unit,
onCancel: () -> Unit,

View file

@ -1028,4 +1028,6 @@ Do you want to switch to this account?</string>
<string name="import_logins">Import Logins</string>
<string name="from_your_computer_follow_these_instructions_to_export_saved_passwords">From your computer, follow these instructions to export saved passwords from your browser or other password manager. Then, safely import them to Bitwarden.</string>
<string name="give_your_vault_a_head_start">Give your vault a head start</string>
<string name="class_3_biometrics_description">Unlock with biometrics requires strong biometric authentication and may not be compatible with all biometric options on this device.</string>
<string name="class_2_biometrics_description">Unlock with biometrics requires strong biometric authentication and is not compatible with the biometrics options available on this device.</string>
</resources>

View file

@ -17,6 +17,7 @@ import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFl
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.toggle.UnlockWithPinState
import com.x8bit.bitwarden.ui.platform.manager.biometrics.BiometricSupportStatus
import com.x8bit.bitwarden.ui.platform.manager.biometrics.BiometricsManager
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
import io.mockk.every
@ -40,7 +41,7 @@ class SetupUnlockScreenTest : BaseComposeTest() {
private val captureBiometricsLockOut = slot<() -> Unit>()
private val captureBiometricsError = slot<() -> Unit>()
private val biometricsManager: BiometricsManager = mockk {
every { isBiometricsSupported } returns true
every { biometricSupportStatus } returns BiometricSupportStatus.CLASS_3_SUPPORTED
every {
promptBiometrics(
onSuccess = capture(captureBiometricsSuccess),

View file

@ -26,6 +26,7 @@ import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFl
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.toggle.UnlockWithPinState
import com.x8bit.bitwarden.ui.platform.manager.biometrics.BiometricSupportStatus
import com.x8bit.bitwarden.ui.platform.manager.biometrics.BiometricsManager
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
@ -60,7 +61,7 @@ class AccountSecurityScreenTest : BaseComposeTest() {
private val captureBiometricsLockOut = slot<() -> Unit>()
private val captureBiometricsError = slot<() -> Unit>()
private val biometricsManager: BiometricsManager = mockk {
every { isBiometricsSupported } returns true
every { biometricSupportStatus } returns BiometricSupportStatus.CLASS_3_SUPPORTED
every {
promptBiometrics(
onSuccess = capture(captureBiometricsSuccess),