From 1493d931f081b363c3e77c01a4d8d6ade4d54e42 Mon Sep 17 00:00:00 2001 From: UnnatiCP <129381063+unnaticleverpush@users.noreply.github.com> Date: Fri, 15 May 2026 12:05:23 +0530 Subject: [PATCH 1/3] hotfix/cp-11566-push-delivery-issues-swp-twipe-report Improve FCM token sync reliability and stale token recovery --- .../manager/SubscriptionManagerFCM.java | 23 +++++++++++++++---- .../service/CleverPushFcmListenerService.java | 7 +++++- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/cleverpush/src/main/java/com/cleverpush/manager/SubscriptionManagerFCM.java b/cleverpush/src/main/java/com/cleverpush/manager/SubscriptionManagerFCM.java index 1e974238..2a2c3917 100755 --- a/cleverpush/src/main/java/com/cleverpush/manager/SubscriptionManagerFCM.java +++ b/cleverpush/src/main/java/com/cleverpush/manager/SubscriptionManagerFCM.java @@ -169,7 +169,7 @@ public void checkChangedPushToken(JSONObject channelConfig, String changedToken) String existingToken = sharedPreferences.getString(CleverPushPreferences.FCM_TOKEN, null); if (existingToken == null) { - return; + Logger.d(LOG_TAG, "No stored FCM token found. Syncing current token."); } new Thread(() -> { @@ -180,16 +180,31 @@ public void checkChangedPushToken(JSONObject channelConfig, String changedToken) } try { String newToken = changedToken != null ? changedToken : getTokenAttempt(senderId); - if (newToken != null && !newToken.equals(existingToken)) { + if (newToken == null) { + Logger.d(LOG_TAG, "checkChangedPushToken: no FCM token available yet, will retry on next sync."); + return; + } + + boolean forcedTokenRefresh = changedToken != null; + boolean tokenMissingLocally = existingToken == null; + boolean tokenChanged = !newToken.equals(existingToken); + + if (forcedTokenRefresh || tokenMissingLocally || tokenChanged) { + if (tokenMissingLocally) { + Logger.i(LOG_TAG, "Persisting FCM token after missing local copy (self-heal)."); + } this.syncSubscription(newToken, new SubscribedCallbackListener() { @Override public void onSuccess(String subscriptionId) { - Logger.i(LOG_TAG, "Synchronized new FCM token: " + newToken); + Logger.i(LOG_TAG, "Synchronized FCM token: " + newToken); + sharedPreferences.edit() + .putString(CleverPushPreferences.FCM_TOKEN, newToken) + .apply(); } @Override public void onFailure(Throwable exception) { - + Logger.e(LOG_TAG, "Failed to sync FCM token", exception); } }, senderId); } else { diff --git a/cleverpush/src/main/java/com/cleverpush/service/CleverPushFcmListenerService.java b/cleverpush/src/main/java/com/cleverpush/service/CleverPushFcmListenerService.java index 6a42b47b..07be5f39 100644 --- a/cleverpush/src/main/java/com/cleverpush/service/CleverPushFcmListenerService.java +++ b/cleverpush/src/main/java/com/cleverpush/service/CleverPushFcmListenerService.java @@ -56,10 +56,15 @@ public void onNewToken(@NonNull String token) { Logger.d(LOG_TAG, "FCM: onNewToken"); SharedPreferences sharedPreferences = SharedPreferencesManager.getSharedPreferences(this); + + // Always persist the latest FCM token locally so we never lose a rotation, + // even if it arrives before the first subscribe() completes. + sharedPreferences.edit().putString(CleverPushPreferences.FCM_TOKEN, token).apply(); + String subscriptionId = sharedPreferences.getString(CleverPushPreferences.SUBSCRIPTION_ID, null); if (subscriptionId == null) { - Logger.d(LOG_TAG, "CleverPushFcmListenerService onNewToken: There is no subscription for CleverPush SDK."); + Logger.d(LOG_TAG, "CleverPushFcmListenerService onNewToken: no subscription yet, token cached for next subscribe."); return; } From bcaf97a2e1fe0176900f287c7db97887e293b938 Mon Sep 17 00:00:00 2001 From: UnnatiCP <129381063+unnaticleverpush@users.noreply.github.com> Date: Fri, 15 May 2026 14:05:45 +0530 Subject: [PATCH 2/3] hotfix/cp-11566-push-delivery-issues-swp-twipe-report PR chnages --- .../manager/SubscriptionManagerFCM.java | 2 ++ .../service/CleverPushFcmListenerService.java | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/cleverpush/src/main/java/com/cleverpush/manager/SubscriptionManagerFCM.java b/cleverpush/src/main/java/com/cleverpush/manager/SubscriptionManagerFCM.java index 2a2c3917..e97998ca 100755 --- a/cleverpush/src/main/java/com/cleverpush/manager/SubscriptionManagerFCM.java +++ b/cleverpush/src/main/java/com/cleverpush/manager/SubscriptionManagerFCM.java @@ -178,8 +178,10 @@ public void checkChangedPushToken(JSONObject channelConfig, String changedToken) Logger.e(LOG_TAG, "SubscriptionManager: Getting FCM Sender ID failed"); return; } + try { String newToken = changedToken != null ? changedToken : getTokenAttempt(senderId); + if (newToken == null) { Logger.d(LOG_TAG, "checkChangedPushToken: no FCM token available yet, will retry on next sync."); return; diff --git a/cleverpush/src/main/java/com/cleverpush/service/CleverPushFcmListenerService.java b/cleverpush/src/main/java/com/cleverpush/service/CleverPushFcmListenerService.java index 07be5f39..ef406722 100644 --- a/cleverpush/src/main/java/com/cleverpush/service/CleverPushFcmListenerService.java +++ b/cleverpush/src/main/java/com/cleverpush/service/CleverPushFcmListenerService.java @@ -56,18 +56,25 @@ public void onNewToken(@NonNull String token) { Logger.d(LOG_TAG, "FCM: onNewToken"); SharedPreferences sharedPreferences = SharedPreferencesManager.getSharedPreferences(this); - - // Always persist the latest FCM token locally so we never lose a rotation, - // even if it arrives before the first subscribe() completes. - sharedPreferences.edit().putString(CleverPushPreferences.FCM_TOKEN, token).apply(); - String subscriptionId = sharedPreferences.getString(CleverPushPreferences.SUBSCRIPTION_ID, null); if (subscriptionId == null) { - Logger.d(LOG_TAG, "CleverPushFcmListenerService onNewToken: no subscription yet, token cached for next subscribe."); + // No subscription yet: the next subscribe() will fetch the current token + // straight from FCM, so nothing is lost by skipping local persistence + // here. We intentionally do NOT write FCM_TOKEN now — that key must + // reflect the last value successfully synced to the server, which is + // what checkChangedPushToken() relies on to detect rotations. + Logger.d(LOG_TAG, "CleverPushFcmListenerService onNewToken: no subscription yet, will be picked up on next subscribe."); return; } + // Persistence of FCM_TOKEN is deliberately gated on a successful server + // sync inside SubscriptionManagerFCM.checkChangedPushToken(...). + // If this sync attempt is dropped (e.g. getChannelConfig's callback never + // fires because CleverPush isn't initialized in this process, or the HTTP + // call fails), the stored token still reflects the last server-known + // value. On the next app start, checkChangedPushToken(config, null) will + // observe that the live FCM token differs from the stored one and re-sync. CleverPush cleverPush = CleverPush.getInstance(this); cleverPush.getChannelConfig( (JSONObject channelConfig) -> cleverPush.getSubscriptionManager().checkChangedPushToken(channelConfig, token)); From 64829a49f5203cd7c453baf8ae17f67959d345d7 Mon Sep 17 00:00:00 2001 From: UnnatiCP <129381063+unnaticleverpush@users.noreply.github.com> Date: Fri, 15 May 2026 15:10:03 +0530 Subject: [PATCH 3/3] hotfix/cp-11566-push-delivery-issues-swp-twipe-report Fixed FCM token retry logic by separating locally cached tokens from successfully synced server tokens. --- .../com/cleverpush/CleverPushPreferences.java | 1 + .../manager/SubscriptionManagerFCM.java | 51 ++++++++++++------- .../service/CleverPushFcmListenerService.java | 25 ++++----- 3 files changed, 44 insertions(+), 33 deletions(-) diff --git a/cleverpush/src/main/java/com/cleverpush/CleverPushPreferences.java b/cleverpush/src/main/java/com/cleverpush/CleverPushPreferences.java index 4f5007bf..7b1ae6c0 100644 --- a/cleverpush/src/main/java/com/cleverpush/CleverPushPreferences.java +++ b/cleverpush/src/main/java/com/cleverpush/CleverPushPreferences.java @@ -3,6 +3,7 @@ public class CleverPushPreferences { public static final String FCM_TOKEN = "CleverPush_FCM_TOKEN"; + public static final String SYNCED_FCM_TOKEN = "CleverPush_SYNCED_FCM_TOKEN"; public static final String HMS_TOKEN = "CleverPush_HMS_TOKEN"; public static final String ADM_TOKEN = "CleverPush_ADM_TOKEN"; public static final String CHANNEL_ID = "CleverPush_CHANNEL_ID"; diff --git a/cleverpush/src/main/java/com/cleverpush/manager/SubscriptionManagerFCM.java b/cleverpush/src/main/java/com/cleverpush/manager/SubscriptionManagerFCM.java index e97998ca..3e34ede9 100755 --- a/cleverpush/src/main/java/com/cleverpush/manager/SubscriptionManagerFCM.java +++ b/cleverpush/src/main/java/com/cleverpush/manager/SubscriptionManagerFCM.java @@ -166,14 +166,19 @@ public void subscribe(JSONObject channelConfig, SubscribedCallbackListener subsc @Override public void checkChangedPushToken(JSONObject channelConfig, String changedToken) { SharedPreferences sharedPreferences = SharedPreferencesManager.getSharedPreferences(this.context); - String existingToken = sharedPreferences.getString(CleverPushPreferences.FCM_TOKEN, null); + + // IMPORTANT: + // Compare against LAST SUCCESSFULLY SYNCED token, + // not the latest locally cached Firebase token. + String existingToken = sharedPreferences.getString(CleverPushPreferences.SYNCED_FCM_TOKEN, null); if (existingToken == null) { - Logger.d(LOG_TAG, "No stored FCM token found. Syncing current token."); + Logger.d(LOG_TAG, "No synced FCM token found. Syncing current token."); } new Thread(() -> { String senderId = this.getSenderIdFromConfig(channelConfig); + if (senderId == null) { Logger.e(LOG_TAG, "SubscriptionManager: Getting FCM Sender ID failed"); return; @@ -192,23 +197,31 @@ public void checkChangedPushToken(JSONObject channelConfig, String changedToken) boolean tokenChanged = !newToken.equals(existingToken); if (forcedTokenRefresh || tokenMissingLocally || tokenChanged) { - if (tokenMissingLocally) { - Logger.i(LOG_TAG, "Persisting FCM token after missing local copy (self-heal)."); - } - this.syncSubscription(newToken, new SubscribedCallbackListener() { - @Override - public void onSuccess(String subscriptionId) { - Logger.i(LOG_TAG, "Synchronized FCM token: " + newToken); - sharedPreferences.edit() - .putString(CleverPushPreferences.FCM_TOKEN, newToken) - .apply(); - } - - @Override - public void onFailure(Throwable exception) { - Logger.e(LOG_TAG, "Failed to sync FCM token", exception); - } - }, senderId); + + this.syncSubscription( + newToken, + new SubscribedCallbackListener() { + + @Override + public void onSuccess(String subscriptionId) { + Logger.i(LOG_TAG, "Synchronized FCM token: " + newToken); + + sharedPreferences.edit() + // latest local token + .putString(CleverPushPreferences.FCM_TOKEN, newToken) + // last successfully synced token + .putString(CleverPushPreferences.SYNCED_FCM_TOKEN, newToken) + .apply(); + } + + @Override + public void onFailure(Throwable exception) { + Logger.e(LOG_TAG, "Failed to sync FCM token", exception); + } + }, + senderId + ); + } else { Logger.d(LOG_TAG, "FCM token has not changed: " + newToken); } diff --git a/cleverpush/src/main/java/com/cleverpush/service/CleverPushFcmListenerService.java b/cleverpush/src/main/java/com/cleverpush/service/CleverPushFcmListenerService.java index ef406722..26260037 100644 --- a/cleverpush/src/main/java/com/cleverpush/service/CleverPushFcmListenerService.java +++ b/cleverpush/src/main/java/com/cleverpush/service/CleverPushFcmListenerService.java @@ -56,27 +56,24 @@ public void onNewToken(@NonNull String token) { Logger.d(LOG_TAG, "FCM: onNewToken"); SharedPreferences sharedPreferences = SharedPreferencesManager.getSharedPreferences(this); + + // Always store latest local token immediately. + // This ensures we never lose token rotations. + sharedPreferences.edit().putString(CleverPushPreferences.FCM_TOKEN, token).apply(); + String subscriptionId = sharedPreferences.getString(CleverPushPreferences.SUBSCRIPTION_ID, null); if (subscriptionId == null) { - // No subscription yet: the next subscribe() will fetch the current token - // straight from FCM, so nothing is lost by skipping local persistence - // here. We intentionally do NOT write FCM_TOKEN now — that key must - // reflect the last value successfully synced to the server, which is - // what checkChangedPushToken() relies on to detect rotations. - Logger.d(LOG_TAG, "CleverPushFcmListenerService onNewToken: no subscription yet, will be picked up on next subscribe."); + Logger.d(LOG_TAG, "CleverPushFcmListenerService onNewToken: no subscription yet, token cached for next subscribe."); return; } - // Persistence of FCM_TOKEN is deliberately gated on a successful server - // sync inside SubscriptionManagerFCM.checkChangedPushToken(...). - // If this sync attempt is dropped (e.g. getChannelConfig's callback never - // fires because CleverPush isn't initialized in this process, or the HTTP - // call fails), the stored token still reflects the last server-known - // value. On the next app start, checkChangedPushToken(config, null) will - // observe that the live FCM token differs from the stored one and re-sync. CleverPush cleverPush = CleverPush.getInstance(this); + cleverPush.getChannelConfig( - (JSONObject channelConfig) -> cleverPush.getSubscriptionManager().checkChangedPushToken(channelConfig, token)); + (JSONObject channelConfig) -> + cleverPush.getSubscriptionManager() + .checkChangedPushToken(channelConfig, token) + ); } }