GDPR - Consent SDK - Consent form translation
Asked Answered
H

3

11

The Consent SDK allows to show a consent form which, however, it is currently in English only (version 1.0.3 of the SDK). SDK page says:

To update consent text of the Google-rendered consent form, modify the consentform.html file included in the Consent SDK as required.

However, consentform.html is provided as an asset and I don't see a way to localize it, especially using gradle. What is the best way to handle localization in this case? And why this was not done in the first place? Europe is not just English.

Hircine answered 26/5, 2018 at 7:44 Comment(1)
Some of the users get pissed when an app is not translated to their language and explaining a legal issue in another language will result lots of 1 star ratings.Blackington
T
18

Since Google's EU Consent dialog is not localizable, I created my own consent dialog which you can translate as usual with strings.xml. It's loosely based on what Google did. This is to be used without mediation:

Custom EU consent dialog

You are free to use my code, however consult your legal advisor, if the text is appropriate for you. I cannot provide legal advice on the consent text that is appropriate for you.

Add to your gradle file:

implementation 'com.google.android.ads.consent:consent-library:1.0.3'

Add member variables:

public boolean mShowNonPersonalizedAdRequests = false;
private AlertDialog mEuDialog;

In onCreate() call checkConsentStatus():

@Override
protected void onCreate(Bundle savedInstanceState) {
    // ...
    checkConsentStatus();
    // ...   
}

Add checkConsentStatus() method which uses Google's Consent SDK:

// https://developers.google.com/admob/android/eu-consent
private void checkConsentStatus(){
    ConsentInformation consentInformation = ConsentInformation.getInstance(this);
    ConsentInformation.getInstance(this).addTestDevice("YOUR-DEVICE-ID"); // enter your device id, if you need it for testing

    String[] publisherIds = {"pub-YOUR-ADMOB-PUB-ID"}; // enter your admob pub-id
    consentInformation.requestConsentInfoUpdate(publisherIds, new ConsentInfoUpdateListener() {
        @Override
        public void onConsentInfoUpdated(ConsentStatus consentStatus) {
            log("User's consent status successfully updated: " +consentStatus);

            if (ConsentInformation.getInstance(MainActivity.this).isRequestLocationInEeaOrUnknown()){
                log("User is from EU");

                /////////////////////////////
                // TESTING - reset the choice
                //ConsentInformation.getInstance(MainActivity.this).setConsentStatus(ConsentStatus.UNKNOWN);
                /////////////////////////////

                // If the returned ConsentStatus is UNKNOWN, collect user's consent.
                if (consentStatus == ConsentStatus.UNKNOWN) {
                    showMyConsentDialog(false);
                }

                // If the returned ConsentStatus is PERSONALIZED or NON_PERSONALIZED
                // the user has already provided consent. Forward consent to the Google Mobile Ads SDK.
                else if (consentStatus == ConsentStatus.NON_PERSONALIZED) {

                    mShowNonPersonalizedAdRequests = true;

                    // The default behavior of the Google Mobile Ads SDK is to serve personalized ads.
                    // If a user has consented to receive only non-personalized ads, you can configure
                    // an AdRequest object with the following code to specify that only non-personalized
                    // ads should be returned.

                }


            } else {
                log("User is NOT from EU");
                // we don't have to do anything
            }

        }

        @Override
        public void onFailedToUpdateConsentInfo(String errorDescription) {
            log("User's consent status failed to update: " +errorDescription);
        }
    });
}

Add showMyConsentDialog() method:

public void showMyConsentDialog(boolean showCancel) {

    AlertDialog.Builder alertDialog = new AlertDialog.Builder(MainActivity.this, R.style.MyAlertDialogStyle);
    LayoutInflater inflater = getLayoutInflater();
    View eu_consent_dialog = inflater.inflate(R.layout.eu_consent, null);

    alertDialog.setView(eu_consent_dialog)
               .setCancelable(false);

    if (showCancel) alertDialog.setPositiveButton(R.string.dialog_close, null);

    mEuDialog = alertDialog.create();
    mEuDialog.show();

    Button btn_eu_consent_yes = eu_consent_dialog.findViewById(R.id.btn_eu_consent_yes);
    Button btn_eu_consent_no = eu_consent_dialog.findViewById(R.id.btn_eu_consent_no);
    Button btn_eu_consent_remove_ads = eu_consent_dialog.findViewById(R.id.btn_eu_consent_remove_ads);
    btn_eu_consent_yes.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mEuDialog.cancel();
            toast(getString(R.string.thank_you), MainActivity.this);
            ConsentInformation.getInstance(MainActivity.this).setConsentStatus(ConsentStatus.PERSONALIZED);
            mShowNonPersonalizedAdRequests = false;
        }
    });
    btn_eu_consent_no.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mEuDialog.cancel();
            toast(getString(R.string.thank_you), MainActivity.this);
            ConsentInformation.getInstance(MainActivity.this).setConsentStatus(ConsentStatus.NON_PERSONALIZED);
            mShowNonPersonalizedAdRequests = true;
        }
    });
    btn_eu_consent_remove_ads.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mEuDialog.cancel();
            IAP_buyAdsFree(); // YOUR REMOVE ADS METHOD
            }
        });

    TextView tv_eu_learn_more = eu_consent_dialog.findViewById(R.id.tv_eu_learn_more);
    tv_eu_learn_more.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            euMoreInfoDialog();
        }
    });  
}

