How to implement UMP SDK correctly for eu consent?
Asked Answered
T

5

29

I have lots of confusion regarding the implementation of the UMP SDK. I have not found much information or a complete tutorial other than google. I am following this and this but unable to understand bellow issues:

  1. Is it required to call MobileAds.initialize() after getting the requesting consent? If so, then where should it be called? It might be called after obtaining the consent:

    public void onConsentFormLoadSuccess(ConsentForm consentForm) {
        if(consentInformation.getConsentStatus() == ConsentInformation.ConsentStatus.OBTAINED) {
    }
    }
    
  2. How would I check if a user is not from EEA? I wanted to request consent or initialize mobile ads based on user location. In Consent SDK, there is a method isRequestLocationInEeaOrUnknown(), but this SDK is deprecated. I have not found something similar in UMP SDK. One approach might be to always requestConsentInfoUpdate and call isConsentFormAvailable inside onConsentInfoUpdateSuccess. This method returns false if the user is not from EEA.

  3. I am always getting consent type consentInformation.getConsentType() 0 or UnKnown. I have tried with different combination but always 0.

  4. Is it required to forward consent information to AdMob SDK or SDK will handle it.

  5. Regarding mediation, I need the consent information but do not know how to get it. From docs: The UMP SDK writes consent status information to local storage

  6. In AdMob -> EU user consent, One of my mediation partners is not included in the Commonly used set of ad technology providers. If I use Custom set of ad technology providers, do I need to include all of Commonly used set of ad technology providers where there are 198 ad tech providers. Or including ad tech providers in Funding Choices is enough.

Tetrarch answered 18/12, 2020 at 3:46 Comment(2)
i need these asnwers too, anyone?Killingsworth
what have you done so far can you share? please!Rame
M
34

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.

  1. 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.

  2. 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)
    }
}

Note PreferenceManager.getDefaultSharedPreferences is not deprecated - you just need to make sure to include the androidx import (import androidx.preference.PreferenceManager). If you include the wrong one (import android.preference.PreferenceManager), it will be marked as deprecated.

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)
}

Edit: Example integration

Here is an example implementation of a ConsentHelper method (in Kotlin) for managing calling the UMP SDK and handling the results. This would be called on app load (e.g. in the activity onCreate) with

ConsentHelper.obtainConsentAndShow(activity) {
    // code to load ads
}

This handles waiting to initialize the MobileAds SDK until after obtaining consent, and then uses a callback to begin loading ads after the consent workflow is complete.

object ConsentHelper {
    private var isMobileAdsInitializeCalled = AtomicBoolean(false)
    private var showingForm = false
    private var showingWarning = false

    private fun initializeMobileAdsSdk(context: Context) {
        if (isMobileAdsInitializeCalled.getAndSet(true)) {
            return
        }

        // Initialize the Google Mobile Ads SDK.
        MobileAds.initialize(context)
    }

    // Called from app settings to determine whether to 
    // show a button so the user can launch the dialog
    fun isUpdateConsentButtonRequired(context: Context) : Boolean {
        val consentInformation = UserMessagingPlatform.getConsentInformation(context)
        return consentInformation.privacyOptionsRequirementStatus ==
                ConsentInformation.PrivacyOptionsRequirementStatus.REQUIRED
    }

    // Called when the user clicks the button to launch
    // the CMP dialog and change their selections
    fun updateConsent(context: Activity) {
        UserMessagingPlatform.showPrivacyOptionsForm(context) { error ->
            val ci = UserMessagingPlatform.getConsentInformation(context)
            handleConsentResult(context, ci, loadAds = {})
        }
    }

    // Called from onCreate or on app load somewhere
    fun obtainConsentAndShow(context: AppCompatActivity, loadAds: ()->Unit) {

        val params = if( BuildConfig.DEBUG ) {
            val debugSettings = ConsentDebugSettings.Builder(context)
                .setDebugGeography(ConsentDebugSettings.DebugGeography.DEBUG_GEOGRAPHY_EEA)
                .addTestDeviceHashedId("YOUR_DEVICE_ID") // Get ID from Logcat
                .build()
            ConsentRequestParameters
                .Builder()
                .setTagForUnderAgeOfConsent(false)
                .setConsentDebugSettings(debugSettings)
                .build()
        }
        else {
            ConsentRequestParameters
                .Builder()
                .setTagForUnderAgeOfConsent(false)
                .build()
        }

        val ci = UserMessagingPlatform.getConsentInformation(context)
        ci.requestConsentInfoUpdate(
            context,
            params,
            {   // Load and show the consent form. Add guard to prevent showing form more than once at a time.
                if( showingForm ) return@requestConsentInfoUpdate

                showingForm = true
                UserMessagingPlatform.loadAndShowConsentFormIfRequired(context) { error: FormError? ->
                    showingForm = false
                    handleConsentResult(context, ci, loadAds)
                }
            },
            { error ->
                // Consent gathering failed.
                Log.w("AD_HANDLER", "${error.errorCode}: ${error.message}")
            })

        // Consent has been gathered already, load ads
        if( ci.canRequestAds() ) {
            initializeMobileAdsSdk(context.applicationContext)
            loadAds()
        }
    }

