Bug 1860133 - Report Meta attribution from the Play Store referrer

fenix/121.0
Roger Yang 8 months ago committed by mergify[bot]
parent cc879baeba
commit 04e63467c4

@ -7611,6 +7611,80 @@ play_store_attribution:
tags: tags:
- Attribution - Attribution
- Performance - Performance
meta_attribution:
app:
type: string
send_in_pings:
- first-session
description: |
The mobile application ID in Meta's attribution.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1860133
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/4171
data_sensitivity:
- technical
notification_emails:
- android-probes@mozilla.com
expires: never
metadata:
tags:
- Attribution
t:
type: string
send_in_pings:
- first-session
description: |
Value tracking user interaction with Meta attribution.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1860133
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/4171
data_sensitivity:
- technical
notification_emails:
- android-probes@mozilla.com
expires: never
metadata:
tags:
- Attribution
data:
type: text
send_in_pings:
- first-session
description: |
The Meta attribution data in encrypted format.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1860133
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/4171
data_sensitivity:
# - technical
- web_activity # This is a workaround so we can use Text type for technical data.
notification_emails:
- android-probes@mozilla.com
expires: never
metadata:
tags:
- Attribution
nonce:
type: string
send_in_pings:
- first-session
description: |
Nonce used to decrypt the encrypted Meta attribution data.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1860133
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/4171
data_sensitivity:
- technical
notification_emails:
- android-probes@mozilla.com
expires: never
metadata:
tags:
- Attribution
browser.search: browser.search:
with_ads: with_ads:
type: labeled_counter type: labeled_counter

@ -73,4 +73,9 @@ object FeatureFlags {
* Allows users to enable Firefox Suggest. * Allows users to enable Firefox Suggest.
*/ */
const val fxSuggest = true const val fxSuggest = true
/**
* Enable Meta attribution.
*/
val metaAttributionEnabled = Config.channel.isNightlyOrDebug
} }

