diff --git a/CHANGELOG b/CHANGELOG index 026fa8223..c9f34bdf3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +Version 6.0.3 (2024-04-18) +-------------------------- +Add Subject config flag to avoid WindowManager bug (#657) +Catch more generic exceptions for InstallReferrer (#647) + Version 6.0.2 (2024-02-28) -------------------------- Fix createTracker call hanging for 10 seconds if run on a background thread (#620) diff --git a/VERSION b/VERSION index 9b9a24420..090ea9dad 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.0.2 +6.0.3 diff --git a/build.gradle b/build.gradle index a957a6ea9..aed28ce6b 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ plugins { subprojects { group = 'com.snowplowanalytics' - version = '6.0.2' + version = '6.0.3' repositories { google() maven { diff --git a/gradle.properties b/gradle.properties index c2eab7516..e01a26e65 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.2 +VERSION_NAME=6.0.3 POM_NAME=snowplow-android-tracker POM_PACKAGING=aar 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 a776f6d41..8e05166cf 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 @@ -49,7 +49,7 @@ class ApplicationInstallEventTest { .installAutotracking(true) createTracker(listOf(trackerConfiguration, plugin)) - Thread.sleep(1000) + Thread.sleep(1500) // check if event was tracked Assert.assertTrue(eventTracked) @@ -67,7 +67,7 @@ class ApplicationInstallEventTest { .installAutotracking(true) createTracker(listOf(trackerConfiguration, plugin)) - Thread.sleep(1000) + Thread.sleep(1500) // check if event was tracked Assert.assertTrue(eventTracked) @@ -78,7 +78,7 @@ class ApplicationInstallEventTest { // create tracker again createTracker(listOf(trackerConfiguration, plugin)) - Thread.sleep(1000) + Thread.sleep(1500) // check if event was tracked Assert.assertFalse(eventTracked) diff --git a/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/media/TestMediaController.kt b/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/media/TestMediaController.kt index 944904b77..5f3ff6e4f 100644 --- a/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/media/TestMediaController.kt +++ b/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/media/TestMediaController.kt @@ -87,7 +87,7 @@ class TestMediaController { ) media?.track(MediaPlayEvent()) - Thread.sleep(100) + Thread.sleep(200) assertEquals(1, trackedEvents.size) assertEquals(eventSchema("play"), firstEvent.schema) @@ -102,7 +102,7 @@ class TestMediaController { ) media?.track(MediaEndEvent()) - Thread.sleep(100) + Thread.sleep(200) assertEquals(1, trackedEvents.size) assertEquals(eventSchema("end"), firstEvent.schema) @@ -119,7 +119,7 @@ class TestMediaController { media?.track(event = MediaSeekEndEvent(), player = MediaPlayerEntity(currentTime = 2.0)) media?.track(event = MediaSeekStartEvent(), player = MediaPlayerEntity(currentTime = 3.0)) - Thread.sleep(100) + Thread.sleep(200) assertEquals(3, trackedEvents.size) assertEquals(2, trackedEvents.filter { it.schema == eventSchema("seek_start") }.size) @@ -136,7 +136,7 @@ class TestMediaController { media?.track(MediaPlayEvent()) media?.track(MediaPauseEvent()) - Thread.sleep(100) + Thread.sleep(200) assertEquals(1, trackedEvents.size) assertEquals(eventSchema("play"), firstEvent.schema) @@ -154,7 +154,7 @@ class TestMediaController { media?.track(MediaPlayEvent()) - Thread.sleep(100) + Thread.sleep(200) assertEquals(1, trackedEvents.size) assertNotNull( @@ -178,7 +178,7 @@ class TestMediaController { ) ) - Thread.sleep(100) + Thread.sleep(200) assertEquals(1, trackedEvents.size) assertNotNull( @@ -199,7 +199,7 @@ class TestMediaController { media?.track(MediaPlaybackRateChangeEvent(newRate = 1.5)) media?.track(MediaPauseEvent()) - Thread.sleep(100) + Thread.sleep(200) assertEquals(2, trackedEvents.size) val rateEvent = trackedEvents.find { it.schema == eventSchema("playback_rate_change") } @@ -238,7 +238,7 @@ class TestMediaController { media?.track(MediaFullscreenChangeEvent(fullscreen = true)) - Thread.sleep(100) + Thread.sleep(200) assertEquals(true, firstEvent.payload.get("fullscreen")) assertEquals(true, firstPlayer?.get("fullscreen")) @@ -253,7 +253,7 @@ class TestMediaController { media?.track(MediaPictureInPictureChangeEvent(pictureInPicture = true)) - Thread.sleep(100) + Thread.sleep(200) assertEquals(true, firstEvent.payload.get("pictureInPicture")) assertEquals(true, firstPlayer?.get("pictureInPicture")) @@ -265,7 +265,7 @@ class TestMediaController { media?.track(MediaAdFirstQuartileEvent()) - Thread.sleep(100) + Thread.sleep(200) assertEquals(25, firstEvent.payload.get("percentProgress")) } @@ -276,7 +276,7 @@ class TestMediaController { media?.track(MediaAdMidpointEvent()) - Thread.sleep(100) + Thread.sleep(200) assertEquals(50, firstEvent.payload.get("percentProgress")) } @@ -287,7 +287,7 @@ class TestMediaController { media?.track(MediaAdThirdQuartileEvent()) - Thread.sleep(100) + Thread.sleep(200) assertEquals(75, firstEvent.payload.get("percentProgress")) } @@ -301,7 +301,7 @@ class TestMediaController { media?.track(MediaAdResumeEvent(percentProgress = 40)) media?.track(MediaAdPauseEvent(percentProgress = 50)) - Thread.sleep(100) + Thread.sleep(200) val adClickEvent = trackedEvents.find { it.schema == eventSchema("ad_click") } assertEquals(15, adClickEvent?.payload?.get("percentProgress")) @@ -326,7 +326,7 @@ class TestMediaController { framesPerSecond = 60 )) - Thread.sleep(100) + Thread.sleep(200) assertEquals("720p", firstEvent.payload.get("previousQuality")) assertEquals("1080p", firstEvent.payload.get("newQuality")) @@ -344,7 +344,7 @@ class TestMediaController { errorDescription = "Failed to load media" )) - Thread.sleep(100) + Thread.sleep(200) assertEquals("501", firstEvent.payload.get("errorCode")) assertEquals("forbidden", firstEvent.payload.get("errorName")) @@ -363,7 +363,7 @@ class TestMediaController { mapOf("url" to "https://www.youtube.com/watch?v=12345") )) - Thread.sleep(100) + Thread.sleep(200) assertEquals("iglu:com.acme/video_played/jsonschema/1-0-0", firstEvent.schema) assertEquals("Video", firstPlayer?.get("label")) @@ -377,7 +377,7 @@ class TestMediaController { media?.track(MediaPlayEvent()) - Thread.sleep(100) + Thread.sleep(200) assertEquals("media1", firstSession?.get("mediaSessionId")) } @@ -401,7 +401,7 @@ class TestMediaController { media.update(player = MediaPlayerEntity(currentTime = 10.0)) media.track(MediaEndEvent()) - Thread.sleep(100) + Thread.sleep(200) val endEvent = trackedEvents.find { it.schema == eventSchema("end") } val lastSession = endEvent?.entities?.find { it.map.get("schema") == sessionSchema }?.map?.get("data") as? Map<*, *> @@ -427,7 +427,7 @@ class TestMediaController { timer.fire() - Thread.sleep(100) + Thread.sleep(200) assertEquals(1, trackedEvents.size) assertEquals(10000L, timer.delay) @@ -452,7 +452,7 @@ class TestMediaController { media.track(MediaPauseEvent()) timer.fire() - Thread.sleep(100) + Thread.sleep(200) assertEquals(4, trackedEvents.size) } @@ -475,7 +475,7 @@ class TestMediaController { timer.fire() } - Thread.sleep(100) + Thread.sleep(200) assertEquals(2, trackedEvents.size) } @@ -498,7 +498,7 @@ class TestMediaController { timer.fire() } - Thread.sleep(100) + Thread.sleep(200) assertEquals(5, trackedEvents.size) } @@ -519,7 +519,7 @@ class TestMediaController { media?.update(player = MediaPlayerEntity(currentTime = i.toDouble())) } - Thread.sleep(100) + Thread.sleep(200) assertEquals(4, trackedEvents.size) assertEquals(3, trackedEvents.filter { it.schema == eventSchema("percent_progress") }.size) @@ -539,7 +539,7 @@ class TestMediaController { media?.update(player = MediaPlayerEntity(currentTime = i.toDouble())) } - Thread.sleep(100) + Thread.sleep(200) assertEquals(2, trackedEvents.size) @@ -562,7 +562,7 @@ class TestMediaController { media?.update(player = MediaPlayerEntity(currentTime = i.toDouble())) } - Thread.sleep(100) + Thread.sleep(200) assertEquals(1, trackedEvents.size) } @@ -586,7 +586,7 @@ class TestMediaController { media?.update(player = MediaPlayerEntity(currentTime = i.toDouble())) } - Thread.sleep(100) + Thread.sleep(200) assertEquals(5, trackedEvents.size) assertEquals(3, trackedEvents.filter { it.schema == eventSchema("percent_progress") }.size) diff --git a/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/PluginsTest.kt b/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/PluginsTest.kt index 6862666ad..7b54edbf4 100644 --- a/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/PluginsTest.kt +++ b/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/PluginsTest.kt @@ -62,7 +62,7 @@ class PluginsTest { val tracker = createTracker(listOf(plugin, testPlugin)) tracker.track(Structured("cat", "act")) - Thread.sleep(100) + Thread.sleep(200) Assert.assertTrue(expectation) } @@ -84,7 +84,7 @@ class PluginsTest { val tracker = createTracker(listOf(plugin1, plugin2, testPlugin)) tracker.track(ScreenView("sv")) - Thread.sleep(100) + Thread.sleep(200) Assert.assertTrue(expectation) } @@ -112,7 +112,7 @@ class PluginsTest { tracker.track(SelfDescribing("schema1", emptyMap())) tracker.track(SelfDescribing("schema2", emptyMap())) - Thread.sleep(100) + Thread.sleep(200) Assert.assertTrue(event1HasEntity!!) Assert.assertFalse(event2HasEntity!!) } @@ -135,7 +135,7 @@ class PluginsTest { tracker.track(SelfDescribing("schema2", emptyMap())) tracker.track(Structured("cat", "act")) - Thread.sleep(100) + Thread.sleep(200) Assert.assertTrue(event1Called) Assert.assertFalse(event2Called) Assert.assertFalse(event3Called) @@ -156,7 +156,7 @@ class PluginsTest { tracker.track(SelfDescribing("schema1", emptyMap())) tracker.track(Structured("cat", "act")) - Thread.sleep(100) + Thread.sleep(200) Assert.assertTrue(structuredCalled) Assert.assertFalse(selfDescribingCalled) } @@ -172,7 +172,7 @@ class PluginsTest { tracker.track(ScreenView("sv")) - Thread.sleep(100) + Thread.sleep(200) Assert.assertTrue(expectation) } @@ -187,7 +187,7 @@ class PluginsTest { tracker.track(ScreenView("sv")) - Thread.sleep(100) + Thread.sleep(200) Assert.assertFalse(pluginCalled) } @@ -203,12 +203,12 @@ class PluginsTest { val tracker = createTracker(listOf(filterPlugin, afterTrackPlugin)) tracker.track(SelfDescribing("s1", emptyMap())) - Thread.sleep(100) + Thread.sleep(200) Assert.assertFalse(afterTrackCalled) tracker.track(SelfDescribing("s2", emptyMap())) - Thread.sleep(100) + Thread.sleep(200) Assert.assertTrue(afterTrackCalled) } diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/InstallReferrerDetails.kt b/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/InstallReferrerDetails.kt index 8999024ad..96e2d878a 100644 --- a/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/InstallReferrerDetails.kt +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/InstallReferrerDetails.kt @@ -75,6 +75,9 @@ class InstallReferrerDetails( } catch (_: RemoteException) { Logger.d(TAG, "Install referrer API remote exception.") callback(null) + } catch (_: NoSuchMethodError) { + Logger.d(TAG, "Upgrade InstallReferrer package to v1.1 or higher to add the install referrer details entity.") + callback(null) } } InstallReferrerResponse.FEATURE_NOT_SUPPORTED -> { @@ -99,7 +102,7 @@ class InstallReferrerDetails( }) } - fun isInstallReferrerPackageAvailable(): Boolean { + private fun isInstallReferrerPackageAvailable(): Boolean { try { Class.forName("com.android.installreferrer.api.InstallReferrerStateListener") return true diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/Subject.kt b/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/Subject.kt index 92c33e9c6..8af9aca43 100755 --- a/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/Subject.kt +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/Subject.kt @@ -179,13 +179,18 @@ class Subject(context: Context, config: SubjectConfigurationInterface?) { field = depth standardPairs[Parameters.COLOR_DEPTH] = depth.toString() } - + /** + * Whether to get the size from the context resources or not. + * By default this is false, the size is obtained from WindowManager. + */ + var useContextResourcesScreenResolution: Boolean = false + init { setDefaultTimezone() setDefaultLanguage() - setDefaultScreenResolution(context) - + setDefaultScreenResolution(context, config?.useContextResourcesScreenResolution) + if (config != null) { config.userId?.let { userId = it } config.networkUserId?.let { networkUserId = it } @@ -220,31 +225,35 @@ class Subject(context: Context, config: SubjectConfigurationInterface?) { } /** - * Sets the default screen resolution - * of the device the Tracker is running - * on. - * - * @param context the android context + * Sets the default screen resolution of the device the tracker is running on. + * @param context the Android context + * @param useContextResourcesScreenResolution whether to get the size from the context resources or not */ - private fun setDefaultScreenResolution(context: Context) { - try { - screenResolution = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - val metrics = - context.getSystemService(WindowManager::class.java).currentWindowMetrics - Size(metrics.bounds.width(), metrics.bounds.height()) - } else { - val windowManager = - context.getSystemService(Context.WINDOW_SERVICE) as? WindowManager - val display = windowManager?.defaultDisplay - val metrics = if (display != null) { - DisplayMetrics().also { display.getRealMetrics(it) } + private fun setDefaultScreenResolution(context: Context, useContextResourcesScreenResolution: Boolean?) { + if (useContextResourcesScreenResolution == true) { + val width = context.resources.displayMetrics.widthPixels + val height = context.resources.displayMetrics.heightPixels + screenResolution = Size(width, height) + } else { + try { + screenResolution = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val metrics = + context.getSystemService(WindowManager::class.java).currentWindowMetrics + Size(metrics.bounds.width(), metrics.bounds.height()) } else { - Resources.getSystem().displayMetrics + val windowManager = + context.getSystemService(Context.WINDOW_SERVICE) as? WindowManager + val display = windowManager?.defaultDisplay + val metrics = if (display != null) { + DisplayMetrics().also { display.getRealMetrics(it) } + } else { + Resources.getSystem().displayMetrics + } + Size(metrics.widthPixels, metrics.heightPixels) } - Size(metrics.widthPixels, metrics.heightPixels) + } catch (e: Throwable) { + Logger.e(TAG, "Failed to set default screen resolution.") } - } catch (e: Exception) { - Logger.e(TAG, "Failed to set default screen resolution.") } } diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/SubjectConfigurationInterface.kt b/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/SubjectConfigurationInterface.kt index 21a6073b2..d416d5c68 100644 --- a/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/SubjectConfigurationInterface.kt +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/SubjectConfigurationInterface.kt @@ -25,4 +25,5 @@ interface SubjectConfigurationInterface { var screenResolution: Size? var screenViewPort: Size? var colorDepth: Int? + var useContextResourcesScreenResolution: Boolean } diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/SubjectControllerImpl.kt b/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/SubjectControllerImpl.kt index ff54153d4..bf77e9b46 100644 --- a/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/SubjectControllerImpl.kt +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/core/tracker/SubjectControllerImpl.kt @@ -92,6 +92,13 @@ class SubjectControllerImpl // Constructors subject.colorDepth = colorDepth } + override var useContextResourcesScreenResolution: Boolean + get() = subject.useContextResourcesScreenResolution + set(useContextResourcesScreenResolution) { + dirtyConfig.useContextResourcesScreenResolution = useContextResourcesScreenResolution + subject.useContextResourcesScreenResolution = useContextResourcesScreenResolution + } + // Private methods private val subject: Subject get() = serviceProvider.getOrMakeSubject() diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/configuration/SubjectConfiguration.kt b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/configuration/SubjectConfiguration.kt index 617d4b4e4..cf1c66999 100644 --- a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/configuration/SubjectConfiguration.kt +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/configuration/SubjectConfiguration.kt @@ -76,6 +76,11 @@ open class SubjectConfiguration() : Configuration, SubjectConfigurationInterface override var colorDepth: Int? get() = _colorDepth ?: sourceConfig?.colorDepth set(value) { _colorDepth = value } + + private var _useContextResourcesScreenResolution: Boolean? = null + override var useContextResourcesScreenResolution: Boolean + get() = _useContextResourcesScreenResolution ?: sourceConfig?.useContextResourcesScreenResolution ?: false + set(value) { _useContextResourcesScreenResolution = value } // Builder methods @@ -168,6 +173,17 @@ open class SubjectConfiguration() : Configuration, SubjectConfigurationInterface return this } + /** + * Set this flag to true to define the default screen resolution (at tracker initialization) + * based on the context's Resources display metrics, rather than the deprecated WindowManager. + * NB: the height value will be smaller using Resources as it doesn't include the menu bar. + * Defaults to false. + */ + fun useContextResourcesScreenResolution(useContextResourcesScreenResolution: Boolean): SubjectConfiguration { + this.useContextResourcesScreenResolution = useContextResourcesScreenResolution + return this + } + // Copyable override fun copy(): SubjectConfiguration { return SubjectConfiguration() @@ -181,6 +197,7 @@ open class SubjectConfiguration() : Configuration, SubjectConfigurationInterface .screenResolution(screenResolution) .screenViewPort(screenViewPort) .colorDepth(colorDepth) + .useContextResourcesScreenResolution(useContextResourcesScreenResolution) } // JSON Formatter diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/event/ScreenView.kt b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/event/ScreenView.kt index 9e7b5dd00..fa76b8cda 100644 --- a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/event/ScreenView.kt +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/event/ScreenView.kt @@ -30,7 +30,7 @@ class ScreenView @JvmOverloads constructor(name: String, screenId: UUID? = null) /** Name of the screen. */ val name: String - /** Identifier of the screen. */ + /** Identifier of the screen view (unique for each screen view event). */ val id: String /** Type of screen. */