    private fun handleConsentResult(context: Activity, ci: ConsentInformation, loadAds: ()->Unit) {

        // Consent has been gathered.
        if( ci.canRequestAds() ) {
            initializeMobileAdsSdk(context.applicationContext)
            logConsentChoices(context)
            loadAds()
        }
        else {
            // This is an error state - should never get here
            logConsentChoices(context)
        }
    }

    private fun logConsentChoices(context: Activity) {
        // After completing the consent workflow, check the
        // strings in SharedPreferences to see what they
        // consented to and act accordingly
        val canShow = canShowAds(context)
        val isEEA = isGDPR(context)

        // Check what level of consent the user actually provided
        println("TEST:    user consent choices")
        println("TEST:      is EEA = $isEEA")
        println("TEST:      can show ads = $canShow")
        println("TEST:      can show personalized ads = ${canShowPersonalizedAds(context)}")

        if( !isEEA ) return

        // handle user choice, activate trial mode, etc

    }
}
Millham answered 9/7, 2021 at 2:33 Comment(42)
Is this possible in Obj-c? Thank youSubcontraoctave
Yes, it should be possible in Obj-C, just have to convert the same logic over.Millham
For Flutter, there seems to be a package. Added below: https://mcmap.net/q/393367/-how-to-implement-ump-sdk-correctly-for-eu-consentValer
Is the consent for ads in general? Or for personalized ads? I can't show ads at all if the user doesn't approve? Can't I set Admob to use non-personalized ads in this case?Nymphalid
Correct - this is for ads in general. Even non-personalized ads require consent to store some cookie indicating that you want non-personalized ads. We have raised this repeatedly with them in their forums here and in many other posts there... AFAIK there is no legal reason they couldn't add a "Consent to non-personalized ads" button like the old form had, but they won't.Millham
@TylerV Wait, this is from 2020 ? The SDK and this rule isn't new? About usage of cookies, it's just to save settings of the user for the current app/website, no? Can't be that the rule goes this far.... Look, it says :"the collection, sharing, and use of personal data for personalization of ads. " . It doesn't say ads in general... google.com/about/company/user-consent-policyNymphalid
That thread has been active for 3 years without a resolution - so no, this isn't new. If you look in the forum, this is asked there frequently. I'm not defending their stance, I think it's absurd and is going to hurt revenue since users can just turn off all ads - but their current rule is "a user needs to provide consent for storage and Google in order to show non-personalized ads". Look in that forum thread, they suggested showing "Limited Ads" to me there (I am TV in that thread). I think the best we're going to get is showing limited ads and limiting the app functions.Millham
@TylerV How do you show "limited ads" that are not personalized, after the user didn't give consent? Does Admob support it? You wrote that the consent is needed to show ads of all kinds, no?Nymphalid
Look, I'm not Admob support - I don't know anything more than what was shared in that forum post I linked. Take some time to read through it, and search on the forum for related posts too. Consent is required for "non-personalized ads" but allegedly there is a third kind of limited ads that will show up if no consent was provided. In practice, I haven't seen ads show up when testing but I don't know if that's just very poor fill rate or some other issue.Millham
We have gotten more info from Google in the AdMob forums - "Limited Ads" don't actually work with AdMob, and may or may not work with mediation partners. Fill rates will be 0 or very low at the moment, so it's not really a valid solution.Millham
@TylerV Is it ok&legal to query the current status using the SharedPreferences? Why aren't there official functions for this? How did you come up with this code? The links you've provided seems very hard to understand. Should we always call requestConsentInfoUpdate even after we know the current status? What about getPrivacyOptionsRequirementStatus? Is this saved somewhere too? Or no need to check on this (docs about this seems weird)?Nymphalid
Yes, it is ok and legal. This is what the Google support folks on their forums recommend. As for why haven't they implemented it themselves, you'll have to ask them. We have tried for years to get them to improve this. Go read through all the threads on this in their forum (groups.google.com/g/google-admob-ads-sdk/c/UcveWmtBm4Q/m/…). As for how I came up with this code - I read the docs linked in the answer to the TCF standard...Millham
@androiddeveloper Or this post from 17 minutes ago asking the same thing (groups.google.com/g/google-admob-ads-sdk/c/NOouZpbjuu8)Millham
For implementation details, they have a guide on that here - I would follow that in terms of when to call requestConsentInfoUpdate and read the shared prefs after that is completed. The consent status expires after something like 13 months, so if you are just reading SharedPrefs you would need to also read the expiration date.Millham
@TylerV I was talking about the special checking in the SharedPreferences. Where did Google recommending about this on the link you've provided? How can I check the expiration date? As for getPrivacyOptionsRequirementStatus, I've found out that as opposed to the docs, it's not returning the last value it had on last session. Can you please update your answer to include both expiration date query and the getPrivacyOptionsRequirementStatus if it's saved too?Nymphalid
Have a look at this thread - "The UMP SDK writes the TC String, which is comprised of many bits that can no longer be translated into a single boolean for "personalization". However, you may read the TC String per the spec and use that to determine app behavior." - it is one of many in there where they recommend this. You can search if you want to find more.Millham
You can look here for some other ways to read the SharedPrefs, including expiration dates. However I don't think that is necessary, just call the usual CMP methods in their docs and it will check the expiration for you already.Millham
If you want to find other information in the TC strings, please read the docs. Search the Google Forum for "TC String" to see tons of results recommending how to use them.Millham
The code for reading the expiration date is here - but like I said, this is not necessary. You can use their regular API for this (assuming they have fixed the bugs in that Github post)Millham
@TylerV Yet there is a simple boolean function of canRequestAds, so we should be able to query this from SharedPrefs too, no? I want to use cached results whenever possible. They are much faster. Thank you about the expiration date. Do you know how to solve about getPrivacyOptionsRequirementStatus , though? It's not working as the docs say about it, returning last session result...Nymphalid
They added canRequstAds recently, but the docs on that just state that it means you have finished collecting consent, not whether the consent gathered is sufficient to actually show ads. In any case, no I don't know about the privacy options. I recommend you ask an actual question about it instead of comments.Millham
@TylerV But their docs show that after canRequestAds they initialize ads. What more can be done except starting to show ads... It's not as if the developer will keep showing the ad-consent dialog... About the expiration date, not sure how this works. The function is private, and it says it will delete it. In the website, it says it's because Google's SDK doesn't use it properly "Google UMP fails to do that and instead serves consent data that needs to be considered outdated".Nymphalid
canRequestAds means you can request them, not that they can be successfully shown. If you don't believe me go test it out. I have tested this all extensively. Im done trying here, if you don't believe me that's fine but this is no longer productive. I don't work for Google, stop trying to argue with me about Google API decisions.Millham
@TylerV Yes, that's the purpose of it, no? That after you use the consent stuff, you can start loading ads and show them. What else you can do... What do you mean to test it? Doesn't the setDebugGeography function just forces to return values that would mean that you need to show the consent? It won't affect the ads as long as I don't really live in GDPR-related places, no? I never saw a difference. If it's not canRequestAds, what else can I use? Isn't your canShowAds doing the same? Is the website still correct about the expiration date?Nymphalid
Did you even read my response? Test it out in your app and you will answer these questions. YES, the debug geography works correctly to test, it's not just returning dummy values. Enable it, add debug logging, and check the response of these functions with different interactions. NO, canRequestAds and canShowAds are not the same. The former just checks if the user has been shown the consent dialog (if in the EEA), the latter checks if they actually provided sufficient consent. Even if they click "deny all" canRequestAds still returns true.Millham
Follow the instructions here to test it. They work. You have to call addTestDeviceHashedId as well as setDebugGeographyMillham
@TylerV Sure I've read. Sorry I've made you upset. I already tested it using what I've written, including addTestDeviceHashedId (they didn't mention there how to fetch the hashID, BTW, but I added it in code). I thought perhaps you are talking about tests I didn't perform. The canRequestAds means that indeed you can start to request ads, because the user has finished with the consent. As developers what else can we do... Denying all doesn't mean there won't be any ads (theoretically, at least), just that they will have less chance to be shown due to more no-fill.Nymphalid
@TylerV In any case, is there a way to get the exact same cached result of canRequestAds and of getPrivacyOptionsRequirementStatus (without using requestConsentInfoUpdate when it's cached already), because that should be enough to start requesting ads? The "canShowAds" seems more restricting according to what you wrote, so it could return false even though we've been through the consent flow already.Nymphalid
FYI, if you want another reference for Google recommending reading SharedPrefs - it's in their official docs nowMillham
@TylerV Yes I've noticed this after I've written to you. I'm very sure it's new because I already visited this exact webpage in the past and I don't remember this. Sadly they don't explain here about fetching canRequestAds (let alone getPrivacyOptionsRequirementStatus ) . They don't even explain what their snippet does exactly by explaining what "Purpose 1" means here (though it says on the link, but it doesn't say how it was fetched as being first character). I really wish they had a proper SDK. Feels weird to reach SharedPreferences to get the values ourselves, and make some sense of them.Nymphalid
"Purpose 1" is the first character. That's what the "1" means. Purpose 2 is the next character... That's all explained in the TCF guide. And they explain pretty clearly what "canRequestAds" does in their docs... "Before requesting ads in your app, check if you have obtained consent from the user using canRequestAds()"Millham
You use getPrivacyOptionsRequirementStatus() to determine if you are required to show a button letting your users re-launch the consent dialog. Again, this is all in their docs, and they have provided this as an API - the fact that you "don't want to use it" doesn't mean it isn't thereMillham
@TylerV About canRequestAds, it's as I wrote... About getPrivacyOptionsRequirementStatus, this wasn't my question and I didn't say I don't know how to use it. My question was about getting the cached result of it, similar to how you did something here using SharedPreferences. I already use this function, but it's working only after each time I call the function requestConsentInfoUpdate. I didn't say anywhere that I don't want to use it.Nymphalid
If you have a question, post a new question, not a comment. This is not the place for explaining how to use the new API.Millham
@TylerV I think this code is missing something very important, either a third value ("unknown") or a new function, and that's in case the data isn't stored yet as requestConsentInfoUpdate wasn't called yet. Shouldn't the functions have another value (or another function) to check if we can even use them? For example, before checking if it's GDPR or not, maybe we can check if "IABTCF_gdprApplies" even exists? For me "isGDPR" and "canShowAds" both return false for before and after calling requestConsentInfoUpdate because of this.Nymphalid
Yes... if you call these before the SDK has written to SharedPreferences you won't be able to read the values that haven't been written yet. That's why you have to call requestConsentInfoUpdate and wait for the callback before using any of this. They provided an API already for updating the TCF string - you need to use it if you want any of this to function. These methods are for deciphering the output of the CMP SDK - you cannot use them if it hasn't output anything.Millham
@TylerV The thing is that I wanted to use the functions here before if possible, so that I could start loading ads sooner when cached data is available already (and not expired). If you already call requestConsentInfoUpdate and wait for the result of it, you already know if it's GDPR or not via its API, and also check that you can start requesting ads (though you won't know what kind of ads). Another thing that's missing here is in case multiple vendors are involved, as here it's assuming only Admob is present, and sadly this makes things more annoying.Nymphalid
@TylerV So I think it should have an additional function/value to tell for the case that data can't be fetched yet, as it doesn't exist or it's expired. This will help to know if it's safe to use what the functions return, or need to wait for requestConsentInfoUpdateNymphalid
That's what canRequestAds does already. You're reinventing the wheel here. I added a sample implementation to the answer that shows how it all fits together. If you want to implement it differently in your app go ahead, but stop trying to have extended discussions here.Millham
@TylerV I wish this was true. Sadly it's not, according to my tests. Calling canRequestAds doesn't check the previous session when called before requestConsentInfoUpdate, ever. Otherwise I would have gotten true on a new session, after getting consent from the previous one or finding out it's not needed. It's also not mentioned on the docs. Same goes for getPrivacyOptionsRequirementStatus, yet there it's mentioned that it should return you the value from previous session.Nymphalid
I have also tested this (just went and tested it again) - and it works. On the second call canRequestAds() is true and ads can be loaded immediately. getPrivacyOptionsRequirementStatus also works as expected. If you are seeing differently it's likely an issue in your implementation - you can compare against the docs if you want. Regardless, "how do I use the SDK without calling the methods it provides" is an entirely different question than the one being asked and answered here. If you have a question, ask a question.Millham
@TylerV Seems I've found the issue: I'm still correct that calling it alone before calling requestConsentInfoUpdate doesn't fetch the data from cache, but if it's done right after calling requestConsentInfoUpdate , it will use the latest cached data. I've noticed it by comparing as you said, to the sample they've shown. Doesn't make sense as it's the opposite of what's documented. This also sadly still means it requires Activity instead, while the code here doesn't require. So the SDK still is not a good way to check the cached data.Nymphalid
D
15

