From ef874c4091e85cfc1441156a109bfef71e2ae34b Mon Sep 17 00:00:00 2001 From: jeffrey-signal Date: Tue, 8 Jul 2025 10:24:26 -0400 Subject: [PATCH] Show settings button in backup key save error dialog only when password manager settings are accessible. To prevent crashes when the "go to settings" button is clicked in the backup key save to password manager error dialog, the "go to settings" button will only be displayed in cases where the intent resolves successfully. --- .../MessageBackupsFlowFragment.kt | 11 +-- .../MessageBackupsKeyRecordScreen.kt | 87 ++++++++++++------- .../remote/BackupKeyDisplayFragment.kt | 7 +- 3 files changed, 63 insertions(+), 42 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowFragment.kt index 9bdc66d7cc..7f0ae1de69 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowFragment.kt @@ -140,22 +140,19 @@ class MessageBackupsFlowFragment : ComposeFragment(), InAppPaymentCheckoutDelega composable(route = MessageBackupsStage.Route.BACKUP_KEY_RECORD.name) { val context = LocalContext.current + val passwordManagerSettingsIntent = BackupKeyCredentialManagerHandler.getCredentialManagerSettingsIntent(requireContext()) MessageBackupsKeyRecordScreen( backupKey = state.accountEntropyPool.displayValue, keySaveState = state.backupKeySaveState, + canOpenPasswordManagerSettings = passwordManagerSettingsIntent != null, onNavigationClick = viewModel::goToPreviousStage, onNextClick = viewModel::goToNextStage, - onCopyToClipboardClick = { - Util.copyToClipboard(context, it, CLIPBOARD_TIMEOUT_SECONDS) - }, + onCopyToClipboardClick = { Util.copyToClipboard(context, it, CLIPBOARD_TIMEOUT_SECONDS) }, onRequestSaveToPasswordManager = viewModel::onBackupKeySaveRequested, onConfirmSaveToPasswordManager = viewModel::onBackupKeySaveConfirmed, onSaveToPasswordManagerComplete = viewModel::onBackupKeySaveCompleted, - onGoToDeviceSettingsClick = { - val intent = BackupKeyCredentialManagerHandler.getCredentialManagerSettingsIntent(requireContext()) - requireContext().startActivity(intent) - } + onGoToPasswordManagerSettingsClick = { requireContext().startActivity(passwordManagerSettingsIntent) } ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsKeyRecordScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsKeyRecordScreen.kt index 4c2dfcf784..974bdda363 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsKeyRecordScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsKeyRecordScreen.kt @@ -41,6 +41,7 @@ import androidx.credentials.CredentialManager import androidx.credentials.exceptions.CreateCredentialCancellationException import androidx.credentials.exceptions.CreateCredentialInterruptedException import androidx.credentials.exceptions.CreateCredentialNoCreateOptionException +import androidx.credentials.exceptions.CreateCredentialProviderConfigurationException import androidx.credentials.exceptions.CreateCredentialUnknownException import org.signal.core.ui.compose.Buttons import org.signal.core.ui.compose.Dialogs @@ -68,13 +69,14 @@ private const val TAG = "MessageBackupsKeyRecordScreen" fun MessageBackupsKeyRecordScreen( backupKey: String, keySaveState: BackupKeySaveState?, + canOpenPasswordManagerSettings: Boolean, onNavigationClick: () -> Unit = {}, onCopyToClipboardClick: (String) -> Unit = {}, onRequestSaveToPasswordManager: () -> Unit = {}, onConfirmSaveToPasswordManager: () -> Unit = {}, onSaveToPasswordManagerComplete: (CredentialManagerResult) -> Unit = {}, onNextClick: () -> Unit = {}, - onGoToDeviceSettingsClick: () -> Unit = {} + onGoToPasswordManagerSettingsClick: () -> Unit = {} ) { val snackbarHostState = remember { SnackbarHostState() } val backupKeyString = remember(backupKey) { @@ -221,33 +223,12 @@ fun MessageBackupsKeyRecordScreen( } } - is BackupKeySaveState.Error -> { - when (keySaveState.errorType) { - is CredentialManagerError.MissingCredentialManager -> { - Dialogs.SimpleAlertDialog( - title = stringResource(R.string.MessageBackupsKeyRecordScreen__cant_save_to_password_manager_title), - body = stringResource(R.string.MessageBackupsKeyRecordScreen__missing_password_manager_message), - confirm = stringResource(R.string.MessageBackupsKeyRecordScreen__go_to_settings), - onConfirm = { onGoToDeviceSettingsClick() }, - dismiss = stringResource(android.R.string.cancel), - onDismiss = { onSaveToPasswordManagerComplete(CredentialManagerResult.UserCanceled) } - ) - } - - is CredentialManagerError.SavePromptDisabled -> { - Dialogs.SimpleAlertDialog( - title = stringResource(R.string.MessageBackupsKeyRecordScreen__cant_save_to_password_manager_title), - body = stringResource(R.string.MessageBackupsKeyRecordScreen__missing_password_manager_message), - confirm = stringResource(R.string.MessageBackupsKeyRecordScreen__go_to_settings), - onConfirm = { onGoToDeviceSettingsClick() }, - dismiss = stringResource(android.R.string.cancel), - onDismiss = { onSaveToPasswordManagerComplete(CredentialManagerResult.UserCanceled) } - ) - } - - is CredentialManagerError.Unexpected -> Unit - } - } + is BackupKeySaveState.Error -> BackupKeySaveErrorDialog( + error = keySaveState, + showPasswordManagerSettingsButton = canOpenPasswordManagerSettings, + onGoToPasswordManagerSettingsClick = onGoToPasswordManagerSettingsClick, + onDismiss = { onSaveToPasswordManagerComplete(CredentialManagerResult.UserCanceled) } + ) null -> Unit } @@ -255,6 +236,48 @@ fun MessageBackupsKeyRecordScreen( } } +@Composable +private fun BackupKeySaveErrorDialog( + error: BackupKeySaveState.Error, + showPasswordManagerSettingsButton: Boolean, + onGoToPasswordManagerSettingsClick: () -> Unit = {}, + onDismiss: () -> Unit = {} +) { + val title: String + val message: String + when (error.errorType) { + is CredentialManagerError.MissingCredentialManager -> { + title = stringResource(R.string.MessageBackupsKeyRecordScreen__cant_save_to_password_manager_title) + message = stringResource(R.string.MessageBackupsKeyRecordScreen__missing_password_manager_message) + } + + is CredentialManagerError.SavePromptDisabled -> { + title = stringResource(R.string.MessageBackupsKeyRecordScreen__cant_save_to_password_manager_title) + message = stringResource(R.string.MessageBackupsKeyRecordScreen__password_save_prompt_disabled_message) + } + + is CredentialManagerError.Unexpected -> return + } + + if (showPasswordManagerSettingsButton) { + Dialogs.SimpleAlertDialog( + title = title, + body = message, + confirm = stringResource(R.string.MessageBackupsKeyRecordScreen__go_to_settings), + onConfirm = { onGoToPasswordManagerSettingsClick() }, + dismiss = stringResource(android.R.string.cancel), + onDismiss = onDismiss + ) + } else { + Dialogs.SimpleMessageDialog( + title = title, + message = message, + dismiss = stringResource(android.R.string.ok), + onDismiss = onDismiss + ) + } +} + private suspend fun saveKeyToCredentialManager( context: Context, backupKey: String @@ -275,7 +298,7 @@ private suspend fun saveKeyToCredentialManager( when (e) { is CreateCredentialCancellationException -> CredentialManagerResult.UserCanceled is CreateCredentialInterruptedException -> CredentialManagerResult.Interrupted(e) - is CreateCredentialNoCreateOptionException -> CredentialManagerError.MissingCredentialManager(e) + is CreateCredentialNoCreateOptionException, is CreateCredentialProviderConfigurationException -> CredentialManagerError.MissingCredentialManager(e) is CreateCredentialUnknownException -> { when { Build.VERSION.SDK_INT <= 33 && e.message?.contains("[28431]") == true -> { @@ -310,7 +333,8 @@ private fun MessageBackupsKeyRecordScreenPreview() { Previews.Preview { MessageBackupsKeyRecordScreen( backupKey = (0 until 63).map { (('A'..'Z') + ('0'..'9')).random() }.joinToString("") + "0", - keySaveState = null + keySaveState = null, + canOpenPasswordManagerSettings = true ) } } @@ -321,7 +345,8 @@ private fun SaveKeyConfirmationDialogPreview() { Previews.Preview { MessageBackupsKeyRecordScreen( backupKey = (0 until 63).map { (('A'..'Z') + ('0'..'9')).random() }.joinToString("") + "0", - keySaveState = BackupKeySaveState.RequestingConfirmation + keySaveState = BackupKeySaveState.RequestingConfirmation, + canOpenPasswordManagerSettings = true ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/BackupKeyDisplayFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/BackupKeyDisplayFragment.kt index 5cfe8b6c70..f30896b37c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/BackupKeyDisplayFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/BackupKeyDisplayFragment.kt @@ -29,20 +29,19 @@ class BackupKeyDisplayFragment : ComposeFragment() { @Composable override fun FragmentContent() { val state by viewModel.uiState.collectAsStateWithLifecycle() + val passwordManagerSettingsIntent = BackupKeyCredentialManagerHandler.getCredentialManagerSettingsIntent(requireContext()) MessageBackupsKeyRecordScreen( backupKey = SignalStore.account.accountEntropyPool.displayValue, keySaveState = state.keySaveState, + canOpenPasswordManagerSettings = passwordManagerSettingsIntent != null, onNavigationClick = { findNavController().popBackStack() }, onCopyToClipboardClick = { Util.copyToClipboard(requireContext(), it, CLIPBOARD_TIMEOUT_SECONDS) }, onRequestSaveToPasswordManager = viewModel::onBackupKeySaveRequested, onConfirmSaveToPasswordManager = viewModel::onBackupKeySaveConfirmed, onSaveToPasswordManagerComplete = viewModel::onBackupKeySaveCompleted, onNextClick = { findNavController().popBackStack() }, - onGoToDeviceSettingsClick = { - val intent = BackupKeyCredentialManagerHandler.getCredentialManagerSettingsIntent(requireContext()) - requireContext().startActivity(intent) - } + onGoToPasswordManagerSettingsClick = { requireContext().startActivity(passwordManagerSettingsIntent) } ) } }