diff --git a/CHANGELOG b/CHANGELOG
index 92fcbe49e..026fa8223 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+Version 6.0.2 (2024-02-28)
+--------------------------
+Fix createTracker call hanging for 10 seconds if run on a background thread (#620)
+
Version 6.0.1 (2024-02-14)
--------------------------
Fix wrong screen entity info assigned for screen end event and in case events are tracked right before screen view (#673)
diff --git a/VERSION b/VERSION
index 5fe607230..9b9a24420 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-6.0.1
+6.0.2
diff --git a/build.gradle b/build.gradle
index c9c25e84b..a957a6ea9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -22,7 +22,7 @@ plugins {
subprojects {
group = 'com.snowplowanalytics'
- version = '6.0.1'
+ version = '6.0.2'
repositories {
google()
maven {
diff --git a/gradle.properties b/gradle.properties
index 88c8c9605..c2eab7516 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -31,7 +31,7 @@ systemProp.org.gradle.internal.http.socketTimeout=120000
SONATYPE_STAGING_PROFILE=comsnowplowanalytics
GROUP=com.snowplowanalytics
POM_ARTIFACT_ID=snowplow-android-tracker
-VERSION_NAME=6.0.1
+VERSION_NAME=6.0.2
POM_NAME=snowplow-android-tracker
POM_PACKAGING=aar
diff --git a/snowplow-demo-kotlin/build.gradle b/snowplow-demo-kotlin/build.gradle
index 90cbc86b4..baaee1a9d 100644
--- a/snowplow-demo-kotlin/build.gradle
+++ b/snowplow-demo-kotlin/build.gradle
@@ -43,4 +43,5 @@ dependencies {
implementation 'androidx.browser:browser:1.5.0'
implementation 'com.google.android.gms:play-services-appset:16.0.2'
implementation "com.android.installreferrer:installreferrer:2.2"
+ implementation "com.google.android.gms:play-services-ads:22.6.0"
}
diff --git a/snowplow-demo-kotlin/src/main/AndroidManifest.xml b/snowplow-demo-kotlin/src/main/AndroidManifest.xml
index 63d5ec095..70a9010b6 100644
--- a/snowplow-demo-kotlin/src/main/AndroidManifest.xml
+++ b/snowplow-demo-kotlin/src/main/AndroidManifest.xml
@@ -6,6 +6,7 @@
+
+
+
diff --git a/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/event/ApplicationInstallEventTest.kt b/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/event/ApplicationInstallEventTest.kt
index eb09f15b6..a776f6d41 100644
--- a/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/event/ApplicationInstallEventTest.kt
+++ b/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/event/ApplicationInstallEventTest.kt
@@ -16,6 +16,7 @@ import android.content.Context
import androidx.preference.PreferenceManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
+import com.snowplowanalytics.core.emitter.Executor
import com.snowplowanalytics.snowplow.Snowplow.createTracker
import com.snowplowanalytics.snowplow.configuration.*
import com.snowplowanalytics.snowplow.controller.TrackerController
@@ -32,6 +33,7 @@ class ApplicationInstallEventTest {
@Before
fun setUp() {
cleanSharedPreferences()
+ Executor.shutdown()
}
// Tests
diff --git a/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/internal/tracker/PlatformContextTest.kt b/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/internal/tracker/PlatformContextTest.kt
index a96a87c45..f7e9ba6a4 100644
--- a/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/internal/tracker/PlatformContextTest.kt
+++ b/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/internal/tracker/PlatformContextTest.kt
@@ -49,7 +49,6 @@ class PlatformContextTest {
fun addsAllMockedInfo() {
val deviceInfoMonitor = MockDeviceInfoMonitor()
val platformContext = PlatformContext(0, 0, deviceInfoMonitor, context = context)
- Thread.sleep(100) // sleep in order to fetch the app set properties
val sdj = platformContext.getMobileContext(false)
val sdjMap = sdj!!.map
val sdjData = sdjMap["data"] as Map<*, *>?
@@ -79,6 +78,7 @@ class PlatformContextTest {
fun updatesMobileInfo() {
val deviceInfoMonitor = MockDeviceInfoMonitor()
val platformContext = PlatformContext(0, 0, deviceInfoMonitor, context = context)
+ platformContext.getMobileContext(false)
Assert.assertEquals(
1,
deviceInfoMonitor.getMethodAccessCount("getSystemAvailableMemory").toLong()
@@ -98,10 +98,25 @@ class PlatformContextTest {
)
}
+ @Test
+ fun doesntFetchPropertiesIfNotRequested() {
+ val deviceInfoMonitor = MockDeviceInfoMonitor()
+ PlatformContext(1000, 0, deviceInfoMonitor, context = context)
+ Assert.assertEquals(
+ 0,
+ deviceInfoMonitor.getMethodAccessCount("getSystemAvailableMemory").toLong()
+ )
+ Assert.assertEquals(
+ 0,
+ deviceInfoMonitor.getMethodAccessCount("getBatteryStateAndLevel").toLong()
+ )
+ }
+
@Test
fun doesntUpdateMobileInfoWithinUpdateWindow() {
val deviceInfoMonitor = MockDeviceInfoMonitor()
val platformContext = PlatformContext(1000, 0, deviceInfoMonitor, context = context)
+ platformContext.getMobileContext(false)
Assert.assertEquals(
1,
deviceInfoMonitor.getMethodAccessCount("getSystemAvailableMemory").toLong()
@@ -125,6 +140,7 @@ class PlatformContextTest {
fun updatesNetworkInfo() {
val deviceInfoMonitor = MockDeviceInfoMonitor()
val platformContext = PlatformContext(0, 0, deviceInfoMonitor, context = context)
+ platformContext.getMobileContext(false)
Assert.assertEquals(1, deviceInfoMonitor.getMethodAccessCount("getNetworkType").toLong())
Assert.assertEquals(
1,
@@ -142,6 +158,7 @@ class PlatformContextTest {
fun doesntUpdateNetworkInfoWithinUpdateWindow() {
val deviceInfoMonitor = MockDeviceInfoMonitor()
val platformContext = PlatformContext(0, 1000, deviceInfoMonitor, context = context)
+ platformContext.getMobileContext(false)
Assert.assertEquals(1, deviceInfoMonitor.getMethodAccessCount("getNetworkType").toLong())
Assert.assertEquals(
1,
@@ -159,6 +176,7 @@ class PlatformContextTest {
fun doesntUpdateNonEphemeralInfo() {
val deviceInfoMonitor = MockDeviceInfoMonitor()
val platformContext = PlatformContext(0, 0, deviceInfoMonitor, context = context)
+ platformContext.getMobileContext(false)
Assert.assertEquals(1, deviceInfoMonitor.getMethodAccessCount("getOsType").toLong())
Assert.assertEquals(1, deviceInfoMonitor.getMethodAccessCount("getTotalStorage").toLong())
platformContext.getMobileContext(false)
@@ -170,22 +188,10 @@ class PlatformContextTest {
fun doesntUpdateIdfaIfNotNull() {
val deviceInfoMonitor = MockDeviceInfoMonitor()
val platformContext = PlatformContext(0, 1, deviceInfoMonitor, context = context)
- Assert.assertEquals(1, deviceInfoMonitor.getMethodAccessCount("getAndroidIdfa").toLong())
platformContext.getMobileContext(false)
Assert.assertEquals(1, deviceInfoMonitor.getMethodAccessCount("getAndroidIdfa").toLong())
- }
-
- @Test
- fun updatesIdfaIfEmptyOrNull() {
- val deviceInfoMonitor = MockDeviceInfoMonitor()
- deviceInfoMonitor.customIdfa = ""
- val platformContext = PlatformContext(0, 1, deviceInfoMonitor, context = context)
- Assert.assertEquals(1, deviceInfoMonitor.getMethodAccessCount("getAndroidIdfa").toLong())
- deviceInfoMonitor.customIdfa = null
- platformContext.getMobileContext(false)
- Assert.assertEquals(2, deviceInfoMonitor.getMethodAccessCount("getAndroidIdfa").toLong())
platformContext.getMobileContext(false)
- Assert.assertEquals(3, deviceInfoMonitor.getMethodAccessCount("getAndroidIdfa").toLong())
+ Assert.assertEquals(1, deviceInfoMonitor.getMethodAccessCount("getAndroidIdfa").toLong())
}
@Test
diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/PlatformContext.kt b/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/PlatformContext.kt
index a11a85f82..2419ed929 100644
--- a/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/PlatformContext.kt
+++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/PlatformContext.kt
@@ -15,7 +15,6 @@ package com.snowplowanalytics.core.tracker
import android.content.Context
import com.snowplowanalytics.core.constants.Parameters
import com.snowplowanalytics.core.constants.TrackerConstants
-import com.snowplowanalytics.core.emitter.Executor
import com.snowplowanalytics.core.utils.DeviceInfoMonitor
import com.snowplowanalytics.core.utils.Util.addToMap
import com.snowplowanalytics.core.utils.Util.mapHasKeys
@@ -43,15 +42,12 @@ class PlatformContext(
private val context: Context,
) {
private val pairs: MutableMap = HashMap()
+ private var initializedPlatformDict = false
private var lastUpdatedEphemeralPlatformDict: Long = 0
private var lastUpdatedEphemeralNetworkDict: Long = 0
-
- init {
- setPlatformDict()
- }
fun getMobileContext(userAnonymisation: Boolean): SelfDescribingJson? {
- updateEphemeralDictsIfNecessary()
+ update()
// If does not contain the required properties, return null
if (!mapHasKeys(
@@ -76,7 +72,11 @@ class PlatformContext(
// --- PRIVATE
@Synchronized
- private fun updateEphemeralDictsIfNecessary() {
+ private fun update() {
+ if (!initializedPlatformDict) {
+ setPlatformDict()
+ }
+
val now = System.currentTimeMillis()
if (now - lastUpdatedEphemeralPlatformDict >= platformDictUpdateFrequency) {
setEphemeralPlatformDict()
@@ -122,26 +122,25 @@ class PlatformContext(
if (shouldTrack(PlatformContextProperty.LANGUAGE)) {
addToMap(Parameters.MOBILE_LANGUAGE, (fromRetrieverOr(retriever.language) { deviceInfoMonitor.language })?.take(8), pairs)
}
+ // IDFA
+ if (shouldTrack(PlatformContextProperty.ANDROID_IDFA)) {
+ addToMap(
+ Parameters.ANDROID_IDFA,
+ fromRetrieverOr(retriever.androidIdfa) {
+ deviceInfoMonitor.getAndroidIdfa(context)
+ },
+ pairs
+ )
+ }
- setEphemeralPlatformDict()
- setEphemeralNetworkDict()
setAppSetId()
+
+ initializedPlatformDict = true
}
private fun setEphemeralPlatformDict() {
lastUpdatedEphemeralPlatformDict = System.currentTimeMillis()
- // IDFA
- if (shouldTrack(PlatformContextProperty.ANDROID_IDFA)) {
- val currentIdfa = pairs[Parameters.ANDROID_IDFA]
- if (currentIdfa == null || currentIdfa.toString().isEmpty()) {
- addToMap(
- Parameters.ANDROID_IDFA,
- fromRetrieverOr(retriever.androidIdfa) { deviceInfoMonitor.getAndroidIdfa(context) },
- pairs
- )
- }
- }
// Battery
val trackBatState = shouldTrack(PlatformContextProperty.BATTERY_STATE)
val trackBatLevel = shouldTrack(PlatformContextProperty.BATTERY_LEVEL)
@@ -194,48 +193,25 @@ class PlatformContext(
/**
* Sets the app set information.
- * The info has to be read on a background thread which often means that the first few
- * tracked events will miss the info. To prevent that happening on the second start-up
- * of the app, the info is saved in general prefs and read from there.
*/
private fun setAppSetId() {
val trackId = shouldTrack(PlatformContextProperty.APP_SET_ID)
val trackScope = shouldTrack(PlatformContextProperty.APP_SET_ID_SCOPE)
if (!trackId && !trackScope) { return }
- val generalPref = context.getSharedPreferences(
- TrackerConstants.SNOWPLOW_GENERAL_VARS,
- Context.MODE_PRIVATE
- )
- val appSetId = fromRetrieverOr(retriever.appSetId) { generalPref.getString(Parameters.APP_SET_ID, null) }
- val appSetIdScope = fromRetrieverOr(retriever.appSetIdScope) { generalPref.getString(Parameters.APP_SET_ID_SCOPE, null) }
-
- if (appSetId != null && appSetIdScope != null) {
- if (trackId) { addToMap(Parameters.APP_SET_ID, appSetId, pairs) }
- if (trackScope) { addToMap(Parameters.APP_SET_ID_SCOPE, appSetIdScope, pairs) }
+ if (retriever.appSetId != null && retriever.appSetIdScope != null) {
+ if (trackId) { addToMap(Parameters.APP_SET_ID, retriever.appSetId?.invoke(), pairs) }
+ if (trackScope) { addToMap(Parameters.APP_SET_ID_SCOPE, retriever.appSetIdScope?.invoke(), pairs) }
} else {
- Executor.execute(TAG) {
- val preferences = generalPref.edit()
- var edited = false
-
- val appSetIdAndScope = deviceInfoMonitor.getAppSetIdAndScope(context)
- val id = fromRetrieverOr(retriever.appSetId) {
- val id = appSetIdAndScope?.first
- preferences.putString(Parameters.APP_SET_ID, id)
- edited = true
- id
- }
- val scope = fromRetrieverOr(retriever.appSetIdScope) {
- val scope = appSetIdAndScope?.second
- preferences.putString(Parameters.APP_SET_ID_SCOPE, scope)
- edited = true
- scope
- }
-
- if (trackId) { addToMap(Parameters.APP_SET_ID, id, pairs) }
- if (trackScope) { addToMap(Parameters.APP_SET_ID_SCOPE, scope, pairs) }
-
- if (edited) { preferences.apply() }
+ val appSetIdAndScope = deviceInfoMonitor.getAppSetIdAndScope(context)
+
+ if (trackId) {
+ val id = fromRetrieverOr(retriever.appSetId) { appSetIdAndScope?.first }
+ addToMap(Parameters.APP_SET_ID, id, pairs)
+ }
+ if (trackScope) {
+ val scope = fromRetrieverOr(retriever.appSetIdScope) { appSetIdAndScope?.second }
+ addToMap(Parameters.APP_SET_ID_SCOPE, scope, pairs)
}
}
}
diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/core/utils/DeviceInfoMonitor.kt b/snowplow-tracker/src/main/java/com/snowplowanalytics/core/utils/DeviceInfoMonitor.kt
index be4a3aab9..55ea9e19e 100644
--- a/snowplow-tracker/src/main/java/com/snowplowanalytics/core/utils/DeviceInfoMonitor.kt
+++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/core/utils/DeviceInfoMonitor.kt
@@ -49,12 +49,14 @@ open class DeviceInfoMonitor {
/**
* The function that actually fetches the Advertising ID.
- * - If called from the UI Thread will throw an Exception
+ * Can't be called on the main UI thread.
*
* @param context the android context
* @return an empty string if limited tracking is on otherwise the advertising id or null
*/
open fun getAndroidIdfa(context: Context): String? {
+ if (Looper.myLooper() == Looper.getMainLooper()) { return null }
+
return try {
val advertisingInfoObject = invokeStaticMethod(
"com.google.android.gms.ads.identifier.AdvertisingIdClient",