mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-02 08:23:00 +01:00
Warning dialogs for local backup restore.
This commit is contained in:
committed by
Cody Henthorne
parent
e657a4adf3
commit
34d87cf6e1
@@ -13,13 +13,15 @@ import org.thoughtcrime.securesms.R
|
||||
|
||||
enum class RestoreLocalBackupDialog {
|
||||
FAILED_TO_LOAD_ARCHIVE,
|
||||
SKIP_RESTORE_WARNING
|
||||
SKIP_RESTORE_WARNING,
|
||||
CONFIRM_DIFFERENT_ACCOUNT
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RestoreLocalBackupDialogDisplay(
|
||||
dialog: RestoreLocalBackupDialog?,
|
||||
onDialogConfirmed: (RestoreLocalBackupDialog) -> Unit,
|
||||
onDialogDenied: (RestoreLocalBackupDialog) -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
when (dialog) {
|
||||
@@ -33,9 +35,9 @@ fun RestoreLocalBackupDialogDisplay(
|
||||
|
||||
RestoreLocalBackupDialog.SKIP_RESTORE_WARNING -> {
|
||||
Dialogs.SimpleAlertDialog(
|
||||
title = "Skip restore?",
|
||||
body = "If you skip restore now you will not be able to restore later. If you re-enable backups after skipping restore, your current backup will be replaced with your new messaging history.",
|
||||
confirm = "Skip restore",
|
||||
title = stringResource(R.string.RestoreLocalBackupDialog__skip_restore),
|
||||
body = stringResource(R.string.RestoreLocalBackupDialog__skip_restore_body),
|
||||
confirm = stringResource(R.string.RestoreLocalBackupDialog__skip_restore_confirm),
|
||||
confirmColor = MaterialTheme.colorScheme.error,
|
||||
onConfirm = {
|
||||
onDialogConfirmed(RestoreLocalBackupDialog.SKIP_RESTORE_WARNING)
|
||||
@@ -44,6 +46,21 @@ fun RestoreLocalBackupDialogDisplay(
|
||||
)
|
||||
}
|
||||
|
||||
RestoreLocalBackupDialog.CONFIRM_DIFFERENT_ACCOUNT -> {
|
||||
Dialogs.SimpleAlertDialog(
|
||||
title = stringResource(R.string.RestoreLocalBackupDialog__restore_to_new_account),
|
||||
body = stringResource(R.string.RestoreLocalBackupDialog__restore_to_new_account_body),
|
||||
confirm = stringResource(R.string.RestoreLocalBackupDialog__restore),
|
||||
dismiss = stringResource(android.R.string.cancel),
|
||||
onConfirm = {
|
||||
onDialogConfirmed(RestoreLocalBackupDialog.CONFIRM_DIFFERENT_ACCOUNT)
|
||||
},
|
||||
onDeny = {
|
||||
onDialogDenied(RestoreLocalBackupDialog.CONFIRM_DIFFERENT_ACCOUNT)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
null -> return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,14 +81,7 @@ class RestoreLocalBackupFragment : ComposeFragment() {
|
||||
.collect {
|
||||
sharedViewModel.registerAccountErrorShown()
|
||||
if (it is RegisterAccountResult.IncorrectRecoveryPassword) {
|
||||
SignalStore.account.resetAccountEntropyPool()
|
||||
SignalStore.account.resetAciAndPniIdentityKeysAfterFailedRestore()
|
||||
sharedViewModel.clearRecoveryPassword()
|
||||
enterBackupKeyViewModel.cancelRegistering()
|
||||
sharedViewModel.intendToRestore(hasOldDevice = false, fromRemote = false, fromLocalV2 = true)
|
||||
findNavController().safeNavigate(
|
||||
RestoreLocalBackupFragmentDirections.goToEnterPhoneNumber(EnterPhoneNumberMode.RESTART_AFTER_COLLECTION)
|
||||
)
|
||||
restoreLocalBackupViewModel.displayDifferentAccountWarning()
|
||||
} else {
|
||||
enterBackupKeyViewModel.handleRegistrationFailure(it)
|
||||
}
|
||||
@@ -141,6 +134,29 @@ class RestoreLocalBackupFragment : ComposeFragment() {
|
||||
findNavController().safeNavigate(RestoreLocalBackupFragmentDirections.goToEnterPhoneNumber(EnterPhoneNumberMode.RESTART_AFTER_COLLECTION))
|
||||
}
|
||||
|
||||
override fun confirmRestoreWithDifferentAccount() {
|
||||
SignalStore.account.resetAccountEntropyPool()
|
||||
SignalStore.account.resetAciAndPniIdentityKeysAfterFailedRestore()
|
||||
sharedViewModel.clearRecoveryPassword()
|
||||
enterBackupKeyViewModel.cancelRegistering()
|
||||
sharedViewModel.intendToRestore(hasOldDevice = false, fromRemote = false, fromLocalV2 = true)
|
||||
findNavController().safeNavigate(
|
||||
RestoreLocalBackupFragmentDirections.goToEnterPhoneNumber(EnterPhoneNumberMode.RESTART_AFTER_COLLECTION)
|
||||
)
|
||||
}
|
||||
|
||||
override fun denyRestoreWithDifferentAccount() {
|
||||
SignalStore.account.resetAccountEntropyPool()
|
||||
SignalStore.account.resetAciAndPniIdentityKeysAfterFailedRestore()
|
||||
SignalStore.backup.localRestoreAccountEntropyPool = null
|
||||
sharedViewModel.clearRecoveryPassword()
|
||||
enterBackupKeyViewModel.cancelRegistering()
|
||||
sharedViewModel.intendToRestore(hasOldDevice = false, fromRemote = false, fromLocalV2 = true)
|
||||
findNavController().safeNavigate(
|
||||
RestoreLocalBackupFragmentDirections.goToEnterPhoneNumber(EnterPhoneNumberMode.COLLECT_FOR_LOCAL_V2_SIGNAL_BACKUPS_RESTORE)
|
||||
)
|
||||
}
|
||||
|
||||
override fun routeToLegacyBackupRestoration(uri: Uri) {
|
||||
sharedViewModel.intendToRestore(hasOldDevice = false, fromRemote = false, fromLocalV2 = false)
|
||||
localBackupRestore.launch(RestoreActivity.getLocalRestoreIntent(requireContext(), uri))
|
||||
|
||||
@@ -158,11 +158,23 @@ fun RestoreLocalBackupNavDisplay(
|
||||
}
|
||||
)
|
||||
|
||||
RestoreLocalBackupDialogDisplay(state.dialog, {
|
||||
if (it == RestoreLocalBackupDialog.SKIP_RESTORE_WARNING) {
|
||||
callback.skipRestore()
|
||||
}
|
||||
}, callback::clearDialog)
|
||||
RestoreLocalBackupDialogDisplay(
|
||||
dialog = state.dialog,
|
||||
onDialogConfirmed = {
|
||||
when (it) {
|
||||
RestoreLocalBackupDialog.SKIP_RESTORE_WARNING -> callback.skipRestore()
|
||||
RestoreLocalBackupDialog.CONFIRM_DIFFERENT_ACCOUNT -> callback.confirmRestoreWithDifferentAccount()
|
||||
else -> Unit
|
||||
}
|
||||
},
|
||||
onDialogDenied = {
|
||||
when (it) {
|
||||
RestoreLocalBackupDialog.CONFIRM_DIFFERENT_ACCOUNT -> callback.denyRestoreWithDifferentAccount()
|
||||
else -> Unit
|
||||
}
|
||||
},
|
||||
onDismiss = callback::clearDialog
|
||||
)
|
||||
}
|
||||
|
||||
data class RestoreLocalBackupState(
|
||||
@@ -177,6 +189,8 @@ interface RestoreLocalBackupCallback {
|
||||
fun displaySkipRestoreWarning()
|
||||
fun clearDialog()
|
||||
fun skipRestore()
|
||||
fun confirmRestoreWithDifferentAccount()
|
||||
fun denyRestoreWithDifferentAccount()
|
||||
fun submitBackupKey()
|
||||
fun routeToLegacyBackupRestoration(uri: Uri)
|
||||
fun onBackupKeyChanged(key: String)
|
||||
@@ -189,6 +203,8 @@ interface RestoreLocalBackupCallback {
|
||||
override fun displaySkipRestoreWarning() = Unit
|
||||
override fun clearDialog() = Unit
|
||||
override fun skipRestore() = Unit
|
||||
override fun confirmRestoreWithDifferentAccount() = Unit
|
||||
override fun denyRestoreWithDifferentAccount() = Unit
|
||||
override fun submitBackupKey() = Unit
|
||||
override fun routeToLegacyBackupRestoration(uri: Uri) = Unit
|
||||
override fun onBackupKeyChanged(key: String) = Unit
|
||||
|
||||
@@ -10,13 +10,17 @@ import android.net.Uri
|
||||
import androidx.lifecycle.ViewModel
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.signal.core.models.AccountEntropyPool
|
||||
import org.signal.core.util.bytes
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.backup.v2.local.ArchiveFileSystem
|
||||
import org.thoughtcrime.securesms.backup.v2.local.LocalArchiver
|
||||
import org.thoughtcrime.securesms.backup.v2.local.SnapshotFileSystem
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
@@ -79,6 +83,31 @@ class RestoreLocalBackupViewModel : ViewModel() {
|
||||
internalState.update { it.copy(dialog = RestoreLocalBackupDialog.SKIP_RESTORE_WARNING) }
|
||||
}
|
||||
|
||||
fun displayDifferentAccountWarning() {
|
||||
internalState.update { it.copy(dialog = RestoreLocalBackupDialog.CONFIRM_DIFFERENT_ACCOUNT) }
|
||||
}
|
||||
|
||||
/** Returns true if the backup at [timestamp] was created by the currently registered account, false if it belongs to a different account. */
|
||||
suspend fun backupBelongsToCurrentAccount(context: Context, backupKey: String, timestamp: Long): Boolean {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val aep = requireNotNull(AccountEntropyPool.parseOrNull(backupKey)) { "Backup key must be valid at submission time" }
|
||||
val messageBackupKey = aep.deriveMessageBackupKey()
|
||||
val dirUri = requireNotNull(SignalStore.backup.newLocalBackupsDirectory) { "Backup directory must be set" }
|
||||
val archiveFileSystem = requireNotNull(ArchiveFileSystem.fromUri(context, Uri.parse(dirUri))) { "Backup directory must be accessible" }
|
||||
val snapshot = requireNotNull(archiveFileSystem.listSnapshots().firstOrNull { it.timestamp == timestamp }) { "Selected snapshot must still exist" }
|
||||
val snapshotFs = SnapshotFileSystem(context, snapshot.file)
|
||||
val actualBackupId = LocalArchiver.getBackupId(snapshotFs, messageBackupKey)
|
||||
if (actualBackupId == null) {
|
||||
Log.w(TAG, "backupBelongsToCurrentAccount: getBackupId returned null, treating as current account")
|
||||
return@withContext true
|
||||
}
|
||||
val expectedBackupId = messageBackupKey.deriveBackupId(SignalStore.account.requireAci())
|
||||
val matches = actualBackupId.value.contentEquals(expectedBackupId.value)
|
||||
Log.d(TAG, "backupBelongsToCurrentAccount: matches=$matches")
|
||||
matches
|
||||
}
|
||||
}
|
||||
|
||||
fun clearDialog() {
|
||||
internalState.update { it.copy(dialog = null) }
|
||||
}
|
||||
|
||||
@@ -99,14 +99,37 @@ class PostRegistrationRestoreLocalBackupFragment : ComposeFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun confirmRestoreWithDifferentAccount() {
|
||||
launchRestore()
|
||||
}
|
||||
|
||||
override fun denyRestoreWithDifferentAccount() {
|
||||
restoreLocalBackupViewModel.clearDialog()
|
||||
}
|
||||
|
||||
override fun submitBackupKey() {
|
||||
AccountEntropyPool.parseOrNull(enterBackupKeyViewModel.backupKey) ?: return
|
||||
|
||||
SignalStore.backup.localRestoreAccountEntropyPool = enterBackupKeyViewModel.backupKey
|
||||
|
||||
val selectedTimestamp = restoreLocalBackupViewModel.state.value.selectedBackup?.timestamp ?: -1L
|
||||
SignalStore.backup.newLocalBackupsSelectedSnapshotTimestamp = selectedTimestamp
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
val belongsToCurrentAccount = restoreLocalBackupViewModel.backupBelongsToCurrentAccount(
|
||||
context = requireContext(),
|
||||
backupKey = enterBackupKeyViewModel.backupKey,
|
||||
timestamp = selectedTimestamp
|
||||
)
|
||||
|
||||
if (belongsToCurrentAccount) {
|
||||
launchRestore()
|
||||
} else {
|
||||
restoreLocalBackupViewModel.displayDifferentAccountWarning()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchRestore() {
|
||||
val selectedTimestamp = restoreLocalBackupViewModel.state.value.selectedBackup?.timestamp ?: -1L
|
||||
SignalStore.backup.localRestoreAccountEntropyPool = enterBackupKeyViewModel.backupKey
|
||||
SignalStore.backup.newLocalBackupsSelectedSnapshotTimestamp = selectedTimestamp
|
||||
startActivity(RestoreLocalBackupActivity.getIntent(requireContext()))
|
||||
requireActivity().supportFinishAfterTransition()
|
||||
}
|
||||
|
||||
@@ -9193,6 +9193,18 @@
|
||||
<string name="RestoreLocalBackupDialog__failed_to_load_archive">Failed to load archive. Please select a different directory.</string>
|
||||
<!-- RestoreLocalBackupDialog: Dismiss button for dialog -->
|
||||
<string name="RestoreLocalBackupDialog__ok">OK</string>
|
||||
<!-- RestoreLocalBackupDialog: Title of the dialog asking the user to confirm skipping backup restore -->
|
||||
<string name="RestoreLocalBackupDialog__skip_restore">Skip restore?</string>
|
||||
<!-- RestoreLocalBackupDialog: Body of the dialog warning the user about the consequences of skipping backup restore -->
|
||||
<string name="RestoreLocalBackupDialog__skip_restore_body">If you skip restore now you will not be able to restore later. If you re-enable backups after skipping restore, your current backup will be replaced with your new messaging history.</string>
|
||||
<!-- RestoreLocalBackupDialog: Confirm button label for the skip restore dialog -->
|
||||
<string name="RestoreLocalBackupDialog__skip_restore_confirm">Skip restore</string>
|
||||
<!-- RestoreLocalBackupDialog: Title of the dialog asking the user to confirm restoring a backup from a different account -->
|
||||
<string name="RestoreLocalBackupDialog__restore_to_new_account">Restore to new account?</string>
|
||||
<!-- RestoreLocalBackupDialog: Body of the dialog warning the user that the backup was created with a different account and they will lose group memberships -->
|
||||
<string name="RestoreLocalBackupDialog__restore_to_new_account_body">The backup you are restoring was created using a different Signal account. You can restore this backup to a new account, but you will no longer be a member of any groups that were restored.</string>
|
||||
<!-- RestoreLocalBackupDialog: Confirm button label for the restore to new account dialog -->
|
||||
<string name="RestoreLocalBackupDialog__restore">Restore</string>
|
||||
|
||||
<!-- RestoreLocalBackupActivity: Toast message when backup restore fails -->
|
||||
<string name="RestoreLocalBackupActivity__backup_restore_failed">Backup restore failed</string>
|
||||
|
||||
Reference in New Issue
Block a user