@ -5,22 +5,26 @@
package org.mozilla.fenix.components.metrics package org.mozilla.fenix.components.metrics
import android.content.Context import android.content.Context
import android.net.UrlQuerySanitizer
import android.os.RemoteException import android.os.RemoteException
import androidx.annotation.VisibleForTesting
import com.android.installreferrer.api.InstallReferrerClient import com.android.installreferrer.api.InstallReferrerClient
import com.android.installreferrer.api.InstallReferrerStateListener import com.android.installreferrer.api.InstallReferrerStateListener
import mozilla.components.support.base.log.logger.Logger
import org.json.JSONException
import org.json.JSONObject
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.GleanMetrics.MetaAttribution
import org.mozilla.fenix.GleanMetrics.PlayStoreAttribution import org.mozilla.fenix.GleanMetrics.PlayStoreAttribution
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
import java.net.URLDecoder
/** /**
* A metrics service used to derive the UTM parameters with the Google Play Install Referrer library. * A metrics service used to derive the UTM parameters with the Google Play Install Referrer library.
* *
* At first startup, the [UTMParams] are derived from the install referrer URL and stored in settings. * At first startup, the [UTMParams] and/or [MetaParams] are derived from the install referrer URL
* and stored in settings.
*/ */
class InstallReferrerMetricsService(private val context: Context) : MetricsService { class InstallReferrerMetricsService(private val context: Context) : MetricsService {
private val logger = Logger("InstallReferrerMetricsService")
override val type = MetricServiceType.Marketing override val type = MetricServiceType.Marketing
private var referrerClient: InstallReferrerClient? = null private var referrerClient: InstallReferrerClient? = null
@ -41,14 +45,26 @@ class InstallReferrerMetricsService(private val context: Context) : MetricsServi
when (responseCode) { when (responseCode) {
InstallReferrerClient.InstallReferrerResponse.OK -> { InstallReferrerClient.InstallReferrerResponse.OK -> {
// Connection established. // Connection established.
try { val installReferrerResponse = try {
val response = client.installReferrer client.installReferrer.installReferrer
recordInstallReferrer(context.settings(), response.installReferrer)
context.settings().utmParamsKnown = true
} catch (e: RemoteException) { } catch (e: RemoteException) {
// NOOP.
// We can't do anything about this. // We can't do anything about this.
logger.error("Failed to retrieve install referrer response", e)
null
} }
if (installReferrerResponse.isNullOrBlank()) {
return
}
val utmParams = UTMParams.parseUTMParameters(installReferrerResponse)
if (FeatureFlags.metaAttributionEnabled) {
MetaParams.extractMetaAttribution(utmParams.content)
?.recordMetaAttribution()
}
utmParams.recordInstallReferrer(context.settings())
context.settings().utmParamsKnown = true
} }
InstallReferrerClient.InstallReferrerResponse.FEATURE_NOT_SUPPORTED -> { InstallReferrerClient.InstallReferrerResponse.FEATURE_NOT_SUPPORTED -> {
@ -81,36 +97,6 @@ class InstallReferrerMetricsService(private val context: Context) : MetricsServi
override fun track(event: Event) = Unit override fun track(event: Event) = Unit
override fun shouldTrack(event: Event): Boolean = false override fun shouldTrack(event: Event): Boolean = false
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal fun recordInstallReferrer(settings: Settings, url: String?) {
if (url.isNullOrBlank()) {
return
}
val params = UTMParams.fromURLString(url)
if (params == null || params.isEmpty()) {
return
}
params.intoSettings(settings)
params.apply {
source?.let {
PlayStoreAttribution.source.set(it)
}
medium?.let {
PlayStoreAttribution.medium.set(it)
}
campaign?.let {
PlayStoreAttribution.campaign.set(it)
}
content?.let {
PlayStoreAttribution.content.set(it)
}
term?.let {
PlayStoreAttribution.term.set(it)
}
}
}
} }
/** /**
@ -134,61 +120,44 @@ class InstallReferrerMetricsService(private val context: Context) : MetricsServi
* which version is more effective. * which version is more effective.
*/ */
data class UTMParams( data class UTMParams(
val source: String?, val source: String,
val medium: String?, val medium: String,
val campaign: String?, val campaign: String,
val term: String?, val content: String,
val content: String?, val term: String,
) { ) {
companion object { companion object {
const val UTM_SOURCE = "utm_source" const val UTM_SOURCE = "utm_source"
const val UTM_MEDIUM = "utm_medium" const val UTM_MEDIUM = "utm_medium"
const val UTM_CAMPAIGN = "utm_campaign" const val UTM_CAMPAIGN = "utm_campaign"
const val UTM_TERM = "utm_term"
const val UTM_CONTENT = "utm_content" const val UTM_CONTENT = "utm_content"
const val UTM_TERM = "utm_term"
/** /**
* Try and unpack the referrer URL by successively URLDecoding the URL. * Try and unpack the referrer URL by successively URLDecoding the URL.
*
* Once the url ceases to decode anymore, it gives up.
*/ */
fun fromURLString(urlString: String): UTMParams? { fun parseUTMParameters(referrerUrl: String): UTMParams {
// Look for the first time 'utm_' is detected, after the first '?'. val utmParams = mutableMapOf<String, String>()
val utmIndex = urlString.indexOf("utm_", urlString.indexOf('?')) val params = referrerUrl.split("&")
if (utmIndex < 0) {
return null for (param in params) {
} val keyValue = param.split("=")
var url = urlString.substring(utmIndex) if (keyValue.size == 2) {
while (true) { val key = keyValue[0]
val params = fromQueryString(url) val value = keyValue[1]
if (!params.isEmpty()) { utmParams[key] = value
return params
}
val newValue = URLDecoder.decode(url, "UTF-8")
if (newValue == url) {
break
} }
url = newValue
} }
return null
}
/** return UTMParams(
* Derive a set of UTM parameters from a string URL. source = utmParams[UTM_SOURCE] ?: "",
*/ medium = utmParams[UTM_MEDIUM] ?: "",
fun fromQueryString(queryString: String): UTMParams = campaign = utmParams[UTM_CAMPAIGN] ?: "",
with(UrlQuerySanitizer()) { content = utmParams[UTM_CONTENT] ?: "",
allowUnregisteredParamaters = true term = utmParams[UTM_TERM] ?: "",
unregisteredParameterValueSanitizer = UrlQuerySanitizer.getUrlAndSpaceLegal() )
parseQuery(queryString) }
UTMParams(
source = getValue(UTM_SOURCE),
medium = getValue(UTM_MEDIUM),
campaign = getValue(UTM_CAMPAIGN),
term = getValue(UTM_TERM),
content = getValue(UTM_CONTENT),
)
}
/** /**
* Derive the set of UTM parameters stored in Settings. * Derive the set of UTM parameters stored in Settings.
@ -199,8 +168,8 @@ data class UTMParams(
source = utmSource, source = utmSource,
medium = utmMedium, medium = utmMedium,
campaign = utmCampaign, campaign = utmCampaign,
term = utmTerm,
content = utmContent, content = utmContent,
term = utmTerm,
) )
} }
} }
@ -210,22 +179,132 @@ data class UTMParams(
*/ */
fun intoSettings(settings: Settings) { fun intoSettings(settings: Settings) {
with(settings) { with(settings) {
utmSource = source ?: "" utmSource = source
utmMedium = medium ?: "" utmMedium = medium
utmCampaign = campaign ?: "" utmCampaign = campaign
utmTerm = term ?: "" utmTerm = term
utmContent = content ?: "" utmContent = content
} }
} }
/** /**
* Return [true] if none of the utm params are set. * Check if this UTM param is empty
*
* @Return [Boolean] true if none of the utm params are set.
*/ */
fun isEmpty(): Boolean { fun isEmpty(): Boolean {
return source.isNullOrBlank() && return source.isBlank() &&
medium.isNullOrBlank() && medium.isBlank() &&
campaign.isNullOrBlank() && campaign.isBlank() &&
term.isNullOrBlank() && term.isBlank() &&
content.isNullOrBlank() content.isBlank()
}
/**
* record UTM params into settings and telemetry
*
* @param settings [Settings] application settings.
*/
fun recordInstallReferrer(settings: Settings) {
if (isEmpty()) {
return
}
intoSettings(settings)
PlayStoreAttribution.source.set(source)
PlayStoreAttribution.medium.set(medium)
PlayStoreAttribution.campaign.set(campaign)
PlayStoreAttribution.content.set(content)
PlayStoreAttribution.term.set(term)
}
}
/**
* Descriptions of Meta attribution parameters comes from
* https://developers.facebook.com/docs/marketing-api/reference/ad-campaign#fields
*
* @property app the ID of application in the referrer response.
* @property t the value of user interaction in the referrer response.
* @property data the encrypted data in the referrer response.
* @property nonce the nonce for decrypting [data] in the referrer response.
*/
data class MetaParams(
val app: String,
val t: String,
val data: String,
val nonce: String,
) {
companion object {
private val logger = Logger("MetaParams")
private const val APP = "app"
private const val T = "t"
private const val SOURCE = "source"
private const val DATA = "data"
private const val NONCE = "nonce"
@Suppress("ReturnCount")
internal fun extractMetaAttribution(contentString: String?): MetaParams? {
if (contentString == null) {
return null
}
val data: String
val nonce: String
val contentJson = try {
JSONObject(contentString)
} catch (e: JSONException) {
logger.error("content is not JSON", e)
// can't recover from this
return null
}
val app = try {
contentJson.optString(APP) ?: ""
} catch (e: JSONException) {
logger.error("failed to extract app", e)
// this is an acceptable outcome
""
}
val t = try {
contentJson.optString(T) ?: ""
} catch (e: JSONException) {
logger.error("failed to extract t", e)
// this is an acceptable outcome
""
}
try {
val source = contentJson.optJSONObject(SOURCE)
data = source?.optString(DATA) ?: ""
nonce = source?.optString(NONCE) ?: ""
} catch (e: JSONException) {
logger.error("failed to extract data or nonce", e)
// can't recover from this
return null
}
if (data.isBlank() || nonce.isBlank()) {
return null
}
return MetaParams(
app = app,
t = t,
data = data,
nonce = nonce,
)
}
}
/**
* record META attribution params to telemetry
*/
fun recordMetaAttribution() {
MetaAttribution.app.set(app)
MetaAttribution.t.set(t)
MetaAttribution.data.set(data)
MetaAttribution.nonce.set(nonce)
} }
} }

@ -10,12 +10,12 @@ import mozilla.components.service.glean.testing.GleanTestRule
import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.fenix.GleanMetrics.MetaAttribution
import org.mozilla.fenix.GleanMetrics.PlayStoreAttribution import org.mozilla.fenix.GleanMetrics.PlayStoreAttribution
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
import org.robolectric.RobolectricTestRunner import org.robolectric.RobolectricTestRunner
@ -28,29 +28,7 @@ internal class InstallReferrerMetricsServiceTest {
val gleanTestRule = GleanTestRule(testContext) val gleanTestRule = GleanTestRule(testContext)
@Test @Test
fun testUtmParamsFromUrl() { fun `WHEN retrieving minimum UTM params from setting THEN result should match`() {
assertEquals("SOURCE", UTMParams.fromQueryString("utm_source=SOURCE").source)
assertEquals("MEDIUM", UTMParams.fromQueryString("utm_medium=MEDIUM").medium)
assertEquals("CAMPAIGN", UTMParams.fromQueryString("utm_campaign=CAMPAIGN").campaign)
assertEquals("TERM", UTMParams.fromQueryString("utm_term=TERM").term)
assertEquals("CONTENT", UTMParams.fromQueryString("utm_content=CONTENT").content)
}
@Test
fun testUtmParamsFromUrlWithSpaces() {
assertEquals("WITH SPACE", UTMParams.fromQueryString("utm_source=WITH+SPACE").source)
assertEquals("WITH SPACE", UTMParams.fromQueryString("utm_medium=WITH%20SPACE").medium)
assertEquals("WITH SPACE", UTMParams.fromQueryString("utm_campaign=WITH SPACE").campaign)
}
@Test
fun testUtmParamsFromUrlWithMissingParams() {
assertNull(UTMParams.fromQueryString("missing=").source)
assertEquals("", UTMParams.fromQueryString("utm_source=").source)
}
@Test
fun testUtmParamsRoundTripThroughSettingsMinimumParams() {
val settings = Settings(context) val settings = Settings(context)
val expected = UTMParams(source = "", medium = "", campaign = "", content = "", term = "") val expected = UTMParams(source = "", medium = "", campaign = "", content = "", term = "")
val observed = UTMParams.fromSettings(settings) val observed = UTMParams.fromSettings(settings)
@ -60,7 +38,7 @@ internal class InstallReferrerMetricsServiceTest {
} }
@Test @Test
fun testUtmParamsRoundTripThroughSettingsMaximumParams() { fun `WHEN retrieving maximum UTM params from setting THEN result should match`() {
val expected = UTMParams(source = "source", medium = "medium", campaign = "campaign", content = "content", term = "term") val expected = UTMParams(source = "source", medium = "medium", campaign = "campaign", content = "content", term = "term")
val settings = Settings(context) val settings = Settings(context)
@ -73,10 +51,10 @@ internal class InstallReferrerMetricsServiceTest {
} }
@Test @Test
fun testInstallReferrerMetricsMinimumParams() { fun `WHEN parsing referrer response with no UTM params from setting THEN UTM params in settings should set to empty strings`() {
val service = InstallReferrerMetricsService(context)
val settings = Settings(context) val settings = Settings(context)
service.recordInstallReferrer(settings, "https://example.com") val params = UTMParams.parseUTMParameters("")
params.recordInstallReferrer(settings)
val expected = UTMParams(source = "", medium = "", campaign = "", content = "", term = "") val expected = UTMParams(source = "", medium = "", campaign = "", content = "", term = "")
val observed = UTMParams.fromSettings(settings) val observed = UTMParams.fromSettings(settings)
@ -92,29 +70,29 @@ internal class InstallReferrerMetricsServiceTest {
} }
@Test @Test
fun testInstallReferrerMetricsPartial() { fun `WHEN parsing referrer response with partial UTM params from setting THEN UTM params in settings should match expected`() {
val service = InstallReferrerMetricsService(context)
val settings = Settings(context) val settings = Settings(context)
service.recordInstallReferrer(settings, "https://example.com?utm_campaign=CAMPAIGN") val params = UTMParams.parseUTMParameters("utm_campaign=CAMPAIGN")
params.recordInstallReferrer(settings)
val expected = UTMParams(source = "", medium = "", campaign = "CAMPAIGN", content = "", term = "") val expected = UTMParams(source = "", medium = "", campaign = "CAMPAIGN", content = "", term = "")
val observed = UTMParams.fromSettings(settings) val observed = UTMParams.fromSettings(settings)
assertEquals(observed, expected) assertEquals(observed, expected)
assertNull(PlayStoreAttribution.source.testGetValue()) assertEquals("", PlayStoreAttribution.source.testGetValue())
assertNull(PlayStoreAttribution.medium.testGetValue()) assertEquals("", PlayStoreAttribution.medium.testGetValue())
assertEquals("CAMPAIGN", PlayStoreAttribution.campaign.testGetValue()) assertEquals("CAMPAIGN", PlayStoreAttribution.campaign.testGetValue())
assertNull(PlayStoreAttribution.content.testGetValue()) assertEquals("", PlayStoreAttribution.content.testGetValue())
assertNull(PlayStoreAttribution.term.testGetValue()) assertEquals("", PlayStoreAttribution.term.testGetValue())
assertFalse(observed.isEmpty()) assertFalse(observed.isEmpty())
} }
@Test @Test
fun testInstallReferrerMetricsMaximumParams() { fun `WHEN parsing referrer response with full UTM params from setting THEN UTM params in settings should match expected`() {
val service = InstallReferrerMetricsService(context)
val settings = Settings(context) val settings = Settings(context)
service.recordInstallReferrer(settings, "https://example.com?utm_source=SOURCE&utm_medium=MEDIUM&utm_campaign=CAMPAIGN&utm_content=CONTENT&utm_term=TERM") val params = UTMParams.parseUTMParameters("utm_source=SOURCE&utm_medium=MEDIUM&utm_campaign=CAMPAIGN&utm_content=CONTENT&utm_term=TERM")
params.recordInstallReferrer(settings)
val expected = UTMParams(source = "SOURCE", medium = "MEDIUM", campaign = "CAMPAIGN", content = "CONTENT", term = "TERM") val expected = UTMParams(source = "SOURCE", medium = "MEDIUM", campaign = "CAMPAIGN", content = "CONTENT", term = "TERM")
val observed = UTMParams.fromSettings(settings) val observed = UTMParams.fromSettings(settings)
@ -130,53 +108,66 @@ internal class InstallReferrerMetricsServiceTest {
} }
@Test @Test
fun testInstallReferrerMetricsShouldTrack() { fun `WHEN Install referrer metrics service should track is called THEN it should always return false`() {
val service = InstallReferrerMetricsService(context) val service = InstallReferrerMetricsService(context)
assertFalse(service.shouldTrack(Event.GrowthData.FirstAppOpenForDay)) assertFalse(service.shouldTrack(Event.GrowthData.FirstAppOpenForDay))
} }
@Test @Test
fun testInstallReferrerMetricsType() { fun `WHEN Install referrer metrics service starts THEN then the service type should be marketing`() {
val service = InstallReferrerMetricsService(context) val service = InstallReferrerMetricsService(context)
assertEquals(MetricServiceType.Marketing, service.type) assertEquals(MetricServiceType.Marketing, service.type)
} }
@Test @Test
fun testDecodeReferrerUrl() { fun `WHEN receiving a Meta encrypted attribution THEN will decrypt correctly`() {
// Example from https://developers.google.com/analytics/devguides/collection/android/v4/campaigns#google-play-url-builder val metaParams = MetaParams.extractMetaAttribution("""{"app":12345, "t":1234567890,"source":{"data":"DATA","nonce":"NONCE"}}""")
val params = UTMParams.fromURLString( val expectedMetaParams = MetaParams("12345", "1234567890", "DATA", "NONCE")
"https://play.google.com/store/apps/details?id=com.example.application" +
"&referrer=utm_source%3Dgoogle" +
"%26utm_medium%3Dcpc" +
"%26utm_term%3Drunning%252Bshoes" +
"%26utm_content%3Dlogolink" +
"%26utm_campaign%3Dspring_sale",
)
assertNotNull(params)
val expected = UTMParams(
source = "google",
medium = "cpc",
campaign = "spring_sale",
content = "logolink",
term = "running+shoes",
)
assertEquals(expected, params) assertEquals(metaParams, expectedMetaParams)
} }
@Test @Test
fun testDecodeReferrerAdMobUrl() { fun `WHEN parsing referrer response with meta attribution THEN both UTM and Meta params should match expected`() {
val expected = UTMParams(source = "SOURCE", medium = "MEDIUM", campaign = "CAMPAIGN", content = "CONTENT", term = "TERM") val utmParams = UTMParams.parseUTMParameters("""utm_content={"app":12345, "t":1234567890,"source":{"data":"DATA","nonce":"NONCE"}}""")
// Generated with https://developers.google.com/analytics/devguides/collection/android/v4/campaigns#google-play-url-builder val expectedUtmParams = UTMParams(source = "", medium = "", campaign = "", content = """{"app":12345, "t":1234567890,"source":{"data":"DATA","nonce":"NONCE"}}""", term = "")
val fromUrl = UTMParams.fromURLString(
"https://play.google.com/store/apps/details?id=org.mozilla.fenix&referrer=utm_source%3DSOURCE%26utm_medium%3DMEDIUM%26utm_term%3DTERM%26utm_content%3DCONTENT%26utm_campaign%3DCAMPAIGN%26anid%3Dadmob", assertEquals(utmParams, expectedUtmParams)
val metaParams = MetaParams.extractMetaAttribution(utmParams.content)
val expectedMetaParams = MetaParams("12345", "1234567890", "DATA", "NONCE")
assertEquals(metaParams, expectedMetaParams)
}
@Test
fun `WHEN recording Meta attribution THEN correct values should be recorded to telemetry`() {
// The data and nonce are from Meta's example https://developers.facebook.com/docs/app-ads/install-referrer/
val metaParams = MetaParams(
"12345",
"1234567890",
"afe56cf6228c6ea8c79da49186e718e92a579824596ae1d0d4d20d7793dca797bd4034ccf467bfae5c79a3981e7a2968c41949237e2b2db678c1c3d39c9ae564c5cafd52f2b77a3dc77bf1bae063114d0283b97417487207735da31ddc1531d5645a9c3e602c195a0ebf69c272aa5fda3a2d781cb47e117310164715a54c7a5a032740584e2789a7b4e596034c16425139a77e507c492b629c848573c714a03a2e7d25b9459b95842332b460f3682d19c35dbc7d53e3a51e0497ff6a6cbb367e760debc4194ae097498108df7b95eac2fa9bac4320077b510be3b7b823248bfe02ae501d9fe4ba179c7de6733c92bf89d523df9e31238ef497b9db719484cbab7531dbf6c5ea5a8087f95d59f5e4f89050e0f1dc03e464168ad76a64cca64b79",
"b7203c6a6fb633d16e9cf5c1",
) )
assertNotNull(fromUrl)
assertEquals(expected, fromUrl)
val fromReferrerAttribute = UTMParams.fromURLString("utm_source%3DSOURCE%26utm_medium%3DMEDIUM%26utm_term%3DTERM%26utm_content%3DCONTENT%26utm_campaign%3DCAMPAIGN%26anid%3Dadmob") assertNull(MetaAttribution.app.testGetValue())
assertNotNull(fromReferrerAttribute) assertNull(MetaAttribution.t.testGetValue())
assertEquals(expected, fromReferrerAttribute) assertNull(MetaAttribution.data.testGetValue())
assertNull(MetaAttribution.nonce.testGetValue())
metaParams.recordMetaAttribution()
val expectedApp = "12345"
val expectedT = "1234567890"
val expectedData = "afe56cf6228c6ea8c79da49186e718e92a579824596ae1d0d4d20d7793dca797bd4034ccf467bfae5c79a3981e7a2968c41949237e2b2db678c1c3d39c9ae564c5cafd52f2b77a3dc77bf1bae063114d0283b97417487207735da31ddc1531d5645a9c3e602c195a0ebf69c272aa5fda3a2d781cb47e117310164715a54c7a5a032740584e2789a7b4e596034c16425139a77e507c492b629c848573c714a03a2e7d25b9459b95842332b460f3682d19c35dbc7d53e3a51e0497ff6a6cbb367e760debc4194ae097498108df7b95eac2fa9bac4320077b510be3b7b823248bfe02ae501d9fe4ba179c7de6733c92bf89d523df9e31238ef497b9db719484cbab7531dbf6c5ea5a8087f95d59f5e4f89050e0f1dc03e464168ad76a64cca64b79"
val expectedNonce = "b7203c6a6fb633d16e9cf5c1"
val recordedApp = MetaAttribution.app.testGetValue()
assertEquals(recordedApp, expectedApp)
val recordedT = MetaAttribution.t.testGetValue()
assertEquals(recordedT, expectedT)
val recordedData = MetaAttribution.data.testGetValue()
assertEquals(recordedData, expectedData)
val recordedNonce = MetaAttribution.nonce.testGetValue()
assertEquals(recordedNonce, expectedNonce)
} }
} }

Loading…
Cancel
Save