mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-23 04:28:35 +00:00
Add wiring and notification for out of remote space error.
This commit is contained in:
@@ -5,10 +5,12 @@
|
|||||||
|
|
||||||
package org.thoughtcrime.securesms.backup.v2
|
package org.thoughtcrime.securesms.backup.v2
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.os.StatFs
|
import android.os.StatFs
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okio.ByteString
|
import okio.ByteString
|
||||||
import okio.ByteString.Companion.toByteString
|
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.ByteSize
|
||||||
import org.signal.core.util.CursorUtil
|
import org.signal.core.util.CursorUtil
|
||||||
import org.signal.core.util.EventTimer
|
import org.signal.core.util.EventTimer
|
||||||
|
import org.signal.core.util.PendingIntentFlags.cancelCurrent
|
||||||
import org.signal.core.util.Stopwatch
|
import org.signal.core.util.Stopwatch
|
||||||
import org.signal.core.util.bytes
|
import org.signal.core.util.bytes
|
||||||
import org.signal.core.util.concurrent.LimitedWorker
|
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.VerificationFailedException
|
||||||
import org.signal.libsignal.zkgroup.backups.BackupLevel
|
import org.signal.libsignal.zkgroup.backups.BackupLevel
|
||||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey
|
import org.signal.libsignal.zkgroup.profiles.ProfileKey
|
||||||
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.attachments.Attachment
|
import org.thoughtcrime.securesms.attachments.Attachment
|
||||||
import org.thoughtcrime.securesms.attachments.Cdn
|
import org.thoughtcrime.securesms.attachments.Cdn
|
||||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
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.stream.PlainTextBackupWriter
|
||||||
import org.thoughtcrime.securesms.backup.v2.ui.BackupAlert
|
import org.thoughtcrime.securesms.backup.v2.ui.BackupAlert
|
||||||
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsType
|
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.components.settings.app.subscription.RecurringInAppPaymentRepository
|
||||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider
|
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider
|
||||||
import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider
|
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.SignalStore
|
||||||
import org.thoughtcrime.securesms.keyvalue.isDecisionPending
|
import org.thoughtcrime.securesms.keyvalue.isDecisionPending
|
||||||
import org.thoughtcrime.securesms.net.SignalNetwork
|
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.Recipient
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||||
|
import org.thoughtcrime.securesms.util.ServiceUtil
|
||||||
import org.thoughtcrime.securesms.util.toMillis
|
import org.thoughtcrime.securesms.util.toMillis
|
||||||
import org.whispersystems.signalservice.api.AccountEntropyPool
|
import org.whispersystems.signalservice.api.AccountEntropyPool
|
||||||
import org.whispersystems.signalservice.api.ApplicationErrorAction
|
import org.whispersystems.signalservice.api.ApplicationErrorAction
|
||||||
@@ -291,8 +299,34 @@ object BackupRepository {
|
|||||||
AppDependencies.jobManager.add(CheckRestoreMediaLeftJob(RestoreAttachmentJob.constructQueueString(RestoreAttachmentJob.RestoreOperation.MANUAL)))
|
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 {
|
fun shouldDisplayOutOfStorageSpaceUx(): Boolean {
|
||||||
return false // TODO [message-backups] Wire into actual error handling.
|
if (shouldNotDisplayBackupFailedMessaging()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return SignalStore.backup.isNotEnoughRemoteStorageSpace
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -511,6 +511,7 @@ private fun RemoteBackupsSettingsContent(
|
|||||||
backupsFrequency = state.backupsFrequency,
|
backupsFrequency = state.backupsFrequency,
|
||||||
canBackUpUsingCellular = state.canBackUpUsingCellular,
|
canBackUpUsingCellular = state.canBackUpUsingCellular,
|
||||||
canRestoreUsingCellular = state.canRestoreUsingCellular,
|
canRestoreUsingCellular = state.canRestoreUsingCellular,
|
||||||
|
canBackUpNow = !state.isOutOfStorageSpace,
|
||||||
contentCallbacks = contentCallbacks
|
contentCallbacks = contentCallbacks
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -813,6 +814,7 @@ private fun LazyListScope.appendBackupDetailsItems(
|
|||||||
backupsFrequency: BackupFrequency,
|
backupsFrequency: BackupFrequency,
|
||||||
canBackUpUsingCellular: Boolean,
|
canBackUpUsingCellular: Boolean,
|
||||||
canRestoreUsingCellular: Boolean,
|
canRestoreUsingCellular: Boolean,
|
||||||
|
canBackUpNow: Boolean,
|
||||||
contentCallbacks: ContentCallbacks
|
contentCallbacks: ContentCallbacks
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
@@ -845,6 +847,7 @@ private fun LazyListScope.appendBackupDetailsItems(
|
|||||||
item {
|
item {
|
||||||
LastBackupRow(
|
LastBackupRow(
|
||||||
lastBackupTimestamp = lastBackupTimestamp,
|
lastBackupTimestamp = lastBackupTimestamp,
|
||||||
|
enabled = canBackUpNow,
|
||||||
onBackupNowClick = contentCallbacks::onBackupNowClick
|
onBackupNowClick = contentCallbacks::onBackupNowClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1421,6 +1424,7 @@ private fun getBackupUploadPhaseProgressString(state: ArchiveUploadProgressState
|
|||||||
@Composable
|
@Composable
|
||||||
private fun LastBackupRow(
|
private fun LastBackupRow(
|
||||||
lastBackupTimestamp: Long,
|
lastBackupTimestamp: Long,
|
||||||
|
enabled: Boolean,
|
||||||
onBackupNowClick: () -> Unit
|
onBackupNowClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
Row(
|
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))
|
Text(text = stringResource(id = R.string.RemoteBackupsSettingsFragment__back_up_now))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1909,6 +1913,7 @@ private fun LastBackupRowPreview() {
|
|||||||
Previews.Preview {
|
Previews.Preview {
|
||||||
LastBackupRow(
|
LastBackupRow(
|
||||||
lastBackupTimestamp = -1,
|
lastBackupTimestamp = -1,
|
||||||
|
enabled = true,
|
||||||
onBackupNowClick = {}
|
onBackupNowClick = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.reactive.asFlow
|
import kotlinx.coroutines.reactive.asFlow
|
||||||
import org.signal.core.util.bytes
|
import org.signal.core.util.bytes
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
|
import org.signal.core.util.mebiBytes
|
||||||
import org.signal.core.util.throttleLatest
|
import org.signal.core.util.throttleLatest
|
||||||
import org.signal.donations.InAppPaymentType
|
import org.signal.donations.InAppPaymentType
|
||||||
import org.thoughtcrime.securesms.backup.ArchiveUploadProgress
|
import org.thoughtcrime.securesms.backup.ArchiveUploadProgress
|
||||||
@@ -223,6 +224,25 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun performStateRefresh(lastPurchase: InAppPaymentTable.InAppPayment?) {
|
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 {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
tier = SignalStore.backup.backupTier,
|
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)
|
val state = BackupStateRepository.resolveBackupState(lastPurchase)
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(backupState = state)
|
it.copy(backupState = state)
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ import org.signal.core.util.Base64
|
|||||||
import org.signal.core.util.Hex
|
import org.signal.core.util.Hex
|
||||||
import org.signal.core.util.getLength
|
import org.signal.core.util.getLength
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
|
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||||
import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
|
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.DialogState
|
||||||
import org.thoughtcrime.securesms.components.settings.app.internal.backup.InternalBackupPlaygroundViewModel.ScreenState
|
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))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.")
|
val remoteStorageQuota = getServerQuota() ?: return Result.retry(defaultBackoff()).logW(TAG, "[$attachmentId] Failed to fetch server quota! Retrying.")
|
||||||
|
|
||||||
if (SignalDatabase.attachments.getEstimatedArchiveMediaSize() > remoteStorageQuota.inWholeBytes) {
|
if (SignalDatabase.attachments.getEstimatedArchiveMediaSize() > remoteStorageQuota.inWholeBytes) {
|
||||||
// [TODO] Handle too much data case
|
BackupRepository.markOutOfRemoteStorageError()
|
||||||
return Result.failure()
|
return Result.failure()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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_FAIL_SPACE_REMAINING = "backup.failed.space.remaining"
|
||||||
private const val KEY_BACKUP_ALREADY_REDEEMED = "backup.already.redeemed"
|
private const val KEY_BACKUP_ALREADY_REDEEMED = "backup.already.redeemed"
|
||||||
private const val KEY_INVALID_BACKUP_VERSION = "backup.invalid.version"
|
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_USER_MANUALLY_SKIPPED_MEDIA_RESTORE = "backup.user.manually.skipped.media.restore"
|
||||||
private const val KEY_BACKUP_EXPIRED_AND_DOWNGRADED = "backup.expired.and.downgraded"
|
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. */
|
/** 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)
|
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
|
* 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.
|
* or pruning orphaned media.
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ public final class NotificationIds {
|
|||||||
public static final int STORY_MESSAGE_DELIVERY_FAILURE = 900000;
|
public static final int STORY_MESSAGE_DELIVERY_FAILURE = 900000;
|
||||||
public static final int UNREGISTERED_NOTIFICATION_ID = 20230102;
|
public static final int UNREGISTERED_NOTIFICATION_ID = 20230102;
|
||||||
public static final int NEW_LINKED_DEVICE = 120400;
|
public static final int NEW_LINKED_DEVICE = 120400;
|
||||||
|
public static final int OUT_OF_REMOTE_STORAGE = 120500;
|
||||||
|
|
||||||
private NotificationIds() { }
|
private NotificationIds() { }
|
||||||
|
|
||||||
|
|||||||
@@ -8586,5 +8586,10 @@
|
|||||||
<!-- Accessibility label describing an unchecked checkbox. -->
|
<!-- Accessibility label describing an unchecked checkbox. -->
|
||||||
<string name="SignalCheckbox_accessibility_unchecked_description">Unticked</string>
|
<string name="SignalCheckbox_accessibility_unchecked_description">Unticked</string>
|
||||||
|
|
||||||
|
<!-- Notification title for when user is out of remote storage -->
|
||||||
|
<string name="Notification_backup_storage_full">Backup storage full</string>
|
||||||
|
<!-- Notification body for when user is out of remote storage -->
|
||||||
|
<string name="Notification_youve_reached_your_backup_storage_limit">You\'ve reached your backup storage limit. Free up space in Signal to continue backing up chats and media.</string>
|
||||||
|
|
||||||
<!-- EOF -->
|
<!-- EOF -->
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user