23

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.

niqueco
  • 2,261
  • 19
  • 39
Patriotic
  • 2,103
  • 4
  • 26
  • 36

5 Answers5

20

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

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)
}
Tyler V
  • 9,694
  • 3
  • 26
  • 52
  • Is this possible in Obj-c? Thank you – Reanimation Jun 08 '22 at 10:17
  • Yes, it should be possible in Obj-C, just have to convert the same logic over. – Tyler V Jun 08 '22 at 12:04
  • For Flutter, there seems to be a package. Added below: https://stackoverflow.com/a/72777100/12098106 – Dabbel Jun 27 '22 at 18:56
  • 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? – android developer Aug 19 '23 at 11:37
  • 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](https://groups.google.com/g/google-admob-ads-sdk/c/UcveWmtBm4Q/m/Fb-rtp2vAwAJ) 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. – Tyler V Aug 19 '23 at 13:27
  • @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... https://www.google.com/about/company/user-consent-policy/ – android developer Aug 19 '23 at 16:24
  • 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. – Tyler V Aug 20 '23 at 03:34
  • @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? – android developer Aug 20 '23 at 12:39
  • 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](https://support.google.com/admob/answer/10105530?hl=en) 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. – Tyler V Aug 20 '23 at 13:21
12

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.

YAQ
  • 121
  • 1
  • 4
  • 2
    I have the same experience... is it working for you now? – franswa Mar 23 '21 at 15:57
  • 2
    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 ;-) – YAQ Mar 25 '21 at 09:03
  • 3
    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. – Tomáš Hubálek Apr 16 '21 at 12:07
  • 3
    This makes UMP useless for most cases in my opinion. I checked the [android UMP SDK release notes](https://developers.google.com/admob/ump/android/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](https://groups.google.com/g/google-admob-ads-sdk/c/KUXEPzh1o_A) (check the dates) – goldensoju Jul 14 '21 at 12:21
  • @goldensoju 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. – Atakan Yildirim Dec 29 '21 at 14:50
  • 1
    @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. – goldensoju Dec 31 '21 at 05:38
3

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.

Simon
  • 1,890
  • 19
  • 26
  • *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. – hata May 29 '22 at 07:30
  • @hata 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. – Simon Aug 06 '22 at 01:33
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

gmongi
  • 1
0

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();
  }
}
Dabbel
  • 2,468
  • 1
  • 8
  • 25