diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java
index 93b4f70a9f..b103e7434b 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java
@@ -18,7 +18,6 @@ package org.thoughtcrime.securesms;
import android.content.Context;
import android.os.Build;
-import android.os.StrictMode;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -87,7 +86,6 @@ import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.util.AppForegroundObserver;
import org.thoughtcrime.securesms.util.AppStartup;
import org.thoughtcrime.securesms.util.DynamicTheme;
-import org.thoughtcrime.securesms.util.Environment;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
import org.thoughtcrime.securesms.util.SignalUncaughtExceptionHandler;
@@ -99,7 +97,6 @@ import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWra
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.security.Security;
-import java.util.Objects;
import java.util.concurrent.TimeUnit;
import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
@@ -221,7 +218,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
ApplicationDependencies.getFrameRateTracker().start();
ApplicationDependencies.getMegaphoneRepository().onAppForegrounded();
ApplicationDependencies.getDeadlockDetector().start();
- SubscriptionKeepAliveJob.launchSubscriberIdKeepAliveJobIfNecessary();
+ SubscriptionKeepAliveJob.enqueueAndTrackTimeIfNecessary();
SignalExecutors.BOUNDED.execute(() -> {
FeatureFlags.refreshIfNecessary();
diff --git a/app/src/main/java/org/thoughtcrime/securesms/badges/models/Badge.kt b/app/src/main/java/org/thoughtcrime/securesms/badges/models/Badge.kt
index 413481ff2c..5baa020083 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/badges/models/Badge.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/badges/models/Badge.kt
@@ -40,6 +40,8 @@ data class Badge(
fun isExpired(): Boolean = expirationTimestamp < System.currentTimeMillis() && expirationTimestamp > 0
fun isBoost(): Boolean = id == BOOST_BADGE_ID
+ fun isGift(): Boolean = id == GIFT_BADGE_ID
+ fun isSubscription(): Boolean = !isBoost() && !isGift()
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
messageDigest.update(id.toByteArray(Key.CHARSET))
diff --git a/app/src/main/java/org/thoughtcrime/securesms/badges/self/expired/ExpiredBadgeBottomSheetDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/badges/self/expired/ExpiredBadgeBottomSheetDialogFragment.kt
index 06c5205240..2ff5eeffc6 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/badges/self/expired/ExpiredBadgeBottomSheetDialogFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/badges/self/expired/ExpiredBadgeBottomSheetDialogFragment.kt
@@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.errors.Un
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.BottomSheetUtil
+import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
/**
* Bottom sheet displaying a fading badge with a notice and action for becoming a subscriber again.
@@ -30,7 +31,8 @@ class ExpiredBadgeBottomSheetDialogFragment : DSLSettingsBottomSheetFragment(
private fun getConfiguration(): DSLConfiguration {
val args = ExpiredBadgeBottomSheetDialogFragmentArgs.fromBundle(requireArguments())
val badge: Badge = args.badge
- val cancellationReason: UnexpectedSubscriptionCancellation? = UnexpectedSubscriptionCancellation.fromStatus(args.cancelationReason)
+ val cancellationReason = UnexpectedSubscriptionCancellation.fromStatus(args.cancelationReason)
+ val chargeFailure: ActiveSubscription.ChargeFailure? = SignalStore.donationsValues().getUnexpectedSubscriptionCancelationChargeFailure()
val isLikelyASustainer = SignalStore.donationsValues().isLikelyASustainer()
val inactive = cancellationReason == UnexpectedSubscriptionCancellation.INACTIVE
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt
index 8ad93202b1..e1797baa96 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt
@@ -31,6 +31,7 @@ import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob
import org.thoughtcrime.securesms.jobs.RetrieveRemoteAnnouncementsJob
import org.thoughtcrime.securesms.jobs.RotateProfileKeyJob
import org.thoughtcrime.securesms.jobs.StorageForcePushJob
+import org.thoughtcrime.securesms.jobs.SubscriptionKeepAliveJob
import org.thoughtcrime.securesms.jobs.SubscriptionReceiptRequestResponseJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.payments.DataExportUtil
@@ -401,6 +402,13 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
enqueueSubscriptionRedemption()
}
)
+
+ clickPref(
+ title = DSLSettingsText.from(R.string.preferences__internal_badges_enqueue_keep_alive),
+ onClick = {
+ enqueueSubscriptionKeepAlive()
+ }
+ )
}
dividerPref()
@@ -573,6 +581,10 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
SubscriptionReceiptRequestResponseJob.createSubscriptionContinuationJobChain().enqueue()
}
+ private fun enqueueSubscriptionKeepAlive() {
+ SubscriptionKeepAliveJob.enqueueAndTrackTime(System.currentTimeMillis())
+ }
+
private fun clearCdsHistory() {
SignalDatabase.cds.clearAll()
SignalStore.misc().cdsToken = null
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/DonationPaymentRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/DonationPaymentRepository.kt
index 5a8e192f50..d81e10dade 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/DonationPaymentRepository.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/DonationPaymentRepository.kt
@@ -277,9 +277,8 @@ class DonationPaymentRepository(activity: Activity) : StripeApi.PaymentIntentFet
).flatMapCompletable {
if (it.status == 200 || it.status == 204) {
Log.d(TAG, "Successfully set user subscription to level $subscriptionLevel with response code ${it.status}", true)
- SignalStore.donationsValues().clearUserManuallyCancelled()
+ SignalStore.donationsValues().updateLocalStateForLocalSubscribe()
scheduleSyncForAccountRecordChange()
- SignalStore.donationsValues().clearLevelOperations()
LevelUpdate.updateProcessingState(false)
Completable.complete()
} else {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeViewModel.kt
index 3e8fa9159d..0c2f370aa2 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeViewModel.kt
@@ -164,12 +164,7 @@ class SubscribeViewModel(
return Single.just(SignalStore.donationsValues().shouldCancelSubscriptionBeforeNextSubscribeAttempt).flatMapCompletable {
if (it) {
donationPaymentRepository.cancelActiveSubscription().doOnComplete {
- SignalStore.donationsValues().setLastEndOfPeriod(0L)
- SignalStore.donationsValues().clearLevelOperations()
- SignalStore.donationsValues().shouldCancelSubscriptionBeforeNextSubscribeAttempt = false
- SignalStore.donationsValues().setUnexpectedSubscriptionCancelationChargeFailure(null)
- SignalStore.donationsValues().unexpectedSubscriptionCancelationReason = null
- SignalStore.donationsValues().unexpectedSubscriptionCancelationTimestamp = 0L
+ SignalStore.donationsValues().updateLocalStateForManualCancellation()
MultiDeviceSubscriptionSyncRequestJob.enqueue()
}
} else {
@@ -183,12 +178,7 @@ class SubscribeViewModel(
disposables += donationPaymentRepository.cancelActiveSubscription().subscribeBy(
onComplete = {
eventPublisher.onNext(DonationEvent.SubscriptionCancelled)
- SignalStore.donationsValues().setLastEndOfPeriod(0L)
- SignalStore.donationsValues().clearLevelOperations()
- SignalStore.donationsValues().markUserManuallyCancelled()
- SignalStore.donationsValues().setUnexpectedSubscriptionCancelationChargeFailure(null)
- SignalStore.donationsValues().unexpectedSubscriptionCancelationReason = null
- SignalStore.donationsValues().unexpectedSubscriptionCancelationTimestamp = 0L
+ SignalStore.donationsValues().updateLocalStateForManualCancellation()
refreshActiveSubscription()
MultiDeviceSubscriptionSyncRequestJob.enqueue()
donationPaymentRepository.scheduleSyncForAccountRecordChange()
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java
index 90933e0a35..b99c1c05b3 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java
@@ -299,6 +299,10 @@ public class RefreshOwnProfileJob extends BaseJob {
Log.d(TAG, "Unexpected expiry due to payment failure.", true);
isDueToPaymentFailure = true;
}
+
+ if (activeSubscription.getChargeFailure() != null) {
+ Log.d(TAG, "Active payment contains a charge failure: " + activeSubscription.getChargeFailure().getCode(), true);
+ }
}
}
@@ -320,6 +324,16 @@ public class RefreshOwnProfileJob extends BaseJob {
Log.d(TAG, "Marking boost badge as expired, should notify next time the conversation list is open.", true);
SignalStore.donationsValues().setExpiredBadge(mostRecentExpiration);
+ } else {
+ Badge badge = SignalStore.donationsValues().getExpiredBadge();
+
+ if (badge != null && badge.isSubscription() && remoteHasSubscriptionBadges) {
+ Log.d(TAG, "Remote has subscription badges. Clearing local expired subscription badge.", true);
+ SignalStore.donationsValues().setExpiredBadge(null);
+ } else if (badge != null && badge.isBoost() && remoteHasBoostBadges) {
+ Log.d(TAG, "Remote has boost badges. Clearing local expired boost badge.", true);
+ SignalStore.donationsValues().setExpiredBadge(null);
+ }
}
if (!remoteHasGiftBadges && localHasGiftBadges) {
@@ -333,6 +347,9 @@ public class RefreshOwnProfileJob extends BaseJob {
Log.d(TAG, "Marking gift badge as expired, should notify next time the manage donations screen is open.", true);
SignalStore.donationsValues().setExpiredGiftBadge(mostRecentExpiration);
+ } else if (remoteHasGiftBadges) {
+ Log.d(TAG, "We have remote gift badges. Clearing local expired gift badge.", true);
+ SignalStore.donationsValues().setExpiredGiftBadge(null);
}
boolean userHasVisibleBadges = badges.stream().anyMatch(SignalServiceProfile.Badge::isVisible);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/SubscriptionKeepAliveJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/SubscriptionKeepAliveJob.java
index 809c68dee9..59f3a493b3 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/SubscriptionKeepAliveJob.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/SubscriptionKeepAliveJob.java
@@ -29,16 +29,20 @@ public class SubscriptionKeepAliveJob extends BaseJob {
private static final String TAG = Log.tag(SubscriptionKeepAliveJob.class);
private static final long JOB_TIMEOUT = TimeUnit.DAYS.toMillis(3);
- public static void launchSubscriberIdKeepAliveJobIfNecessary() {
+ public static void enqueueAndTrackTimeIfNecessary() {
long nextLaunchTime = SignalStore.donationsValues().getLastKeepAliveLaunchTime() + TimeUnit.DAYS.toMillis(3);
long now = System.currentTimeMillis();
if (nextLaunchTime <= now) {
- ApplicationDependencies.getJobManager().add(new SubscriptionKeepAliveJob());
- SignalStore.donationsValues().setLastKeepAliveLaunchTime(now);
+ enqueueAndTrackTime(now);
}
}
+ public static void enqueueAndTrackTime(long now) {
+ ApplicationDependencies.getJobManager().add(new SubscriptionKeepAliveJob());
+ SignalStore.donationsValues().setLastKeepAliveLaunchTime(now);
+ }
+
private SubscriptionKeepAliveJob() {
this(new Parameters.Builder()
.setQueue(KEY)
@@ -70,6 +74,12 @@ public class SubscriptionKeepAliveJob extends BaseJob {
@Override
protected void onRun() throws Exception {
+ synchronized (SubscriptionReceiptRequestResponseJob.MUTEX) {
+ doRun();
+ }
+ }
+
+ private void doRun() throws Exception {
Subscriber subscriber = SignalStore.donationsValues().getSubscriber();
if (subscriber == null) {
return;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/DonationsValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/DonationsValues.kt
index 7ce1bce866..daf2a2d56e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/DonationsValues.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/DonationsValues.kt
@@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.keyvalue
+import androidx.annotation.WorkerThread
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.subjects.BehaviorSubject
import io.reactivex.rxjava3.subjects.Subject
@@ -8,6 +9,7 @@ import org.signal.donations.StripeApi
import org.thoughtcrime.securesms.badges.Badges
import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.database.model.databaseprotos.BadgeList
+import org.thoughtcrime.securesms.jobs.SubscriptionReceiptRequestResponseJob
import org.thoughtcrime.securesms.payments.currency.CurrencyUtil
import org.thoughtcrime.securesms.subscription.LevelUpdateOperation
import org.thoughtcrime.securesms.subscription.Subscriber
@@ -287,4 +289,63 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign
var shouldCancelSubscriptionBeforeNextSubscribeAttempt: Boolean
get() = getBoolean(SHOULD_CANCEL_SUBSCRIPTION_BEFORE_NEXT_SUBSCRIBE_ATTEMPT, false)
set(value) = putBoolean(SHOULD_CANCEL_SUBSCRIPTION_BEFORE_NEXT_SUBSCRIBE_ATTEMPT, value)
+
+ /**
+ * Consolidates a bunch of data clears that should occur whenever a user manually cancels their
+ * subscription:
+ *
+ * 1. Clears keep-alive flag
+ * 1. Clears level operation
+ * 1. Marks the user as manually cancelled
+ * 1. Clears out unexpected cancelation state
+ * 1. Clears expired badge if it is for a subscription
+ */
+ @WorkerThread
+ fun updateLocalStateForManualCancellation() {
+ synchronized(SubscriptionReceiptRequestResponseJob.MUTEX) {
+ Log.d(TAG, "[updateLocalStateForManualCancellation] Clearing donation values.")
+
+ setLastEndOfPeriod(0L)
+ clearLevelOperations()
+ markUserManuallyCancelled()
+ shouldCancelSubscriptionBeforeNextSubscribeAttempt = false
+ setUnexpectedSubscriptionCancelationChargeFailure(null)
+ unexpectedSubscriptionCancelationReason = null
+ unexpectedSubscriptionCancelationTimestamp = 0L
+
+ val expiredBadge = getExpiredBadge()
+ if (expiredBadge != null && expiredBadge.isSubscription()) {
+ Log.d(TAG, "[updateLocalStateForManualCancellation] Clearing expired badge.")
+ setExpiredBadge(null)
+ }
+ }
+ }
+
+ /**
+ * Consolidates a bunch of data clears that should occur whenever a user begins a new subscription:
+ *
+ * 1. Manual cancellation marker
+ * 1. Any set level operations
+ * 1. Unexpected cancelation flags
+ * 1. Expired badge, if it is of a subscription
+ */
+ @WorkerThread
+ fun updateLocalStateForLocalSubscribe() {
+ synchronized(SubscriptionReceiptRequestResponseJob.MUTEX) {
+ Log.d(TAG, "[updateLocalStateForLocalSubscribe] Clearing donation values.")
+
+ clearUserManuallyCancelled()
+ clearLevelOperations()
+ shouldCancelSubscriptionBeforeNextSubscribeAttempt = false
+ setUnexpectedSubscriptionCancelationChargeFailure(null)
+ unexpectedSubscriptionCancelationReason = null
+ unexpectedSubscriptionCancelationTimestamp = 0L
+
+ val expiredBadge = getExpiredBadge()
+ if (expiredBadge != null && expiredBadge.isSubscription()) {
+ Log.d(TAG, "[updateLocalStateForLocalSubscribe] Clearing expired badge.")
+ setExpiredBadge(null)
+ }
+ }
+ }
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.java b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.java
index 8369e2cf84..9dd0130dd5 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.java
@@ -9,6 +9,7 @@ import androidx.annotation.VisibleForTesting;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
+import org.signal.core.util.SetUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.SignalDatabase;
@@ -22,7 +23,6 @@ import org.thoughtcrime.securesms.payments.Entropy;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.subscription.Subscriber;
import org.thoughtcrime.securesms.util.Base64;
-import org.signal.core.util.SetUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.storage.SignalAccountRecord;
@@ -160,9 +160,7 @@ public final class StorageSyncHelper {
SignalStore.donationsValues().setDisplayBadgesOnProfile(update.getNew().isDisplayBadgesOnProfile());
if (update.getNew().isSubscriptionManuallyCancelled()) {
- SignalStore.donationsValues().markUserManuallyCancelled();
- SignalStore.donationsValues().setUnexpectedSubscriptionCancelationReason(null);
- SignalStore.donationsValues().setUnexpectedSubscriptionCancelationTimestamp(0L);
+ SignalStore.donationsValues().updateLocalStateForManualCancellation();
} else {
SignalStore.donationsValues().clearUserManuallyCancelled();
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e3229cef7f..78be0a8ca4 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2738,6 +2738,7 @@
Disable Telecom integration
Badges
Enqueue redemption.
+ Enqueue keep-alive.
Release channel
Fetch release channel
Set last version seen back 10 versions