diff --git a/pillarbox-analytics/src/androidTest/java/ch/srgssr/pillarbox/analytics/TestComScoreSrg.kt b/pillarbox-analytics/src/androidTest/java/ch/srgssr/pillarbox/analytics/TestComScoreSrg.kt index 2238b3624..d92f2bae0 100644 --- a/pillarbox-analytics/src/androidTest/java/ch/srgssr/pillarbox/analytics/TestComScoreSrg.kt +++ b/pillarbox-analytics/src/androidTest/java/ch/srgssr/pillarbox/analytics/TestComScoreSrg.kt @@ -5,9 +5,10 @@ package ch.srgssr.pillarbox.analytics import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation -import ch.srgssr.pillarbox.analytics.comscore.ComScoreLabelInternal +import ch.srgssr.pillarbox.analytics.comscore.ComScoreLabel import ch.srgssr.pillarbox.analytics.comscore.ComScorePageView import ch.srgssr.pillarbox.analytics.comscore.ComScoreSrg +import ch.srgssr.pillarbox.analytics.comscore.ComScoreUserConsent import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow @@ -41,7 +42,7 @@ class TestComScoreSrg { val pageTitle = "Title" val pageView = ComScorePageView(name = pageTitle) val actualLabels = ArrayList>() - val expectedLabels = listOf(mapOf(Pair(ComScoreLabelInternal.C8.label, pageTitle))) + val expectedLabels = listOf(mapOf(Pair(ComScoreLabel.C8.label, pageTitle))) val job = launch(dispatcher) { tracker.pageViewFlow.take(1).toList(actualLabels) } @@ -58,7 +59,7 @@ class TestComScoreSrg { val labels = mapOf(Pair("key1", "value01"), Pair("key2", " ")) val pageView = ComScorePageView(name = pageTitle, labels = labels) val actualLabels = ArrayList>() - val expectedLabels = listOf(mapOf(Pair(ComScoreLabelInternal.C8.label, pageTitle), Pair("key1", "value01"))) + val expectedLabels = listOf(mapOf(Pair(ComScoreLabel.C8.label, pageTitle), Pair("key1", "value01"))) val job = launch(dispatcher) { tracker.pageViewFlow.take(1).toList(actualLabels) } @@ -67,6 +68,30 @@ class TestComScoreSrg { job.cancel() } + @Test + fun testUserConsentGiven() { + val userConsent = ComScoreUserConsent.GIVEN + val expectedLabel = "1" + comScore.setUserConsent(userConsent) + Assert.assertEquals(expectedLabel, comScore.getPersistentLabel(ComScoreLabel.CS_UC_FR.label)) + } + + @Test + fun testUserConsentNotGiven() { + val userConsent = ComScoreUserConsent.REFUSED + val expectedLabel = "0" + comScore.setUserConsent(userConsent) + Assert.assertEquals(expectedLabel, comScore.getPersistentLabel(ComScoreLabel.CS_UC_FR.label)) + } + + @Test + fun testUserConsentUnknown() { + val userConsent = ComScoreUserConsent.UNKNOWN + val expectedLabel = "" + comScore.setUserConsent(userConsent) + Assert.assertEquals(expectedLabel, comScore.getPersistentLabel(ComScoreLabel.CS_UC_FR.label)) + } + private class PageViewTracking : ComScoreSrg.DebugListener { val pageViewFlow = MutableSharedFlow>(extraBufferCapacity = 1, replay = 1) diff --git a/pillarbox-analytics/src/androidTest/java/ch/srgssr/pillarbox/analytics/TestCommandersAct.kt b/pillarbox-analytics/src/androidTest/java/ch/srgssr/pillarbox/analytics/TestCommandersAct.kt index 101e49681..4c5debc38 100644 --- a/pillarbox-analytics/src/androidTest/java/ch/srgssr/pillarbox/analytics/TestCommandersAct.kt +++ b/pillarbox-analytics/src/androidTest/java/ch/srgssr/pillarbox/analytics/TestCommandersAct.kt @@ -6,6 +6,7 @@ package ch.srgssr.pillarbox.analytics import androidx.test.platform.app.InstrumentationRegistry import ch.srgssr.pillarbox.analytics.commandersact.CommandersActEvent +import ch.srgssr.pillarbox.analytics.commandersact.CommandersActLabels import ch.srgssr.pillarbox.analytics.commandersact.CommandersActPageView import ch.srgssr.pillarbox.analytics.commandersact.CommandersActSrg import org.junit.Assert @@ -39,4 +40,12 @@ class TestCommandersAct { ) Assert.assertTrue(true) } + + @Test + fun testConsentServices() { + val services = listOf("service1", "service2") + val expected = "service1,service2" + commandersAct.setConsentServices(services) + Assert.assertEquals(expected, commandersAct.getPermanentDataLabel(CommandersActLabels.CONSENT_SERVICES.label)) + } } diff --git a/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/AnalyticsConfig.kt b/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/AnalyticsConfig.kt index f7c9b8b2e..7993c6f8d 100644 --- a/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/AnalyticsConfig.kt +++ b/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/AnalyticsConfig.kt @@ -12,6 +12,7 @@ package ch.srgssr.pillarbox.analytics * @property sourceKey The CommandersAct sourceKey given by the analytics team. * @property nonLocalizedApplicationName Application name for the analytics, by default the application name defined in the manifest. You can set * it to null if the application name is not localized. + * @property userConsent User consent to transmit to ComScore and CommandersAct. * @property comScorePersistentLabels initial ComScore persistent labels. * @property commandersActPersistentLabels initial CommandersAct persistent labels. */ @@ -20,6 +21,7 @@ data class AnalyticsConfig( val appSiteName: String, val sourceKey: String, val nonLocalizedApplicationName: String? = null, + val userConsent: UserConsent = UserConsent(), val comScorePersistentLabels: Map? = null, val commandersActPersistentLabels: Map? = null, ) { diff --git a/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/UserConsent.kt b/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/UserConsent.kt new file mode 100644 index 000000000..c17109dbc --- /dev/null +++ b/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/UserConsent.kt @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2023. SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.analytics + +import ch.srgssr.pillarbox.analytics.comscore.ComScoreUserConsent + +/** + * User consent + * + * @property comScore ComScore user consent. + * @property commandersActConsentServices CommandersAct consent services list. + */ +data class UserConsent( + val comScore: ComScoreUserConsent = ComScoreUserConsent.UNKNOWN, + val commandersActConsentServices: List = emptyList() +) diff --git a/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/commandersact/CommandersAct.kt b/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/commandersact/CommandersAct.kt index 520bb9353..fad119868 100644 --- a/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/commandersact/CommandersAct.kt +++ b/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/commandersact/CommandersAct.kt @@ -55,4 +55,11 @@ interface CommandersAct { * @return null if not found. */ fun getPermanentDataLabel(label: String): String? + + /** + * Set consent services + * + * @param consentServices The list of consent services by the user. + */ + fun setConsentServices(consentServices: List) } diff --git a/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/commandersact/CommandersActSrg.kt b/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/commandersact/CommandersActSrg.kt index 2bb5ddf38..d37f81c58 100644 --- a/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/commandersact/CommandersActSrg.kt +++ b/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/commandersact/CommandersActSrg.kt @@ -103,6 +103,10 @@ internal class CommandersActSrg( return tcServerSide.getPermanentData(label) } + override fun setConsentServices(consentServices: List) { + tcServerSide.addPermanentData(CommandersActLabels.CONSENT_SERVICES.label, consentServices.joinToString(",")) + } + /** * Override application name if [AnalyticsConfig.nonLocalizedApplicationName] is not empty. * Useful for application that localized their application name and want to have same name for analytics. diff --git a/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScore.kt b/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScore.kt index 8840d1d16..a1b2d568d 100644 --- a/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScore.kt +++ b/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScore.kt @@ -37,4 +37,11 @@ interface ComScore { * @return null if not found. */ fun getPersistentLabel(label: String): String? + + /** + * Set user consent + * + * @param userConsent + */ + fun setUserConsent(userConsent: ComScoreUserConsent) } diff --git a/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScoreLabel.kt b/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScoreLabel.kt index d1f79c578..24c20ba55 100644 --- a/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScoreLabel.kt +++ b/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScoreLabel.kt @@ -4,12 +4,9 @@ */ package ch.srgssr.pillarbox.analytics.comscore -/** - * Public ComScore label - */ -object ComScoreLabel { - /** - * Please refer to ComScore android implementation guide section 2.5. - */ - const val USER_CONSENT = "cs_ucfr" +internal enum class ComScoreLabel(val label: String) { + C8("c8"), + MP_BRAND("mp_brand"), + MP_V("mp_v"), + CS_UC_FR("cs_ucfr") } diff --git a/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScoreLabelInternal.kt b/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScoreLabelInternal.kt deleted file mode 100644 index a6b7f48e3..000000000 --- a/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScoreLabelInternal.kt +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright (c) 2023. SRG SSR. All rights reserved. - * License information is available from the LICENSE file. - */ -package ch.srgssr.pillarbox.analytics.comscore - -internal enum class ComScoreLabelInternal(val label: String) { - C8("c8"), - MP_BRAND("mp_brand"), - MP_V("mp_v"), -} diff --git a/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScorePageView.kt b/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScorePageView.kt index 5711ed8df..ecf477065 100644 --- a/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScorePageView.kt +++ b/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScorePageView.kt @@ -24,7 +24,7 @@ data class ComScorePageView( fun toLabels(): Map { val labels = HashMap() labels.putAll(this.labels.filterValues { value -> value.isNotBlank() }) - labels[ComScoreLabelInternal.C8.label] = name + labels[ComScoreLabel.C8.label] = name return labels } } diff --git a/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScoreSrg.kt b/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScoreSrg.kt index aa0c86af3..fd1c053c8 100644 --- a/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScoreSrg.kt +++ b/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScoreSrg.kt @@ -53,11 +53,15 @@ internal object ComScoreSrg : ComScore { return this } this.config = config + val persistentLabels = HashMap() config.comScorePersistentLabels?.let { labels -> persistentLabels.putAll(labels) } + val userConsentLabel = getUserConsentPair(config.userConsent.comScore) + persistentLabels[userConsentLabel.first] = userConsentLabel.second + val versionName: String = try { // When unit testing from library packageInfo.versionName is null! context.applicationContext.packageManager.getPackageInfo(context.applicationContext.packageName, 0).versionName @@ -66,8 +70,8 @@ internal object ComScoreSrg : ComScore { Log.e("COMSCORE", "Cannot find package", e) BuildConfig.VERSION_NAME } - persistentLabels[ComScoreLabelInternal.MP_V.label] = versionName - persistentLabels[ComScoreLabelInternal.MP_BRAND.label] = config.vendor.toString() + persistentLabels[ComScoreLabel.MP_V.label] = versionName + persistentLabels[ComScoreLabel.MP_BRAND.label] = config.vendor.toString() val publisher = PublisherConfiguration.Builder() .publisherId(publisherId) .persistentLabels(persistentLabels) @@ -121,6 +125,23 @@ internal object ComScoreSrg : ComScore { return configuration.getPersistentLabel(label) } + override fun setUserConsent(userConsent: ComScoreUserConsent) { + putPersistentLabels(mapOf(getUserConsentPair(userConsent))) + // Analytics.notifyHiddenEvent() // FIXME Send updated user consent or wait next page view or media event? + } + + /** + * Values from ComScore documentation section 2.5.2 + */ + private fun getUserConsentPair(userConsent: ComScoreUserConsent): Pair { + val value = when (userConsent) { + ComScoreUserConsent.GIVEN -> "1" + ComScoreUserConsent.REFUSED -> "0" + ComScoreUserConsent.UNKNOWN -> "" + } + return Pair(ComScoreLabel.CS_UC_FR.label, value) + } + private fun checkInitialized() { requireNotNull(config) { "ComScore init has to be called before start." } } diff --git a/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScoreUserConsent.kt b/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScoreUserConsent.kt new file mode 100644 index 000000000..13cb48734 --- /dev/null +++ b/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScoreUserConsent.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023. SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.analytics.comscore + +/** + * Com score user consent + */ +enum class ComScoreUserConsent { + /** + * Unknown + * + * User has not taken an action. + */ + UNKNOWN, + + /** + * Given + * + * User has given consent. + */ + GIVEN, + + /** + * Refuse + * + * User has not given consent or has opted out. + */ + REFUSED, + ; +} diff --git a/pillarbox-analytics/src/test/java/ch/srgssr/pillarbox/analytics/ComScoreEventTest.kt b/pillarbox-analytics/src/test/java/ch/srgssr/pillarbox/analytics/ComScoreEventTest.kt index 809694411..e9de50dcc 100644 --- a/pillarbox-analytics/src/test/java/ch/srgssr/pillarbox/analytics/ComScoreEventTest.kt +++ b/pillarbox-analytics/src/test/java/ch/srgssr/pillarbox/analytics/ComScoreEventTest.kt @@ -4,7 +4,7 @@ */ package ch.srgssr.pillarbox.analytics -import ch.srgssr.pillarbox.analytics.comscore.ComScoreLabelInternal +import ch.srgssr.pillarbox.analytics.comscore.ComScoreLabel import ch.srgssr.pillarbox.analytics.comscore.ComScorePageView import org.junit.Assert import org.junit.Test @@ -17,7 +17,7 @@ class ComScoreEventTest { val pageView = ComScorePageView(title) val actual = pageView.toLabels() val expected = HashMap().apply { - this[ComScoreLabelInternal.C8.label] = "title 1" + this[ComScoreLabel.C8.label] = "title 1" } Assert.assertEquals(actual, expected) } @@ -36,7 +36,7 @@ class ComScoreEventTest { val pageView = ComScorePageView(title, customLabels) val actual = pageView.toLabels() val expected = HashMap().apply { - this[ComScoreLabelInternal.C8.label] = "title 1" + this[ComScoreLabel.C8.label] = "title 1" this["key1"] = "value1" } Assert.assertEquals(actual, expected) diff --git a/pillarbox-analytics/src/test/java/ch/srgssr/pillarbox/analytics/TestSRGAnalyticsPageViews.kt b/pillarbox-analytics/src/test/java/ch/srgssr/pillarbox/analytics/TestSRGAnalyticsPageViews.kt index 784a687ee..89900a348 100644 --- a/pillarbox-analytics/src/test/java/ch/srgssr/pillarbox/analytics/TestSRGAnalyticsPageViews.kt +++ b/pillarbox-analytics/src/test/java/ch/srgssr/pillarbox/analytics/TestSRGAnalyticsPageViews.kt @@ -10,6 +10,7 @@ import ch.srgssr.pillarbox.analytics.commandersact.CommandersActPageView import ch.srgssr.pillarbox.analytics.commandersact.TCMediaEvent import ch.srgssr.pillarbox.analytics.comscore.ComScore import ch.srgssr.pillarbox.analytics.comscore.ComScorePageView +import ch.srgssr.pillarbox.analytics.comscore.ComScoreUserConsent import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow @@ -71,7 +72,7 @@ class TestSRGAnalyticsPageViews { Assert.assertTrue(pageViewFlow.tryEmit(pageView)) } - override fun putPersistentLabels(labels: Map?) { + override fun putPersistentLabels(labels: Map) { // Nothing } @@ -83,6 +84,10 @@ class TestSRGAnalyticsPageViews { // Nothing return null } + + override fun setUserConsent(userConsent: ComScoreUserConsent) { + // Nothing + } } private class DummyCommandersAct : CommandersAct { @@ -113,5 +118,9 @@ class TestSRGAnalyticsPageViews { return null } + override fun setConsentServices(consentServices: List) { + // Nothing + } + } } diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/DemoApplication.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/DemoApplication.kt index bd2f9d9d6..b650de040 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/DemoApplication.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/DemoApplication.kt @@ -7,8 +7,8 @@ package ch.srgssr.pillarbox.demo import android.app.Application import ch.srgssr.pillarbox.analytics.AnalyticsConfig import ch.srgssr.pillarbox.analytics.SRGAnalytics.initSRGAnalytics -import ch.srgssr.pillarbox.analytics.commandersact.CommandersActLabels -import ch.srgssr.pillarbox.analytics.comscore.ComScoreLabel +import ch.srgssr.pillarbox.analytics.UserConsent +import ch.srgssr.pillarbox.analytics.comscore.ComScoreUserConsent /** * Demo application @@ -18,13 +18,17 @@ class DemoApplication : Application() { override fun onCreate() { super.onCreate() + // Defaults values + val initialUserConsent = UserConsent( + comScore = ComScoreUserConsent.UNKNOWN, + commandersActConsentServices = emptyList() + ) val config = AnalyticsConfig( vendor = AnalyticsConfig.Vendor.SRG, nonLocalizedApplicationName = "Pillarbox", appSiteName = "pillarbox-demo-android", sourceKey = AnalyticsConfig.SOURCE_KEY_SRG_DEBUG, - commandersActPersistentLabels = mapOf(Pair(CommandersActLabels.CONSENT_SERVICES.label, "service1,service2")), - comScorePersistentLabels = mapOf(Pair(ComScoreLabel.USER_CONSENT, "")) + userConsent = initialUserConsent ) initSRGAnalytics(config = config) }