Coverage Summary for Class: InAppMessageDelayedManager (cloud.mindbox.mobile_sdk.inapp.presentation)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| InAppMessageDelayedManager |
85.7%
(6/7)
|
81.8%
(18/22)
|
96%
(48/50)
|
96.7%
(147/152)
|
| InAppMessageDelayedManager$clearSession$$inlined$launchWithLock$1 |
0%
(0/1)
|
|
| InAppMessageDelayedManager$Companion |
|
| InAppMessageDelayedManager$PendingInApp |
100%
(1/1)
|
|
100%
(5/5)
|
100%
(22/22)
|
| InAppMessageDelayedManager$process-FEttOiU$$inlined$launchWithLock$1 |
0%
(0/1)
|
|
| InAppMessageDelayedManager$processQueue$$inlined$launchWithLock$1 |
0%
(0/1)
|
|
| InAppMessageDelayedManager$scheduleNextProcess$1$1 |
100%
(1/1)
|
|
100%
(2/2)
|
100%
(15/15)
|
| InAppMessageDelayedManager$special$$inlined$compareBy$1 |
0%
(0/1)
|
|
| InAppMessageDelayedManager$special$$inlined$thenBy$1 |
0%
(0/1)
|
|
| InAppMessageDelayedManager$special$$inlined$thenByDescending$1 |
0%
(0/1)
|
|
| Total |
53.3%
(8/15)
|
81.8%
(18/22)
|
96.5%
(55/57)
|
97.4%
(184/189)
|
package cloud.mindbox.mobile_sdk.inapp.presentation
import cloud.mindbox.mobile_sdk.Mindbox
import cloud.mindbox.mobile_sdk.inapp.domain.models.InApp
import cloud.mindbox.mobile_sdk.logger.mindboxLogD
import cloud.mindbox.mobile_sdk.models.Milliseconds
import cloud.mindbox.mobile_sdk.logger.mindboxLogI
import cloud.mindbox.mobile_sdk.pollIf
import cloud.mindbox.mobile_sdk.utils.TimeProvider
import cloud.mindbox.mobile_sdk.utils.launchWithLock
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.sync.Mutex
import java.util.concurrent.PriorityBlockingQueue
import java.util.concurrent.atomic.AtomicLong
internal class InAppMessageDelayedManager(private val timeProvider: TimeProvider, dispatcher: CoroutineDispatcher) {
companion object {
private const val DEFAULT_INITIAL_CAPACITY = 2
}
private val sequenceNumber = AtomicLong(0)
private val coroutineScope = CoroutineScope(dispatcher + SupervisorJob() + Mindbox.coroutineExceptionHandler)
private var nextProcessQueueJob: Job? = null
private val processingMutex = Mutex()
private val pendingInAppComparator = compareBy<PendingInApp> { it.showTimeMillis }
.thenByDescending { it.inApp.isPriority }
.thenBy { it.sequenceNumber }
private val pendingInApps = PriorityBlockingQueue(
DEFAULT_INITIAL_CAPACITY,
pendingInAppComparator
)
private val _inAppToShowFlow = MutableSharedFlow<Pair<InApp, Milliseconds>>()
val inAppToShowFlow = _inAppToShowFlow.asSharedFlow()
private data class PendingInApp(
val inApp: InApp,
val showTimeMillis: Long,
val sequenceNumber: Long,
val preparedTimeMs: Milliseconds,
)
internal fun process(inApp: InApp, preparedTimeMs: Milliseconds) {
coroutineScope.launchWithLock(processingMutex) {
mindboxLogD("Processing In-App: ${inApp.id}, Priority: ${inApp.isPriority}, Delay: ${inApp.delayTime}")
val delay = inApp.delayTime?.interval ?: 0L
val showTime = timeProvider.currentTimeMillis() + delay
pendingInApps.put(
PendingInApp(
inApp = inApp,
showTimeMillis = showTime,
sequenceNumber = sequenceNumber.getAndIncrement(),
preparedTimeMs = preparedTimeMs,
)
)
processQueue()
}
}
internal fun onAppResumed() {
mindboxLogI("App resumed, re-evaluating scheduled In-Apps.")
processQueue()
}
private fun processQueue() {
if (pendingInApps.isEmpty()) return
coroutineScope.launchWithLock(processingMutex) {
nextProcessQueueJob?.cancel()
val now = timeProvider.currentTimeMillis()
pendingInApps.pollIf { it.showTimeMillis <= now }?.let { showCandidate ->
mindboxLogI("Winner found: ${showCandidate.inApp.id}. Emitting to show.")
_inAppToShowFlow.emit(showCandidate.inApp to showCandidate.preparedTimeMs)
do {
val inApp = pendingInApps.pollIf { it.showTimeMillis <= now }.also { discarded ->
mindboxLogI("Discarding other ready In-App: ${discarded?.inApp?.id}")
}
} while (inApp != null)
}
scheduleNextProcess()
}
}
private fun scheduleNextProcess() {
pendingInApps.peek()?.let { nextInApp ->
val now = timeProvider.currentTimeMillis()
val delay = (nextInApp.showTimeMillis - now).coerceAtLeast(0)
mindboxLogI("Scheduling next In-App ${nextInApp.inApp.id} with delay: $delay ms.")
nextProcessQueueJob = coroutineScope.launch {
delay(delay)
processQueue()
}
}
}
internal fun clearSession() {
coroutineScope.launchWithLock(processingMutex) {
nextProcessQueueJob?.cancel()
pendingInApps.clear()
}
}
}