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