Prevent "Free" tier from being upgraded in the background.

This commit is contained in:
Alex Hart
2025-06-24 11:11:32 -03:00
committed by GitHub
parent 1719122f5e
commit ccc4acdef9
3 changed files with 46 additions and 30 deletions

View File

@@ -169,7 +169,7 @@ private fun BackupsSettingsContent(
OtherWaysToBackUpHeading()
}
is BackupState.ActiveFree, is BackupState.ActivePaid -> {
is BackupState.ActiveFree, is BackupState.ActivePaid, is BackupState.Canceled -> {
ActiveBackupsRow(
backupState = backupsSettingsState.backupState,
onBackupsRowClick = onBackupsRowClick,
@@ -210,15 +210,6 @@ private fun BackupsSettingsContent(
OtherWaysToBackUpHeading()
}
is BackupState.Canceled -> {
ActiveBackupsRow(
backupState = backupsSettingsState.backupState,
lastBackupAt = backupsSettingsState.lastBackupAt
)
OtherWaysToBackUpHeading()
}
is BackupState.SubscriptionMismatchMissingGooglePlay -> {
ActiveBackupsRow(
backupState = backupsSettingsState.backupState,
@@ -421,13 +412,25 @@ private fun ActiveBackupsRow(
when (val type = backupState.messageBackupsType) {
is MessageBackupsType.Paid -> {
Text(
text = stringResource(
val body = if (backupState is BackupState.Canceled) {
stringResource(R.string.BackupsSettingsFragment__subscription_canceled)
} else {
stringResource(
R.string.BackupsSettingsFragment_s_month_renews_s,
FiatMoneyUtil.format(LocalContext.current.resources, type.pricePerMonth),
DateUtils.formatDateWithYear(Locale.getDefault(), backupState.renewalTime.inWholeMilliseconds)
),
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
val color = if (backupState is BackupState.Canceled) {
MaterialTheme.colorScheme.error
} else {
MaterialTheme.colorScheme.onSurfaceVariant
}
Text(
text = body,
color = color,
style = MaterialTheme.typography.bodyMedium
)
}

View File

@@ -71,43 +71,43 @@ class BackupSubscriptionCheckJob private constructor(parameters: Parameters) : C
override suspend fun doRun(): Result {
if (!SignalStore.account.isRegistered) {
Log.i(TAG, "User is not registered. Clearing mismatch value and exiting.")
Log.i(TAG, "User is not registered. Clearing mismatch value and exiting.", true)
SignalStore.backup.subscriptionStateMismatchDetected = false
return Result.success()
}
if (!RemoteConfig.messageBackups) {
Log.i(TAG, "Message backups feature is not available. Clearing mismatch value and exiting.")
Log.i(TAG, "Message backups feature is not available. Clearing mismatch value and exiting.", true)
SignalStore.backup.subscriptionStateMismatchDetected = false
return Result.success()
}
if (!AppDependencies.billingApi.isApiAvailable()) {
Log.i(TAG, "Google Play Billing API is not available on this device. Clearing mismatch value and exiting.")
Log.i(TAG, "Google Play Billing API is not available on this device. Clearing mismatch value and exiting.", true)
SignalStore.backup.subscriptionStateMismatchDetected = false
return Result.success()
}
if (SignalStore.backup.deletionState != DeletionState.NONE) {
Log.i(TAG, "User is in the process of or has delete their backup. Clearing mismatch value and exiting.")
Log.i(TAG, "User is in the process of or has delete their backup. Clearing mismatch value and exiting.", true)
SignalStore.backup.subscriptionStateMismatchDetected = false
return Result.success()
}
if (!SignalStore.backup.areBackupsEnabled) {
Log.i(TAG, "Backups are not enabled on this device. Clearing mismatch value and exiting.")
Log.i(TAG, "Backups are not enabled on this device. Clearing mismatch value and exiting.", true)
SignalStore.backup.subscriptionStateMismatchDetected = false
return Result.success()
}
val purchase: BillingPurchaseResult = AppDependencies.billingApi.queryPurchases()
Log.i(TAG, "Retrieved purchase result from Billing api: $purchase")
Log.i(TAG, "Retrieved purchase result from Billing api: $purchase", true)
val hasActivePurchase = purchase is BillingPurchaseResult.Success && purchase.isAcknowledged
val product: BillingProduct? = AppDependencies.billingApi.queryProduct()
if (product == null) {
Log.w(TAG, "Google Play Billing product not available. Exiting.")
Log.w(TAG, "Google Play Billing product not available. Exiting.", true)
return Result.failure()
}
@@ -115,7 +115,7 @@ class BackupSubscriptionCheckJob private constructor(parameters: Parameters) : C
val inAppPayment = SignalDatabase.inAppPayments.getLatestInAppPaymentByType(InAppPaymentType.RECURRING_BACKUP)
if (inAppPayment?.state == InAppPaymentTable.State.PENDING) {
Log.i(TAG, "User has a pending in-app payment. Clearing mismatch value and re-checking later.")
Log.i(TAG, "User has a pending in-app payment. Clearing mismatch value and re-checking later.", true)
SignalStore.backup.subscriptionStateMismatchDetected = false
return Result.success()
}
@@ -125,9 +125,12 @@ class BackupSubscriptionCheckJob private constructor(parameters: Parameters) : C
checkForFailedOrCanceledSubscriptionState(activeSubscription)
Log.i(TAG, "Synchronizing backup tier with value from server.")
BackupRepository.getBackupTier().runIfSuccessful {
SignalStore.backup.backupTier = it
val isSignalSubscriptionFailedOrCanceled = activeSubscription?.isFailedPayment == true || activeSubscription?.isCanceled == true
if (hasActiveSignalSubscription && !isSignalSubscriptionFailedOrCanceled) {
Log.i(TAG, "Detected an active, non-failed, non-canceled signal subscription. Synchronizing backup tier with value from server.", true)
BackupRepository.getBackupTier().runIfSuccessful {
SignalStore.backup.backupTier = it
}
}
val hasActivePaidBackupTier = SignalStore.backup.backupTier == MessageBackupTier.PAID
@@ -135,14 +138,14 @@ class BackupSubscriptionCheckJob private constructor(parameters: Parameters) : C
val hasValidInactiveState = !hasActivePaidBackupTier && !hasActiveSignalSubscription && !hasActivePurchase
val purchaseToken = if (hasActivePurchase) {
(purchase as BillingPurchaseResult.Success).purchaseToken
purchase.purchaseToken
} else {
null
}
val hasTokenMismatch = purchaseToken?.let { hasLocalDevicePurchaseTokenMismatch(purchaseToken) } == true
if (hasActiveSignalSubscription && hasTokenMismatch) {
Log.i(TAG, "Encountered token mismatch with an active Signal subscription. Attempting to redeem against latest token.")
Log.i(TAG, "Encountered token mismatch with an active Signal subscription. Attempting to redeem against latest token.", true)
enqueueRedemptionForNewToken(purchaseToken, product.price)
SignalStore.backup.subscriptionStateMismatchDetected = false
return Result.success()
@@ -152,9 +155,17 @@ class BackupSubscriptionCheckJob private constructor(parameters: Parameters) : C
SignalStore.backup.subscriptionStateMismatchDetected = false
return Result.success()
} else {
Log.w(TAG, "State mismatch: (hasActivePaidBackupTier: $hasActivePaidBackupTier, hasActiveSignalSubscription: $hasActiveSignalSubscription, hasActivePurchase: $hasActivePurchase). Setting mismatch value and exiting.", true)
SignalStore.backup.subscriptionStateMismatchDetected = true
return Result.success()
val isGooglePlayBillingCanceled = purchase is BillingPurchaseResult.Success && !purchase.isAutoRenewing
if (isGooglePlayBillingCanceled && (!hasActiveSignalSubscription || isSignalSubscriptionFailedOrCanceled)) {
Log.i(TAG, "Valid cancel state. Clearing mismatch. (isGooglePlayBillingCanceled: true, hasActiveSignalSubscription: $hasActiveSignalSubscription, isSignalSubscriptionFailedOrCanceled: $isSignalSubscriptionFailedOrCanceled", true)
SignalStore.backup.subscriptionStateMismatchDetected = false
return Result.success()
} else {
Log.w(TAG, "State mismatch: (hasActivePaidBackupTier: $hasActivePaidBackupTier, hasActiveSignalSubscription: $hasActiveSignalSubscription, hasActivePurchase: $hasActivePurchase). Setting mismatch value and exiting.", true)
SignalStore.backup.subscriptionStateMismatchDetected = true
return Result.success()
}
}
}
}

View File

@@ -8072,6 +8072,8 @@
<string name="BackupsSettingsFragment_s_month_renews_s">%1$s/month, renews %2$s</string>
<!-- Subtitle for row for active backup, placeholder is last date of backup -->
<string name="BackupsSettingsFragment_last_backup_s">Last backup %1$s</string>
<!-- Subtitle for row for canceled backup -->
<string name="BackupsSettingsFragment__subscription_canceled">Subscription canceled</string>
<!-- Subtitle for row for no backup ever created -->
<string name="BackupsSettingsFragment_automatic_backups_with_signals">Automatic backups with Signal\'s secure end-to-end encrypted storage service.</string>
<!-- Subtitle for row for backups that are active but subscription not found -->