From c75a6c9715ea94b1602eed7cd077fb372a47549a Mon Sep 17 00:00:00 2001 From: Michelle Tang Date: Wed, 18 Jun 2025 14:37:00 -0400 Subject: [PATCH] Sync backup tier to account record. --- .../securesms/backup/v2/BackupRepository.kt | 8 ++++++++ .../subscription/MessageBackupsFlowViewModel.kt | 5 +++++ .../app/backups/BackupsSettingsViewModel.kt | 6 ++++++ .../securesms/jobs/BackupDeleteJob.kt | 4 ++++ .../securesms/jobs/BackupMessagesJob.kt | 7 +++++++ .../securesms/storage/StorageSyncHelper.kt | 17 +++++++++++++++++ .../jobs/InAppPaymentRecurringContextJobTest.kt | 15 +++++++++++++++ 7 files changed, 62 insertions(+) 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 5c1c2b71d5..4d308fc78a 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 @@ -100,6 +100,7 @@ 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.storage.StorageSyncHelper import org.thoughtcrime.securesms.util.RemoteConfig import org.thoughtcrime.securesms.util.ServiceUtil import org.thoughtcrime.securesms.util.toMillis @@ -168,6 +169,7 @@ object BackupRepository { Log.w(TAG, "Local device thought it was on PAID tier. Downgrading to FREE tier.") SignalStore.backup.backupTier = MessageBackupTier.FREE SignalStore.backup.backupExpiredAndDowngraded = true + scheduleSyncForAccountChange() } SignalStore.uiHints.markHasEverEnabledRemoteBackups() @@ -1137,6 +1139,7 @@ object BackupRepository { SignalStore.backup.lastCheckInMillis = System.currentTimeMillis() SignalStore.backup.lastCheckInSnoozeMillis = 0 SignalStore.backup.clearDownloadNotifierState() + scheduleSyncForAccountChange() } /** @@ -1655,6 +1658,11 @@ object BackupRepository { RemoteConfig.restoreAfterRegistration } + private fun scheduleSyncForAccountChange() { + SignalDatabase.recipients.markNeedsSync(Recipient.self().id) + StorageSyncHelper.scheduleSyncForDataChange() + } + private fun File.deleteAllFilesWithPrefix(prefix: String) { this.listFiles()?.filter { it.name.startsWith(prefix) }?.forEach { it.delete() } } 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 baae34d0e9..2a73092929 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 @@ -44,6 +44,7 @@ import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.jobs.InAppPaymentPurchaseTokenJob import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.storage.StorageSyncHelper import org.thoughtcrime.securesms.util.RemoteConfig import org.whispersystems.signalservice.api.storage.IAPSubscriptionId import org.whispersystems.signalservice.internal.push.SubscriptionsConfiguration @@ -236,6 +237,10 @@ class MessageBackupsFlowViewModel( private fun validateTypeAndUpdateState(state: MessageBackupsFlowState): MessageBackupsFlowState { return when (state.selectedMessageBackupTier!!) { MessageBackupTier.FREE -> { + viewModelScope.launch(SignalDispatchers.IO) { + SignalDatabase.recipients.markNeedsSync(Recipient.self().id) + StorageSyncHelper.scheduleSyncForDataChange() + } SignalStore.backup.backupTier = MessageBackupTier.FREE SignalStore.uiHints.markHasEverEnabledRemoteBackups() diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/BackupsSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/BackupsSettingsViewModel.kt index c62391a57b..268da57ce4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/BackupsSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/BackupsSettingsViewModel.kt @@ -26,6 +26,8 @@ import org.thoughtcrime.securesms.backup.v2.MessageBackupTier import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.storage.StorageSyncHelper import org.thoughtcrime.securesms.util.Environment import org.thoughtcrime.securesms.util.InternetConnectionObserver import org.thoughtcrime.securesms.util.RemoteConfig @@ -108,6 +110,10 @@ class BackupsSettingsViewModel : ViewModel() { fun onBackupTierInternalOverrideChanged(tier: MessageBackupTier?) { SignalStore.backup.backupTierInternalOverride = tier SignalStore.backup.deletionState = DeletionState.NONE + viewModelScope.launch(SignalDispatchers.Default) { + SignalDatabase.recipients.markNeedsSync(Recipient.self().id) + StorageSyncHelper.scheduleSyncForDataChange() + } refreshState() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupDeleteJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupDeleteJob.kt index f219d11371..34b91f7ea3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupDeleteJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupDeleteJob.kt @@ -18,6 +18,8 @@ import org.thoughtcrime.securesms.jobmanager.Job import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint import org.thoughtcrime.securesms.jobs.protos.BackupDeleteJobData import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.storage.StorageSyncHelper import org.whispersystems.signalservice.api.NetworkResult /** @@ -219,6 +221,8 @@ class BackupDeleteJob private constructor( Log.d(TAG, "Clearing local backup state.") SignalStore.backup.disableBackups() + SignalDatabase.recipients.markNeedsSync(Recipient.self().id) + StorageSyncHelper.scheduleSyncForDataChange() SignalDatabase.attachments.clearAllArchiveData() addStageToCompletions(BackupDeleteJobData.Stage.CLEAR_LOCAL_STATE) return Result.success() 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 0a9de1f196..34c23bf664 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupMessagesJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupMessagesJob.kt @@ -22,6 +22,8 @@ import org.thoughtcrime.securesms.jobmanager.impl.WifiConstraint import org.thoughtcrime.securesms.jobs.protos.BackupMessagesJobData import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.providers.BlobProvider +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.storage.StorageSyncHelper import org.whispersystems.signalservice.api.NetworkResult import org.whispersystems.signalservice.api.messages.AttachmentTransferProgress import org.whispersystems.signalservice.api.messages.SignalServiceAttachment @@ -150,6 +152,11 @@ class BackupMessagesJob private constructor( when (val result = BackupRepository.uploadBackupFile(backupSpec, it, tempBackupFile.length(), progressListener)) { is NetworkResult.Success -> { Log.i(TAG, "Successfully uploaded backup file.") + if (!SignalStore.backup.hasBackupBeenUploaded) { + Log.i(TAG, "First time making a backup - scheduling a storage sync.") + SignalDatabase.recipients.markNeedsSync(Recipient.self().id) + StorageSyncHelper.scheduleSyncForDataChange() + } SignalStore.backup.hasBackupBeenUploaded = true } diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.kt b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.kt index c1fc4497bd..609d94b256 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.kt @@ -7,6 +7,7 @@ import okio.ByteString.Companion.toByteString import org.signal.core.util.Base64.encodeWithPadding import org.signal.core.util.SqlUtil import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.backup.v2.MessageBackupTier import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.getSubscriber import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.isUserManuallyCancelled import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.setSubscriber @@ -174,6 +175,14 @@ object StorageSyncHelper { color = StorageSyncModels.localToRemoteUsernameColor(SignalStore.misc.usernameQrCodeColorScheme) ) } + + hasBackup = SignalStore.backup.areBackupsEnabled && SignalStore.backup.hasBackupBeenUploaded + if (SignalStore.backup.areBackupsEnabled && SignalStore.backup.backupTier != null) { + backupTier = getBackupLevelValue(SignalStore.backup.backupTier!!) + } else if (SignalStore.backup.backupTierInternalOverride != null) { + backupTier = getBackupLevelValue(SignalStore.backup.backupTierInternalOverride!!) + } + notificationProfileManualOverride = getNotificationProfileManualOverride() getSubscriber(InAppPaymentSubscriberRecord.Type.DONATION)?.let { @@ -190,6 +199,14 @@ object StorageSyncHelper { return accountRecord.toSignalAccountRecord(StorageId.forAccount(storageId)).toSignalStorageRecord() } + // TODO: Currently we don't have access to the private values of the BackupLevel. Update when it becomes available. + private fun getBackupLevelValue(tier: MessageBackupTier): Long { + return when (tier) { + MessageBackupTier.FREE -> 200 + MessageBackupTier.PAID -> 201 + } + } + private fun getNotificationProfileManualOverride(): AccountRecord.NotificationProfileManualOverride { val profile = SignalDatabase.notificationProfiles.getProfile(SignalStore.notificationProfile.manuallyEnabledProfile) return if (profile != null && profile.deletedTimestampMs == 0L) { diff --git a/app/src/test/java/org/thoughtcrime/securesms/jobs/InAppPaymentRecurringContextJobTest.kt b/app/src/test/java/org/thoughtcrime/securesms/jobs/InAppPaymentRecurringContextJobTest.kt index 5e73b6b6c7..2c1a7fb87d 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/jobs/InAppPaymentRecurringContextJobTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/jobs/InAppPaymentRecurringContextJobTest.kt @@ -9,6 +9,7 @@ import assertk.assertions.isTrue import io.mockk.every import io.mockk.mockk import io.mockk.mockkObject +import io.mockk.mockkStatic import io.mockk.verify import org.junit.Before import org.junit.Rule @@ -23,11 +24,14 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaym import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.toInAppPaymentDataChargeFailure import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsTestRule import org.thoughtcrime.securesms.database.InAppPaymentTable +import org.thoughtcrime.securesms.database.RecipientTable import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord import org.thoughtcrime.securesms.database.model.databaseprotos.BadgeList import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData import org.thoughtcrime.securesms.dependencies.AppDependencies +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.storage.StorageSyncHelper import org.thoughtcrime.securesms.testutil.MockAppDependenciesRule import org.thoughtcrime.securesms.testutil.MockSignalStoreRule import org.thoughtcrime.securesms.testutil.SystemOutLogger @@ -51,6 +55,8 @@ class InAppPaymentRecurringContextJobTest { @get:Rule val inAppPaymentsTestRule = InAppPaymentsTestRule() + lateinit var recipientTable: RecipientTable + @Before fun setUp() { Log.initialize(SystemOutLogger()) @@ -58,6 +64,15 @@ class InAppPaymentRecurringContextJobTest { every { mockSignalStore.account.isRegistered } returns true every { mockSignalStore.inAppPayments.setLastEndOfPeriod(any()) } returns Unit + recipientTable = mockk(relaxed = true) + every { SignalDatabase.recipients } returns recipientTable + + mockkObject(Recipient) + every { Recipient.self() } returns Recipient() + + mockkStatic(StorageSyncHelper::class) + every { StorageSyncHelper.scheduleSyncForDataChange() } returns Unit + mockkObject(InAppPaymentsRepository) every { InAppPaymentsRepository.generateRequestCredential() } returns mockk { every { serialize() } returns byteArrayOf()