Allow restoration over cellular

This commit is contained in:
Michelle Tang
2024-12-05 13:02:36 -05:00
committed by Greyson Parrelli
parent 0c86ff1f84
commit a7d7c1da8d
11 changed files with 128 additions and 10 deletions

View File

@@ -82,6 +82,7 @@ fun BackupStatusRow(
is BackupStatusData.RestoringMedia -> {
Text(
text = getRestoringMediaString(backupStatusData),
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(horizontal = dimensionResource(CoreUiR.dimen.gutter))
)
}
@@ -168,15 +169,15 @@ private fun getRestoringMediaString(backupStatusData: BackupStatusData.Restoring
return when (backupStatusData.restoreStatus) {
BackupStatusData.RestoreStatus.NORMAL -> {
stringResource(
R.string.BackupStatusRow__downloading_s_of_s_s,
R.string.BackupStatusRow__restoring_s_of_s_s,
backupStatusData.bytesDownloaded.toUnitString(2),
backupStatusData.bytesTotal.toUnitString(2),
"%d".format((backupStatusData.progress * 100).roundToInt())
)
}
BackupStatusData.RestoreStatus.LOW_BATTERY -> stringResource(R.string.BackupStatus__status_device_has_low_battery)
BackupStatusData.RestoreStatus.WAITING_FOR_INTERNET -> stringResource(R.string.BackupStatus__status_no_internet)
BackupStatusData.RestoreStatus.WAITING_FOR_WIFI -> stringResource(R.string.BackupStatus__status_waiting_for_wifi)
BackupStatusData.RestoreStatus.LOW_BATTERY -> stringResource(R.string.BackupStatusRow__restore_device_has_low_battery)
BackupStatusData.RestoreStatus.WAITING_FOR_INTERNET -> stringResource(R.string.BackupStatusRow__restore_no_internet)
BackupStatusData.RestoreStatus.WAITING_FOR_WIFI -> stringResource(R.string.BackupStatusRow__restore_waiting_for_wifi)
BackupStatusData.RestoreStatus.FINISHED -> stringResource(R.string.BackupStatus__restore_complete)
}
}

View File

@@ -108,7 +108,7 @@ class MediaRestoreProgressBanner(private val listener: RestoreProgressBannerList
.throttleLatest(1.seconds)
.map {
when {
!WifiConstraint.isMet(AppDependencies.application) -> BackupStatusData.RestoringMedia(restoreStatus = BackupStatusData.RestoreStatus.WAITING_FOR_WIFI)
!WifiConstraint.isMet(AppDependencies.application) && !SignalStore.backup.restoreWithCellular -> BackupStatusData.RestoringMedia(restoreStatus = BackupStatusData.RestoreStatus.WAITING_FOR_WIFI)
!NetworkConstraint.isMet(AppDependencies.application) -> BackupStatusData.RestoringMedia(restoreStatus = BackupStatusData.RestoreStatus.WAITING_FOR_INTERNET)
!BatteryNotLowConstraint.isMet() -> BackupStatusData.RestoringMedia(restoreStatus = BackupStatusData.RestoreStatus.LOW_BATTERY)
else -> {

View File

@@ -141,6 +141,7 @@ class RemoteBackupsSettingsFragment : ComposeFragment() {
backupsEnabled = state.backupsEnabled,
lastBackupTimestamp = state.lastBackupTimestamp,
canBackUpUsingCellular = state.canBackUpUsingCellular,
canRestoreUsingCellular = state.canRestoreUsingCellular,
backupsFrequency = state.backupsFrequency,
requestedDialog = state.dialog,
requestedSnackbar = state.snackbar,
@@ -239,6 +240,10 @@ class RemoteBackupsSettingsFragment : ComposeFragment() {
override fun onLearnMoreAboutBackupFailure() {
BackupAlertBottomSheet.create(BackupAlert.BackupFailed).show(parentFragmentManager, null)
}
override fun onRestoreUsingCellularClick(canUseCellular: Boolean) {
viewModel.setCanRestoreUsingCellular(canUseCellular)
}
}
private fun displayBackupKey() {
@@ -321,6 +326,7 @@ private interface ContentCallbacks {
fun onLearnMoreAboutLostSubscription() = Unit
fun onContactSupport() = Unit
fun onLearnMoreAboutBackupFailure() = Unit
fun onRestoreUsingCellularClick(canUseCellular: Boolean) = Unit
}
@Composable
@@ -330,6 +336,7 @@ private fun RemoteBackupsSettingsContent(
backupRestoreState: BackupRestoreState,
lastBackupTimestamp: Long,
canBackUpUsingCellular: Boolean,
canRestoreUsingCellular: Boolean,
backupsFrequency: BackupFrequency,
requestedDialog: RemoteBackupsSettingsState.Dialog,
requestedSnackbar: RemoteBackupsSettingsState.Snackbar,
@@ -403,6 +410,14 @@ private fun RemoteBackupsSettingsContent(
onLearnMoreClick = contentCallbacks::onLearnMoreAboutBackupFailure
)
}
item {
Rows.ToggleRow(
checked = canRestoreUsingCellular,
text = stringResource(id = R.string.RemoteBackupsSettingsFragment__restore_using_cellular),
onCheckChanged = contentCallbacks::onRestoreUsingCellularClick
)
}
} else if (backupRestoreState is BackupRestoreState.Ready && backupState is RemoteBackupsSettingsState.BackupState.Canceled) {
item {
BackupReadyToDownloadRow(
@@ -420,6 +435,7 @@ private fun RemoteBackupsSettingsContent(
backupSize = backupSize,
backupsFrequency = backupsFrequency,
canBackUpUsingCellular = canBackUpUsingCellular,
canRestoreUsingCellular = canRestoreUsingCellular,
contentCallbacks = contentCallbacks
)
} else {
@@ -540,6 +556,7 @@ private fun LazyListScope.appendBackupDetailsItems(
backupSize: Long,
backupsFrequency: BackupFrequency,
canBackUpUsingCellular: Boolean,
canRestoreUsingCellular: Boolean,
contentCallbacks: ContentCallbacks
) {
item {
@@ -1205,6 +1222,7 @@ private fun RemoteBackupsSettingsContentPreview() {
backupsEnabled = true,
lastBackupTimestamp = -1,
canBackUpUsingCellular = false,
canRestoreUsingCellular = false,
backupsFrequency = BackupFrequency.MANUAL,
requestedDialog = RemoteBackupsSettingsState.Dialog.NONE,
requestedSnackbar = RemoteBackupsSettingsState.Snackbar.NONE,

View File

@@ -14,6 +14,7 @@ import kotlin.time.Duration.Companion.seconds
data class RemoteBackupsSettingsState(
val backupsEnabled: Boolean,
val canBackUpUsingCellular: Boolean = false,
val canRestoreUsingCellular: Boolean = false,
val backupState: BackupState = BackupState.Loading,
val backupSize: Long = 0,
val backupsFrequency: BackupFrequency = BackupFrequency.DAILY,

View File

@@ -61,7 +61,8 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
lastBackupTimestamp = SignalStore.backup.lastBackupTime,
backupSize = SignalStore.backup.totalBackupSize,
backupsFrequency = SignalStore.backup.backupFrequency,
canBackUpUsingCellular = SignalStore.backup.backupWithCellular
canBackUpUsingCellular = SignalStore.backup.backupWithCellular,
canRestoreUsingCellular = SignalStore.backup.restoreWithCellular
)
)
@@ -109,6 +110,11 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
_state.update { it.copy(canBackUpUsingCellular = canBackUpUsingCellular) }
}
fun setCanRestoreUsingCellular(canRestoreUsingCellular: Boolean) {
SignalStore.backup.restoreWithCellular = canRestoreUsingCellular
_state.update { it.copy(canRestoreUsingCellular = canRestoreUsingCellular) }
}
fun setBackupsFrequency(backupsFrequency: BackupFrequency) {
SignalStore.backup.backupFrequency = backupsFrequency
_state.update { it.copy(backupsFrequency = backupsFrequency) }

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.jobmanager.impl
import android.app.Application
import android.app.job.JobInfo
import android.content.Context
import org.thoughtcrime.securesms.jobmanager.Constraint
import org.thoughtcrime.securesms.keyvalue.SignalStore
/**
* Constraint that, when added, means that a job cannot be performed unless the user either has Wifi or, if they enabled it, cellular
*/
class RestoreAttachmentConstraint(private val application: Application) : Constraint {
companion object {
const val KEY = "RestoreAttachmentConstraint"
fun isMet(context: Context): Boolean {
if (SignalStore.backup.restoreWithCellular) {
return NetworkConstraint.isMet(context)
}
return WifiConstraint.isMet(context)
}
}
override fun isMet(): Boolean {
return isMet(application)
}
override fun getFactoryKey(): String = KEY
override fun applyToJobInfo(jobInfoBuilder: JobInfo.Builder) = Unit
class Factory(val application: Application) : Constraint.Factory<RestoreAttachmentConstraint> {
override fun create(): RestoreAttachmentConstraint {
return RestoreAttachmentConstraint(application)
}
}
}

View File

@@ -0,0 +1,28 @@
package org.thoughtcrime.securesms.jobmanager.impl
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobmanager.ConstraintObserver
/**
* An observer for the [RestoreAttachmentConstraint]. This is called
* when users change whether or not restoring is allowed via cellular
*/
object RestoreAttachmentConstraintObserver : ConstraintObserver {
private const val REASON = "RestoreAttachmentConstraint"
private var notifier: ConstraintObserver.Notifier? = null
override fun register(notifier: ConstraintObserver.Notifier) {
this.notifier = notifier
}
/**
* Let the observer know that the restore using cellular flag has changed.
*/
fun onChange() {
if (RestoreAttachmentConstraint.isMet(AppDependencies.application)) {
notifier?.onConstraintMet(REASON)
}
}
}

View File

@@ -25,6 +25,8 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraintObserver;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkOrCellServiceConstraint;
import org.thoughtcrime.securesms.jobmanager.impl.NotInCallConstraint;
import org.thoughtcrime.securesms.jobmanager.impl.NotInCallConstraintObserver;
import org.thoughtcrime.securesms.jobmanager.impl.RestoreAttachmentConstraint;
import org.thoughtcrime.securesms.jobmanager.impl.RestoreAttachmentConstraintObserver;
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint;
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver;
import org.thoughtcrime.securesms.jobmanager.impl.WifiConstraint;
@@ -386,6 +388,7 @@ public final class JobManagerFactories {
put(NotInCallConstraint.KEY, new NotInCallConstraint.Factory());
put(SqlCipherMigrationConstraint.KEY, new SqlCipherMigrationConstraint.Factory(application));
put(WifiConstraint.KEY, new WifiConstraint.Factory(application));
put(RestoreAttachmentConstraint.KEY, new RestoreAttachmentConstraint.Factory(application));
}};
}
@@ -397,7 +400,8 @@ public final class JobManagerFactories {
new DecryptionsDrainedConstraintObserver(),
new NotInCallConstraintObserver(),
ChangeNumberConstraintObserver.INSTANCE,
DataRestoreConstraintObserver.INSTANCE);
DataRestoreConstraintObserver.INSTANCE,
RestoreAttachmentConstraintObserver.INSTANCE);
}
public static List<JobMigration> getJobMigrations(@NonNull Application application) {

View File

@@ -20,7 +20,7 @@ import org.thoughtcrime.securesms.events.PartProgressEvent
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobmanager.JobLogger.format
import org.thoughtcrime.securesms.jobmanager.impl.BatteryNotLowConstraint
import org.thoughtcrime.securesms.jobmanager.impl.WifiConstraint
import org.thoughtcrime.securesms.jobmanager.impl.RestoreAttachmentConstraint
import org.thoughtcrime.securesms.jobs.protos.RestoreAttachmentJobData
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.MmsException
@@ -108,7 +108,7 @@ class RestoreAttachmentJob private constructor(
private constructor(messageId: Long, attachmentId: AttachmentId, manual: Boolean, queue: String) : this(
Parameters.Builder()
.setQueue(queue)
.addConstraint(WifiConstraint.KEY)
.addConstraint(RestoreAttachmentConstraint.KEY)
.addConstraint(BatteryNotLowConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(30))
.build(),

View File

@@ -7,6 +7,7 @@ import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.backup.RestoreState
import org.thoughtcrime.securesms.backup.v2.BackupFrequency
import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
import org.thoughtcrime.securesms.jobmanager.impl.RestoreAttachmentConstraintObserver
import org.thoughtcrime.securesms.keyvalue.protos.ArchiveUploadProgressState
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.archive.ArchiveServiceCredential
@@ -48,6 +49,7 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
private const val KEY_CDN_MEDIA_PATH = "backup.cdn.mediaPath"
private const val KEY_BACKUP_OVER_CELLULAR = "backup.useCellular"
private const val KEY_RESTORE_OVER_CELLULAR = "backup.restore.useCellular"
private const val KEY_OPTIMIZE_STORAGE = "backup.optimizeStorage"
private const val KEY_BACKUPS_INITIALIZED = "backup.initialized"
@@ -82,6 +84,13 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
var optimizeStorage: Boolean by booleanValue(KEY_OPTIMIZE_STORAGE, false)
var backupWithCellular: Boolean by booleanValue(KEY_BACKUP_OVER_CELLULAR, false)
var restoreWithCellular: Boolean
get() = getBoolean(KEY_RESTORE_OVER_CELLULAR, false)
set(value) {
putBoolean(KEY_RESTORE_OVER_CELLULAR, value)
RestoreAttachmentConstraintObserver.onChange()
}
var nextBackupTime: Long by longValue(KEY_NEXT_BACKUP_TIME, -1)
var lastBackupTime: Long
get() = getLong(KEY_LAST_BACKUP_TIME, -1)

View File

@@ -7542,7 +7542,13 @@
<!-- Content description for x icon at the end of the linear progress indicator -->
<string name="BackupStatusRow__cancel_download">Cancel download</string>
<!-- Backup progress. Placeholders are size restored, size to restore, and percent, i.e. 50MB of 100MB (50%) -->
<string name="BackupStatusRow__downloading_s_of_s_s">Downloading: %1$s of %2$s (%3$s%%)</string>
<string name="BackupStatusRow__restoring_s_of_s_s">Restoring: %1$s of %2$s (%3$s%%)</string>
<!-- Text displayed under progress bar when restoring media pauses for Wi-Fi -->
<string name="BackupStatusRow__restore_waiting_for_wifi">Restore paused: Waiting for Wi-Fi…</string>
<!-- Text displayed under progress bar when restoring media pauses for internet -->
<string name="BackupStatusRow__restore_no_internet">Restore paused: No internet…</string>
<!-- Text displayed under progress bar when restoring media pauses for low battery -->
<string name="BackupStatusRow__restore_device_has_low_battery">Restore paused: Device has low battery</string>
<!-- Notice that there is not enough free space to continue restore. Placeholder is required space, for example 1.6GB -->
<string name="BackupStatusRow__not_enough_space">Not enough space to download your Backup. To continue free up %1$s of space.</string>
<!-- Text row label to skip download -->
@@ -7651,6 +7657,8 @@
<string name="RemoteBackupsSettingsFragment__payment_history">Payment history</string>
<!-- Section header for backup information -->
<string name="RemoteBackupsSettingsFragment__backup_details">Backup details</string>
<!-- Toggle row label to allow the restoration of a backup to occur while on cellular connection -->
<string name="RemoteBackupsSettingsFragment__restore_using_cellular">Restore using cellular</string>
<!-- Row label for backup size -->
<string name="RemoteBackupsSettingsFragment__backup_size">Backup size</string>
<!-- Row label for backup frequency -->