diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/local/RestoreLocalBackupDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/local/RestoreLocalBackupDialog.kt
index d72d8596a9..38f9c7f4f9 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/local/RestoreLocalBackupDialog.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/local/RestoreLocalBackupDialog.kt
@@ -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
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/local/RestoreLocalBackupFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/local/RestoreLocalBackupFragment.kt
index 6925c65fea..e762e2f2d5 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/local/RestoreLocalBackupFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/local/RestoreLocalBackupFragment.kt
@@ -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))
diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/local/RestoreLocalBackupNavDisplay.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/local/RestoreLocalBackupNavDisplay.kt
index 49fb885408..1e176e79d8 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/local/RestoreLocalBackupNavDisplay.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/local/RestoreLocalBackupNavDisplay.kt
@@ -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
diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/local/RestoreLocalBackupViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/local/RestoreLocalBackupViewModel.kt
index 6b0d357767..f1b2d86bad 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/local/RestoreLocalBackupViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/local/RestoreLocalBackupViewModel.kt
@@ -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) }
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/restore/local/PostRegistrationRestoreLocalBackupFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/restore/local/PostRegistrationRestoreLocalBackupFragment.kt
index a57b9acb8c..36d3ba5d27 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/restore/local/PostRegistrationRestoreLocalBackupFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/restore/local/PostRegistrationRestoreLocalBackupFragment.kt
@@ -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()
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 31cc99854e..b18a99ad0e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -9193,6 +9193,18 @@
Failed to load archive. Please select a different directory.
OK
+
+ Skip restore?
+
+ 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.
+
+ Skip restore
+
+ Restore to new account?
+
+ 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.
+
+ Restore
Backup restore failed