From bf58bcfd32160f55b5fb28cde15a93691e624da5 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Tue, 3 Nov 2026 18:43:34 +0100 Subject: fix(app): end the screen lock prompt on sensor errors BiometricAuthenticator mapped onAuthenticationError(ERROR_UNABLE_TO_PROCESS) to Retry, but an error callback always closes the prompt. The settings screen keeps its one-prompt-at-a-time guard set on Retry, so the gated switches stayed locked until the section was left. The error now maps to Failure; Retry comes only from onAuthenticationFailed, while the prompt is still open. Once the device screen lock is removed, the biometric app lock could not be turned off: the prompt returns HardwareUnavailableOrDisabled and nothing was written. Turning it off without a usable screen lock now writes the value directly, as the passphrase cache row already does. The settings category rows and PreferenceAction rows get Role.Button for accessibility services. Co-Authored-By: Claude Opus 5.5 --- .../passwordstore/ui/settings/PreferenceRows.kt | 3 ++- .../ui/settings/SettingsDestination.kt | 22 ++++++++++++++-------- .../passwordstore/ui/settings/SettingsScreen.kt | 3 ++- .../util/auth/BiometricAuthenticator.kt | 5 +++-- 4 files changed, 21 insertions(+), 12 deletions(-) (limited to 'app/src/main/java') diff --git a/app/src/main/java/app/passwordstore/ui/settings/PreferenceRows.kt b/app/src/main/java/app/passwordstore/ui/settings/PreferenceRows.kt index f40db8b0..cfaa741d 100644 --- a/app/src/main/java/app/passwordstore/ui/settings/PreferenceRows.kt +++ b/app/src/main/java/app/passwordstore/ui/settings/PreferenceRows.kt @@ -60,7 +60,8 @@ fun PreferenceAction( ) { ListItem( headlineContent = { Text(title) }, - modifier = modifier.clickable(enabled = enabled, onClick = onClick).dimmed(enabled), + modifier = + modifier.clickable(enabled = enabled, role = Role.Button, onClick = onClick).dimmed(enabled), supportingContent = if (summary != null) { { Text(summary) } diff --git a/app/src/main/java/app/passwordstore/ui/settings/SettingsDestination.kt b/app/src/main/java/app/passwordstore/ui/settings/SettingsDestination.kt index 1885c1ee..9fb25e29 100644 --- a/app/src/main/java/app/passwordstore/ui/settings/SettingsDestination.kt +++ b/app/src/main/java/app/passwordstore/ui/settings/SettingsDestination.kt @@ -133,7 +133,8 @@ fun SettingsSectionDestination( if (prompting) return prompting = true BiometricAuthenticator.authenticate(activity, title) { result -> - // Retry: a finger was not recognized, the prompt stays open. + // Retry: a finger was not recognized, the prompt stays open. Errors close it and are + // terminal (Failure, Canceled*, HardwareUnavailableOrDisabled). if (result !is Result.Retry) prompting = false onResult(result) } @@ -149,14 +150,19 @@ fun SettingsSectionDestination( activity.autofillManager?.disableAutofillServices() refresh() } - PreferenceKeys.BIOMETRIC_AUTH_2 -> - authenticate(R.string.biometric_prompt_title) { result -> - if (result is Result.Success) { - viewModel.putBoolean(key, value) - // Shortcuts were built for the old lock setting, as the old screen knew. - activity.getSystemService()?.removeAllDynamicShortcuts() - } + PreferenceKeys.BIOMETRIC_AUTH_2 -> { + fun write() { + viewModel.putBoolean(key, value) + // Shortcuts were built for the old lock setting, as the old screen knew. + activity.getSystemService()?.removeAllDynamicShortcuts() } + // Without a screen lock no prompt can open: turning the app lock off needs none. + if (!value && !BiometricAuthenticator.canAuthenticate(activity)) write() + else + authenticate(R.string.biometric_prompt_title) { result -> + if (result is Result.Success) write() + } + } Feature.EnablePGPPassphraseCache.configKey -> if (!BiometricAuthenticator.canAuthenticate(activity)) viewModel.setPassphraseCache(false) else diff --git a/app/src/main/java/app/passwordstore/ui/settings/SettingsScreen.kt b/app/src/main/java/app/passwordstore/ui/settings/SettingsScreen.kt index 2327551c..7d45ad74 100644 --- a/app/src/main/java/app/passwordstore/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/app/passwordstore/ui/settings/SettingsScreen.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.input.KeyboardType import app.passwordstore.BuildConfig import app.passwordstore.R @@ -102,7 +103,7 @@ fun SettingsScreen( SettingsSection.entries.forEach { section -> ListItem( headlineContent = { Text(stringResource(section.title)) }, - modifier = Modifier.clickable { onSection(section) }, + modifier = Modifier.clickable(role = Role.Button) { onSection(section) }, leadingContent = { Icon(painterResource(section.icon), contentDescription = null) }, colors = rowColors(), ) diff --git a/app/src/main/java/app/passwordstore/util/auth/BiometricAuthenticator.kt b/app/src/main/java/app/passwordstore/util/auth/BiometricAuthenticator.kt index 4aad4588..98f2229a 100644 --- a/app/src/main/java/app/passwordstore/util/auth/BiometricAuthenticator.kt +++ b/app/src/main/java/app/passwordstore/util/auth/BiometricAuthenticator.kt @@ -46,7 +46,7 @@ object BiometricAuthenticator { /** * An incorrect biometric was entered, but the prompt UI is offering the option to retry the - * operation. + * operation. The prompt is still open; a terminal result follows. */ data object Retry : Result() @@ -100,7 +100,8 @@ object BiometricAuthenticator { when (errorCode) { /** Keep in sync with [androidx.biometric.BiometricPrompt.AuthenticationError] */ BiometricPrompt.ERROR_HW_UNAVAILABLE -> callback(Result.HardwareUnavailableOrDisabled) - BiometricPrompt.ERROR_UNABLE_TO_PROCESS -> callback(Result.Retry) + // An error always closes the prompt, so none of them is a Retry. + BiometricPrompt.ERROR_UNABLE_TO_PROCESS, BiometricPrompt.ERROR_TIMEOUT -> callback( Result.Failure( -- cgit v1.2.3