As far as I experience / understand Google Funding Choices through User Messaging Platform (actually it is not even clear why this has two different name) is TOTALLY USELESS WITHIN THE EU.

You are welcome to correct me, but as I experience on 8.3.2021:

If user clicks “Manage options” and then “Submit” and leaves the “Store and/or access information on a device” switch OFF then AdMob does not show any advertisement to the user. Thus, you may end up paying for resources (cloud services, employee, etc.) to provide a free app to your user. Based on the starting date of emerging the issue (the date of the posts and comments I see in this topic) it is a low priority problem to Google and/or to AdMob. consentInformation.getConsentType() always returns value 0. This actually proves (or at least I think) that how low priority this issue has on their list. It would be possible to check whether user consented to serve non-personalized ads at least through this getter. Then we could show him instructions how to properly opt-out and let him use the app for free. However, it seems that this is out of the interests of the developers.

Once again, anybody is welcome to correct me, maybe only I had this negative experience.

Dyspeptic answered 8/3, 2021 at 10:36 Comment(6)
I have the same experience... is it working for you now?Houseman
I did not try since 8.3.2021. You can try to search for another consent management platform (e.g. Tealium for Xamarin) or you can also exchange AdMob for one of its competitors. This is one of the best ways to force companies to make quality products ;-)Dyspeptic
I have exactly the same experience. What is hard to understand is that even on Google's support forum they recommend use other consent frameworks. I have feeling that UMP is stillborn baby.Freitas
This makes UMP useless for most cases in my opinion. I checked the android UMP SDK release notes and two days ago they made an update (after 12 months). The main reason I wanted to use UMP was to show the ad technology vendor list to the (GDPR) users. But nah...I will just download the vendor list and publish it on the website and/or in the app...wasted the last 3 days with UMP. And google support is incredibly ludicrous in this case: Example (check the dates)Selenaselenate
@Selenaselenate Hey, are you currently using it? Can't we show personalized ads without using UMP? Many apps don't use it and I'm a bit confused.Edgardoedge
@AtakanYildirim No, I made my own implementation. Not sure when Google's UMP will be ready for use. Haven't checked the status of it since July though. However, I wouldn't recommend personalized ads for the EU without informing about the ad vendors.Selenaselenate
S
4

