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 4ae1285a84..6b4c76deda 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
@@ -5,10 +5,12 @@
package org.thoughtcrime.securesms.backup.v2
+import android.app.PendingIntent
import android.database.Cursor
import android.os.Environment
import android.os.StatFs
import androidx.annotation.WorkerThread
+import androidx.core.app.NotificationCompat
import kotlinx.coroutines.withContext
import okio.ByteString
import okio.ByteString.Companion.toByteString
@@ -17,6 +19,7 @@ import org.signal.core.util.Base64
import org.signal.core.util.ByteSize
import org.signal.core.util.CursorUtil
import org.signal.core.util.EventTimer
+import org.signal.core.util.PendingIntentFlags.cancelCurrent
import org.signal.core.util.Stopwatch
import org.signal.core.util.bytes
import org.signal.core.util.concurrent.LimitedWorker
@@ -38,6 +41,7 @@ import org.signal.core.util.withinTransaction
import org.signal.libsignal.zkgroup.VerificationFailedException
import org.signal.libsignal.zkgroup.backups.BackupLevel
import org.signal.libsignal.zkgroup.profiles.ProfileKey
+import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.attachments.Attachment
import org.thoughtcrime.securesms.attachments.Cdn
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
@@ -62,6 +66,7 @@ import org.thoughtcrime.securesms.backup.v2.stream.PlainTextBackupReader
import org.thoughtcrime.securesms.backup.v2.stream.PlainTextBackupWriter
import org.thoughtcrime.securesms.backup.v2.ui.BackupAlert
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsType
+import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
import org.thoughtcrime.securesms.components.settings.app.subscription.RecurringInAppPaymentRepository
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider
import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider
@@ -90,9 +95,12 @@ import org.thoughtcrime.securesms.keyvalue.KeyValueStore
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.keyvalue.isDecisionPending
import org.thoughtcrime.securesms.net.SignalNetwork
+import org.thoughtcrime.securesms.notifications.NotificationChannels
+import org.thoughtcrime.securesms.notifications.NotificationIds
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.RemoteConfig
+import org.thoughtcrime.securesms.util.ServiceUtil
import org.thoughtcrime.securesms.util.toMillis
import org.whispersystems.signalservice.api.AccountEntropyPool
import org.whispersystems.signalservice.api.ApplicationErrorAction
@@ -291,8 +299,34 @@ object BackupRepository {
AppDependencies.jobManager.add(CheckRestoreMediaLeftJob(RestoreAttachmentJob.constructQueueString(RestoreAttachmentJob.RestoreOperation.MANUAL)))
}
+ fun markOutOfRemoteStorageError() {
+ val context = AppDependencies.application
+
+ val pendingIntent = PendingIntent.getActivity(context, 0, AppSettingsActivity.remoteBackups(context), cancelCurrent())
+ val notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().APP_ALERTS)
+ .setSmallIcon(R.drawable.ic_notification)
+ .setContentTitle(context.getString(R.string.Notification_backup_storage_full))
+ .setContentText(context.getString(R.string.Notification_youve_reached_your_backup_storage_limit))
+ .setContentIntent(pendingIntent)
+ .setAutoCancel(true)
+ .build()
+
+ ServiceUtil.getNotificationManager(context).notify(NotificationIds.OUT_OF_REMOTE_STORAGE, notification)
+
+ SignalStore.backup.isNotEnoughRemoteStorageSpace = true
+ }
+
+ fun clearOutOfRemoteStorageError() {
+ SignalStore.backup.isNotEnoughRemoteStorageSpace = false
+ ServiceUtil.getNotificationManager(AppDependencies.application).cancel(NotificationIds.OUT_OF_REMOTE_STORAGE)
+ }
+
fun shouldDisplayOutOfStorageSpaceUx(): Boolean {
- return false // TODO [message-backups] Wire into actual error handling.
+ if (shouldNotDisplayBackupFailedMessaging()) {
+ return false
+ }
+
+ return SignalStore.backup.isNotEnoughRemoteStorageSpace
}
/**
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsFragment.kt
index 68af51677f..e93fc5e314 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsFragment.kt
@@ -511,6 +511,7 @@ private fun RemoteBackupsSettingsContent(
backupsFrequency = state.backupsFrequency,
canBackUpUsingCellular = state.canBackUpUsingCellular,
canRestoreUsingCellular = state.canRestoreUsingCellular,
+ canBackUpNow = !state.isOutOfStorageSpace,
contentCallbacks = contentCallbacks
)
} else {
@@ -813,6 +814,7 @@ private fun LazyListScope.appendBackupDetailsItems(
backupsFrequency: BackupFrequency,
canBackUpUsingCellular: Boolean,
canRestoreUsingCellular: Boolean,
+ canBackUpNow: Boolean,
contentCallbacks: ContentCallbacks
) {
item {
@@ -845,6 +847,7 @@ private fun LazyListScope.appendBackupDetailsItems(
item {
LastBackupRow(
lastBackupTimestamp = lastBackupTimestamp,
+ enabled = canBackUpNow,
onBackupNowClick = contentCallbacks::onBackupNowClick
)
}
@@ -1421,6 +1424,7 @@ private fun getBackupUploadPhaseProgressString(state: ArchiveUploadProgressState
@Composable
private fun LastBackupRow(
lastBackupTimestamp: Long,
+ enabled: Boolean,
onBackupNowClick: () -> Unit
) {
Row(
@@ -1462,7 +1466,7 @@ private fun LastBackupRow(
}
}
- Buttons.MediumTonal(onClick = onBackupNowClick) {
+ Buttons.MediumTonal(onClick = onBackupNowClick, enabled = enabled) {
Text(text = stringResource(id = R.string.RemoteBackupsSettingsFragment__back_up_now))
}
}
@@ -1909,6 +1913,7 @@ private fun LastBackupRowPreview() {
Previews.Preview {
LastBackupRow(
lastBackupTimestamp = -1,
+ enabled = true,
onBackupNowClick = {}
)
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsViewModel.kt
index a7d151c46f..cbd527ee7e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/remote/RemoteBackupsSettingsViewModel.kt
@@ -22,6 +22,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.reactive.asFlow
import org.signal.core.util.bytes
import org.signal.core.util.logging.Log
+import org.signal.core.util.mebiBytes
import org.signal.core.util.throttleLatest
import org.signal.donations.InAppPaymentType
import org.thoughtcrime.securesms.backup.ArchiveUploadProgress
@@ -223,6 +224,25 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
}
private suspend fun performStateRefresh(lastPurchase: InAppPaymentTable.InAppPayment?) {
+ if (BackupRepository.shouldDisplayOutOfStorageSpaceUx()) {
+ val paidType = BackupRepository.getBackupsType(MessageBackupTier.PAID) as? MessageBackupsType.Paid
+
+ if (paidType != null) {
+ val remoteStorageAllowance = paidType.storageAllowanceBytes.bytes
+ val estimatedSize = SignalDatabase.attachments.getEstimatedArchiveMediaSize().bytes
+
+ if (estimatedSize + 300.mebiBytes <= remoteStorageAllowance) {
+ BackupRepository.clearOutOfRemoteStorageError()
+ }
+
+ _state.update {
+ it.copy(
+ totalAllowedStorageSpace = estimatedSize.toUnitString()
+ )
+ }
+ }
+ }
+
_state.update {
it.copy(
tier = SignalStore.backup.backupTier,
@@ -237,17 +257,6 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
)
}
- if (BackupRepository.shouldDisplayOutOfStorageSpaceUx()) {
- val paidType = BackupRepository.getBackupsType(MessageBackupTier.PAID) as? MessageBackupsType.Paid
- if (paidType != null) {
- _state.update {
- it.copy(
- totalAllowedStorageSpace = paidType.storageAllowanceBytes.bytes.toUnitString()
- )
- }
- }
- }
-
val state = BackupStateRepository.resolveBackupState(lastPurchase)
_state.update {
it.copy(backupState = state)
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 dd07d58a20..4b3249af74 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
@@ -70,6 +70,7 @@ import org.signal.core.util.Base64
import org.signal.core.util.Hex
import org.signal.core.util.getLength
import org.thoughtcrime.securesms.R
+import org.thoughtcrime.securesms.backup.v2.BackupRepository
import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
import org.thoughtcrime.securesms.components.settings.app.internal.backup.InternalBackupPlaygroundViewModel.DialogState
import org.thoughtcrime.securesms.components.settings.app.internal.backup.InternalBackupPlaygroundViewModel.ScreenState
@@ -521,6 +522,13 @@ fun Screen(
}
)
+ Rows.TextRow(
+ text = "Mark out of remote storage space",
+ onClick = {
+ BackupRepository.markOutOfRemoteStorageError()
+ }
+ )
+
Spacer(modifier = Modifier.height(8.dp))
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/CopyAttachmentToArchiveJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/CopyAttachmentToArchiveJob.kt
index b4873d8bdd..1a6dcb9c5e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/CopyAttachmentToArchiveJob.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/CopyAttachmentToArchiveJob.kt
@@ -130,7 +130,7 @@ class CopyAttachmentToArchiveJob private constructor(private val attachmentId: A
val remoteStorageQuota = getServerQuota() ?: return Result.retry(defaultBackoff()).logW(TAG, "[$attachmentId] Failed to fetch server quota! Retrying.")
if (SignalDatabase.attachments.getEstimatedArchiveMediaSize() > remoteStorageQuota.inWholeBytes) {
- // [TODO] Handle too much data case
+ BackupRepository.markOutOfRemoteStorageError()
return Result.failure()
}
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 55b099b380..f2c661b97e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/BackupValues.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/BackupValues.kt
@@ -71,6 +71,7 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
private const val KEY_BACKUP_FAIL_SPACE_REMAINING = "backup.failed.space.remaining"
private const val KEY_BACKUP_ALREADY_REDEEMED = "backup.already.redeemed"
private const val KEY_INVALID_BACKUP_VERSION = "backup.invalid.version"
+ private const val KEY_NOT_ENOUGH_REMOTE_STORAGE_SPACE = "backup.not.enough.remote.storage.space"
private const val KEY_USER_MANUALLY_SKIPPED_MEDIA_RESTORE = "backup.user.manually.skipped.media.restore"
private const val KEY_BACKUP_EXPIRED_AND_DOWNGRADED = "backup.expired.and.downgraded"
@@ -314,6 +315,8 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
/** Store that lets you interact with media ZK credentials. */
val mediaCredentials = CredentialStore(KEY_MEDIA_CREDENTIALS, KEY_MEDIA_CDN_READ_CREDENTIALS, KEY_MEDIA_CDN_READ_CREDENTIALS_TIMESTAMP)
+ var isNotEnoughRemoteStorageSpace by booleanValue(KEY_NOT_ENOUGH_REMOTE_STORAGE_SPACE, false)
+
/**
* If true, it means we have been told that remote storage is full, but we have not yet run any of our "garbage collection" tasks, like committing deletes
* or pruning orphaned media.
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationIds.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationIds.java
index d660265518..16992d7453 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationIds.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationIds.java
@@ -32,6 +32,7 @@ public final class NotificationIds {
public static final int STORY_MESSAGE_DELIVERY_FAILURE = 900000;
public static final int UNREGISTERED_NOTIFICATION_ID = 20230102;
public static final int NEW_LINKED_DEVICE = 120400;
+ public static final int OUT_OF_REMOTE_STORAGE = 120500;
private NotificationIds() { }
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a2b9dd0b3c..edab413f01 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -8586,5 +8586,10 @@
Unticked
+
+ Backup storage full
+
+ You\'ve reached your backup storage limit. Free up space in Signal to continue backing up chats and media.
+