Created
November 8, 2023 12:38
-
-
Save piszoka/0467eb021b0ad82014d1c324efc5e511 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context | |
import android.content.SharedPreferences | |
import android.content.pm.ApplicationInfo | |
import android.util.Log | |
import androidx.preference.PreferenceManager | |
//## HINT: https://stackoverflow.com/a/70324431 | |
//## HINT: https://groups.google.com/g/google-admob-ads-sdk/c/InmLIbFm8Xg/m/h4fhgUwmAgAJ | |
// https://groups.google.com/g/google-admob-ads-sdk/c/InmLIbFm8Xg/m/HLKLt2B1AgAJ | |
// https://support.google.com/admob/answer/9760862#consent-policies describes the requirements for the different types of serving | |
// modes for EU traffic: | |
// | |
//- Personalized ads - Consent for purposes 1,3,4, Legitimate Interest for 2, 7, 9, 10 | |
//- Non personalized ads - Consent for purpose 1, Legitimate Interest for 2, 7, 9, 10 | |
//- Limited ads - No consent for purpose 1. | |
//## https://groups.google.com/g/google-admob-ads-sdk/ --> search for UMP | |
// Further sources: | |
// https://support.google.com/admanager/answer/9461778 | |
// | |
// Purposes: | |
// 1 - Store and/or access information on a device | |
// 2 - Select basic ads | |
// 3 - Create a personalised ads profile | |
// 4 - Select personalised ads | |
// 5 - Create a personalised content profile | |
// 6 - Select personalised content | |
// 7 - Measure ad performance | |
// 8 - Measure content performance | |
// 9 - Apply market research to generate audience insights | |
// 10 - Develop and improve products | |
// | |
// Special purposes: | |
// 1 - Ensure security, prevent fraud, and debug | |
// 2 - Technically deliver ads or content | |
// | |
// Features: | |
// 1 - Match and combine offline data sources | |
// 2 - Link different devices | |
// 3 - Receive and send automatically sent device characteristics for identification | |
// | |
// Special features: | |
// 1 - Use precise geolocation data | |
// 2 - Actively scan device characteristics for identification | |
@Suppress("MagicNumber") | |
object GoogleAdmobConsentUtils { | |
// 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 | |
//## search here for Google: https://iabeurope.eu/vendor-list-tcf/ | |
private const val GOOGLE_TCF_VENDOR_ID: Int = 755 | |
private val Context.prefMngr: SharedPreferences get() = PreferenceManager.getDefaultSharedPreferences(this.applicationContext) | |
//## IABTCF_PurposeConsents - A string of 0's and 1's up to 10 entries long indicating whether the user provided consent for the | |
//## 10 different purposes | |
private val SharedPreferences.purposeConsents get() = this.getString("IABTCF_PurposeConsents", "") ?: "" | |
//## A string of 0s and 1s that is arbitrarily long, indicating whether a given vendor has been given consent for the previously | |
//## mentioned purposes. Each vendor has an ID indicating their position in the string. For example Google's ID is 755, so if | |
//## Google has been given consent then the 755th character in this string would be a "1". The full vendor list is available here: | |
//## https://iabeurope.eu/vendor-list-tcf/ | |
private val SharedPreferences.vendorConsents get() = this.getString("IABTCF_VendorConsents", "") ?: "" | |
//## A string of 0's and 1's up to 10 entries long indicating whether the app has legitimate interest for the 10 different purposes | |
private val SharedPreferences.vendorLIs get() = this.getString("IABTCF_VendorLegitimateInterests", "") ?: "" | |
//## Similar to the vendor consent string, except that it indicates if the vendor has legitimate interest for the previously | |
//## indicated purposes. | |
private val SharedPreferences.purposeLIs get() = this.getString("IABTCF_PurposeLegitimateInterests", "") ?: "" | |
// whether the user is in the EEA | |
fun isGDPR( | |
ctx: Context | |
): Boolean = ctx.prefMngr.getInt("IABTCF_gdprApplies", 0) == 1 | |
fun canShowAds( | |
ctx: Context | |
): Boolean { | |
val prefs: SharedPreferences = ctx.prefMngr | |
val purposeConsents: String = prefs.purposeConsents | |
val purposeLIs: String = prefs.purposeLIs | |
val hasGoogleVendorConsent: Boolean = prefs.vendorConsents.hasAttribute(index = GOOGLE_TCF_VENDOR_ID) | |
val hasGoogleVendorLI: Boolean = prefs.vendorLIs.hasAttribute(index = GOOGLE_TCF_VENDOR_ID) | |
debug( | |
ctx = ctx, | |
debugStr = "canShowAds", | |
consentForPurposes = listOf(1), | |
hasConsentOrLegitimateInterestForPurposes = listOf(2, 7, 9, 10), | |
purposeConsent = purposeConsents, | |
purposeLI = purposeLIs, | |
hasGoogleVendorConsent = hasGoogleVendorConsent, | |
hasGoogleVendorLI = hasGoogleVendorLI | |
) | |
// Minimum required for at least non-personalized ads | |
return hasConsentFor( | |
purposes = listOf(1), | |
purposeConsent = purposeConsents, | |
hasVendorConsent = hasGoogleVendorConsent | |
) && | |
hasConsentOrLegitimateInterestFor( | |
purposes = listOf(2, 7, 9, 10), | |
purposeConsent = purposeConsents, | |
purposeLI = purposeLIs, | |
hasVendorConsent = hasGoogleVendorConsent, | |
hasVendorLI = hasGoogleVendorLI | |
) | |
} | |
fun canShowPersonalizedAds( | |
ctx: Context | |
): Boolean { | |
val prefs: SharedPreferences = ctx.prefMngr | |
val purposeConsents: String = prefs.purposeConsents | |
val purposeLIs: String = prefs.purposeLIs | |
val hasGoogleVendorConsent: Boolean = prefs.vendorConsents.hasAttribute(index = GOOGLE_TCF_VENDOR_ID) | |
val hasGoogleVendorLI: Boolean = prefs.vendorLIs.hasAttribute(index = GOOGLE_TCF_VENDOR_ID) | |
debug( | |
ctx = ctx, | |
debugStr = "canShowPersonalizedAds", | |
consentForPurposes = listOf(1, 3, 4), | |
hasConsentOrLegitimateInterestForPurposes = listOf(2, 7, 9, 10), | |
purposeConsent = purposeConsents, | |
purposeLI = purposeLIs, | |
hasGoogleVendorConsent = hasGoogleVendorConsent, | |
hasGoogleVendorLI = hasGoogleVendorLI | |
) | |
return hasConsentFor( | |
purposes = listOf(1, 3, 4), | |
purposeConsent = purposeConsents, | |
hasVendorConsent = hasGoogleVendorConsent | |
) && | |
hasConsentOrLegitimateInterestFor( | |
purposes = listOf(2, 7, 9, 10), | |
purposeConsent = purposeConsents, | |
purposeLI = purposeLIs, | |
hasVendorConsent = hasGoogleVendorConsent, | |
hasVendorLI = hasGoogleVendorLI | |
) | |
} | |
// Check if a binary string has a "1" at position "index" (1-based) | |
private fun String.hasAttribute( | |
index: Int | |
): Boolean = this.getOrNull(index - 1) == '1' | |
// Check if consent is given for a list of purposes | |
private fun hasConsentFor( | |
purposes: List<Int>, | |
purposeConsent: String, | |
hasVendorConsent: Boolean | |
): Boolean = hasVendorConsent && | |
purposes.all { p: Int -> purposeConsent.hasAttribute(index = p) } | |
// 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 = purposes.all { p: Int -> | |
(hasVendorLI && purposeLI.hasAttribute(index = p)) || | |
(hasVendorConsent && purposeConsent.hasAttribute(index = p)) | |
} | |
private fun debug( | |
ctx: Context, | |
debugStr: String, | |
consentForPurposes: List<Int>, | |
hasConsentOrLegitimateInterestForPurposes: List<Int>, | |
purposeConsent: String, | |
purposeLI: String, | |
hasGoogleVendorConsent: Boolean, | |
hasGoogleVendorLI: Boolean | |
) { | |
val isDebuggable: Boolean = 0 != (ctx.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) | |
if (!isDebuggable) return | |
val purposes1: String = consentForPurposes.joinToString(separator = ", ") { purpose: Int -> | |
val hasConsent: Boolean = | |
hasConsentFor(purposes = listOf(purpose), purposeConsent = purposeConsent, hasVendorConsent = hasGoogleVendorConsent) | |
return@joinToString "$purpose-$hasConsent" | |
} | |
val purposes2: String = hasConsentOrLegitimateInterestForPurposes.joinToString(separator = ", ") { purpose: Int -> | |
val hasConsent: Boolean = hasConsentOrLegitimateInterestFor( | |
purposes = listOf(purpose), | |
purposeConsent = purposeConsent, | |
purposeLI = purposeLI, | |
hasVendorConsent = hasGoogleVendorConsent, | |
hasVendorLI = hasGoogleVendorLI | |
) | |
return@joinToString "$purpose-$hasConsent" | |
} | |
val outStr: String = listOf( | |
debugStr, | |
" hasGoogleVendorConsent: $hasGoogleVendorConsent", | |
" hasGoogleVendorLI: $hasGoogleVendorLI", | |
" hasConsent for purposes: $purposes1", | |
" hasConsentOrLegitimateInterest for purposes: $purposes2" | |
).joinToString(separator = "\n") | |
Log.d("GoogleAdmobConsentUtils", outStr) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment