I've been working through this myself and while I don't have answers to all your questions, I have figured out a few of them.
The UMP writes its output to some strings in SharedPreferences
, outlined here. You can write some helper methods to query these strings to find out what level of ad consent the user has given or whether the user is EEA or not.
How to check if the user is EEA? You can check the IABTCF_gdprApplies
integer in SharedPreferences and if it is 1, the user is EEA. If it is 0 the user is not.
How to get the consent type? This part gets more complicated. The Google docs here outline what permissions are needed for personalized and non-personalized ads. To get this you need to look at 4 strings from the SharedPreference: IABTCF_PurposeConsents
, IABTCF_PurposeLegitimateInterests
, IABTCF_VendorConsents
and IABTCF_VendorLegitimateInterests
. As others have noted, it is nearly impossible for a user to actually select the non-personalized ad configuration since they have to not only select "Store Information on Device" but also scroll through hundreds of non-alphabetically organized vendors to find and also select "Google" (vendor ID 755 in those strings). This means that for all practical purposes they will either select personalized ads (Consent All) or have a nice ad-free app they paid nothing for. You can at least use these checks to put up a paywall, disable Cloud features, or otherwise handle that scenario as you see fit.
I made some helper methods to find these states.
Kotlin
fun isGDPR(): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext)
val gdpr = prefs.getInt("IABTCF_gdprApplies", 0)
return gdpr == 1
}
fun canShowAds(): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext)
//https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/TCFv2/IAB%20Tech%20Lab%20-%20CMP%20API%20v2.md#in-app-details
//https://support.google.com/admob/answer/9760862?hl=en&ref_topic=9756841
val purposeConsent = prefs.getString("IABTCF_PurposeConsents", "") ?: ""
val vendorConsent = prefs.getString("IABTCF_VendorConsents","") ?: ""
val vendorLI = prefs.getString("IABTCF_VendorLegitimateInterests","") ?: ""
val purposeLI = prefs.getString("IABTCF_PurposeLegitimateInterests","") ?: ""
val googleId = 755
val hasGoogleVendorConsent = hasAttribute(vendorConsent, index=googleId)
val hasGoogleVendorLI = hasAttribute(vendorLI, index=googleId)
// Minimum required for at least non-personalized ads
return hasConsentFor(listOf(1), purposeConsent, hasGoogleVendorConsent)
&& hasConsentOrLegitimateInterestFor(listOf(2,7,9,10), purposeConsent, purposeLI, hasGoogleVendorConsent, hasGoogleVendorLI)
}
fun canShowPersonalizedAds(): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext)
//https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/TCFv2/IAB%20Tech%20Lab%20-%20CMP%20API%20v2.md#in-app-details
//https://support.google.com/admob/answer/9760862?hl=en&ref_topic=9756841
val purposeConsent = prefs.getString("IABTCF_PurposeConsents", "") ?: ""
val vendorConsent = prefs.getString("IABTCF_VendorConsents","") ?: ""
val vendorLI = prefs.getString("IABTCF_VendorLegitimateInterests","") ?: ""
val purposeLI = prefs.getString("IABTCF_PurposeLegitimateInterests","") ?: ""
val googleId = 755
val hasGoogleVendorConsent = hasAttribute(vendorConsent, index=googleId)
val hasGoogleVendorLI = hasAttribute(vendorLI, index=googleId)
return hasConsentFor(listOf(1,3,4), purposeConsent, hasGoogleVendorConsent)
&& hasConsentOrLegitimateInterestFor(listOf(2,7,9,10), purposeConsent, purposeLI, hasGoogleVendorConsent, hasGoogleVendorLI)
}
// Check if a binary string has a "1" at position "index" (1-based)
private fun hasAttribute(input: String, index: Int): Boolean {
return input.length >= index && input[index-1] == '1'
}
// Check if consent is given for a list of purposes
private fun hasConsentFor(purposes: List<Int>, purposeConsent: String, hasVendorConsent: Boolean): Boolean {
return purposes.all { p -> hasAttribute(purposeConsent, p)} && hasVendorConsent
}
// Check if a vendor either has consent or legitimate interest for a list of purposes
private fun hasConsentOrLegitimateInterestFor(purposes: List<Int>, purposeConsent: String, purposeLI: String, hasVendorConsent: Boolean, hasVendorLI: Boolean): Boolean {
return purposes.all { p ->
(hasAttribute(purposeLI, p) && hasVendorLI) ||
(hasAttribute(purposeConsent, p) && hasVendorConsent)
}
}
Swift
func isGDPR() -> Bool {
let settings = UserDefaults.standard
let gdpr = settings.integer(forKey: "IABTCF_gdprApplies")
return gdpr == 1
}
// Check if a binary string has a "1" at position "index" (1-based)
private func hasAttribute(input: String, index: Int) -> Bool {
return input.count >= index && String(Array(input)[index-1]) == "1"
}
// Check if consent is given for a list of purposes
private func hasConsentFor(_ purposes: [Int], _ purposeConsent: String, _ hasVendorConsent: Bool) -> Bool {
return purposes.allSatisfy { i in hasAttribute(input: purposeConsent, index: i) } && hasVendorConsent
}
// Check if a vendor either has consent or legitimate interest for a list of purposes
private func hasConsentOrLegitimateInterestFor(_ purposes: [Int], _ purposeConsent: String, _ purposeLI: String, _ hasVendorConsent: Bool, _ hasVendorLI: Bool) -> Bool {
return purposes.allSatisfy { i in
(hasAttribute(input: purposeLI, index: i) && hasVendorLI) ||
(hasAttribute(input: purposeConsent, index: i) && hasVendorConsent)
}
}
private func canShowAds() -> Bool {
let settings = UserDefaults.standard
//https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/TCFv2/IAB%20Tech%20Lab%20-%20CMP%20API%20v2.md#in-app-details
//https://support.google.com/admob/answer/9760862?hl=en&ref_topic=9756841
let purposeConsent = settings.string(forKey: "IABTCF_PurposeConsents") ?? ""
let vendorConsent = settings.string(forKey: "IABTCF_VendorConsents") ?? ""
let vendorLI = settings.string(forKey: "IABTCF_VendorLegitimateInterests") ?? ""
let purposeLI = settings.string(forKey: "IABTCF_PurposeLegitimateInterests") ?? ""
let googleId = 755
let hasGoogleVendorConsent = hasAttribute(input: vendorConsent, index: googleId)
let hasGoogleVendorLI = hasAttribute(input: vendorLI, index: googleId)
// Minimum required for at least non-personalized ads
return hasConsentFor([1], purposeConsent, hasGoogleVendorConsent)
&& hasConsentOrLegitimateInterestFor([2,7,9,10], purposeConsent, purposeLI, hasGoogleVendorConsent, hasGoogleVendorLI)
}
private func canShowPersonalizedAds() -> Bool {
let settings = UserDefaults.standard
//https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/TCFv2/IAB%20Tech%20Lab%20-%20CMP%20API%20v2.md#in-app-details
//https://support.google.com/admob/answer/9760862?hl=en&ref_topic=9756841
// required for personalized ads
let purposeConsent = settings.string(forKey: "IABTCF_PurposeConsents") ?? ""
let vendorConsent = settings.string(forKey: "IABTCF_VendorConsents") ?? ""
let vendorLI = settings.string(forKey: "IABTCF_VendorLegitimateInterests") ?? ""
let purposeLI = settings.string(forKey: "IABTCF_PurposeLegitimateInterests") ?? ""
let googleId = 755
let hasGoogleVendorConsent = hasAttribute(input: vendorConsent, index: googleId)
let hasGoogleVendorLI = hasAttribute(input: vendorLI, index: googleId)
return hasConsentFor([1,3,4], purposeConsent, hasGoogleVendorConsent)
&& hasConsentOrLegitimateInterestFor([2,7,9,10], purposeConsent, purposeLI, hasGoogleVendorConsent, hasGoogleVendorLI)
}