diff --git a/README.md b/README.md index 3ea430a..25d9e17 100644 --- a/README.md +++ b/README.md @@ -137,11 +137,16 @@ that production DCNs only listen to TLS traffic. The `insecure: true` option is developers running the DCN locally for testing. - `apiKey` An optional API key for authentication. If the API Endpoint is enabled as private, a Service Account API key will be required. - `customUserAgent` An optional custom user agent string for network requests. -- `skipAdvertisingIdDetection` Boolean flag to skip the detection of advertising IDs. Default is false. +- `skipAdvertisingIdDetection` Boolean flag to skip the detection of advertising IDs. Default is false. Can be +changed at any time, for example when the user grants or withdraws consent, and applies from the next call. - `consents` Optional `OptableConsents` object for providing custom consent information. If not provided, default values will be used. - `origin` An optional value for the HTTP `Origin` header sent with Optable API requests. Unrelated to `originSlug`. - `cacheTtl` The time to live, in seconds, of the targeting data cached by the `targeting` API. Default value is 24 hours. See [Caching Targeting Data](#caching-targeting-data). +- `advertisingIdTimeoutMs` How long, in milliseconds, an `identify` or `targeting` call waits for the Google Advertising +ID before it is sent without it. Default value is 100, and the minimum is 1: a smaller value waits 1 millisecond. +Expect reading the Google Advertising ID to take tens of milliseconds, depending on the device, so a much lower value +means most calls are sent without it. To never add the Google Advertising ID, use `skipAdvertisingIdDetection`. ```kotlin val config = OptableConfig( @@ -157,6 +162,7 @@ val config = OptableConfig( consents = OptableConsents(), origin = "https://www.acmeco.com", cacheTtl = 24 * 60 * 60L, + advertisingIdTimeoutMs = 100L, ) ``` diff --git a/android_sdk/src/main/java/co/optable/sdk/OptableConfig.kt b/android_sdk/src/main/java/co/optable/sdk/OptableConfig.kt index 391bdf7..009698d 100644 --- a/android_sdk/src/main/java/co/optable/sdk/OptableConfig.kt +++ b/android_sdk/src/main/java/co/optable/sdk/OptableConfig.kt @@ -14,11 +14,17 @@ import android.content.Context * @param apiKey An optional API key for authentication. If the API Endpoint is enabled as private, a Service Account API key will be required. * @param customUserAgent An optional custom user agent string for network requests. * @param skipAdvertisingIdDetection Boolean flag to skip the detection of advertising IDs. Default is false. + * Settable at any time, for example when the user grants or withdraws consent; applies from the next call. * @param consents Optional `OptableConsents` object for providing custom consent information. If not provided, default values will be used. * @param origin An optional value for the HTTP `Origin` header sent with Optable API requests. Unrelated to `originSlug`. * Settable after construction, which is how Java callers set it without passing every preceding parameter. * @param cacheTtl The targeting cache time to live in seconds. Default value is 24 hours. * A value of zero or less disables the cache. + * @param advertisingIdTimeoutMs How long, in milliseconds, an `identify` or `targeting` call waits for the Google + * Advertising ID before it is sent without it. Default value is 100. The minimum is 1: a smaller value waits 1 ms. + * Expect reading the Advertising ID to take tens of milliseconds, depending on the device, so a much lower value + * means most calls are sent without it. There is no setting that waits forever; to never add the Advertising ID, + * use `skipAdvertisingIdDetection`. Settable after construction, like `origin`. */ class OptableConfig @JvmOverloads constructor( providedContext: Context, @@ -29,14 +35,16 @@ class OptableConfig @JvmOverloads constructor( internal val insecure: Boolean = false, internal val apiKey: String? = null, internal val customUserAgent: String? = null, - internal val skipAdvertisingIdDetection: Boolean = false, + @Volatile var skipAdvertisingIdDetection: Boolean = false, var consents: OptableConsents = OptableConsents(), var origin: String? = null, internal val cacheTtl: Long = DEFAULT_TARGETING_CACHE_TTL_SECONDS, + @Volatile var advertisingIdTimeoutMs: Long = DEFAULT_ADVERTISING_ID_TIMEOUT_MS, ) { companion object { const val DEFAULT_TARGETING_CACHE_TTL_SECONDS: Long = 24 * 60 * 60L + const val DEFAULT_ADVERTISING_ID_TIMEOUT_MS: Long = 100L } internal val context = providedContext.applicationContext diff --git a/android_sdk/src/main/java/co/optable/sdk/OptableSDK.kt b/android_sdk/src/main/java/co/optable/sdk/OptableSDK.kt index 2c1d270..3bb3c68 100644 --- a/android_sdk/src/main/java/co/optable/sdk/OptableSDK.kt +++ b/android_sdk/src/main/java/co/optable/sdk/OptableSDK.kt @@ -35,25 +35,19 @@ class OptableSDK( private val appInfoHolder = AppInfoHolder(config) private val networkClient = createNetworkClient() private val useCases = UseCases() - private val identifiersEncoder = IdentifiersEncoder(googleAdIdManager) + private val identifiersEncoder = IdentifiersEncoder() private val ceh = CoroutineExceptionHandler { _, e -> Log.e("OptableSDK", "Internal exception: $e") } private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO + ceh) - init { - scope.launch { - googleAdIdManager.fetchAdvertisingId() - } - } - /** * Calls the Optable DCN "identify" API, passing it the list of IDs, * a list of type-prefixed identifiers. */ fun identify(ids: List, listener: OptableResultListener) { scope.launch { - googleAdIdManager.deferredTask.await() - val encodedIds = identifiersEncoder.encode(ids) + val googleAdId = googleAdIdManager.getId() + val encodedIds = identifiersEncoder.encode(ids, googleAdId) val response = networkClient.identify(encodedIds) MainScope().launch { @@ -157,9 +151,9 @@ class OptableSDK( listener: OptableResultListener, ) { scope.launch { - googleAdIdManager.deferredTask.await() - val encodedIds = identifiersEncoder.encode(ids) - val encodedHids = identifiersEncoder.encode(hids) + val googleAdId = googleAdIdManager.getId() + val encodedIds = identifiersEncoder.encode(ids, googleAdId) + val encodedHids = identifiersEncoder.encode(hids, googleAdId) val response = networkClient.targeting( ids = encodedIds, hids = encodedHids, diff --git a/android_sdk/src/main/java/co/optable/sdk/core/GoogleAdIdManager.kt b/android_sdk/src/main/java/co/optable/sdk/core/GoogleAdIdManager.kt index 5ffb46a..d696516 100644 --- a/android_sdk/src/main/java/co/optable/sdk/core/GoogleAdIdManager.kt +++ b/android_sdk/src/main/java/co/optable/sdk/core/GoogleAdIdManager.kt @@ -3,9 +3,10 @@ package co.optable.sdk.core import android.util.Log import co.optable.sdk.OptableConfig import com.google.android.gms.ads.identifier.AdvertisingIdClient -import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.async import kotlinx.coroutines.withTimeoutOrNull internal class GoogleAdIdManager( @@ -13,38 +14,36 @@ internal class GoogleAdIdManager( ) { companion object { - private var adId: String? = null - private var limitAdTracking: Boolean? = null + const val MIN_TIMEOUT_MS = 1L } - val deferredTask = CompletableDeferred() - - fun getId(): String? { - if (limitAdTracking == true) { - return null - } - return adId - } - - suspend fun fetchAdvertisingId() { + // getAdvertisingIdInfo() blocks and can't be cancelled, so reads run here and a caller stops waiting after + // config.advertisingIdTimeoutMs (at least MIN_TIMEOUT_MS), leaving a slow read to finish on its own. + private val readScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + + /** + * Reads the GAID for one API call. Google says not to cache it, since the user can reset it or opt out at any + * time, so it's read every time. Returns null when skipAdvertisingIdDetection is set, the user has opted out, or + * the read fails or takes longer than config.advertisingIdTimeoutMs, which is raised to MIN_TIMEOUT_MS when lower: + * a read always takes some time, so without a wait no GAID could ever be added. + */ + suspend fun getId(): String? { if (config.skipAdvertisingIdDetection) { - deferredTask.complete(null) - return + return null } - val id = withContext(Dispatchers.IO) { - withTimeoutOrNull(3_000) { - fetch() - } + val read = readScope.async { read() } + return withTimeoutOrNull(config.advertisingIdTimeoutMs.coerceAtLeast(MIN_TIMEOUT_MS)) { + read.await() } - deferredTask.complete(id) } - private fun fetch(): String? { + private fun read(): String? { try { val adInfo = AdvertisingIdClient.getAdvertisingIdInfo(config.context) - adId = adInfo.id - limitAdTracking = adInfo.isLimitAdTrackingEnabled + if (adInfo.isLimitAdTrackingEnabled) { + return null + } return adInfo.id } catch (exception: Exception) { Log.w("OptableGaidManager", "Failed to fetch advertising ID: " + exception.message) diff --git a/android_sdk/src/main/java/co/optable/sdk/core/IdentifiersEncoder.kt b/android_sdk/src/main/java/co/optable/sdk/core/IdentifiersEncoder.kt index 1564edb..5dbf733 100644 --- a/android_sdk/src/main/java/co/optable/sdk/core/IdentifiersEncoder.kt +++ b/android_sdk/src/main/java/co/optable/sdk/core/IdentifiersEncoder.kt @@ -9,9 +9,7 @@ import java.util.* /** * Utility object responsible for encoding raw identifiers into Optable EIDs. */ -internal class IdentifiersEncoder( - private val googleAdIdManager: GoogleAdIdManager, -) { +internal class IdentifiersEncoder { companion object { private const val EMAIL = "e" @@ -31,10 +29,12 @@ internal class IdentifiersEncoder( } - fun encode(identifiers: List): List { + /** + * @param googleAdId The device's GAID, added first when not null. + */ + fun encode(identifiers: List, googleAdId: String? = null): List { val result = mutableListOf() - val googleAdId = googleAdIdManager.getId() if (googleAdId != null) { result.addIfNotNull(GAID, googleAdId, ::normalize) } diff --git a/android_sdk/src/test/java/co/optable/sdk/OptableConfigTest.kt b/android_sdk/src/test/java/co/optable/sdk/OptableConfigTest.kt index 4afc023..19205dc 100644 --- a/android_sdk/src/test/java/co/optable/sdk/OptableConfigTest.kt +++ b/android_sdk/src/test/java/co/optable/sdk/OptableConfigTest.kt @@ -43,6 +43,7 @@ class OptableConfigTest { assertEquals(mockApplicationContext, config.context) assertEquals(24 * 60 * 60L, config.cacheTtl) assertEquals(OptableConfig.DEFAULT_TARGETING_CACHE_TTL_SECONDS, config.cacheTtl) + assertEquals(100L, config.advertisingIdTimeoutMs) } @Test @@ -60,7 +61,8 @@ class OptableConfigTest { skipAdvertisingIdDetection = true, consents = customConsents, cacheTtl = 60L, - origin = "https://www.optable.co" + origin = "https://www.optable.co", + advertisingIdTimeoutMs = 250L, ) assertEquals("custom-tenant", config.tenant) @@ -74,6 +76,18 @@ class OptableConfigTest { assertEquals(customConsents, config.consents) assertEquals("https://www.optable.co", config.origin) assertEquals(60L, config.cacheTtl) + assertEquals(250L, config.advertisingIdTimeoutMs) + } + + @Test + fun `advertising ID settings should be settable after construction`() { + val config = OptableConfig(mockContext, "test-tenant", "test-slug") + + config.skipAdvertisingIdDetection = true + config.advertisingIdTimeoutMs = 250L + + assertTrue(config.skipAdvertisingIdDetection) + assertEquals(250L, config.advertisingIdTimeoutMs) } @Test diff --git a/android_sdk/src/test/java/co/optable/sdk/OptableIdentifiersTest.kt b/android_sdk/src/test/java/co/optable/sdk/OptableIdentifiersTest.kt index 96cda77..5c2f3a5 100644 --- a/android_sdk/src/test/java/co/optable/sdk/OptableIdentifiersTest.kt +++ b/android_sdk/src/test/java/co/optable/sdk/OptableIdentifiersTest.kt @@ -1,9 +1,6 @@ package co.optable.sdk -import co.optable.sdk.core.GoogleAdIdManager import co.optable.sdk.core.IdentifiersEncoder -import io.mockk.every -import io.mockk.mockk import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test @@ -13,14 +10,10 @@ import java.util.* class OptableIdentifiersTest { private lateinit var identifiersEncoder: IdentifiersEncoder - private lateinit var mockGoogleAdIdManager: GoogleAdIdManager @Before fun setUp() { - mockGoogleAdIdManager = mockk() - every { mockGoogleAdIdManager.getId() } returns null - - identifiersEncoder = IdentifiersEncoder(mockGoogleAdIdManager) + identifiersEncoder = IdentifiersEncoder() } @Test @@ -224,8 +217,6 @@ class OptableIdentifiersTest { @Test fun `custom gaid`() { - every { mockGoogleAdIdManager.getId() } returns null - val actual = identifiersEncoder.encode(listOf(OptableIdentifier.GoogleGaid("customId"))) val expected = listOf("g:customid") assertEquals(expected, actual) @@ -233,36 +224,28 @@ class OptableIdentifiersTest { @Test fun `both gaids, send two`() { - every { mockGoogleAdIdManager.getId() } returns "managerId" - - val actual = identifiersEncoder.encode(listOf(OptableIdentifier.GoogleGaid("customId"))) + val actual = identifiersEncoder.encode(listOf(OptableIdentifier.GoogleGaid("customId")), "managerId") val expected = listOf("g:managerid", "g:customid") assertEquals(expected, actual) } @Test fun `both gaids equals, send only one`() { - every { mockGoogleAdIdManager.getId() } returns "sameId" - - val actual = identifiersEncoder.encode(listOf(OptableIdentifier.GoogleGaid("sameid"))) + val actual = identifiersEncoder.encode(listOf(OptableIdentifier.GoogleGaid("sameid")), "sameId") val expected = listOf("g:sameid") assertEquals(expected, actual) } @Test fun `gaid from manager`() { - every { mockGoogleAdIdManager.getId() } returns "managerId" - - val actual = identifiersEncoder.encode(emptyList()) + val actual = identifiersEncoder.encode(emptyList(), "managerId") val expected = listOf("g:managerid") assertEquals(expected, actual) } @Test fun `gaid from manager null`() { - every { mockGoogleAdIdManager.getId() } returns null - - val actual = identifiersEncoder.encode(emptyList()) + val actual = identifiersEncoder.encode(emptyList(), null) assertEquals(emptyList(), actual) } diff --git a/android_sdk/src/test/java/co/optable/sdk/core/GoogleAdIdManagerTest.kt b/android_sdk/src/test/java/co/optable/sdk/core/GoogleAdIdManagerTest.kt index f0126d5..fc14e64 100644 --- a/android_sdk/src/test/java/co/optable/sdk/core/GoogleAdIdManagerTest.kt +++ b/android_sdk/src/test/java/co/optable/sdk/core/GoogleAdIdManagerTest.kt @@ -5,17 +5,14 @@ import android.util.Log import co.optable.sdk.OptableConfig import com.google.android.gms.ads.identifier.AdvertisingIdClient import io.mockk.* -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.launch -import kotlinx.coroutines.test.advanceTimeBy -import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test -@OptIn(ExperimentalCoroutinesApi::class) class GoogleAdIdManagerTest { private lateinit var config: OptableConfig @@ -28,6 +25,7 @@ class GoogleAdIdManagerTest { config = mockk { every { context } returns this@GoogleAdIdManagerTest.context every { skipAdvertisingIdDetection } returns false + every { advertisingIdTimeoutMs } returns 1_000 } manager = GoogleAdIdManager(config) @@ -36,151 +34,96 @@ class GoogleAdIdManagerTest { every { Log.w(any(), any()) } returns 0 mockkStatic(AdvertisingIdClient::class) - - resetCompanionState() } @After fun teardown() { unmockkAll() - resetCompanionState() } - @Test - fun `getId returns null initially when nothing is fetched`() { - assertNull(manager.getId()) + private fun adInfo(adId: String, limitAdTracking: Boolean = false) = mockk { + every { id } returns adId + every { isLimitAdTrackingEnabled } returns limitAdTracking } @Test - fun `fetchAdvertisingId completes deferred with null when skip detection is true`() = runTest { - every { config.skipAdvertisingIdDetection } returns true + fun `getId returns the ad id`() = runBlocking { + every { AdvertisingIdClient.getAdvertisingIdInfo(context) } returns adInfo("12345-abcde") - manager.fetchAdvertisingId() + assertEquals("12345-abcde", manager.getId()) + } - assertNull(manager.deferredTask.await()) + @Test + fun `getId reads the ad id on every call`() = runBlocking { + every { AdvertisingIdClient.getAdvertisingIdInfo(context) } returnsMany listOf( + adInfo("first-id"), + adInfo("reset-id"), + adInfo("reset-id", limitAdTracking = true), + ) + + assertEquals("first-id", manager.getId()) + assertEquals("reset-id", manager.getId()) assertNull(manager.getId()) - - verify(exactly = 0) { AdvertisingIdClient.getAdvertisingIdInfo(any()) } + verify(exactly = 3) { AdvertisingIdClient.getAdvertisingIdInfo(context) } } @Test - fun `fetchAdvertisingId successfully fetches ad id and updates state`() = runTest { - val expectedAdId = "12345-abcde" - val mockInfo = mockk { - every { id } returns expectedAdId - every { isLimitAdTrackingEnabled } returns false - } - every { AdvertisingIdClient.getAdvertisingIdInfo(context) } returns mockInfo + fun `getId returns null when limit ad tracking is enabled`() = runBlocking { + every { AdvertisingIdClient.getAdvertisingIdInfo(context) } returns adInfo("12345-abcde", limitAdTracking = true) - manager.fetchAdvertisingId() - - assertEquals(expectedAdId, manager.deferredTask.await()) - assertEquals(expectedAdId, manager.getId()) + assertNull(manager.getId()) } @Test - fun `fetchAdvertisingId sets id but getId returns null when limit ad tracking is enabled`() = runTest { - val expectedAdId = "12345-abcde" - val mockInfo = mockk { - every { id } returns expectedAdId - every { isLimitAdTrackingEnabled } returns true - } - every { AdvertisingIdClient.getAdvertisingIdInfo(context) } returns mockInfo - - manager.fetchAdvertisingId() - - assertEquals(expectedAdId, manager.deferredTask.await()) + fun `getId does not read the ad id when skip detection is true`() = runBlocking { + every { config.skipAdvertisingIdDetection } returns true assertNull(manager.getId()) + verify(exactly = 0) { AdvertisingIdClient.getAdvertisingIdInfo(any()) } } @Test - fun `fetchAdvertisingId completes with null gracefully when exception occurs`() = runTest { - every { AdvertisingIdClient.getAdvertisingIdInfo(context) } throws IllegalStateException("Google Play Services not available") + fun `changing skip detection applies from the next call`() = runBlocking { + every { AdvertisingIdClient.getAdvertisingIdInfo(context) } returns adInfo("12345-abcde") - manager.fetchAdvertisingId() - - assertNull(manager.deferredTask.await()) + assertEquals("12345-abcde", manager.getId()) + every { config.skipAdvertisingIdDetection } returns true assertNull(manager.getId()) - - verify(exactly = 1) { Log.w("OptableGaidManager", any()) } + every { config.skipAdvertisingIdDetection } returns false + assertEquals("12345-abcde", manager.getId()) } - @Test - fun `deferredTask await blocks until fetchAdvertisingId completes`() = runTest { - val expectedAdId = "async-test-id" - val mockInfo = mockk { - every { id } returns expectedAdId - every { isLimitAdTrackingEnabled } returns false - } - every { AdvertisingIdClient.getAdvertisingIdInfo(context) } returns mockInfo - - val testManager = GoogleAdIdManager(config) - - var awaitCompleted = false - val awaitJob = launch { - testManager.deferredTask.await() - awaitCompleted = true - } - - advanceTimeBy(100) - assertEquals("await() should be suspended until fetchAdvertisingId completes", false, awaitCompleted) - - testManager.fetchAdvertisingId() + fun `getId still reads the ad id when the timeout is below the minimum`() = runBlocking { + every { config.advertisingIdTimeoutMs } returns 0 + every { AdvertisingIdClient.getAdvertisingIdInfo(context) } returns adInfo("12345-abcde") - awaitJob.join() + manager.getId() - assertEquals("await() should complete after fetchAdvertisingId", true, awaitCompleted) - assertEquals(expectedAdId, testManager.getId()) + verify(timeout = 1_000, exactly = 1) { AdvertisingIdClient.getAdvertisingIdInfo(context) } } @Test - fun `multiple calls to deferredTask await return the same result`() = runTest { - val expectedAdId = "shared-id-12345" - val mockInfo = mockk { - every { id } returns expectedAdId - every { isLimitAdTrackingEnabled } returns false - } - every { AdvertisingIdClient.getAdvertisingIdInfo(context) } returns mockInfo - - val testManager = GoogleAdIdManager(config) - - val results = mutableListOf() - val jobs = List(5) { - launch { - val result = testManager.deferredTask.await() - results.add(result) - } - } - - advanceTimeBy(50) - assertEquals("No results should be available yet", 0, results.size) - - testManager.fetchAdvertisingId() - - jobs.forEach { it.join() } - - assertEquals(5, results.size) - results.forEach { result -> - assertEquals(expectedAdId, result) - } + fun `getId returns null gracefully when exception occurs`() = runBlocking { + every { AdvertisingIdClient.getAdvertisingIdInfo(context) } throws IllegalStateException("Google Play Services not available") - verify(exactly = 1) { AdvertisingIdClient.getAdvertisingIdInfo(context) } + assertNull(manager.getId()) + verify(exactly = 1) { Log.w("OptableGaidManager", any()) } } + @Test + fun `getId stops waiting after the timeout`() = runBlocking { + every { config.advertisingIdTimeoutMs } returns 50 + every { AdvertisingIdClient.getAdvertisingIdInfo(context) } answers { + Thread.sleep(1_000) + adInfo("slow-id") + } - private fun resetCompanionState() { - try { - val adIdField = GoogleAdIdManager::class.java.getDeclaredField("adId") - adIdField.isAccessible = true - adIdField.set(null, null) + val start = System.currentTimeMillis() + val id = manager.getId() + val elapsed = System.currentTimeMillis() - start - val limitField = GoogleAdIdManager::class.java.getDeclaredField("limitAdTracking") - limitField.isAccessible = true - limitField.set(null, null) - } catch (e: Exception) { - e.printStackTrace() - } + assertNull(id) + assertTrue("waited $elapsed ms", elapsed < 900) } } diff --git a/android_sdk/src/test/java/co/optable/sdk/core/IdentifiersEncoderTest.kt b/android_sdk/src/test/java/co/optable/sdk/core/IdentifiersEncoderTest.kt index 871b71c..02a022f 100644 --- a/android_sdk/src/test/java/co/optable/sdk/core/IdentifiersEncoderTest.kt +++ b/android_sdk/src/test/java/co/optable/sdk/core/IdentifiersEncoderTest.kt @@ -5,8 +5,6 @@ import org.junit.Assert.* import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.kotlin.mock -import org.mockito.kotlin.whenever import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config @@ -15,12 +13,10 @@ import org.robolectric.annotation.Config class IdentifiersEncoderTest { private lateinit var identifiersEncoder: IdentifiersEncoder - private lateinit var mockGoogleAdIdManager: GoogleAdIdManager @Before fun setUp() { - mockGoogleAdIdManager = mock() - identifiersEncoder = IdentifiersEncoder(mockGoogleAdIdManager) + identifiersEncoder = IdentifiersEncoder() } @Test @@ -45,9 +41,10 @@ class IdentifiersEncoderTest { @Test fun `primary ids include device gaid`() { - whenever(mockGoogleAdIdManager.getId()).thenReturn("38400000-8cf0-11bd-b23e-10b96e40000d") - - val ids = identifiersEncoder.encode(listOf(OptableIdentifier.IPv6("2001:db8::1"))) + val ids = identifiersEncoder.encode( + listOf(OptableIdentifier.IPv6("2001:db8::1")), + "38400000-8cf0-11bd-b23e-10b96e40000d", + ) assertEquals( listOf("g:38400000-8cf0-11bd-b23e-10b96e40000d", "i6:2001:db8::1"), @@ -58,9 +55,7 @@ class IdentifiersEncoderTest { @Test fun `caller gaid matching device gaid is deduped for primary ids`() { val deviceGaid = "38400000-8cf0-11bd-b23e-10b96e40000d" - whenever(mockGoogleAdIdManager.getId()).thenReturn(deviceGaid) - - val ids = identifiersEncoder.encode(listOf(OptableIdentifier.GoogleGaid(deviceGaid))) + val ids = identifiersEncoder.encode(listOf(OptableIdentifier.GoogleGaid(deviceGaid)), deviceGaid) assertEquals(listOf("g:$deviceGaid"), ids) }