diff --git a/src/main/kotlin/com/mparticle/kits/AppsFlyerKit.kt b/src/main/kotlin/com/mparticle/kits/AppsFlyerKit.kt index d4cd230..cac16a3 100644 --- a/src/main/kotlin/com/mparticle/kits/AppsFlyerKit.kt +++ b/src/main/kotlin/com/mparticle/kits/AppsFlyerKit.kt @@ -496,23 +496,25 @@ class AppsFlyerKit : AppsFlyerConsentValues.DENIED.consentValue -> adPersonalizationConsentValue = false } - val clientConsentSettings = parseToNestedMap(consentState.toString()) + consentState?.let { state -> + val clientConsentSettings = parseToNestedMap(state.toString()) - parseConsentMapping(settings[CONSENT_MAPPING]).iterator().forEach { currentConsent -> + parseConsentMapping(settings[CONSENT_MAPPING]).iterator().forEach { currentConsent -> - val isConsentAvailable = - searchKeyInNestedMap(clientConsentSettings, key = currentConsent.key) + val isConsentAvailable = + searchKeyInNestedMap(clientConsentSettings, key = currentConsent.key) - if (isConsentAvailable != null) { - val isConsentGranted: Boolean = - JSONObject(isConsentAvailable.toString()).opt("consented") as Boolean + if (isConsentAvailable != null) { + val isConsentGranted: Boolean = + JSONObject(isConsentAvailable.toString()).opt("consented") as Boolean - when (currentConsent.value) { - "ad_storage" -> adStorageConsentValue = isConsentGranted + when (currentConsent.value) { + "ad_storage" -> adStorageConsentValue = isConsentGranted - "ad_user_data" -> adUserDataConsentValue = isConsentGranted + "ad_user_data" -> adUserDataConsentValue = isConsentGranted - "ad_personalization" -> adPersonalizationConsentValue = isConsentGranted + "ad_personalization" -> adPersonalizationConsentValue = isConsentGranted + } } } } diff --git a/src/test/kotlin/com/mparticle/kits/AppsflyerKitTests.kt b/src/test/kotlin/com/mparticle/kits/AppsflyerKitTests.kt index e2dfbe3..9ecc842 100644 --- a/src/test/kotlin/com/mparticle/kits/AppsflyerKitTests.kt +++ b/src/test/kotlin/com/mparticle/kits/AppsflyerKitTests.kt @@ -5,6 +5,7 @@ import android.content.Context import android.net.Uri import com.appsflyer.AppsFlyerLib import com.mparticle.MParticle +import com.mparticle.MParticle.LogLevel import com.mparticle.MParticleOptions import com.mparticle.commerce.CommerceEvent import com.mparticle.commerce.Product @@ -15,6 +16,7 @@ import com.mparticle.identity.IdentityApi import com.mparticle.identity.MParticleUser import com.mparticle.internal.CoreCallbacks import com.mparticle.internal.CoreCallbacks.KitListener +import com.mparticle.internal.Logger import org.json.JSONArray import org.json.JSONException import org.json.JSONObject @@ -244,6 +246,106 @@ class AppsflyerKitTests { Assert.assertEquals(false, notExpectedConsentKey3) } + @Test + @Throws(Exception::class) + fun testSetConsentWhenGDPRAppliesWithoutConsentStateUsesDefaults() { + val map = HashMap() + map["defaultAdStorageConsent"] = "Granted" + map["gdprApplies"] = "true" + map["consentMapping"] = + "[{\\\"jsmap\\\":null,\\\"map\\\":\\\"Performance\\\",\\\"maptype\\\":\\\"ConsentPurposes\\\",\\\"value\\\":\\\"ad_user_data\\\"},{\\\"jsmap\\\":null,\\\"map\\\":\\\"Marketing\\\",\\\"maptype\\\":\\\"ConsentPurposes\\\",\\\"value\\\":\\\"ad_personalization\\\"},{\\\"jsmap\\\":null,\\\"map\\\":\\\"testconsent\\\",\\\"maptype\\\":\\\"ConsentPurposes\\\",\\\"value\\\":\\\"ad_storage\\\"}]" + map["defaultAdUserDataConsent"] = "Denied" + map["defaultAdPersonalizationConsent"] = "Unspecified" + + kit.configuration = + KitConfiguration.createKitConfiguration(JSONObject().put("as", map.toMutableMap())) + + val method: Method = + AppsFlyerKit::class.java.getDeclaredMethod( + "setConsent", + ConsentState::class.java, + ) + method.isAccessible = true + method.invoke(kit, null) + + val afConsentResults = appsflyer.getConsentState() + Assert.assertEquals(true, afConsentResults["isUserSubjectToGDPR"]) + Assert.assertEquals(false, afConsentResults["hasConsentForDataUsage"]) + Assert.assertFalse(afConsentResults.containsKey("hasConsentForAdsPersonalization")) + Assert.assertEquals(true, afConsentResults["hasConsentForAdStorage"]) + } + + @Test + @Throws(Exception::class) + fun testSetConsentWhenGDPRAppliesWithoutConsentStateDoesNotLogJsonError() { + val map = HashMap() + map["gdprApplies"] = "true" + map["defaultAdStorageConsent"] = "Granted" + map["consentMapping"] = + "[{\\\"jsmap\\\":null,\\\"map\\\":\\\"Marketing\\\",\\\"maptype\\\":\\\"ConsentPurposes\\\",\\\"value\\\":\\\"ad_personalization\\\"}]" + + kit.configuration = + KitConfiguration.createKitConfiguration(JSONObject().put("as", map.toMutableMap())) + + val errorLog = RecordingLogHandler() + Logger.setLogHandler(errorLog) + Logger.setMinLogLevel(LogLevel.ERROR, true) + try { + val method: Method = + AppsFlyerKit::class.java.getDeclaredMethod( + "setConsent", + ConsentState::class.java, + ) + method.isAccessible = true + method.invoke(kit, null) + } finally { + Logger.setLogHandler(Logger.DefaultLogHandler()) + } + + Assert.assertNull( + "setConsent(null) must not invoke parseToNestedMap; got error: ${errorLog.lastError}", + errorLog.lastError, + ) + } + + private class RecordingLogHandler : Logger.AbstractLogHandler() { + var lastError: Throwable? = null + + override fun isADBLoggable( + tag: String?, + logLevel: Int, + ): Boolean = true + + override fun verbose( + error: Throwable?, + message: String?, + ) {} + + override fun info( + error: Throwable?, + message: String?, + ) {} + + override fun debug( + error: Throwable?, + message: String?, + ) {} + + override fun warning( + error: Throwable?, + message: String?, + ) {} + + override fun error( + error: Throwable?, + message: String?, + ) { + if (error != null) { + lastError = error + } + } + } + @Test @Throws(Exception::class) fun testConsentWhenGDPRAppliedWithConsentDefaults() {