Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement UMP SDK correctly for eu consent?

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.

like image 586
Patriotic Avatar asked Dec 18 '20 03:12

Patriotic


3 Answers

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)
}
like image 181
Tyler V Avatar answered Nov 13 '22 05:11

Tyler V


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.

like image 30
YAQ Avatar answered Nov 13 '22 04:11

YAQ


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.

like image 3
Simon Avatar answered Nov 13 '22 04:11

Simon