I would like to bring some thoughts:

1. Is it required to call MobileAds.initialize() after getting the requesting consent?

Yes it is

2. How would I check if a user is not from EEA?

You can use consentInformation.getConsentStatus() like this:

if (consentInformation.getConsentStatus()== ConsentInformation.ConsentStatus.NOT_REQUIRED) {

}

You can test this function with this:

ConsentRequestParameters.Builder consentRequest = new ConsentRequestParameters.Builder()
    .setTagForUnderAgeOfConsent(false);
        
ConsentDebugSettings debugSettings = new ConsentDebugSettings.Builder(activity)
    .setDebugGeography(ConsentDebugSettings.DebugGeography.DEBUG_GEOGRAPHY_NOT_EEA)
    //.setDebugGeography(ConsentDebugSettings.DebugGeography.DEBUG_GEOGRAPHY_EEA)
    //.setDebugGeography(ConsentDebugSettings.DebugGeography.DEBUG_GEOGRAPHY_DISABLED)
    .addTestDeviceHashedId("Your device Hashed Id")
    .build();
    
consentRequest.setConsentDebugSettings(debugSettings);
ConsentRequestParameters consentRequestParameters = consentRequest.build()

But don't forget to call consentInformation.reset() each time.

3. I am always getting consent type consentInformation.getConsentType() 0.