This is the consent layout, save to eu_consent.xml:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <LinearLayout
        android:id="@+id/ll_eu_consent"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="@dimen/activity_horizontal_margin"
        >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/eu_consent_text"
            android:textSize="14sp"
            android:paddingBottom="6dp"
            />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/eu_consent_question"
            android:textSize="14sp"
            android:paddingBottom="6dp"
            android:textStyle="bold"
        />

        <Button
            android:id="@+id/btn_eu_consent_yes"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/eu_consent_yes"
            android:textSize="13sp"
            />

        <Button
            android:id="@+id/btn_eu_consent_no"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/eu_consent_no"
            android:textSize="13sp"
            android:layout_marginTop="6dp"
            android:layout_marginBottom="6dp"
            />

        <Button
            android:id="@+id/btn_eu_consent_remove_ads"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/action_remove_ads"
            android:textSize="13sp"
            />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/eu_consent_change_setting"
            android:textSize="14sp"
            android:paddingTop="6dp"
            android:paddingBottom="6dp"
            />

        <TextView
            android:id="@+id/tv_eu_learn_more"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/learn_more"
            android:textSize="14sp"
            android:ellipsize="marquee"
            android:fadingEdge="horizontal"
            android:paddingTop="6dp"
            android:paddingBottom="6dp"
            android:textColor="@color/blue"
            style="@style/SelectableItem"
            />

    </LinearLayout>

</ScrollView>

Add euMoreInfoDialog():

private void euMoreInfoDialog(){

    AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this, R.style.MyAlertDialogStyle);

    ScrollView sv = new ScrollView(this);
    LinearLayout ll = new LinearLayout(this);
    ll.setOrientation(LinearLayout.VERTICAL);

    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
            LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.WRAP_CONTENT);
    params.setMargins(40, 20, 40, 20);

    TextView tv_my_privacy_policy = new TextView(this);
    String link = "<a href="+PRIVACY_URL+">"+getResources().getString(R.string.app_name)+"</a>";
    tv_my_privacy_policy.setText(Html.fromHtml(link));
    tv_my_privacy_policy.setMovementMethod(LinkMovementMethod.getInstance());
    ll.addView(tv_my_privacy_policy, params);

    TextView tv_google_partners = new TextView(this);
    tv_google_partners.setText(R.string.google_partners);
    tv_google_partners.setPadding(40,40,40,20);
    ll.addView(tv_google_partners);

    List<AdProvider> adProviders = ConsentInformation.getInstance(this).getAdProviders();
    for (AdProvider adProvider : adProviders) {
        //log("adProvider: " +adProvider.getName()+ " " +adProvider.getPrivacyPolicyUrlString());
        link = "<a href="+adProvider.getPrivacyPolicyUrlString()+">"+adProvider.getName()+"</a>";
        TextView tv_adprovider = new TextView(this);
        tv_adprovider.setText(Html.fromHtml(link));
        tv_adprovider.setMovementMethod(LinkMovementMethod.getInstance());
        ll.addView(tv_adprovider, params);
    }
    sv.addView(ll);

    builder.setTitle(R.string.privacy_policy)
           .setView(sv)
           .setPositiveButton(R.string.dialog_close, null);

    final AlertDialog createDialog = builder.create();
    createDialog.show();

}

In your AdMob web interface select the ad technology providers you wish to use. I suggest you don't select more than 20 (or so), because I assume the euMoreInfoDialog() will become very slow if you select too many providers.

Add to onDestroy() to prevent errors on screen rotate:

@Override
public void onDestroy(){
    // ...
    if (mEuDialog != null && mEuDialog.isShowing()) mEuDialog.cancel();
    // ...
    super.onDestroy();
}

When you make an ad request, check the value of mShowNonPersonalizedAdRequests and add "npa" to the request if necessary:

Bundle extras = new Bundle();
if (mShowNonPersonalizedAdRequests)
    extras.putString("npa", "1");

AdRequest adRequest = new AdRequest.Builder()
    .addTestDevice(AdRequest.DEVICE_ID_EMULATOR)
    .addTestDevice("YOUR-DEVICE-ID-GOES-HERE") // insert your device id
    .addNetworkExtrasBundle(AdMobAdapter.class, extras)
    .build();

And lastly, add strings for all your languages to strings.xml:

