From ae5e4cace3b05bb91f7115b119abe2b72c2aa861 Mon Sep 17 00:00:00 2001 From: Patrick Honkonen Date: Wed, 3 Dec 2025 14:13:37 -0500 Subject: [PATCH 1/2] Update BitwardenPasswordField semantics to support accessibility for visible passwords Improve accessibility support in `BitwardenPasswordField` by ensuring Talkback can read the password aloud when the user chooses to make it visible, while explicitly marking the content as sensitive data. Behavioral changes: * When the "show password" toggle is active, Talkback will now read the actual password text instead of silence or obfuscated characters. This behavior occurs regardless of the system-wide "Speak Passwords" setting due to the custom visual transformation usage. * The field is semantically marked as containing sensitive data. Specific changes: * Apply a `semantics` modifier to the internal `TextField` in `BitwardenPasswordField`. * Set `contentDescription` to the actual text value only when `showPassword` is true; otherwise, it defaults to an empty string. * Set the `isSensitiveData` semantic property to `true`. --- .../components/field/BitwardenPasswordField.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/field/BitwardenPasswordField.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/field/BitwardenPasswordField.kt index 18842041baf..37528b3e6f8 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/field/BitwardenPasswordField.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/field/BitwardenPasswordField.kt @@ -36,6 +36,9 @@ import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalTextToolbar import androidx.compose.ui.platform.TextToolbar import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.isSensitiveData +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation @@ -228,6 +231,19 @@ fun BitwardenPasswordField( ) }, modifier = Modifier + .semantics { + // Content description must be set to the field value in order for Talkback + // to speak the password aloud when it is visible because we apply a custom + // VisualTransformation to the text, which prevents Talkback from reading + // the value as it normally would. + // NOTE: This overrides the default behavior of Talkback's "Speak Passwords" + // setting. When visible, the password will always be spoken aloud + // regardless of the user's configuration. + contentDescription = textFieldValue.text + .takeUnless { !showPassword } + .orEmpty() + isSensitiveData = true + } .nullableTestTag(tag = passwordFieldTestTag) .fillMaxWidth() .onFocusChanged { focusState -> focused = focusState.isFocused }, From da57763635dda7474b40925c597df75f272e1348 Mon Sep 17 00:00:00 2001 From: Patrick Honkonen Date: Wed, 3 Dec 2025 16:03:12 -0500 Subject: [PATCH 2/2] Fix logic for password field content description Simplify the conditional logic used to set the `contentDescription` for the `BitwardenPasswordField`. The previous implementation used a double negative (`takeUnless { !showPassword }`) to determine if the password text should be exposed to accessibility services. This change replaces it with the more direct and readable `takeIf { showPassword }`, ensuring the content description is only populated when the password is explicitly visible. --- .../ui/platform/components/field/BitwardenPasswordField.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/field/BitwardenPasswordField.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/field/BitwardenPasswordField.kt index 37528b3e6f8..2bee206698c 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/field/BitwardenPasswordField.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/field/BitwardenPasswordField.kt @@ -240,7 +240,7 @@ fun BitwardenPasswordField( // setting. When visible, the password will always be spoken aloud // regardless of the user's configuration. contentDescription = textFieldValue.text - .takeUnless { !showPassword } + .takeIf { showPassword } .orEmpty() isSensitiveData = true }