Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,7 @@ dependencies {
// dex
implementation "androidx.multidex:multidex:2.0.1"

// Handle app lifecycle
implementation "androidx.lifecycle:lifecycle-process:2.3.1"

}
70 changes: 55 additions & 15 deletions sdk/src/main/java/cloud/mindbox/mobile_sdk/Mindbox.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package cloud.mindbox.mobile_sdk

import android.app.Activity
import android.app.Application
import android.content.Context
import androidx.lifecycle.Lifecycle.State.RESUMED
import androidx.lifecycle.ProcessLifecycleOwner
import cloud.mindbox.mobile_sdk.logger.Level
import cloud.mindbox.mobile_sdk.logger.MindboxLogger
import cloud.mindbox.mobile_sdk.managers.*
Expand All @@ -17,12 +20,18 @@ import java.util.concurrent.TimeUnit

object Mindbox {

/**
* Used for determination app open from push
*/
const val IS_OPENED_FROM_PUSH_BUNDLE_KEY = "isOpenedFromPush"

private const val OPERATION_NAME_REGEX = "^[A-Za-z0-9-\\.]{1,249}\$"

private val mindboxJob = Job()
private val mindboxScope = CoroutineScope(Default + mindboxJob)
private val deviceUuidCallbacks = ConcurrentHashMap<String, (String) -> Unit>()
private val fmsTokenCallbacks = ConcurrentHashMap<String, (String?) -> Unit>()
private lateinit var lifecycleManager: LifecycleManager

/**
* Subscribe to gets token of Firebase Messaging Service used by SDK
Expand Down Expand Up @@ -55,9 +64,9 @@ object Mindbox {
/**
* Returns date of FMS token saving
*/
fun getFmsTokenSaveDate(): String =
runCatching { return MindboxPreferences.firebaseTokenSaveDate }
.returnOnException { "" }
fun getFmsTokenSaveDate(): String = runCatching {
return MindboxPreferences.firebaseTokenSaveDate
}.returnOnException { "" }

/**
* Returns SDK version
Expand Down Expand Up @@ -186,20 +195,42 @@ object Mindbox {
mindboxScope.launch {
if (MindboxPreferences.isFirstInitialize) {
firstInitialization(context, configuration)
sendTrackVisitEvent(context)
} else {
updateAppInfo(context)
MindboxEventManager.sendEventsIfExist(context)
}
sendTrackVisitEvent(context, configuration.endpointId)
}

// Handle back app in foreground
(context.applicationContext as? Application)?.apply {
val applicationLifecycle = ProcessLifecycleOwner.get().lifecycle

// Handle back app in foreground
val lifecycleManager = LifecycleManager {
runBlocking(Dispatchers.IO) {
sendTrackVisitEvent(context, configuration.endpointId)
if (!Mindbox::lifecycleManager.isInitialized) {
val activity = context as? Activity
if (applicationLifecycle.currentState == RESUMED && activity == null) {
MindboxLogger.e(
this@Mindbox,
"Incorrect context type for calling init in this place"
)
}

lifecycleManager = LifecycleManager(
currentActivityName = activity?.javaClass?.name,
currentIntent = activity?.intent,
onTrackVisitReady = { source, requestUrl ->
runBlocking(Dispatchers.IO) {
sendTrackVisitEvent(context, source, requestUrl)
}
}
)
} else {
unregisterActivityLifecycleCallbacks(lifecycleManager)
applicationLifecycle.removeObserver(lifecycleManager)
}
(context.applicationContext as? Application)
?.registerActivityLifecycleCallbacks(lifecycleManager)

registerActivityLifecycleCallbacks(lifecycleManager)
applicationLifecycle.addObserver(lifecycleManager)
}
}.returnOnException { }
}
Expand Down Expand Up @@ -314,14 +345,22 @@ object Mindbox {
}.logOnException()
}

private fun sendTrackVisitEvent(context: Context, endpointId: String) {
private fun sendTrackVisitEvent(
context: Context,
@TrackVisitSource source: String? = null,
requestUrl: String? = null
) = runCatching {
val applicationContext = context.applicationContext
val endpointId = DbManager.getConfigurations()?.endpointId ?: return
val trackVisitData = TrackVisitData(
ianaTimeZone = TimeZone.getDefault().id,
endpointId = endpointId
endpointId = endpointId,
source = source,
requestUrl = requestUrl
)

MindboxEventManager.appStarted(context, trackVisitData)
}
MindboxEventManager.appStarted(applicationContext, trackVisitData)
}.logOnException()

private fun deliverDeviceUuid(deviceUuid: String) {
Executors.newSingleThreadScheduledExecutor().schedule({
Expand All @@ -340,4 +379,5 @@ object Mindbox {
}
}, 1, TimeUnit.SECONDS)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,59 @@ package cloud.mindbox.mobile_sdk.managers

import android.app.Activity
import android.app.Application
import android.content.Intent
import android.os.Bundle
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import cloud.mindbox.mobile_sdk.Mindbox.IS_OPENED_FROM_PUSH_BUNDLE_KEY
import cloud.mindbox.mobile_sdk.logOnException
import cloud.mindbox.mobile_sdk.logger.MindboxLogger
import cloud.mindbox.mobile_sdk.models.DIRECT
import cloud.mindbox.mobile_sdk.models.LINK
import cloud.mindbox.mobile_sdk.models.PUSH
import cloud.mindbox.mobile_sdk.returnOnException

internal class LifecycleManager(
private val onAppStarted: () -> Unit
) : Application.ActivityLifecycleCallbacks {
private var currentActivityName: String?,
private var currentIntent: Intent?,
private var onTrackVisitReady: (source: String?, requestUrl: String?) -> Unit
) : Application.ActivityLifecycleCallbacks, LifecycleObserver {

private var currentActivity: Activity? = null
companion object {

private const val SCHEMA_HTTP = "http"
private const val SCHEMA_HTTPS = "https"
private const val MAX_INTENT_HASHES_SIZE = 50

}

private var isAppInBackground = true
private var isIntentChanged = true
private val intentHashes = mutableListOf<Int>()

override fun onActivityCreated(activity: Activity, p1: Bundle?) {

}

override fun onActivityStarted(activity: Activity) {
if (currentActivity?.javaClass?.name == activity.javaClass.name) {
onAppStarted()
} else {
currentActivity = activity
}
runCatching {
val areActivitiesEqual = currentActivityName == activity.javaClass.name
val intent = activity.intent
isIntentChanged = if (currentIntent != intent) {
updateActivityParameters(activity)
intent?.hashCode()?.let(::updateHashesList) ?: true
} else {
false
}

if (isAppInBackground || !isIntentChanged) {
isAppInBackground = false
return
}

sendTrackVisit(activity.intent, areActivitiesEqual)
}.logOnException()
}

override fun onActivityResumed(activity: Activity) {
Expand All @@ -31,8 +66,8 @@ internal class LifecycleManager(
}

override fun onActivityStopped(activity: Activity) {
if (currentActivity == null) {
currentActivity = activity
if (currentIntent == null || currentActivityName == null) {
updateActivityParameters(activity)
}
}

Expand All @@ -44,4 +79,48 @@ internal class LifecycleManager(

}

@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
private fun onAppMovedToBackground() {
isAppInBackground = true
}

@OnLifecycleEvent(Lifecycle.Event.ON_START)
private fun onAppMovedToForeground() = currentIntent?.let(::sendTrackVisit)

private fun updateActivityParameters(activity: Activity) = runCatching {
currentActivityName = activity.javaClass.name
currentIntent = activity.intent
}.logOnException()

private fun sendTrackVisit(intent: Intent, areActivitiesEqual: Boolean = true) = runCatching {
val source = if (isIntentChanged) source(intent) else DIRECT

if (areActivitiesEqual || source != DIRECT) {
val requestUrl = if (source == LINK) intent.data?.toString() else null
onTrackVisitReady.invoke(source, requestUrl)

MindboxLogger.d(this, "Track visit event with source $source and url $requestUrl")
}
}.logOnException()

private fun source(intent: Intent?) = runCatching {
when {
intent?.scheme == SCHEMA_HTTP || intent?.scheme == SCHEMA_HTTPS -> LINK
intent?.extras?.getBoolean(IS_OPENED_FROM_PUSH_BUNDLE_KEY) == true -> PUSH
else -> DIRECT
}
}.returnOnException { null }

private fun updateHashesList(code: Int) = runCatching {
if (!intentHashes.contains(code)) {
if (intentHashes.size >= MAX_INTENT_HASHES_SIZE) {
intentHashes.removeAt(0)
}
intentHashes.add(code)
true
} else {
false
}
}.returnOnException { true }

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package cloud.mindbox.mobile_sdk.managers
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import cloud.mindbox.mobile_sdk.logOnException
import cloud.mindbox.mobile_sdk.returnOnException

internal object SharedPreferencesManager {

Expand Down Expand Up @@ -39,7 +41,7 @@ internal object SharedPreferencesManager {
fun put(
key: String,
value: String?
) = preferences.edit().putString(key, value).apply()
) = runCatching { preferences.edit().putString(key, value).apply() }.logOnException()

/**
* Saves [Boolean] into the Preferences.
Expand All @@ -50,7 +52,7 @@ internal object SharedPreferencesManager {
fun put(
key: String,
value: Boolean
) = preferences.edit().putBoolean(key, value).apply()
) = runCatching { preferences.edit().putBoolean(key, value).apply() }.logOnException()

/**
* Saves [Int] into the Preferences.
Expand All @@ -61,7 +63,7 @@ internal object SharedPreferencesManager {
fun put(
key: String,
value: Int
) = preferences.edit().putInt(key, value).apply()
) = runCatching { preferences.edit().putInt(key, value).apply() }.logOnException()

/**
* Used to retrieve [String] object from the Preferences.
Expand All @@ -73,7 +75,9 @@ internal object SharedPreferencesManager {
fun getString(
key: String,
defaultValue: String? = null
): String? = preferences.getString(key, defaultValue)
): String? = runCatching {
preferences.getString(key, defaultValue)
}.returnOnException { defaultValue }

/**
* Used to retrieve [Boolean] object from the Preferences.
Expand All @@ -85,7 +89,9 @@ internal object SharedPreferencesManager {
fun getBoolean(
key: String,
defaultValue: Boolean = false
): Boolean = preferences.getBoolean(key, defaultValue)
): Boolean = runCatching {
preferences.getBoolean(key, defaultValue)
}.returnOnException { defaultValue }

/**
* Used to retrieve [Int] object from the Preferences.
Expand All @@ -97,8 +103,12 @@ internal object SharedPreferencesManager {
fun getInt(
key: String,
defaultValue: Int = DEFAULT_INT_VALUE
): Int = preferences.getInt(key, defaultValue)
): Int = runCatching {
preferences.getInt(key, defaultValue)
}.returnOnException { defaultValue }

internal fun deleteAll() = preferences.edit().clear().apply()
internal fun deleteAll() = runCatching {
preferences.edit().clear().apply()
}.exceptionOrNull()

}
13 changes: 12 additions & 1 deletion sdk/src/main/java/cloud/mindbox/mobile_sdk/models/InitData.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package cloud.mindbox.mobile_sdk.models

import androidx.annotation.StringDef

private const val INIT_DATA_VERSION = 0

internal const val DIRECT = "direct"
internal const val LINK = "link"
internal const val PUSH = "push"

internal data class InitData(
val token: String,
val isTokenAvailable: Boolean,
Expand All @@ -28,5 +34,10 @@ internal data class TrackClickData(

internal data class TrackVisitData(
val ianaTimeZone: String,
val endpointId: String
val endpointId: String,
@TrackVisitSource val source: String? = null,
val requestUrl: String? = null
)

@StringDef(DIRECT, LINK, PUSH)
internal annotation class TrackVisitSource