<!-- EU GDPR Consent texts -->
<string name="eu_consent_text">Dear user!\n\nWe use Google Admob to show ads. Ads support our work, and enable further development of this app. In line with the new European Data Protection Regulation (GDPR), we need your consent to serve ads tailored for you.</string>
<string name="eu_consent_question">Can your data be used to show ads tailored for you?</string>
<string name="learn_more">Learn how your data is used</string>
<string name="google_partners">Google and its partners:</string>
<string name="eu_consent_yes">Yes, continue to show relevant ads</string>
<string name="eu_consent_no">No, show ads that are irrelevant</string>
<string name="eu_consent_change_setting">You can change this setting anytime in the \"About\" window.</string>
<string name="thank_you">Thank you!</string>

That's it!

(Note: log() and toast() are my methods, replace them with your own. PRIVACY_URL is your String url to your privacy policy.)

Tortilla answered 27/5, 2018 at 20:32 Comment(11)
How to get consent for the full list of ad technology providers? Just showing the list is enough? Note: Google's EU User Consent Policy requires that you collect consent for the full list of ad technology providers configured for your publisher IDs before displaying personalized ads, even if you are using a third-party mediation solution to send ad request to Google.Heptavalent
Amar Ilindra: If you use mediation, you're on your own. The above solution is for Google Consent SDK without mediation. From their documentation: Google currently is unable to obtain and handle consent for mediation networks, so you'll need to obtain and handle consent for each ad network separately.. I disabled mediation to avoid trouble, until things settle down.Tortilla
No, As far as I understood, it says if we're showing personalized ads, we need to get consent for the full list of ad technology providers. (Even if we are using third-party mediation) Currently, I'm showing only non-personalised and Ad-Free optionsHeptavalent
Amar Ilindra: That is correct. You have to get consent for the full list of ad technology providers (you can select them in AdMob). As I understand Google's documentation, if you don't use mediation that's OK. You just have to use your own dialog. From their documentation: However, you'll need to determine how the list of providers should be made available to your users and present your own consent form to your users..Tortilla
So, Let's say I'm using 20 ad technology providers with my own consent dialog. User selects Personalised ads, then should I show another dialog with checkbox's near all these 20 ad technology providers?Heptavalent
As I understand Google's documentation, the selected (let's say 20) ad providers will have the consent forwarded to them from Google. If you don't agree with their privacy policies, you shouldn't use personalized ads.Tortilla
Let us continue this discussion in chat.Heptavalent
Hi lenoch ! Very nice but i think you forgot to mention the device identifiers ! Like this "We’ll partner with Google and use a unique identifier on your device to respect your data usage choice" . Both personalized and non-personalized are using this so we have to get consent of this too. Without this the form is not validOnfre
Hi could you please add your styles too. thanx in advance @style/SelectableItem and R.style.MyAlertDialogStyleOnfre
Frank: You can safely delete those style attributes, it will work just fine. Those are some of my (non-related) customizations. About the text, thank you for poiting that out, of course you can customize the text the way you want.Tortilla
In addition to that you can translate missing strings using stringx: stringx.ioArris
S
3

In Android Studio select the Project files view, then go to External Libraries> then look for consent library, then click by right mouse button on classes.dex and choose Show in Explorer. Next go to upper folder and there search for assets folder and consetform.html, there are two folders for that library - maybe, for debug and release build? But I just found out it works.

edit: now it not works, due to android studio update, this solution works: https://mcmap.net/q/1015195/-where-can-i-find-the-consentform-html-of-consent-sdk-by-google

Send answered 26/5, 2018 at 10:34 Comment(2)
This works, however you still end up with just one language. It would be nice if Google added the option to specify the consentform.html form file. You would then create multiple forms, and choose one based on user's language setting ... And BTW, the default text is horrible. It makes it sound like YOU are the one collecting data, but in fact it's Google.Tortilla
how do you select project files view ? , I tried editing mine that was in the assets>debug folder on it says it is a generated file and should not be edited, it goes back to its original form when you try to edit itSinusoidal
H
0

I took the ConsentFormClasses.jar and created an own MyConsentFormClasses.jar. In this jar-File I took the class 'ConsentForm.class' and added a new method load(String locale).

Difference to the original method load()is the call of the webiew.

this.webView.loadUrl("file:///android_asset/consentform.html")

changed to

this.webView.loadUrl("file:///android_asset/consentform_"+locale+".html")

The string variable locale I defined as string resource. In the Assests folder I put the following files e.g consentform_en.html, consentform_fr.html, consentform_pl.html.

Building the new ConsentForm.Class und create MyConsentFormClasses.jar, copied the jar in the libsfolder of my project and configured AndroidStudio dependencies...

The disadvantage to have to make changes in the source code is bearable for me. I hope that Google will provide a proper solution here soon

GGK

Hoax answered 4/8, 2020 at 13:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.