Skip to content

Commit 6aee5c5

Browse files
Merge pull request #143 from mindbox-moscow/release-1.0.37
Release 1.0.37
2 parents 2dcd7e0 + 1d1d2e2 commit 6aee5c5

File tree

15 files changed

+279
-63
lines changed

15 files changed

+279
-63
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ android.enableJetifier=true
2121
kotlin.code.style=official
2222

2323
# SDK version property
24-
SDK_VERSION_NAME=1.0.36
24+
SDK_VERSION_NAME=1.0.37

sdk/build.gradle

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ dependencies {
117117

118118
// Firebase messaging
119119
implementation platform('com.google.firebase:firebase-bom:26.1.1')
120-
implementation 'com.google.firebase:firebase-analytics'
121120
implementation 'com.google.firebase:firebase-messaging'
122121
implementation 'com.google.android.gms:play-services-basement:17.5.0'
123122

@@ -142,4 +141,7 @@ dependencies {
142141
// dex
143142
implementation "androidx.multidex:multidex:2.0.1"
144143

144+
// Handle app lifecycle
145+
implementation "androidx.lifecycle:lifecycle-process:2.3.1"
146+
145147
}

sdk/src/main/java/cloud/mindbox/mobile_sdk/Mindbox.kt

Lines changed: 77 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package cloud.mindbox.mobile_sdk
22

3+
import android.app.Activity
34
import android.app.Application
45
import android.content.Context
6+
import android.content.Intent
7+
import androidx.lifecycle.Lifecycle.State.RESUMED
8+
import androidx.lifecycle.ProcessLifecycleOwner
59
import androidx.annotation.DrawableRes
610
import cloud.mindbox.mobile_sdk.logger.Level
711
import cloud.mindbox.mobile_sdk.logger.MindboxLogger
@@ -20,12 +24,18 @@ import java.util.concurrent.TimeUnit
2024

2125
object Mindbox {
2226

27+
/**
28+
* Used for determination app open from push
29+
*/
30+
const val IS_OPENED_FROM_PUSH_BUNDLE_KEY = "isOpenedFromPush"
31+
2332
private const val OPERATION_NAME_REGEX = "^[A-Za-z0-9-\\.]{1,249}\$"
2433

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

3040
/**
3141
* Subscribe to gets token of Firebase Messaging Service used by SDK
@@ -58,9 +68,9 @@ object Mindbox {
5868
/**
5969
* Returns date of FMS token saving
6070
*/
61-
fun getFmsTokenSaveDate(): String =
62-
runCatching { return MindboxPreferences.firebaseTokenSaveDate }
63-
.returnOnException { "" }
71+
fun getFmsTokenSaveDate(): String = runCatching {
72+
return MindboxPreferences.firebaseTokenSaveDate
73+
}.returnOnException { "" }
6474

6575
/**
6676
* Returns SDK version
@@ -189,24 +199,67 @@ object Mindbox {
189199
mindboxScope.launch {
190200
if (MindboxPreferences.isFirstInitialize) {
191201
firstInitialization(context, configuration)
202+
val isTrackVisitNotSent = Mindbox::lifecycleManager.isInitialized
203+
&& !lifecycleManager.isTrackVisitSent()
204+
if (isTrackVisitNotSent) {
205+
sendTrackVisitEvent(context, DIRECT)
206+
}
192207
} else {
193208
updateAppInfo(context)
194209
MindboxEventManager.sendEventsIfExist(context)
195210
}
196-
sendTrackVisitEvent(context, configuration.endpointId)
211+
}
197212

198-
// Handle back app in foreground
199-
val lifecycleManager = LifecycleManager {
200-
runBlocking(Dispatchers.IO) {
201-
sendTrackVisitEvent(context, configuration.endpointId)
213+
// Handle back app in foreground
214+
(context.applicationContext as? Application)?.apply {
215+
val applicationLifecycle = ProcessLifecycleOwner.get().lifecycle
216+
217+
if (!Mindbox::lifecycleManager.isInitialized) {
218+
val activity = context as? Activity
219+
val isApplicationResumed = applicationLifecycle.currentState == RESUMED
220+
if (isApplicationResumed && activity == null) {
221+
MindboxLogger.e(
222+
this@Mindbox,
223+
"Incorrect context type for calling init in this place"
224+
)
202225
}
226+
227+
lifecycleManager = LifecycleManager(
228+
currentActivityName = activity?.javaClass?.name,
229+
currentIntent = activity?.intent,
230+
isAppInBackground = !isApplicationResumed,
231+
onTrackVisitReady = { source, requestUrl ->
232+
runBlocking(Dispatchers.IO) {
233+
sendTrackVisitEvent(context, source, requestUrl)
234+
}
235+
}
236+
)
237+
} else {
238+
unregisterActivityLifecycleCallbacks(lifecycleManager)
239+
applicationLifecycle.removeObserver(lifecycleManager)
240+
lifecycleManager.wasReinitialized()
203241
}
204-
(context.applicationContext as? Application)
205-
?.registerActivityLifecycleCallbacks(lifecycleManager)
242+
243+
registerActivityLifecycleCallbacks(lifecycleManager)
244+
applicationLifecycle.addObserver(lifecycleManager)
206245
}
207246
}.returnOnException { }
208247
}
209248

249+
/**
250+
* Send track visit event after link or push was clicked for [Activity] with launchMode equals
251+
* "singleTop" or "singleTask" or if a client used the [Intent.FLAG_ACTIVITY_SINGLE_TOP] or
252+
* [Intent.FLAG_ACTIVITY_NEW_TASK]
253+
* flag when calling {@link #startActivity}.
254+
*
255+
* @param intent new intent for activity, which was received in [Activity.onNewIntent] method
256+
*/
257+
fun onNewIntent(intent: Intent?) = runCatching {
258+
if (Mindbox::lifecycleManager.isInitialized) {
259+
lifecycleManager.onNewIntent(intent)
260+
}
261+
}.logOnException()
262+
210263
/**
211264
* Specifies log level for Mindbox
212265
*
@@ -367,14 +420,22 @@ object Mindbox {
367420
}.logOnException()
368421
}
369422

370-
private fun sendTrackVisitEvent(context: Context, endpointId: String) {
423+
private fun sendTrackVisitEvent(
424+
context: Context,
425+
@TrackVisitSource source: String? = null,
426+
requestUrl: String? = null
427+
) = runCatching {
428+
val applicationContext = context.applicationContext
429+
val endpointId = DbManager.getConfigurations()?.endpointId ?: return
371430
val trackVisitData = TrackVisitData(
372431
ianaTimeZone = TimeZone.getDefault().id,
373-
endpointId = endpointId
432+
endpointId = endpointId,
433+
source = source,
434+
requestUrl = requestUrl
374435
)
375436

376-
MindboxEventManager.appStarted(context, trackVisitData)
377-
}
437+
MindboxEventManager.appStarted(applicationContext, trackVisitData)
438+
}.logOnException()
378439

379440
private fun deliverDeviceUuid(deviceUuid: String) {
380441
Executors.newSingleThreadScheduledExecutor().schedule({
@@ -393,4 +454,5 @@ object Mindbox {
393454
}
394455
}, 1, TimeUnit.SECONDS)
395456
}
396-
}
457+
458+
}

sdk/src/main/java/cloud/mindbox/mobile_sdk/managers/LifecycleManager.kt

Lines changed: 133 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,66 @@ package cloud.mindbox.mobile_sdk.managers
22

33
import android.app.Activity
44
import android.app.Application
5+
import android.content.Intent
56
import android.os.Bundle
7+
import androidx.lifecycle.Lifecycle
8+
import androidx.lifecycle.LifecycleObserver
9+
import androidx.lifecycle.OnLifecycleEvent
10+
import cloud.mindbox.mobile_sdk.Mindbox.IS_OPENED_FROM_PUSH_BUNDLE_KEY
11+
import cloud.mindbox.mobile_sdk.logOnException
12+
import cloud.mindbox.mobile_sdk.logger.MindboxLogger
13+
import cloud.mindbox.mobile_sdk.models.DIRECT
14+
import cloud.mindbox.mobile_sdk.models.LINK
15+
import cloud.mindbox.mobile_sdk.models.PUSH
16+
import cloud.mindbox.mobile_sdk.returnOnException
17+
import java.util.*
18+
import kotlin.concurrent.timer
619

720
internal class LifecycleManager(
8-
private val onAppStarted: () -> Unit
9-
) : Application.ActivityLifecycleCallbacks {
21+
private var currentActivityName: String?,
22+
private var currentIntent: Intent?,
23+
private var onTrackVisitReady: (source: String?, requestUrl: String?) -> Unit,
24+
private var isAppInBackground: Boolean
25+
) : Application.ActivityLifecycleCallbacks, LifecycleObserver {
1026

11-
private var currentActivity: Activity? = null
27+
companion object {
28+
29+
private const val SCHEMA_HTTP = "http"
30+
private const val SCHEMA_HTTPS = "https"
31+
32+
private const val TIMER_PERIOD = 1200000L
33+
private const val MAX_INTENT_HASHES_SIZE = 50
34+
35+
}
36+
37+
private var isIntentChanged = true
38+
private var timer: Timer? = null
39+
private val intentHashes = mutableListOf<Int>()
40+
41+
private var skipSendingTrackVisit = false
1242

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

1545
}
1646

1747
override fun onActivityStarted(activity: Activity) {
18-
if (currentActivity?.javaClass?.name == activity.javaClass.name) {
19-
onAppStarted()
20-
} else {
21-
currentActivity = activity
22-
}
48+
runCatching {
49+
val areActivitiesEqual = currentActivityName == activity.javaClass.name
50+
val intent = activity.intent
51+
isIntentChanged = if (currentIntent != intent) {
52+
updateActivityParameters(activity)
53+
intent?.hashCode()?.let(::updateHashesList) ?: true
54+
} else {
55+
false
56+
}
57+
58+
if (isAppInBackground || !isIntentChanged) {
59+
isAppInBackground = false
60+
return
61+
}
62+
63+
sendTrackVisit(activity.intent, areActivitiesEqual)
64+
}.logOnException()
2365
}
2466

2567
override fun onActivityResumed(activity: Activity) {
@@ -31,8 +73,8 @@ internal class LifecycleManager(
3173
}
3274

3375
override fun onActivityStopped(activity: Activity) {
34-
if (currentActivity == null) {
35-
currentActivity = activity
76+
if (currentIntent == null || currentActivityName == null) {
77+
updateActivityParameters(activity)
3678
}
3779
}
3880

@@ -44,4 +86,85 @@ internal class LifecycleManager(
4486

4587
}
4688

89+
fun isTrackVisitSent(): Boolean {
90+
currentIntent?.let(::sendTrackVisit)
91+
return currentIntent != null
92+
}
93+
94+
fun wasReinitialized() {
95+
skipSendingTrackVisit = true
96+
}
97+
98+
fun onNewIntent(newIntent: Intent?) = newIntent?.let { intent ->
99+
if (intent.data != null || intent.extras?.get(IS_OPENED_FROM_PUSH_BUNDLE_KEY) == true) {
100+
isIntentChanged = updateHashesList(intent.hashCode())
101+
sendTrackVisit(intent)
102+
skipSendingTrackVisit = isAppInBackground
103+
}
104+
}
105+
106+
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
107+
private fun onAppMovedToBackground() {
108+
isAppInBackground = true
109+
cancelKeepAliveTimer()
110+
}
111+
112+
@OnLifecycleEvent(Lifecycle.Event.ON_START)
113+
private fun onAppMovedToForeground() = if (!skipSendingTrackVisit) {
114+
currentIntent?.let(::sendTrackVisit)
115+
} else {
116+
skipSendingTrackVisit = false
117+
}
118+
119+
private fun updateActivityParameters(activity: Activity) = runCatching {
120+
currentActivityName = activity.javaClass.name
121+
currentIntent = activity.intent
122+
}.logOnException()
123+
124+
private fun sendTrackVisit(intent: Intent, areActivitiesEqual: Boolean = true) = runCatching {
125+
val source = if (isIntentChanged) source(intent) else DIRECT
126+
127+
if (areActivitiesEqual || source != DIRECT) {
128+
val requestUrl = if (source == LINK) intent.data?.toString() else null
129+
onTrackVisitReady.invoke(source, requestUrl)
130+
startKeepAliveTimer()
131+
132+
MindboxLogger.d(this, "Track visit event with source $source and url $requestUrl")
133+
}
134+
}.logOnException()
135+
136+
private fun source(intent: Intent?) = runCatching {
137+
when {
138+
intent?.scheme == SCHEMA_HTTP || intent?.scheme == SCHEMA_HTTPS -> LINK
139+
intent?.extras?.getBoolean(IS_OPENED_FROM_PUSH_BUNDLE_KEY) == true -> PUSH
140+
else -> DIRECT
141+
}
142+
}.returnOnException { null }
143+
144+
private fun updateHashesList(code: Int) = runCatching {
145+
if (!intentHashes.contains(code)) {
146+
if (intentHashes.size >= MAX_INTENT_HASHES_SIZE) {
147+
intentHashes.removeAt(0)
148+
}
149+
intentHashes.add(code)
150+
true
151+
} else {
152+
false
153+
}
154+
}.returnOnException { true }
155+
156+
private fun startKeepAliveTimer() = runCatching {
157+
cancelKeepAliveTimer()
158+
timer = timer(
159+
initialDelay = TIMER_PERIOD,
160+
period = TIMER_PERIOD,
161+
action = { onTrackVisitReady.invoke(null, null) }
162+
)
163+
}.logOnException()
164+
165+
private fun cancelKeepAliveTimer() = runCatching {
166+
timer?.cancel()
167+
timer = null
168+
}.logOnException()
169+
47170
}

sdk/src/main/java/cloud/mindbox/mobile_sdk/managers/PushNotificationManager.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cloud.mindbox.mobile_sdk.managers
22

33
import android.app.Notification.DEFAULT_ALL
4+
import android.app.Notification.VISIBILITY_PRIVATE
45
import android.app.NotificationChannel
56
import android.app.NotificationManager
67
import android.app.PendingIntent
@@ -59,6 +60,7 @@ internal object PushNotificationManager {
5960
.setPriority(NotificationCompat.PRIORITY_HIGH)
6061
.setDefaults(DEFAULT_ALL)
6162
.setAutoCancel(true)
63+
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
6264
.handlePushClick(context, notificationId, uniqueKey)
6365
.handleActions(context, notificationId, uniqueKey, pushActions)
6466
.handleImageByUrl(data[DATA_IMAGE_URL], title, description)
@@ -82,6 +84,7 @@ internal object PushNotificationManager {
8284
val importance = NotificationManager.IMPORTANCE_HIGH
8385
val channel = NotificationChannel(channelId, channelName, importance).apply {
8486
channelDescription.let { description = it }
87+
lockscreenVisibility = VISIBILITY_PRIVATE
8588
}
8689

8790
notificationManager.createNotificationChannel(channel)

0 commit comments

Comments
 (0)