diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt index d89e848e98..ecaa627229 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt @@ -115,8 +115,7 @@ object BackupRepository { @WorkerThread fun turnOffAndDeleteBackup() { RecurringInAppPaymentRepository.cancelActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) - SignalStore.backup.areBackupsEnabled = false - SignalStore.backup.backupTier = null + SignalStore.backup.disableBackups() } private fun createSignalDatabaseSnapshot(baseName: String): SignalDatabase { diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt index 65450fe5ea..b96b3e4ada 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt @@ -182,7 +182,6 @@ class MessageBackupsFlowViewModel( private fun validateTypeAndUpdateState(state: MessageBackupsFlowState): MessageBackupsFlowState { return when (state.selectedMessageBackupTier!!) { MessageBackupTier.FREE -> { - SignalStore.backup.areBackupsEnabled = true SignalStore.backup.backupTier = MessageBackupTier.FREE SignalStore.uiHints.markHasEverEnabledRemoteBackups() @@ -260,8 +259,7 @@ class MessageBackupsFlowViewModel( ) ) - Log.d(TAG, "Enabling backups and enqueueing InAppPaymentPurchaseTokenJob chain.") - SignalStore.backup.areBackupsEnabled = true + Log.d(TAG, "Enqueueing InAppPaymentPurchaseTokenJob chain.") SignalStore.uiHints.markHasEverEnabledRemoteBackups() InAppPaymentPurchaseTokenJob.createJobChain(inAppPayment).enqueue() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/backup/InternalBackupPlaygroundFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/backup/InternalBackupPlaygroundFragment.kt index a92d407f96..3a14f2ffeb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/backup/InternalBackupPlaygroundFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/backup/InternalBackupPlaygroundFragment.kt @@ -37,6 +37,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.RadioButton import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface @@ -57,6 +58,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -72,6 +74,7 @@ import org.signal.core.util.getLength import org.signal.core.util.roundedString import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.attachments.AttachmentId +import org.thoughtcrime.securesms.backup.v2.MessageBackupTier import org.thoughtcrime.securesms.components.settings.app.internal.backup.InternalBackupPlaygroundViewModel.BackupState import org.thoughtcrime.securesms.components.settings.app.internal.backup.InternalBackupPlaygroundViewModel.BackupUploadState import org.thoughtcrime.securesms.components.settings.app.internal.backup.InternalBackupPlaygroundViewModel.ScreenState @@ -181,7 +184,8 @@ class InternalBackupPlaygroundFragment : ComposeFragment() { .setMessage("This will delete all of your chats! Make sure you've finished a backup first, we don't check for you. Only do this on a test device!") .setPositiveButton("Wipe and restore") { _, _ -> viewModel.wipeAllDataAndRestoreFromRemote() } .show() - } + }, + onBackupTierSelected = { tier -> viewModel.onBackupTierSelected(tier) } ) }, mediaContent = { snackbarHostState -> @@ -274,9 +278,17 @@ fun Screen( onUploadToRemoteClicked: () -> Unit = {}, onCheckRemoteBackupStateClicked: () -> Unit = {}, onTriggerBackupJobClicked: () -> Unit = {}, - onWipeDataAndRestoreClicked: () -> Unit = {} + onWipeDataAndRestoreClicked: () -> Unit = {}, + onBackupTierSelected: (MessageBackupTier?) -> Unit = {} ) { val scrollState = rememberScrollState() + val options = remember { + mapOf( + "None" to null, + "Free" to MessageBackupTier.FREE, + "Paid" to MessageBackupTier.PAID + ) + } Surface { Column( @@ -287,6 +299,21 @@ fun Screen( .verticalScroll(scrollState) .padding(16.dp) ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text("Tier", fontWeight = FontWeight.Bold) + options.forEach { option -> + Row(verticalAlignment = Alignment.CenterVertically) { + RadioButton( + selected = option.value == state.backupTier, + onClick = { onBackupTierSelected(option.value) } + ) + Text(option.key) + } + } + } + + Dividers.Default() + Buttons.LargePrimary( onClick = onTriggerBackupJobClicked ) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/backup/InternalBackupPlaygroundViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/backup/InternalBackupPlaygroundViewModel.kt index 09f71c6b28..8c7ffab495 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/backup/InternalBackupPlaygroundViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/backup/InternalBackupPlaygroundViewModel.kt @@ -24,6 +24,7 @@ import org.thoughtcrime.securesms.attachments.AttachmentId import org.thoughtcrime.securesms.attachments.DatabaseAttachment import org.thoughtcrime.securesms.backup.v2.BackupMetadata import org.thoughtcrime.securesms.backup.v2.BackupRepository +import org.thoughtcrime.securesms.backup.v2.MessageBackupTier import org.thoughtcrime.securesms.backup.v2.local.ArchiveFileSystem import org.thoughtcrime.securesms.backup.v2.local.ArchiveResult import org.thoughtcrime.securesms.backup.v2.local.LocalArchiver @@ -65,7 +66,8 @@ class InternalBackupPlaygroundViewModel : ViewModel() { canReadWriteBackupDirectory = SignalStore.settings.signalBackupDirectory?.let { val file = DocumentFile.fromTreeUri(AppDependencies.application, it) file != null && file.canWrite() && file.canRead() - } ?: false + } ?: false, + backupTier = SignalStore.backup.backupTier ) ) val state: State = _state @@ -217,6 +219,11 @@ class InternalBackupPlaygroundViewModel : ViewModel() { } } + fun onBackupTierSelected(backupTier: MessageBackupTier?) { + SignalStore.backup.backupTier = backupTier + _state.value = _state.value.copy(backupTier = backupTier) + } + private fun restoreFromRemote() { _state.value = _state.value.copy(backupState = BackupState.IMPORT_IN_PROGRESS) @@ -417,7 +424,8 @@ class InternalBackupPlaygroundViewModel : ViewModel() { val uploadState: BackupUploadState = BackupUploadState.NONE, val remoteBackupState: RemoteBackupState = RemoteBackupState.Unknown, val plaintext: Boolean, - val canReadWriteBackupDirectory: Boolean = false + val canReadWriteBackupDirectory: Boolean = false, + val backupTier: MessageBackupTier? = null ) enum class BackupState(val inProgress: Boolean = false) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupMessagesJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupMessagesJob.kt index 0f736fe2cd..0bcc0f7a69 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupMessagesJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupMessagesJob.kt @@ -84,7 +84,10 @@ class BackupMessagesJob private constructor(parameters: Parameters) : Job(parame FileInputStream(tempBackupFile).use { when (val result = BackupRepository.uploadBackupFile(it, tempBackupFile.length())) { - is NetworkResult.Success -> Log.i(TAG, "Successfully uploaded backup file.") + is NetworkResult.Success -> { + Log.i(TAG, "Successfully uploaded backup file.") + SignalStore.backup.hasBackupBeenUploaded = true + } is NetworkResult.NetworkError -> return Result.retry(defaultBackoff()) is NetworkResult.StatusCodeError -> return Result.retry(defaultBackoff()) is NetworkResult.ApplicationError -> throw result.throwable @@ -110,11 +113,11 @@ class BackupMessagesJob private constructor(parameters: Parameters) : Job(parame stopwatch.split("used-space") stopwatch.stop(TAG) - if (SignalDatabase.attachments.doAnyAttachmentsNeedArchiveUpload()) { + if (SignalStore.backup.backsUpMedia && SignalDatabase.attachments.doAnyAttachmentsNeedArchiveUpload()) { Log.i(TAG, "Enqueuing attachment backfill job.") AppDependencies.jobManager.add(ArchiveAttachmentBackfillJob()) } else { - Log.i(TAG, "No attachments need to be uploaded, we can finish.") + Log.i(TAG, "No attachments need to be uploaded, we can finish. Tier: ${SignalStore.backup.backupTier}") ArchiveUploadProgress.onMessageBackupFinishedEarly() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/BackupValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/BackupValues.kt index 6ac3058917..7bfd947991 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/BackupValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/BackupValues.kt @@ -41,14 +41,14 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) { private const val KEY_ARCHIVE_UPLOAD_STATE = "backup.archiveUploadState" - /** - * Specifies whether remote backups are enabled on this device. - */ - private const val KEY_BACKUPS_ENABLED = "backup.enabled" + private const val KEY_BACKUP_UPLOADED = "backup.backupUploaded" private val cachedCdnCredentialsExpiresIn: Duration = 12.hours } + override fun onFirstEverAppLaunch() = Unit + override fun getKeysToIncludeInBackup(): List = emptyList() + private var cachedCdnCredentialsTimestamp: Long by longValue(KEY_CDN_READ_CREDENTIALS_TIMESTAMP, 0L) private var cachedCdnCredentials: String? by stringValue(KEY_CDN_READ_CREDENTIALS, null) var cachedBackupDirectory: String? by stringValue(KEY_CDN_BACKUP_DIRECTORY, null) @@ -56,9 +56,6 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) { var usedBackupMediaSpace: Long by longValue(KEY_BACKUP_USED_MEDIA_SPACE, 0L) var lastBackupProtoSize: Long by longValue(KEY_BACKUP_LAST_PROTO_SIZE, 0L) - override fun onFirstEverAppLaunch() = Unit - override fun getKeysToIncludeInBackup(): List = emptyList() - var restoreState: RestoreState by enumValue(KEY_RESTORE_STATE, RestoreState.NONE, RestoreState.serializer) var optimizeStorage: Boolean by booleanValue(KEY_OPTIMIZE_STORAGE, false) var backupWithCellular: Boolean by booleanValue(KEY_BACKUP_OVER_CELLULAR, false) @@ -81,18 +78,25 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) { @JvmName("backsUpMedia") get() = backupTier == MessageBackupTier.PAID - var areBackupsEnabled: Boolean - get() { - return getBoolean(KEY_BACKUPS_ENABLED, false) - } - set(value) { - store - .beginWrite() - .putBoolean(KEY_BACKUPS_ENABLED, value) - .putLong(KEY_NEXT_BACKUP_TIME, -1) - .putBoolean(KEY_BACKUPS_INITIALIZED, false) - .apply() - } + /** True if the user has backups enabled, otherwise false. */ + val areBackupsEnabled: Boolean + get() = backupTier != null + + /** True if we believe we have successfully uploaded a backup, otherwise false. */ + var hasBackupBeenUploaded: Boolean by booleanValue(KEY_BACKUP_UPLOADED, false) + + /** + * Call when the user disables backups. Clears/resets all relevant fields. + */ + fun disableBackups() { + store + .beginWrite() + .putLong(KEY_NEXT_BACKUP_TIME, -1) + .putBoolean(KEY_BACKUPS_INITIALIZED, false) + .putBoolean(KEY_BACKUP_UPLOADED, false) + .apply() + backupTier = null + } var backupsInitialized: Boolean by booleanValue(KEY_BACKUPS_INITIALIZED, false) diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InAppPaymentValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InAppPaymentValues.kt index 65ca82d2fd..80f9a982fd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InAppPaymentValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InAppPaymentValues.kt @@ -471,8 +471,7 @@ class InAppPaymentValues internal constructor(store: KeyValueStore) : SignalStor } else { markBackupSubscriptionpManuallyCancelled() - SignalStore.backup.areBackupsEnabled = false - SignalStore.backup.backupTier = null + SignalStore.backup.disableBackups() } val subscriber = InAppPaymentsRepository.getSubscriber(subscriberType) @@ -514,7 +513,6 @@ class InAppPaymentValues internal constructor(store: KeyValueStore) : SignalStor } else { clearBackupSubscriptionManuallyCancelled() - SignalStore.backup.areBackupsEnabled = true SignalStore.backup.backupTier = MessageBackupTier.PAID SignalStore.uiHints.markHasEverEnabledRemoteBackups() }