diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/DebugMenuRepository.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/DebugMenuRepository.kt index 94971979c95..3f015c88a7f 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/DebugMenuRepository.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/DebugMenuRepository.kt @@ -43,6 +43,11 @@ interface DebugMenuRepository { */ fun resetCoachMarkTourStatuses() + /** + * Resets the value for displaying the accessibility disclaimer. + */ + fun resetAccessibilityDisclaimer() + /** * Manipulates the state to force showing the onboarding carousel. * diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/DebugMenuRepositoryImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/DebugMenuRepositoryImpl.kt index a31cd010ea3..7cf6dadb4d1 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/DebugMenuRepositoryImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/DebugMenuRepositoryImpl.kt @@ -64,6 +64,10 @@ class DebugMenuRepositoryImpl( settingsDiskSource.storeShouldShowAddLoginCoachMark(shouldShow = null) } + override fun resetAccessibilityDisclaimer() { + settingsDiskSource.hasShownAccessibilityDisclaimer = null + } + override fun modifyStateToShowOnboardingCarousel( userStateUpdateTrigger: () -> Unit, ) { diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/accessibilitydisclosure/AccessibilityDisclosureScreen.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/accessibilitydisclosure/AccessibilityDisclosureScreen.kt index cbb6db9454e..7650e70d8d9 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/accessibilitydisclosure/AccessibilityDisclosureScreen.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/accessibilitydisclosure/AccessibilityDisclosureScreen.kt @@ -63,8 +63,8 @@ fun AccessibilityDisclosureScreen( .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top), ) { AccessibilityDisclosureContent( - onAcceptClick = { - viewModel.trySendAction(AccessibilityDisclosureAction.AcceptClicked) + iUnderstandClick = { + viewModel.trySendAction(AccessibilityDisclosureAction.IUnderstandClick) }, onCloseAppClick = { viewModel.trySendAction(AccessibilityDisclosureAction.CloseAppClick) @@ -74,9 +74,10 @@ fun AccessibilityDisclosureScreen( } } +@Suppress("LongMethod") @Composable private fun AccessibilityDisclosureContent( - onAcceptClick: () -> Unit, + iUnderstandClick: () -> Unit, onCloseAppClick: () -> Unit, modifier: Modifier = Modifier, ) { @@ -111,8 +112,32 @@ private fun AccessibilityDisclosureContent( Spacer(modifier = Modifier.height(height = 12.dp)) Text( - text = stringResource(id = BitwardenString.accessibility_disclosure_start_up_text), + text = stringResource(id = BitwardenString.accessibility_disclosure_start_up_text_1), style = BitwardenTheme.typography.bodyMedium, + color = BitwardenTheme.colorScheme.text.secondary, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + + Spacer(modifier = Modifier.height(height = 12.dp)) + + Text( + text = stringResource(id = BitwardenString.accessibility_disclosure_start_up_text_2), + style = BitwardenTheme.typography.bodySmall, + color = BitwardenTheme.colorScheme.text.primary, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + + Spacer(modifier = Modifier.height(height = 12.dp)) + + Text( + text = stringResource(id = BitwardenString.accessibility_disclosure_start_up_text_3), + style = BitwardenTheme.typography.bodySmall, color = BitwardenTheme.colorScheme.text.primary, textAlign = TextAlign.Center, modifier = Modifier @@ -123,8 +148,8 @@ private fun AccessibilityDisclosureContent( Spacer(modifier = Modifier.height(height = 24.dp)) BitwardenFilledButton( - label = stringResource(id = BitwardenString.accept), - onClick = onAcceptClick, + label = stringResource(id = BitwardenString.i_understand), + onClick = iUnderstandClick, modifier = Modifier .fillMaxWidth() .standardHorizontalMargin(), @@ -150,7 +175,7 @@ private fun AccessibilityDisclosureContent( private fun AccessibilityDisclosureContent_preview() { BitwardenTheme { AccessibilityDisclosureContent( - onAcceptClick = {}, + iUnderstandClick = {}, onCloseAppClick = {}, modifier = Modifier.fillMaxSize(), ) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/accessibilitydisclosure/AccessibilityDisclosureViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/accessibilitydisclosure/AccessibilityDisclosureViewModel.kt index 7e8f747a9e6..2422972e29b 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/accessibilitydisclosure/AccessibilityDisclosureViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/accessibilitydisclosure/AccessibilityDisclosureViewModel.kt @@ -22,12 +22,12 @@ class AccessibilityDisclosureViewModel @Inject constructor( ) { override fun handleAction(action: AccessibilityDisclosureAction) { when (action) { - AccessibilityDisclosureAction.AcceptClicked -> handleAcceptClicked() + AccessibilityDisclosureAction.IUnderstandClick -> handleIUnderstandClick() AccessibilityDisclosureAction.CloseAppClick -> handleCloseAppClick() } } - private fun handleAcceptClicked() { + private fun handleIUnderstandClick() { settingsRepository.accessibilityDisclaimerHasBeenShown() sendEvent(AccessibilityDisclosureEvent.Dismiss) } @@ -63,9 +63,9 @@ sealed class AccessibilityDisclosureEvent { */ sealed class AccessibilityDisclosureAction { /** - * User clicked the accept button. + * User clicked the "I understand" button. */ - data object AcceptClicked : AccessibilityDisclosureAction() + data object IUnderstandClick : AccessibilityDisclosureAction() /** * User clicked the close app button. diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuScreen.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuScreen.kt index dfe62753cf7..ed1b311018a 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuScreen.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuScreen.kt @@ -101,7 +101,15 @@ fun DebugMenuScreen( viewModel.trySendAction(DebugMenuAction.RestartOnboardingCarousel) }, ) - Spacer(Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(height = 16.dp)) + BitwardenFilledButton( + label = stringResource(id = BitwardenString.reset_accessibility_disclaimer), + onClick = { viewModel.trySendAction(DebugMenuAction.ResetAccessibilityDisclaimer) }, + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + Spacer(modifier = Modifier.height(height = 8.dp)) BitwardenFilledButton( label = stringResource(BitwardenString.reset_coach_mark_tour_status), onClick = { viewModel.trySendAction(DebugMenuAction.ResetCoachMarkTourStatuses) }, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuViewModel.kt index 036db8ea483..2e8cbad3009 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuViewModel.kt @@ -67,9 +67,14 @@ class DebugMenuViewModel @Inject constructor( DebugMenuAction.ClearSsoCookies -> handleClearSsoCookies() DebugMenuAction.ResetPremiumUpgradeBanner -> handleResetPremiumUpgradeBanner() DebugMenuAction.ShowUpgradedToPremiumCard -> handleShowUpgradedToPremiumCard() + DebugMenuAction.ResetAccessibilityDisclaimer -> handleResetAccessibilityDisclaimer() } } + private fun handleResetAccessibilityDisclaimer() { + debugMenuRepository.resetAccessibilityDisclaimer() + } + private fun handleShowUpgradedToPremiumCard() { debugMenuRepository.showUpgradedToPremiumCard() } @@ -186,6 +191,11 @@ sealed class DebugMenuAction { */ data object RestartOnboarding : DebugMenuAction() + /** + * The user has clicked the reset accessibility disclaimer button. + */ + data object ResetAccessibilityDisclaimer : DebugMenuAction() + /** * The user has clicked the restart onboarding button for the onboarding section. */ diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/repository/DebugMenuRepositoryTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/repository/DebugMenuRepositoryTest.kt index ee7ad797c5c..221ed433935 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/repository/DebugMenuRepositoryTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/repository/DebugMenuRepositoryTest.kt @@ -159,6 +159,17 @@ class DebugMenuRepositoryTest { assertTrue(lambdaHasBeenCalled) } + @Test + fun `resetAccessibilityDisclaimer calls settings disk source setting value back to null`() { + every { mockSettingsDiskSource.hasShownAccessibilityDisclaimer = null } just runs + + debugMenuRepository.resetAccessibilityDisclaimer() + + verify(exactly = 1) { + mockSettingsDiskSource.hasShownAccessibilityDisclaimer = null + } + } + @Test fun `resetCoachMarkTourStatuses calls settings disk source setting values back to null`() { every { diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/accessibilitydisclosure/AccessibilityDisclosureScreenTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/accessibilitydisclosure/AccessibilityDisclosureScreenTest.kt index 4d3562d5359..a1c569248dd 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/accessibilitydisclosure/AccessibilityDisclosureScreenTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/accessibilitydisclosure/AccessibilityDisclosureScreenTest.kt @@ -40,13 +40,13 @@ class AccessibilityDisclosureScreenTest : BitwardenComposeTest() { } @Test - fun `accept button click should send AcceptClicked action`() { + fun `i understand button click should send IUnderstandClick action`() { composeTestRule - .onNodeWithText(text = "Accept") + .onNodeWithText(text = "I understand") .performScrollTo() .performClick() verify(exactly = 1) { - viewModel.trySendAction(AccessibilityDisclosureAction.AcceptClicked) + viewModel.trySendAction(AccessibilityDisclosureAction.IUnderstandClick) } } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/accessibilitydisclosure/AccessibilityDisclosureViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/accessibilitydisclosure/AccessibilityDisclosureViewModelTest.kt index ee1d5d1e6c0..520a67e9137 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/accessibilitydisclosure/AccessibilityDisclosureViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/accessibilitydisclosure/AccessibilityDisclosureViewModelTest.kt @@ -25,10 +25,10 @@ class AccessibilityDisclosureViewModelTest : BaseViewModelTest() { } @Test - fun `AcceptClicked should mark disclaimer as shown and emit Dismiss event`() = runTest { + fun `IUnderstandClick should mark disclaimer as shown and emit Dismiss event`() = runTest { val viewModel = createViewModel() viewModel.eventFlow.test { - viewModel.trySendAction(AccessibilityDisclosureAction.AcceptClicked) + viewModel.trySendAction(AccessibilityDisclosureAction.IUnderstandClick) assertEquals(AccessibilityDisclosureEvent.Dismiss, awaitItem()) } verify(exactly = 1) { diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuScreenTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuScreenTest.kt index bfa96db60a7..bd0a714ca9d 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuScreenTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuScreenTest.kt @@ -171,6 +171,18 @@ class DebugMenuScreenTest : BitwardenComposeTest() { } } + @Test + fun `reset accessibility disclaimer should send ResetAccessibilityDisclaimer action`() { + composeTestRule + .onNodeWithText("Reset accessibility disclaimer") + .performScrollTo() + .performClick() + + verify(exactly = 1) { + viewModel.trySendAction(DebugMenuAction.ResetAccessibilityDisclaimer) + } + } + @Test fun `reset all coach mark tours should send ResetCoachMarkTourStatuses action`() { composeTestRule diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuViewModelTest.kt index 77fa2368f76..d7a6a9f7a33 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuViewModelTest.kt @@ -39,6 +39,7 @@ class DebugMenuViewModelTest : BaseViewModelTest() { coEvery { resetFeatureFlagOverrides() } just runs every { updateFeatureFlag(any(), any()) } just runs every { resetOnboardingStatusForCurrentUser() } just runs + every { resetAccessibilityDisclaimer() } just runs every { modifyStateToShowOnboardingCarousel(userStateUpdateTrigger = any()) } answers { @@ -134,6 +135,15 @@ class DebugMenuViewModelTest : BaseViewModelTest() { } } + @Test + fun `ResetAccessibilityDisclaimer should call repository to reset values`() { + val viewModel = createViewModel() + viewModel.trySendAction(DebugMenuAction.ResetAccessibilityDisclaimer) + verify(exactly = 1) { + mockDebugMenuRepository.resetAccessibilityDisclaimer() + } + } + @Test fun `handleResetCoachMarkTourStatuses should call repository to reset values`() { val viewModel = createViewModel() diff --git a/ui/src/main/res/values/strings.xml b/ui/src/main/res/values/strings.xml index d6fe64ec833..028f7547326 100644 --- a/ui/src/main/res/values/strings.xml +++ b/ui/src/main/res/values/strings.xml @@ -612,7 +612,10 @@ select Add TOTP to store the key safely Generate an email alias with an external forwarding service. Accessibility Service Disclosure Bitwarden uses the Accessibility Service to search for login fields in apps and websites, then establish the appropriate field IDs for entering a username & password when a match for the app or site is found. We do not store any of the information presented to us by the service, nor do we make any attempt to control any on-screen elements beyond text entry of credentials. - Bitwarden offers an optional autofill method that uses Android’s Accessibility Service to detect login fields in apps and websites. If you choose to enable it, Bitwarden will identify the appropriate fields and enter your credentials when a match is found. We do not store any information observed by the service, and we do not control any on-screen elements beyond credential entry. + This notice is required before Bitwarden can offer accessibility-based autofill. + Bitwarden offers an optional autofill method that uses Android’s Accessibility Service to detect login fields. If you enable it, Bitwarden will identify the appropriate fields and fill in your credentials when a match is found. + We don’t store any information about what’s onscreen while you use the Accessibility Service, and we don’t control any onscreen elements beyond credential entry. + I understand Accept Decline Login request has already expired. diff --git a/ui/src/main/res/values/strings_non_localized.xml b/ui/src/main/res/values/strings_non_localized.xml index e40236a0153..ac68c35f9da 100644 --- a/ui/src/main/res/values/strings_non_localized.xml +++ b/ui/src/main/res/values/strings_non_localized.xml @@ -27,6 +27,7 @@ Show Onboarding Carousel This will force the change to app state which will cause the first time carousel to show. The carousel will continue to show for any \"new\" account until a login is completed. May need to exit debug menu manually. Reset all coach mark tours + Reset accessibility disclaimer Generate crash Generate error report Error reports