getConsentType() is useless and was removed in user-messaging-platform:2.0.0. For me the reason is simple: with this new platform, there's no more a double state, the user granted, the user didn't granted. Now it's more like a 3 states: 1-yes_for_all, 2-no_for_all, 3-customized_by_user

4. Is it required to forward consent information to AdMob SDK or SDK will handle it.

Admob SDK will handle it. That's why you don't need the getConsentType() unless you wanted to show the user choices. But for that, it just better to reopen the consent form. Too bad the consent form doesn't load the correct settings of the user.

5. Regarding mediation, I need the consent information but do not know how to get it.

Here as stated by @Tyler V.

6. In AdMob -> EU user consent, One of my mediation partners is not included in the Commonly used set of ad technology providers. If I use Custom set of ad technology providers, do I need to include all of Commonly used set of ad technology providers where there are 198 ad tech providers. Or including ad tech providers in Funding Choices is enough.

I think including ad tech providers in Funding Choices is enough.

Spike answered 3/1, 2022 at 4:16 Comment(2)
Admob SDK will handle it -- that is what I wanted to know. I wonder why the official document doesn't clearly mention that the consent result is hold by them on the cloud.Nephelinite
@Nephelinite the result is not "hold by them on the cloud". The result is saved as a string in the SharedPreferences of the app. Admob SDK will read this string before providing ads.Spike
V
1

