diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/BackupsSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/BackupsSettingsFragment.kt index f416eb1691..2320fcef0b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/BackupsSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/BackupsSettingsFragment.kt @@ -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 ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupSubscriptionCheckJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupSubscriptionCheckJob.kt index 55555aff1e..df365aab51 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupSubscriptionCheckJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupSubscriptionCheckJob.kt @@ -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() + } } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6d57c8d141..6327e55b2a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -8072,6 +8072,8 @@ %1$s/month, renews %2$s Last backup %1$s + + Subscription canceled Automatic backups with Signal\'s secure end-to-end encrypted storage service.