diff --git a/OneSignalSDK/detekt/detekt-baseline-notifications.xml b/OneSignalSDK/detekt/detekt-baseline-notifications.xml
index 8ee6e8c58..b40dadcd4 100644
--- a/OneSignalSDK/detekt/detekt-baseline-notifications.xml
+++ b/OneSignalSDK/detekt/detekt-baseline-notifications.xml
@@ -137,7 +137,7 @@
LongParameterList:INotificationGenerationWorkManager.kt$INotificationGenerationWorkManager$( context: Context, osNotificationId: String, androidNotificationId: Int, jsonPayload: JSONObject?, timestamp: Long, isRestoring: Boolean, isHighPriority: Boolean, )
LongParameterList:INotificationRepository.kt$INotificationRepository$( id: String, groupId: String?, collapseKey: String?, shouldDismissIdenticals: Boolean, isOpened: Boolean, androidId: Int, title: String?, body: String?, expireTime: Long, jsonPayload: String, )
LongParameterList:NotificationLifecycleService.kt$NotificationLifecycleService$( private val _applicationService: IApplicationService, private val _time: ITime, private val _configModelStore: ConfigModelStore, private val _influenceManager: IInfluenceManager, private val _subscriptionManager: ISubscriptionManager, private val _deviceService: IDeviceService, private val _backend: INotificationBackendService, private val _receiveReceiptWorkManager: IReceiveReceiptWorkManager, private val _analyticsTracker: IAnalyticsTracker, )
- LoopWithTooManyJumpStatements:NotificationLifecycleService.kt$NotificationLifecycleService$for (i in 0 until data.length()) { val notificationId = NotificationFormatHelper.getOSNotificationIdFromJson(data[i] as JSONObject?) ?: continue if (postedOpenedNotifIds.contains(notificationId)) { continue } postedOpenedNotifIds.add(notificationId) suspendifyWithErrorHandling( useIO = true, // or false for CPU operations block = { _backend.updateNotificationAsOpened( appId, notificationId, subscriptionId, deviceType, ) }, onError = { ex -> if (ex is BackendException) { Logging.error("Notification opened confirmation failed with statusCode: ${ex.statusCode} response: ${ex.response}") } else { Logging.error("Unexpected error in notification opened confirmation", ex) } }, ) }
+ LoopWithTooManyJumpStatements:NotificationLifecycleService.kt$NotificationLifecycleService$for (i in 0 until data.length()) { val notificationId = NotificationFormatHelper.getOSNotificationIdFromJson(data[i] as JSONObject?) ?: continue if (postedOpenedNotifIds.contains(notificationId)) { continue } postedOpenedNotifIds.add(notificationId) suspendifyWithErrorHandling( useIO = true, // or false for CPU operations block = { confirmNotificationOpened(appId, notificationId, subscriptionId, deviceType) }, onError = { ex -> if (ex is BackendException) { Logging.info("Notification opened confirmation failed with statusCode: ${ex.statusCode} response: ${ex.response}") } else { Logging.info("Unexpected error in notification opened confirmation", ex) } }, ) }
MagicNumber:FirebaseAnalyticsTracker.kt$FirebaseAnalyticsTracker$1000
MagicNumber:FirebaseAnalyticsTracker.kt$FirebaseAnalyticsTracker$30
MagicNumber:FirebaseAnalyticsTracker.kt$FirebaseAnalyticsTracker$60
diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/lifecycle/impl/NotificationLifecycleService.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/lifecycle/impl/NotificationLifecycleService.kt
index 480671000..ccc51f9f8 100644
--- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/lifecycle/impl/NotificationLifecycleService.kt
+++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/lifecycle/impl/NotificationLifecycleService.kt
@@ -5,6 +5,7 @@ import android.content.ActivityNotFoundException
import android.content.Context
import com.onesignal.common.AndroidUtils
import com.onesignal.common.JSONUtils
+import com.onesignal.common.NetworkUtils
import com.onesignal.common.events.CallbackProducer
import com.onesignal.common.events.EventProducer
import com.onesignal.common.exceptions.BackendException
@@ -35,6 +36,7 @@ import com.onesignal.notifications.internal.receivereceipt.IReceiveReceiptWorkMa
import com.onesignal.session.internal.influence.IInfluenceManager
import com.onesignal.user.internal.subscriptions.ISubscriptionManager
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import org.json.JSONArray
import org.json.JSONException
@@ -146,12 +148,7 @@ internal class NotificationLifecycleService(
useIO = true,
// or false for CPU operations
block = {
- _backend.updateNotificationAsOpened(
- appId,
- notificationId,
- subscriptionId,
- deviceType,
- )
+ confirmNotificationOpened(appId, notificationId, subscriptionId, deviceType)
},
onError = { ex ->
if (ex is BackendException) {
@@ -200,6 +197,35 @@ internal class NotificationLifecycleService(
extWillShowInForegroundCallback.fire { it.onWillDisplay(willDisplayEvent) }
}
+ private suspend fun confirmNotificationOpened(
+ appId: String,
+ notificationId: String,
+ subscriptionId: String,
+ deviceType: IDeviceService.DeviceType,
+ ) {
+ for (attempt in 1..NetworkUtils.maxNetworkRequestAttemptCount) {
+ try {
+ _backend.updateNotificationAsOpened(appId, notificationId, subscriptionId, deviceType)
+ return
+ } catch (ex: BackendException) {
+ val responseType = NetworkUtils.getResponseStatusType(ex.statusCode)
+ if (responseType != NetworkUtils.ResponseStatusType.RETRYABLE || attempt >= NetworkUtils.maxNetworkRequestAttemptCount) {
+ throw ex
+ }
+ val retryAfterMs = ex.retryAfterSeconds?.let { it * MILLIS_PER_SECOND } ?: 0L
+ val backoffMs = (attempt * RETRY_BACKOFF_MS).toLong()
+ val delayMs = maxOf(retryAfterMs, backoffMs)
+ Logging.info("Notification opened confirmation attempt $attempt failed (statusCode: ${ex.statusCode}), retrying in ${delayMs}ms")
+ delay(delayMs)
+ }
+ }
+ }
+
+ companion object {
+ private const val MILLIS_PER_SECOND = 1_000L
+ private const val RETRY_BACKOFF_MS = 15_000
+ }
+
/**
* In addition to using the setters to set all of the handlers you can also create your own implementation
* within a separate class and give your AndroidManifest.xml a special meta data tag