In case of using Flutter and interested in the current consent status, it seems possible by using:

iabtcf_consent_info

https://pub.dev/packages/iabtcf_consent_info

Using the package above with this helper class works fine for me.

Call canShowAds() to figure out whether the user should hit a paywall.

/// Call canShowAds() to determine whether ads are to be shown at all.
/// Useful for setting up a paywall.
///
/// Methods return NULL if no consent info could be read (yet).
class AdmobConsentHelper {

  /// General Data Protection Regulation (EU) (GDPR) is a regulation
  /// in EU law on data protection and privacy in the European Union (EU)
  /// and the European Economic Area (EEA).
  Future<bool?> isGDPR() async {
    return (await _consentInfo())?.gdprApplies;
  }

  Future<bool?> canShowAds() async {
    // 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

    BasicConsentInfo? info = await _consentInfo();
    if (info != null) {
      if (info is ConsentInfo) {
        List<DataUsagePurpose> list1 = [
          DataUsagePurpose.storeAndAccessInformationOnADevice // 1
        ];

        List<DataUsagePurpose> list2 = [
          DataUsagePurpose.selectBasicAds, // 2
          DataUsagePurpose.measureAdPerformance, // 7
          DataUsagePurpose.applyMarketResearchToGenerateAudienceInsights, // 9
          DataUsagePurpose.developAndImproveProducts // 10
        ];

        return _hasConsent(info, list1) &&
            _hasConsentOrLegitimateInterest(info, list2);
      }
      return true;
    }
    return null;
  }

  Future<bool?> canShowPersonalizedAds() async {
    // 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

    BasicConsentInfo? info = await _consentInfo();
    if (info != null) {
      if (info is ConsentInfo) {
        List<DataUsagePurpose> list1 = [
          DataUsagePurpose.storeAndAccessInformationOnADevice, // 1
          DataUsagePurpose.createAPersonalisedAdsProfile, // 3
          DataUsagePurpose.selectPersonalisedAds, // 4
        ];

        List<DataUsagePurpose> list2 = [
          DataUsagePurpose.selectBasicAds, // 2
          DataUsagePurpose.measureAdPerformance, // 7
          DataUsagePurpose.applyMarketResearchToGenerateAudienceInsights, // 9
          DataUsagePurpose.developAndImproveProducts // 10
        ];

        return _hasConsent(info, list1) &&
            _hasConsentOrLegitimateInterest(info, list2);
      }
      return true;
    }
    return null;
  }

  _hasConsentOrLegitimateInterest(
      ConsentInfo info, List<DataUsagePurpose> purposes) {
    return purposes.every((purpose) =>
        info.publisherConsent.contains(purpose) ||
        info.publisherLegitimateInterests.contains(purpose));
  }

  _hasConsent(ConsentInfo info, List<DataUsagePurpose> purposes) {
    return purposes.every((purpose) => info.publisherConsent.contains(purpose));
  }

  Future<BasicConsentInfo?> _consentInfo() async {
    return await IabtcfConsentInfo.instance.currentConsentInfo();
  }
}
Valer answered 27/6, 2022 at 18:55 Comment(2)
What is this language? It seems Java, but is it? Also, about "canShowAds", is it the same as ConsentInformation.canRequestAds? If not, how can I fetch the cached value of it without calling "requestConsentInfoUpdate"?Nymphalid
@android_developer It is a Dart package.Valer
D
0

I have the same doubts, in particular on point 3, even in my case the consent type is always 0. I saw in this video that based on that value a different adRequest is initialized

About point 2, I set the Consentdebugsettings with user no EEA (DEBUG_GEOGRAPHY_NOT_EEA,little different from what was done on the official guide) and the method consentInformation.isConsentFormAvailable() returned false

Donohoe answered 26/12, 2020 at 22:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.