diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupUnlockScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupUnlockScreen.kt index b461671b0..8348bb5e1 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupUnlockScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupUnlockScreen.kt @@ -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, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/toggle/BitwardenUnlockWithBiometricsSwitch.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/toggle/BitwardenUnlockWithBiometricsSwitch.kt index f2b45ffbd..e1c6694e9 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/toggle/BitwardenUnlockWithBiometricsSwitch.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/toggle/BitwardenUnlockWithBiometricsSwitch.kt @@ -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, ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreen.kt index 062b7d876..bc6cd45ca 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreen.kt @@ -232,7 +232,7 @@ fun AccountSecurityScreen( .padding(horizontal = 16.dp), ) BitwardenUnlockWithBiometricsSwitch( - isBiometricsSupported = biometricsManager.isBiometricsSupported, + biometricSupportStatus = biometricsManager.biometricSupportStatus, isChecked = state.isUnlockWithBiometricsEnabled || showBiometricsPrompt, onDisableBiometrics = remember(viewModel) { { diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/biometrics/BiometricsManager.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/biometrics/BiometricsManager.kt index 8292f00a2..d5ae7a6ab 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/biometrics/BiometricsManager.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/biometrics/BiometricsManager.kt @@ -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, +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/biometrics/BiometricsManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/biometrics/BiometricsManagerImpl.kt index 0bb6efc95..8ecdc17f5 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/biometrics/BiometricsManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/biometrics/BiometricsManagerImpl.kt @@ -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, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ac22e5c01..392b19b92 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1028,4 +1028,6 @@ Do you want to switch to this account? Import Logins From your computer, follow these instructions to export saved passwords from your browser or other password manager. Then, safely import them to Bitwarden. Give your vault a head start + Unlock with biometrics requires strong biometric authentication and may not be compatible with all biometric options on this device. + Unlock with biometrics requires strong biometric authentication and is not compatible with the biometrics options available on this device. diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupUnlockScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupUnlockScreenTest.kt index 7cc4843ab..8715e1744 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupUnlockScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupUnlockScreenTest.kt @@ -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), diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreenTest.kt index 72684a280..b1692e0da 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreenTest.kt @@ -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),