diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsFragment.kt
index da3699c2dd..5b4027c7df 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsFragment.kt
@@ -73,10 +73,16 @@ class AccountSettingsFragment : ComposeFragment() {
val removalBiometrics = rememberBiometricsAuthentication(
promptTitle = stringResource(AppSettingsR.string.AccountSettingsFragment__unlock_to_remove_two_factor_method),
educationSheetMessage = stringResource(AppSettingsR.string.AccountSettingsFragment__to_remove_this_method_confirm_its_you),
- onAuthenticationFailed = { viewModel.onEvent(AccountSettingsEvent.MethodRemovalAuthenticationFailed) }
+ onAuthenticationFailed = { viewModel.onEvent(AccountSettingsEvent.AuthenticationFailed) }
)
- CollectActions(viewModel.actions) { action -> handleAction(action, removalBiometrics) }
+ val signalLoginBiometrics = rememberBiometricsAuthentication(
+ promptTitle = stringResource(AppSettingsR.string.AccountSettingsFragment__unlock_to_view_signal_login),
+ educationSheetMessage = stringResource(AppSettingsR.string.AccountSettingsFragment__to_view_your_signal_login_confirm_its_you),
+ onAuthenticationFailed = { viewModel.onEvent(AccountSettingsEvent.AuthenticationFailed) }
+ )
+
+ CollectActions(viewModel.actions) { action -> handleAction(action, removalBiometrics, signalLoginBiometrics) }
AccountSettingsScreen(
state = state,
@@ -84,12 +90,17 @@ class AccountSettingsFragment : ComposeFragment() {
)
}
- private fun handleAction(action: AccountSettingsAction, removalBiometrics: BiometricsAuthentication) {
+ private fun handleAction(action: AccountSettingsAction, removalBiometrics: BiometricsAuthentication, signalLoginBiometrics: BiometricsAuthentication) {
when (action) {
AccountSettingsAction.NavigateBack -> requireActivity().onBackPressedDispatcher.onBackPressed()
AccountSettingsAction.LaunchCreatePinFlow -> pinFlowLauncher.launch(CreateSvrPinActivity.getIntentForPinCreate(requireContext()))
AccountSettingsAction.LaunchChangePinFlow -> pinFlowLauncher.launch(CreateSvrPinActivity.getIntentForPinChangeFromSettings(requireContext()))
AccountSettingsAction.ShowPinCreatedConfirmation -> Snackbar.make(requireView(), R.string.ConfirmKbsPinFragment__pin_created, Snackbar.LENGTH_LONG).show()
+ AccountSettingsAction.AuthenticateToViewSignalLoginDetails -> {
+ signalLoginBiometrics.withBiometricsAuthentication {
+ viewModel.onEvent(AccountSettingsEvent.SignalLoginDetailsAuthenticated)
+ }
+ }
AccountSettingsAction.NavigateToSignalLoginDetails -> findNavController().safeNavigate(R.id.action_accountSettingsFragment_to_signalLoginViewDetailsFragment)
AccountSettingsAction.NavigateToTotpSetup -> findNavController().safeNavigate(R.id.action_accountSettingsFragment_to_authenticatorSetupFragment)
is AccountSettingsAction.NavigateToRenameTotpApp -> {
@@ -103,7 +114,7 @@ class AccountSettingsFragment : ComposeFragment() {
viewModel.onEvent(AccountSettingsEvent.MethodRemovalAuthenticated(action.method))
}
}
- AccountSettingsAction.ShowRemovalAuthenticationFailed -> toast(AppSettingsR.string.AccountSettingsFragment__authentication_required)
+ AccountSettingsAction.ShowAuthenticationFailed -> toast(AppSettingsR.string.AccountSettingsFragment__authentication_required)
AccountSettingsAction.ShowTotpAppRemoved -> toast(AppSettingsR.string.AccountSettingsFragment__authenticator_app_removed)
AccountSettingsAction.ShowTotpAppRemovalFailed -> toast(AppSettingsR.string.AccountSettingsFragment__couldnt_remove_authenticator_app)
// TODO Open the two-factor authentication support article once one exists.
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModel.kt
index 810b995b16..55e6a85181 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModel.kt
@@ -83,6 +83,9 @@ class AccountSettingsViewModel(
applyRegistrationLockConfirmed()
}
AccountSettingsEvent.AccountAndRecoveryClicked -> {
+ _actions.send(AccountSettingsAction.AuthenticateToViewSignalLoginDetails)
+ }
+ AccountSettingsEvent.SignalLoginDetailsAuthenticated -> {
_actions.send(AccountSettingsAction.NavigateToSignalLoginDetails)
}
AccountSettingsEvent.AddTotpAppClicked -> {
@@ -100,8 +103,8 @@ class AccountSettingsViewModel(
is AccountSettingsEvent.MethodRemovalAuthenticated -> {
applyMethodRemovalAuthenticated(event.method)
}
- AccountSettingsEvent.MethodRemovalAuthenticationFailed -> {
- _actions.send(AccountSettingsAction.ShowRemovalAuthenticationFailed)
+ AccountSettingsEvent.AuthenticationFailed -> {
+ _actions.send(AccountSettingsAction.ShowAuthenticationFailed)
}
AccountSettingsEvent.RemoveTotpAppConfirmed -> {
applyRemoveTotpAppConfirmed()
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/signallogin/SignalLoginViewDetailsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/signallogin/SignalLoginViewDetailsFragment.kt
index 4728c5aa3e..1dadc668b6 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/signallogin/SignalLoginViewDetailsFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/signallogin/SignalLoginViewDetailsFragment.kt
@@ -21,6 +21,7 @@ import org.signal.core.util.Util
import org.signal.passwordmanager.SignalCredentialManager
import org.signal.signallogin.pdf.SignalLoginPdfRenderer
import org.signal.signallogin.viewdetails.SignalLoginViewDetailsScreen
+import org.thoughtcrime.securesms.components.TemporaryScreenshotSecurity
/**
* Shows the account and recovery keys that make up the user's Signal Login, the same way registration does.
@@ -49,6 +50,8 @@ class SignalLoginViewDetailsFragment : ComposeFragment() {
override fun FragmentContent() {
val state by viewModel.state.collectAsStateWithLifecycle()
+ TemporaryScreenshotSecurity.bind()
+
CollectActions(viewModel.actions) { action -> handleAction(action) }
SignalLoginViewDetailsScreen(
diff --git a/app/src/test/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModelTest.kt
index 1dc8751d25..2bb33be7ff 100644
--- a/app/src/test/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModelTest.kt
+++ b/app/src/test/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModelTest.kt
@@ -444,7 +444,7 @@ class AccountSettingsViewModelTest {
}
@Test
- fun `MethodRemovalAuthenticationFailed says so and removes nothing`() = runTest(testDispatcher) {
+ fun `AuthenticationFailed says so and removes nothing`() = runTest(testDispatcher) {
every { repository.isPhoneNumberless() } returns true
coEvery { repository.getTwoFactorMethods() } returns methods(TOTP_APP)
@@ -452,9 +452,9 @@ class AccountSettingsViewModelTest {
val actions = collectActions(viewModel.actions)
viewModel.onEvent(AccountSettingsEvent.RemoveMethodClicked(TOTP_APP))
- viewModel.onEvent(AccountSettingsEvent.MethodRemovalAuthenticationFailed)
+ viewModel.onEvent(AccountSettingsEvent.AuthenticationFailed)
- assertThat(actions.last()).isEqualTo(AccountSettingsAction.ShowRemovalAuthenticationFailed)
+ assertThat(actions.last()).isEqualTo(AccountSettingsAction.ShowAuthenticationFailed)
assertThat(viewModel.state.value.dialog).isEqualTo(Dialog.None)
coVerify(exactly = 0) { repository.removeTotpApp(any()) }
}
@@ -546,7 +546,7 @@ class AccountSettingsViewModelTest {
}
@Test
- fun `AccountAndRecoveryClicked opens the Signal Login details screen`() = runTest(testDispatcher) {
+ fun `AccountAndRecoveryClicked asks for the screen lock first`() = runTest(testDispatcher) {
every { repository.isPhoneNumberless() } returns true
val viewModel = createViewModel()
@@ -554,6 +554,18 @@ class AccountSettingsViewModelTest {
viewModel.onEvent(AccountSettingsEvent.AccountAndRecoveryClicked)
+ assertThat(actions.last()).isEqualTo(AccountSettingsAction.AuthenticateToViewSignalLoginDetails)
+ }
+
+ @Test
+ fun `SignalLoginDetailsAuthenticated opens the Signal Login details screen`() = runTest(testDispatcher) {
+ every { repository.isPhoneNumberless() } returns true
+
+ val viewModel = createViewModel()
+ val actions = collectActions(viewModel.actions)
+
+ viewModel.onEvent(AccountSettingsEvent.SignalLoginDetailsAuthenticated)
+
assertThat(actions.last()).isEqualTo(AccountSettingsAction.NavigateToSignalLoginDetails)
}
diff --git a/feature/app-settings/src/main/java/org/signal/appsettings/account/AccountSettingsAction.kt b/feature/app-settings/src/main/java/org/signal/appsettings/account/AccountSettingsAction.kt
index 071870966b..30c02439ea 100644
--- a/feature/app-settings/src/main/java/org/signal/appsettings/account/AccountSettingsAction.kt
+++ b/feature/app-settings/src/main/java/org/signal/appsettings/account/AccountSettingsAction.kt
@@ -27,6 +27,9 @@ sealed interface AccountSettingsAction {
/** Tell the user their PIN was created. */
data object ShowPinCreatedConfirmation : AccountSettingsAction
+ /** Ask the user to get past their screen lock before we show them the account and recovery keys. */
+ data object AuthenticateToViewSignalLoginDetails : AccountSettingsAction
+
/** Open the screen that shows the account and recovery keys that make up the Signal Login. */
data object NavigateToSignalLoginDetails : AccountSettingsAction
@@ -39,8 +42,8 @@ sealed interface AccountSettingsAction {
/** Ask the user to get past their screen lock before we remove [method] from the account. */
data class AuthenticateToRemoveMethod(val method: TwoFactorMethod) : AccountSettingsAction
- /** Tell the user we couldn't confirm it was them, so nothing was removed. */
- data object ShowRemovalAuthenticationFailed : AccountSettingsAction
+ /** Tell the user we couldn't confirm it was them, so whatever they asked for didn't happen. */
+ data object ShowAuthenticationFailed : AccountSettingsAction
/** Tell the user their authenticator app was removed. */
data object ShowTotpAppRemoved : AccountSettingsAction
diff --git a/feature/app-settings/src/main/java/org/signal/appsettings/account/AccountSettingsEvent.kt b/feature/app-settings/src/main/java/org/signal/appsettings/account/AccountSettingsEvent.kt
index 9c3ef68841..800551f950 100644
--- a/feature/app-settings/src/main/java/org/signal/appsettings/account/AccountSettingsEvent.kt
+++ b/feature/app-settings/src/main/java/org/signal/appsettings/account/AccountSettingsEvent.kt
@@ -16,9 +16,12 @@ sealed interface AccountSettingsEvent {
/** The user tapped the navigation (back) icon. */
data object NavigateBackClicked : AccountSettingsEvent
- /** The user tapped the Signal Login card, which shows the account and recovery keys. */
+ /** The user tapped the Signal Login card, which asks for the screen lock before showing the account and recovery keys. */
data object AccountAndRecoveryClicked : AccountSettingsEvent
+ /** The user got past their screen lock, so we can show them the account and recovery keys. */
+ data object SignalLoginDetailsAuthenticated : AccountSettingsEvent
+
/** The user tapped the row that either creates or changes their PIN. */
data object ModifyPinClicked : AccountSettingsEvent
@@ -60,8 +63,8 @@ sealed interface AccountSettingsEvent {
/** The user got past their screen lock, so we can go on asking them to confirm removing [method]. */
data class MethodRemovalAuthenticated(val method: TwoFactorMethod) : AccountSettingsEvent
- /** The screen lock turned the user away, so [RemoveMethodClicked] goes no further. */
- data object MethodRemovalAuthenticationFailed : AccountSettingsEvent
+ /** The screen lock turned the user away, so whatever asked for it goes no further. */
+ data object AuthenticationFailed : AccountSettingsEvent
/** The user confirmed removing the authenticator app named by the open dialog, which removes it. */
data object RemoveTotpAppConfirmed : AccountSettingsEvent
diff --git a/feature/app-settings/src/main/res/values/strings.xml b/feature/app-settings/src/main/res/values/strings.xml
index 6314e00dab..49a0f5ae33 100644
--- a/feature/app-settings/src/main/res/values/strings.xml
+++ b/feature/app-settings/src/main/res/values/strings.xml
@@ -76,7 +76,11 @@
Unlock to remove two-factor method
To remove this method, confirm it\'s you
-
+
+ Unlock to view Signal Login
+
+ To view your Signal Login, confirm it\'s you
+
Authentication required
Can\'t add authenticator app