Coverage Summary for Class: InAppMessageManagerImpl (cloud.mindbox.mobile_sdk.inapp.presentation)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| InAppMessageManagerImpl |
40%
(6/15)
|
75%
(6/8)
|
71.7%
(33/46)
|
61.1%
(132/216)
|
| InAppMessageManagerImpl$1 |
0%
(0/1)
|
|
0%
(0/2)
|
0%
(0/7)
|
| InAppMessageManagerImpl$Companion |
|
| InAppMessageManagerImpl$handleInAppFromDelayedManager$1 |
|
| InAppMessageManagerImpl$handleInAppFromDelayedManager$2 |
100%
(1/1)
|
|
100%
(3/3)
|
100%
(33/33)
|
| InAppMessageManagerImpl$handleInAppFromDelayedManager$2$1 |
100%
(1/1)
|
60%
(6/10)
|
91.7%
(11/12)
|
87.2%
(82/94)
|
| InAppMessageManagerImpl$handleInAppFromDelayedManager$2$1$1 |
25%
(1/4)
|
|
57.1%
(4/7)
|
66.7%
(30/45)
|
| InAppMessageManagerImpl$handleInAppFromDelayedManager$2$1$2 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/7)
|
| InAppMessageManagerImpl$handleInAppFromInteractor$1 |
|
| InAppMessageManagerImpl$handleInAppFromInteractor$2 |
100%
(1/1)
|
|
100%
(3/3)
|
100%
(26/26)
|
| InAppMessageManagerImpl$handleSessionExpiration$1 |
0%
(0/1)
|
0%
(0/2)
|
0%
(0/11)
|
0%
(0/81)
|
| InAppMessageManagerImpl$handleSessionExpiration$1$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/4)
|
| InAppMessageManagerImpl$listenEventAndInApp$1 |
100%
(1/1)
|
|
100%
(3/3)
|
100%
(42/42)
|
| InAppMessageManagerImpl$listenEventAndInApp$1$1 |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(12/12)
|
| InAppMessageManagerImpl$listenEventAndInApp$1$2 |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(11/11)
|
| InAppMessageManagerImpl$listenEventAndInApp$1$3 |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(11/11)
|
| InAppMessageManagerImpl$onPauseCurrentActivity$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/6)
|
| InAppMessageManagerImpl$onResumeCurrentActivity$1 |
0%
(0/1)
|
|
0%
(0/2)
|
0%
(0/18)
|
| InAppMessageManagerImpl$onResumeCurrentActivity$1$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/6)
|
| InAppMessageManagerImpl$onResumeCurrentActivity$1$2 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/4)
|
| InAppMessageManagerImpl$onStopCurrentActivity$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/6)
|
| InAppMessageManagerImpl$registerCurrentActivity$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/6)
|
| InAppMessageManagerImpl$registerInAppCallback$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/6)
|
| InAppMessageManagerImpl$requestConfig$$inlined$CoroutineExceptionHandler$1 |
0%
(0/2)
|
|
| InAppMessageManagerImpl$requestConfig$2 |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(12/12)
|
| InAppMessageManagerImpl$unregisterInAppCallback$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/4)
|
| Total |
36.6%
(15/41)
|
60%
(12/20)
|
59.8%
(61/102)
|
59.5%
(391/657)
|
package cloud.mindbox.mobile_sdk.inapp.presentation
import android.app.Activity
import cloud.mindbox.mobile_sdk.InitializeLock
import cloud.mindbox.mobile_sdk.Mindbox
import cloud.mindbox.mobile_sdk.inapp.data.managers.SessionStorageManager
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.InAppActionCallbacks
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.interactors.InAppInteractor
import cloud.mindbox.mobile_sdk.inapp.domain.models.InAppType
import cloud.mindbox.mobile_sdk.inapp.domain.models.OnInAppClick
import cloud.mindbox.mobile_sdk.inapp.domain.models.OnInAppDismiss
import cloud.mindbox.mobile_sdk.inapp.domain.models.OnInAppShown
import cloud.mindbox.mobile_sdk.logger.MindboxLoggerImpl
import cloud.mindbox.mobile_sdk.logger.mindboxLogI
import cloud.mindbox.mobile_sdk.managers.MindboxEventManager
import cloud.mindbox.mobile_sdk.managers.UserVisitManager
import cloud.mindbox.mobile_sdk.millisToTimeSpan
import cloud.mindbox.mobile_sdk.models.Milliseconds
import cloud.mindbox.mobile_sdk.models.Timestamp
import cloud.mindbox.mobile_sdk.monitoring.domain.interfaces.MonitoringInteractor
import cloud.mindbox.mobile_sdk.repository.MindboxPreferences
import cloud.mindbox.mobile_sdk.utils.TimeProvider
import cloud.mindbox.mobile_sdk.utils.loggingRunCatching
import com.android.volley.VolleyError
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach
internal class InAppMessageManagerImpl(
private val inAppMessageViewDisplayer: InAppMessageViewDisplayer,
private val inAppInteractor: InAppInteractor,
private val defaultDispatcher: CoroutineDispatcher,
private val monitoringInteractor: MonitoringInteractor,
private val sessionStorageManager: SessionStorageManager,
private val userVisitManager: UserVisitManager,
private val inAppMessageDelayedManager: InAppMessageDelayedManager,
private val timeProvider: TimeProvider
) : InAppMessageManager {
init {
sessionStorageManager.addSessionExpirationListener {
mindboxLogI("Start a new session now!")
handleSessionExpiration()
}
}
private var processingJob: Job? = null
private val inAppScope =
CoroutineScope(defaultDispatcher + SupervisorJob() + Mindbox.coroutineExceptionHandler)
override fun listenEventAndInApp() {
processingJob = inAppScope.launch {
launch {
inAppInteractor.listenToTargetingEvents()
}
launch {
handleInAppFromInteractor()
}
launch {
handleInAppFromDelayedManager()
}
}
}
private suspend fun handleInAppFromInteractor() {
inAppInteractor.processEventAndConfig()
.onEach { (inApp, preparedTimeMs) ->
mindboxLogI("Got in-app from interactor: ${inApp.id}. Processing with DelayedManager.")
inAppMessageDelayedManager.process(inApp, preparedTimeMs)
}
.collect()
}
private suspend fun handleInAppFromDelayedManager() {
inAppMessageDelayedManager.inAppToShowFlow.collect { (inApp, preparedTimeMs) ->
mindboxLogI("Got in-app from DelayedManager: ${inApp.id}")
withContext(Dispatchers.Main) {
if (inAppMessageViewDisplayer.isInAppActive()) {
mindboxLogI("InApp is active. Skip ${inApp.id}")
return@withContext
}
if (!inAppInteractor.areShowAndFrequencyLimitsAllowed(inApp)) {
mindboxLogI("InApp ${inApp.id} failed final show-limits and frequency check. Skipping.")
return@withContext
}
val inAppMessage = inApp.form.variants.firstOrNull()
if (inAppMessage == null) {
mindboxLogI("InApp ${inApp.id} has no variants to show. Skipping.")
return@withContext
}
var renderStartTime = Timestamp(0L)
val tags = inApp.tags?.takeIf { it.isNotEmpty() }
inAppMessageViewDisplayer.tryShowInAppMessage(
inAppType = inAppMessage,
onRenderStart = { renderStartTime = timeProvider.currentTimestamp() },
inAppActionCallbacks = object : InAppActionCallbacks {
override val onInAppClick = OnInAppClick {
inAppInteractor.sendInAppClicked(inAppMessage.inAppId)
}
override val onInAppShown = OnInAppShown {
handleInAppShown(renderStartTime, preparedTimeMs, inAppMessage, tags)
}
override val onInAppDismiss = OnInAppDismiss {
inAppInteractor.saveInAppDismissTime()
}
}
)
}
}
}
/**
* In case of 404 clear config
* In case of other network error use cached version
* Otherwise do nothing
**/
override fun requestConfig(): Job {
return inAppScope.launch(CoroutineExceptionHandler { _, error ->
if (error is VolleyError) {
when (error.networkResponse?.statusCode) {
CONFIG_NOT_FOUND -> {
MindboxLoggerImpl.w(InAppMessageManagerImpl, "Config not found", error)
MindboxPreferences.inAppConfig = ""
}
else -> {
sessionStorageManager.configFetchingError = true
// needed to trigger flow event
MindboxPreferences.inAppConfig = MindboxPreferences.inAppConfig
MindboxLoggerImpl.e(InAppMessageManagerImpl, "Failed to get config", error)
}
}
} else {
MindboxPreferences.inAppConfig = MindboxPreferences.inAppConfig
MindboxLoggerImpl.e(
this@InAppMessageManagerImpl::class,
"Failed to get config",
error
)
}
}) {
inAppInteractor.fetchMobileConfig()
}
}
override fun initLogs() {
monitoringInteractor.processLogs()
}
override fun registerInAppCallback(inAppCallback: InAppCallback) = loggingRunCatching {
inAppMessageViewDisplayer.registerInAppCallback(inAppCallback)
}
override fun unregisterInAppCallback(): Unit = loggingRunCatching {
inAppMessageViewDisplayer.unregisterInAppCallback()
}
override fun registerCurrentActivity(activity: Activity): Unit = loggingRunCatching {
inAppMessageViewDisplayer.registerCurrentActivity(activity)
}
override fun onPauseCurrentActivity(activity: Activity): Unit = loggingRunCatching {
inAppMessageViewDisplayer.onPauseCurrentActivity(activity)
}
override fun onStopCurrentActivity(activity: Activity): Unit = loggingRunCatching {
inAppMessageViewDisplayer.onStopCurrentActivity(activity)
}
override fun onResumeCurrentActivity(activity: Activity): Unit = loggingRunCatching {
inAppMessageViewDisplayer.onResumeCurrentActivity(
activity = activity,
isNeedToShow = { !sessionStorageManager.isSessionExpiredOnLastCheck() },
onAppResumed = { inAppMessageDelayedManager.onAppResumed() }
)
}
override fun handleSessionExpiration() {
inAppScope.launch {
withContext(Dispatchers.Main) {
inAppMessageViewDisplayer.dismissCurrentInApp()
}
processingJob?.cancel()
inAppInteractor.resetInAppConfigAndEvents()
sessionStorageManager.clearSessionData()
userVisitManager.saveUserVisit()
inAppMessageDelayedManager.clearSession()
InitializeLock.reset(InitializeLock.State.APP_STARTED)
listenEventAndInApp()
initLogs()
MindboxEventManager.eventFlow.emit(MindboxEventManager.appStarted())
requestConfig().join()
}
}
private fun handleInAppShown(
renderStartTime: Timestamp,
preparedTimeMs: Milliseconds,
inAppMessage: InAppType,
tags: Map<String, String>?
) {
val shownTime = timeProvider.currentTimestamp()
val renderTime = shownTime - renderStartTime
mindboxLogI("Render time is ${renderTime.ms}ms, prepared time is ${preparedTimeMs.interval}ms")
val timeToDisplay = (preparedTimeMs.interval + renderTime.ms).millisToTimeSpan()
inAppInteractor.saveShownInApp(inAppMessage.inAppId, shownTime.ms, timeToDisplay, tags)
}
companion object {
const val CONFIG_NOT_FOUND = 404
}
}