From a719aa229108baf2b39771df24a938992642e83f Mon Sep 17 00:00:00 2001 From: John Rosendahl Date: Mon, 9 Nov 2026 18:25:20 -0700 Subject: [PATCH] Keep the Google Advertising ID per SDK instance GoogleAdIdManager kept the ad ID and the Limit Ad Tracking flag in a companion object, shared by every OptableSDK instance. An instance created with skipAdvertisingIdDetection = true, for example after the user withdrew consent, still sent the GAID an earlier instance had read, until the process restarted. Keep both per instance. Every instance already fetched on creation, so the shared state saved nothing. Co-Authored-By: Claude Opus 5.5 --- .../co/optable/sdk/core/GoogleAdIdManager.kt | 7 ++- .../optable/sdk/core/GoogleAdIdManagerTest.kt | 47 ++++++++++++++----- 2 files changed, 37 insertions(+), 17 deletions(-) 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..571fb0b 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 @@ -12,10 +12,9 @@ internal class GoogleAdIdManager( val config: OptableConfig, ) { - companion object { - private var adId: String? = null - private var limitAdTracking: Boolean? = null - } + // Per instance, so an instance created with skipAdvertisingIdDetection never sends an ID another instance read. + private var adId: String? = null + private var limitAdTracking: Boolean? = null val deferredTask = CompletableDeferred() 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..4512648 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 @@ -36,14 +36,11 @@ class GoogleAdIdManagerTest { every { Log.w(any(), any()) } returns 0 mockkStatic(AdvertisingIdClient::class) - - resetCompanionState() } @After fun teardown() { unmockkAll() - resetCompanionState() } @Test @@ -169,18 +166,42 @@ class GoogleAdIdManagerTest { verify(exactly = 1) { AdvertisingIdClient.getAdvertisingIdInfo(context) } } + @Test + fun `getId returns null when skip detection is true even after another instance fetched an ad id`() = runTest { + val mockInfo = mockk { + every { id } returns "12345-abcde" + every { isLimitAdTrackingEnabled } returns false + } + every { AdvertisingIdClient.getAdvertisingIdInfo(context) } returns mockInfo + manager.fetchAdvertisingId() + + val skipConfig = mockk { + every { context } returns this@GoogleAdIdManagerTest.context + every { skipAdvertisingIdDetection } returns true + } + val skipManager = GoogleAdIdManager(skipConfig) + skipManager.fetchAdvertisingId() - private fun resetCompanionState() { - try { - val adIdField = GoogleAdIdManager::class.java.getDeclaredField("adId") - adIdField.isAccessible = true - adIdField.set(null, null) + assertNull(skipManager.getId()) + } - val limitField = GoogleAdIdManager::class.java.getDeclaredField("limitAdTracking") - limitField.isAccessible = true - limitField.set(null, null) - } catch (e: Exception) { - e.printStackTrace() + @Test + fun `limit ad tracking read by one instance does not affect another`() = runTest { + val limitedInfo = mockk { + every { id } returns "12345-abcde" + every { isLimitAdTrackingEnabled } returns true } + val unlimitedInfo = mockk { + every { id } returns "12345-abcde" + every { isLimitAdTrackingEnabled } returns false + } + every { AdvertisingIdClient.getAdvertisingIdInfo(context) } returnsMany listOf(unlimitedInfo, limitedInfo) + + manager.fetchAdvertisingId() + val laterManager = GoogleAdIdManager(config) + laterManager.fetchAdvertisingId() + + assertEquals("12345-abcde", manager.getId()) + assertNull(laterManager.getId()) } }