mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-17 07:23:21 +01:00
Surface error when local backup restore directory becomes inaccessible.
This commit is contained in:
@@ -96,6 +96,8 @@ import org.signal.core.util.logging.Log
|
||||
import org.signal.donations.StripeApi
|
||||
import org.signal.mediasend.MediaSendActivityContract
|
||||
import org.thoughtcrime.securesms.backup.v2.ArchiveRestoreProgress
|
||||
import org.thoughtcrime.securesms.backup.v2.ArchiveRestoreProgressState
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.CouldNotCompleteBackupRestoreSheet
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.verify.VerifyBackupKeyActivity
|
||||
import org.thoughtcrime.securesms.calls.YouAreAlreadyInACallSnackbar.show
|
||||
import org.thoughtcrime.securesms.calls.log.CallLogFilter
|
||||
@@ -342,6 +344,19 @@ class MainActivity :
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
ArchiveRestoreProgress
|
||||
.stateFlow
|
||||
.filter { it.restoreStatus == ArchiveRestoreProgressState.RestoreStatus.LOCAL_RESTORE_DIRECTORY_UNAVAILABLE }
|
||||
.collect {
|
||||
ArchiveRestoreProgress.clearLocalRestoreDirectoryError()
|
||||
CouldNotCompleteBackupRestoreSheet().show(supportFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
|
||||
Log.i(TAG, "Local restore directory became unavailable.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
supportFragmentManager.setFragmentResultListener(
|
||||
|
||||
@@ -157,6 +157,11 @@ object ArchiveRestoreProgress {
|
||||
update()
|
||||
}
|
||||
|
||||
fun clearLocalRestoreDirectoryError() {
|
||||
SignalStore.backup.localRestoreDirectoryError = false
|
||||
update()
|
||||
}
|
||||
|
||||
fun clearFinishedStatus() {
|
||||
store.update { state ->
|
||||
if (state.restoreStatus == ArchiveRestoreProgressState.RestoreStatus.FINISHED) {
|
||||
@@ -193,7 +198,11 @@ object ArchiveRestoreProgress {
|
||||
!NetworkConstraint.isMet(AppDependencies.application) -> ArchiveRestoreProgressState.RestoreStatus.WAITING_FOR_INTERNET
|
||||
!BatteryNotLowConstraint.isMet() -> ArchiveRestoreProgressState.RestoreStatus.LOW_BATTERY
|
||||
!DiskSpaceNotLowConstraint.isMet() -> ArchiveRestoreProgressState.RestoreStatus.NOT_ENOUGH_DISK_SPACE
|
||||
restoreState == RestoreState.NONE -> if (state.hasActivelyRestoredThisRun) ArchiveRestoreProgressState.RestoreStatus.FINISHED else ArchiveRestoreProgressState.RestoreStatus.NONE
|
||||
restoreState == RestoreState.NONE -> when {
|
||||
SignalStore.backup.localRestoreDirectoryError -> ArchiveRestoreProgressState.RestoreStatus.LOCAL_RESTORE_DIRECTORY_UNAVAILABLE
|
||||
state.hasActivelyRestoredThisRun -> ArchiveRestoreProgressState.RestoreStatus.FINISHED
|
||||
else -> ArchiveRestoreProgressState.RestoreStatus.NONE
|
||||
}
|
||||
else -> {
|
||||
val availableBytes = SignalStore.backup.spaceAvailableOnDiskBytes
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ data class ArchiveRestoreProgressState(
|
||||
WAITING_FOR_INTERNET,
|
||||
WAITING_FOR_WIFI,
|
||||
NOT_ENOUGH_DISK_SPACE,
|
||||
FINISHED
|
||||
FINISHED,
|
||||
LOCAL_RESTORE_DIRECTORY_UNAVAILABLE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.backup.v2.ui
|
||||
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import org.signal.core.ui.compose.ComposeBottomSheetDialogFragment
|
||||
import org.signal.core.ui.compose.DayNightPreviews
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
|
||||
/**
|
||||
* Sheet displayed when the user's backup restoration failed during media import. Generally due
|
||||
* to the files no longer being available.
|
||||
*/
|
||||
class CouldNotCompleteBackupRestoreSheet : ComposeBottomSheetDialogFragment() {
|
||||
@Composable
|
||||
override fun SheetContent() {
|
||||
CouldNotCompleteBackupRestoreSheetContent(
|
||||
onOkClick = { dismiss() },
|
||||
onLearnMoreClick = {
|
||||
dismiss()
|
||||
CommunicationActions.openBrowserLink(requireContext(), getString(R.string.backup_support_url))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CouldNotCompleteBackupRestoreSheetContent(
|
||||
onOkClick: () -> Unit = {},
|
||||
onLearnMoreClick: () -> Unit = {}
|
||||
) {
|
||||
val ok = stringResource(android.R.string.ok)
|
||||
val primaryActionButtonState: BackupAlertActionButtonState = remember(ok, onOkClick) {
|
||||
BackupAlertActionButtonState(
|
||||
label = ok,
|
||||
callback = onOkClick
|
||||
)
|
||||
}
|
||||
|
||||
val learnMore = stringResource(R.string.preferences__app_icon_learn_more)
|
||||
val secondaryActionButtonState: BackupAlertActionButtonState = remember(learnMore, onLearnMoreClick) {
|
||||
BackupAlertActionButtonState(
|
||||
label = learnMore,
|
||||
callback = onLearnMoreClick
|
||||
)
|
||||
}
|
||||
|
||||
BackupAlertBottomSheetContainer(
|
||||
icon = {
|
||||
BackupAlertIcon(iconColors = BackupsIconColors.Error)
|
||||
},
|
||||
title = stringResource(R.string.CouldNotCompleteBackupRestoreSheet__title),
|
||||
primaryActionButtonState = primaryActionButtonState,
|
||||
secondaryActionButtonState = secondaryActionButtonState
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.CouldNotCompleteBackupRestoreSheet__body_error)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.CouldNotCompleteBackupRestoreSheet__body_retry)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
private fun CouldNotCompleteBackupRestoreSheetContentPreview() {
|
||||
Previews.BottomSheetContentPreview {
|
||||
CouldNotCompleteBackupRestoreSheetContent()
|
||||
}
|
||||
}
|
||||
@@ -164,10 +164,10 @@ private fun ArchiveRestoreProgressState.iconResource(): Int {
|
||||
RestoreStatus.WAITING_FOR_INTERNET,
|
||||
RestoreStatus.WAITING_FOR_WIFI,
|
||||
RestoreStatus.LOW_BATTERY -> R.drawable.symbol_backup_light
|
||||
|
||||
RestoreStatus.NOT_ENOUGH_DISK_SPACE -> R.drawable.symbol_backup_error_24
|
||||
RestoreStatus.FINISHED -> CoreUiR.drawable.symbol_check_circle_24
|
||||
RestoreStatus.NONE -> throw IllegalStateException()
|
||||
RestoreStatus.NONE,
|
||||
RestoreStatus.LOCAL_RESTORE_DIRECTORY_UNAVAILABLE -> throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +199,8 @@ private fun ArchiveRestoreProgressState.iconColor(): Color {
|
||||
RestoreStatus.NOT_ENOUGH_DISK_SPACE -> BackupsIconColors.Warning.foreground
|
||||
|
||||
RestoreStatus.FINISHED -> BackupsIconColors.Success.foreground
|
||||
RestoreStatus.NONE -> throw IllegalStateException()
|
||||
RestoreStatus.NONE,
|
||||
RestoreStatus.LOCAL_RESTORE_DIRECTORY_UNAVAILABLE -> throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +234,8 @@ private fun ArchiveRestoreProgressState.title(): String {
|
||||
}
|
||||
|
||||
RestoreStatus.FINISHED -> stringResource(R.string.BackupStatus__restore_complete)
|
||||
RestoreStatus.NONE -> throw IllegalStateException()
|
||||
RestoreStatus.NONE,
|
||||
RestoreStatus.LOCAL_RESTORE_DIRECTORY_UNAVAILABLE -> throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +279,8 @@ private fun ArchiveRestoreProgressState.status(): String? {
|
||||
RestoreStatus.LOW_BATTERY -> stringResource(R.string.BackupStatus__status_device_has_low_battery)
|
||||
RestoreStatus.NOT_ENOUGH_DISK_SPACE -> null
|
||||
RestoreStatus.FINISHED -> this.totalToRestoreThisRun.toUnitString()
|
||||
RestoreStatus.NONE -> throw IllegalStateException()
|
||||
RestoreStatus.NONE,
|
||||
RestoreStatus.LOCAL_RESTORE_DIRECTORY_UNAVAILABLE -> throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -217,7 +217,8 @@ private fun progressColor(backupStatusData: ArchiveRestoreProgressState): Color
|
||||
RestoreStatus.LOW_BATTERY,
|
||||
RestoreStatus.NOT_ENOUGH_DISK_SPACE -> BackupsIconColors.Warning.foreground
|
||||
RestoreStatus.FINISHED -> BackupsIconColors.Success.foreground
|
||||
RestoreStatus.NONE -> BackupsIconColors.Normal.foreground
|
||||
RestoreStatus.NONE,
|
||||
RestoreStatus.LOCAL_RESTORE_DIRECTORY_UNAVAILABLE -> BackupsIconColors.Normal.foreground
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ import org.signal.core.util.Util
|
||||
import org.signal.core.util.getLength
|
||||
import org.thoughtcrime.securesms.MainActivity
|
||||
import org.thoughtcrime.securesms.backup.isIdle
|
||||
import org.thoughtcrime.securesms.backup.v2.ArchiveRestoreProgress
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.BackupAlert
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.BackupAlertBottomSheet
|
||||
@@ -270,6 +271,10 @@ class InternalBackupPlaygroundFragment : ComposeFragment() {
|
||||
.setNegativeButton("Cancel", null)
|
||||
.show()
|
||||
},
|
||||
onTriggerLocalRestoreDirectoryError = {
|
||||
SignalStore.backup.localRestoreDirectoryError = true
|
||||
ArchiveRestoreProgress.forceUpdate()
|
||||
},
|
||||
onDisplayInitialBackupFailureSheet = {
|
||||
BackupRepository.displayInitialBackupFailureNotification()
|
||||
BackupAlertBottomSheet
|
||||
@@ -366,6 +371,7 @@ fun Screen(
|
||||
onImportEncryptedBackupFromDiskConfirmed: (aci: String, backupKey: String) -> Unit = { _, _ -> },
|
||||
onClearLocalMediaBackupState: () -> Unit = {},
|
||||
onDeleteRemoteBackup: () -> Unit = {},
|
||||
onTriggerLocalRestoreDirectoryError: () -> Unit = {},
|
||||
onDisplayInitialBackupFailureSheet: () -> Unit = {}
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
@@ -584,6 +590,12 @@ fun Screen(
|
||||
onClick = onClearLocalMediaBackupState
|
||||
)
|
||||
|
||||
Rows.TextRow(
|
||||
text = "Trigger local restore directory error",
|
||||
label = "Simulates the restore directory becoming inaccessible during a local backup restore.",
|
||||
onClick = onTriggerLocalRestoreDirectoryError
|
||||
)
|
||||
|
||||
Dividers.Default()
|
||||
|
||||
Rows.TextRow(
|
||||
|
||||
@@ -6,9 +6,11 @@ package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import android.net.Uri
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.backup.v2.ArchiveRestoreProgress
|
||||
import org.thoughtcrime.securesms.backup.v2.local.ArchiveFileSystem
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobs.protos.LocalBackupRestoreMediaJobData
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
@@ -46,6 +48,7 @@ class LocalBackupRestoreMediaJob private constructor(
|
||||
val archiveFileSystem = when (backupDirectoryUri.scheme) {
|
||||
"content" -> ArchiveFileSystem.openForRestore(context, backupDirectoryUri) ?: run {
|
||||
Log.w(TAG, "Unable to open backup directory: $backupDirectoryUri")
|
||||
SignalStore.backup.localRestoreDirectoryError = true
|
||||
return Result.failure()
|
||||
}
|
||||
else -> ArchiveFileSystem.fromFile(context, File(backupDirectoryUri.path!!))
|
||||
@@ -56,7 +59,11 @@ class LocalBackupRestoreMediaJob private constructor(
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
override fun onFailure() = Unit
|
||||
override fun onFailure() {
|
||||
ArchiveRestoreProgress.allMediaRestored()
|
||||
// forceUpdate in case restoreState was already NONE and allMediaRestored() skipped its update()
|
||||
ArchiveRestoreProgress.forceUpdate()
|
||||
}
|
||||
|
||||
class Factory : Job.Factory<LocalBackupRestoreMediaJob> {
|
||||
override fun create(parameters: Parameters, serializedData: ByteArray?): LocalBackupRestoreMediaJob {
|
||||
|
||||
@@ -143,7 +143,9 @@ class RestoreLocalAttachmentJob private constructor(
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
val streamSupplier = StreamSupplier { ArchiveFileSystem.openInputStream(context, restoreUri) ?: throw IOException("Unable to open stream") }
|
||||
val streamSupplier = StreamSupplier {
|
||||
ArchiveFileSystem.openInputStream(context, restoreUri) ?: throw IOException("Unable to open stream for $restoreUri")
|
||||
}
|
||||
|
||||
try {
|
||||
val iv = ByteArray(16)
|
||||
|
||||
@@ -105,6 +105,7 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
private const val KEY_NEW_LOCAL_BACKUPS_LAST_BACKUP_TIME = "backup.new_local_backups_last_backup_time"
|
||||
private const val KEY_NEW_LOCAL_BACKUPS_SELECTED_SNAPSHOT_TIMESTAMP = "backup.new_local_backups_selected_snapshot_timestamp"
|
||||
private const val KEY_LOCAL_RESTORE_ACCOUNT_ENTROPY_POOL = "backup.local_restore_account_entropy_pool"
|
||||
private const val KEY_LOCAL_RESTORE_DIRECTORY_ERROR = "backup.local_restore_directory_error"
|
||||
|
||||
private const val KEY_UPLOAD_BANNER_VISIBLE = "backup.upload_banner_visible"
|
||||
|
||||
@@ -498,6 +499,7 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
* the account AEP because the local backup may belong to a different account (e.g., after ACI change).
|
||||
*/
|
||||
var localRestoreAccountEntropyPool: String? by stringValue(KEY_LOCAL_RESTORE_ACCOUNT_ENTROPY_POOL, null as String?)
|
||||
var localRestoreDirectoryError: Boolean by booleanValue(KEY_LOCAL_RESTORE_DIRECTORY_ERROR, false)
|
||||
|
||||
/**
|
||||
* When we are told by the server that we are out of storage space, we should show
|
||||
|
||||
@@ -9806,5 +9806,14 @@
|
||||
<!-- Label for internal-only section showing groups with same members -->
|
||||
<string name="AddGroupDetailsFragment__groups_with_same_members" translatable="false">Groups with same members (Labs)</string>
|
||||
|
||||
<!-- Title of the sheet shown when a local backup restore could not be completed -->
|
||||
<string name="CouldNotCompleteBackupRestoreSheet__title">Can\'t restore backup</string>
|
||||
|
||||
<!-- Body of the sheet shown when a local backup restore could not be completed, explaining the likely cause -->
|
||||
<string name="CouldNotCompleteBackupRestoreSheet__body_error">An error occurred and your backup can\'t be restored. This may be because the backup folder was moved on your device while your backup was restoring.</string>
|
||||
|
||||
<!-- Body of the sheet shown when a local backup restore could not be completed, explaining how to try again -->
|
||||
<string name="CouldNotCompleteBackupRestoreSheet__body_retry">To try again, uninstall and re-install Signal on this device, and choose \"Restore or transfer\".</string>
|
||||
|
||||
<!-- EOF -->
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user