From 2792b9e6762132e870ea861239439754dbfdecd1 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Mon, 8 Sep 2025 16:34:06 -0400 Subject: [PATCH] Add prompt to re-enable local backups post restore. --- .../RestoreLocalBackupFragment.kt | 87 ++++++++++++++++++- 1 file changed, 84 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/restore/restorelocalbackup/RestoreLocalBackupFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/restore/restorelocalbackup/RestoreLocalBackupFragment.kt index f4cbdcdb74..76e15627a0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/restore/restorelocalbackup/RestoreLocalBackupFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/restore/restorelocalbackup/RestoreLocalBackupFragment.kt @@ -5,11 +5,18 @@ package org.thoughtcrime.securesms.restore.restorelocalbackup +import android.app.Activity +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.Intent +import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.widget.EditText import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.RequiresApi import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import androidx.core.widget.doOnTextChanged @@ -25,6 +32,7 @@ import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.LoggingFragment import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.backup.BackupEvent +import org.thoughtcrime.securesms.backup.BackupPassphrase import org.thoughtcrime.securesms.components.ViewBinderDelegate import org.thoughtcrime.securesms.databinding.FragmentRestoreLocalBackupBinding import org.thoughtcrime.securesms.keyvalue.SignalStore @@ -32,6 +40,7 @@ import org.thoughtcrime.securesms.registration.fragments.RegistrationViewDelegat import org.thoughtcrime.securesms.restore.RestoreActivity import org.thoughtcrime.securesms.restore.RestoreRepository import org.thoughtcrime.securesms.restore.RestoreViewModel +import org.thoughtcrime.securesms.service.LocalBackupListener import org.thoughtcrime.securesms.util.BackupUtil import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.ViewModelFactory @@ -50,6 +59,34 @@ class RestoreLocalBackupFragment : LoggingFragment(R.layout.fragment_restore_loc RestoreLocalBackupViewModel(fileBackupUri) } ) + + private val selectBackupDirectory = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + when (val resultCode = result.resultCode) { + Activity.RESULT_OK -> { + val backupDirectoryUri: Uri? = result.data?.data + + if (backupDirectoryUri != null) { + Log.i(TAG, "Re-enabling backups with new directory") + val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION + + SignalStore.settings.setSignalBackupDirectory(backupDirectoryUri) + requireContext().contentResolver.takePersistableUriPermission(backupDirectoryUri, takeFlags) + + enableLocalBackups(requireContext()) + resumeRegistrationAfterLocalBackupRestore() + } else { + Log.w(TAG, "Backup directory URI is null, reshowing dialog to re-enable") + onLocalBackupRestoreCompletedSuccessfully() + } + } + Activity.RESULT_CANCELED -> { + Log.w(TAG, "Backup directory selection canceled, reshowing dialog to re-enable") + onLocalBackupRestoreCompletedSuccessfully() + } + else -> Log.w(TAG, "Backup directory selection activity ended with unknown result code: $resultCode") + } + } + private val binding: FragmentRestoreLocalBackupBinding by ViewBinderDelegate(FragmentRestoreLocalBackupBinding::bind) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -72,7 +109,7 @@ class RestoreLocalBackupFragment : LoggingFragment(R.layout.fragment_restore_loc if (SignalStore.settings.isBackupEnabled) { Log.i(TAG, "Backups enabled, so a backup must have been previously restored.") - onBackupCompletedSuccessfully() + onLocalBackupRestoreCompletedSuccessfully() return } @@ -98,7 +135,7 @@ class RestoreLocalBackupFragment : LoggingFragment(R.layout.fragment_restore_loc restoreLocalBackupViewModel.importResult.observe(viewLifecycleOwner) { importResult -> when (importResult) { null -> Unit - RestoreRepository.BackupImportResult.SUCCESS -> onBackupCompletedSuccessfully() + RestoreRepository.BackupImportResult.SUCCESS -> onLocalBackupRestoreCompletedSuccessfully() else -> { handleBackupImportError(importResult) restoreLocalBackupViewModel.backupImportErrorShown() @@ -109,12 +146,56 @@ class RestoreLocalBackupFragment : LoggingFragment(R.layout.fragment_restore_loc restoreLocalBackupViewModel.prepareRestore(requireContext()) } - private fun onBackupCompletedSuccessfully() { + private fun onLocalBackupRestoreCompletedSuccessfully() { Log.d(TAG, "onBackupCompletedSuccessfully()") + if (BackupUtil.isUserSelectionRequired(requireContext()) && !BackupUtil.canUserAccessBackupDirectory(requireContext())) { + displayConfirmationDialog(requireContext()) + } else { + enableLocalBackups(requireContext()) + resumeRegistrationAfterLocalBackupRestore() + } + } + + private fun resumeRegistrationAfterLocalBackupRestore() { val activity = requireActivity() as RestoreActivity activity.onBackupCompletedSuccessfully() } + @RequiresApi(29) + private fun displayConfirmationDialog(context: Context) { + Log.d(TAG, "Showing continue using local backups dialog") + MaterialAlertDialogBuilder(context) + .setTitle(R.string.RestoreBackupFragment__restore_complete) + .setMessage(R.string.RestoreBackupFragment__to_continue_using_backups_please_choose_a_folder) + .setPositiveButton(R.string.RestoreBackupFragment__choose_folder) { _, _ -> + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) + intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION) + try { + Log.d(TAG, "Showing external file picker for new backup directory") + selectBackupDirectory.launch(intent) + } catch (_: ActivityNotFoundException) { + Log.w(TAG, "No external activity found") + Toast.makeText(context, R.string.BackupDialog_no_file_picker_available, Toast.LENGTH_LONG).show() + BackupPassphrase.set(context, null) + resumeRegistrationAfterLocalBackupRestore() + } + } + .setNegativeButton(R.string.RestoreBackupFragment__not_now) { _, _ -> + BackupPassphrase.set(context, null) + resumeRegistrationAfterLocalBackupRestore() + } + .setCancelable(false) + .show() + } + + private fun enableLocalBackups(context: Context) { + if (BackupUtil.canUserAccessBackupDirectory(context)) { + LocalBackupListener.setNextBackupTimeToIntervalFromNow(context) + SignalStore.settings.isBackupEnabled = true + LocalBackupListener.schedule(context) + } + } + override fun onStart() { super.onStart() EventBus.getDefault().register(this)