17

The upcoming Google policy changes, compel us to implement a dialog to notify EU users about cookie/device identifier usage for advertising and analytics. I'd like to show this dialog only to EU users. I don't want to use additional permissions (e.g. android.permission.ACCESS_COARSE_LOCATION). Therefore I have created a function to test for EU users:

Android

boolean showCookieHint()
{
    final SharedPreferences settings = getSharedPreferences("localPreferences", Context.MODE_PRIVATE);
    if (settings.getBoolean("termsAccepted", true)  == false) return false;

    List<String> list = new ArrayList<String>();
    list.add("AT"); //Austria
    list.add("BE"); //Belgium
    list.add("BG"); //Bulgaria
    list.add("HR"); //Croatia
    list.add("CY"); //Cyprus
    list.add("CZ"); //Czech Republic
    list.add("DK"); //Denmark
    list.add("EE"); //Estonia
    list.add("FI"); //Finland
    list.add("FR"); //France
    list.add("GF"); //French Guiana
    list.add("PF"); //French Polynesia
    list.add("TF"); //French Southern Territories
    list.add("DE"); //Germany
    list.add("GR"); //Greece
    list.add("HU"); //Hungary
    list.add("IE"); //Ireland
    list.add("IT"); //Italy
    list.add("LV"); //Latvia
    list.add("LT"); //Lithuania
    list.add("LU"); //Luxembourg
    list.add("MT"); //Malta
    list.add("NL"); //Netherlands
    list.add("PL"); //Poland
    list.add("PT"); //Portugal
    list.add("RO"); //Romania
    list.add("SK"); //Slovakia
    list.add("SI"); //Slovenia
    list.add("ES"); //Spain
    list.add("SE"); //Sweden
    list.add("ES"); //Spain
    list.add("GB"); //United Kingdom of Great Britain and Northern Ireland

    boolean error = false;

    /* is eu sim ? */
    try {
        final TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
        String simCountry = tm.getSimCountryIso();
        if (simCountry != null && simCountry.length() == 2) { 
            simCountry = simCountry.toUpperCase();
            for (int i = 0; i < list.size(); ++i) {
                if (list.get(i).equalsIgnoreCase(simCountry) == true) {
                    ASCore.log(TAG, "is EU User (sim)");
                    return true;
                }
            }
        }
    }
    catch (Exception e) { error = true; }


    /* is eu network */
    try {  
        final TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
        if (tm.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA) {
            String networkCountry = tm.getNetworkCountryIso();
            if (networkCountry != null && networkCountry.length() == 2) {
                networkCountry = networkCountry.toUpperCase();
                for (int i = 0; i < list.size(); ++i) {
                    if (list.get(i).equalsIgnoreCase(networkCountry) == true) {
                        ASCore.log(TAG, "is EU User (net)");
                        return true;
                    }
                }
            }
        }
    }
    catch (Exception e) { error = true; }

    /* is eu time zone id */
    try {
        String tz = TimeZone.getDefault().getID().toLowerCase();
        if (tz.length() < 10) {
            error = true;
        } else if (tz.contains("euro") == true) {
            ASCore.log(TAG, "is EU User (time)");
            return true;
        }
    } catch (Exception e) {
        error = true;
    }

    /* is eu time zone id */
    try {
        String tz = TimeZone.getDefault().getID().toLowerCase();
        if (tz.length() < 10) {
            error = true;
        } else if (tz.contains("europe") == true) {
            ASCore.log(TAG, "is EU User (time) ");
            return true;
        }
    } catch (Exception e) {
        error = true;
    }


    if (error == true) {
        ASCore.log(TAG, "is EU User (err)");
        return true;
    }

    return false;
}

iOS

-(bool) showCookieHint {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults boolForKey:@"termsAccepted"]) return false;

CTTelephonyNetworkInfo *network_Info = [CTTelephonyNetworkInfo new];
CTCarrier *carrier = network_Info.subscriberCellularProvider;


std::vector<NSString*> list;
list.push_back(@"AT"); //Austria
list.push_back(@"BE"); //Belgium
list.push_back(@"BG"); //Bulgaria
list.push_back(@"HR"); //Croatia
list.push_back(@"CY"); //Cyprus
list.push_back(@"CZ"); //Czech Republic
list.push_back(@"DK"); //Denmark
list.push_back(@"EE"); //Estonia
list.push_back(@"FI"); //Finland
list.push_back(@"FR"); //France
list.push_back(@"GF"); //French Guiana
list.push_back(@"PF"); //French Polynesia
list.push_back(@"TF"); //French Southern Territories
list.push_back(@"DE"); //Germany
list.push_back(@"GR"); //Greece
list.push_back(@"HU"); //Hungary
list.push_back(@"IE"); //Ireland
list.push_back(@"IT"); //Italy
list.push_back(@"LV"); //Latvia
list.push_back(@"LT"); //Lithuania
list.push_back(@"LU"); //Luxembourg
list.push_back(@"MT"); //Malta
list.push_back(@"NL"); //Netherlands
list.push_back(@"PL"); //Poland
list.push_back(@"PT"); //Portugal
list.push_back(@"RO"); //Romania
list.push_back(@"SK"); //Slovakia
list.push_back(@"SI"); //Slovenia
list.push_back(@"ES"); //Spain
list.push_back(@"SE"); //Sweden
list.push_back(@"ES"); //Spain
list.push_back(@"GB"); //United Kingdom of Great Britain and Northern Ireland

/* is eu sim ? */
NSString* sim = carrier.isoCountryCode;
if (sim != nil) {
    if ([sim length] == 2) {
        NSString* simU = [sim uppercaseString];
        for (int i = 0; i < list.size(); ++i) {
            if ([list[i] compare:simU] == 0) {
                ASCore::log("Core", "is EU User (sim)");
                return true;
            }
        }
    }
}

/* is eu network */
NSString* net = carrier.mobileCountryCode;
if (net != nil) {
    if ([net length] == 2) {
        NSString* netU = [net uppercaseString];
        for (int i = 0; i < list.size(); ++i) {
            if ([list[i] compare:netU] == 0) {
                ASCore::log("Core", "is EU User (net)");
                return true;
            }
        }
    }
}


bool error = false;
/* is local eu time zone id */
NSTimeZone* timeZoneLocal = [NSTimeZone localTimeZone];
NSString* time1 = [[timeZoneLocal name] lowercaseString];
if ([time1 length] > 10) {
    if ([time1 containsString:@"europe"]) {
        ASCore::log("Core", "is EU User (local time)");
        return true;
    }
} else error = true;


/* is default eu time zone id */
NSTimeZone *timeZoneDefault = [NSTimeZone defaultTimeZone];
NSString *time2 = [[timeZoneDefault name] lowercaseString];
if ([time2 length] > 10) {
    if ([time2 containsString:@"europe"]) {
        ASCore::log("Core", "is EU User (default time)");
        return true;
    }
} else error = true;


if (error == true) {
    ASCore::log("Core", "is EU User (err)");
    return true;
}

return false;
}

Is my function enough to detect EU-Users?

Thanks

Ronald

Zippy
  • 3,826
  • 5
  • 43
  • 96
Mr.Betatester
  • 495
  • 3
  • 17
  • On Android you will need additional permission: android.permission.READ_PHONE_STATE – DzungPV Aug 08 '15 at 16:10
  • 2
    [getNetworkCountryIso](http://developer.android.com/reference/android/telephony/TelephonyManager.html#getNetworkCountryIso()) and [getSimCountryIso](http://developer.android.com/reference/android/telephony/TelephonyManager.html#getSimCountryIso()) doesn't need this permission (e.g. [getDeviceId](http://developer.android.com/reference/android/telephony/TelephonyManager.html#getDeviceId()) needs this permission) – Mr.Betatester Aug 09 '15 at 16:34
  • Instead of a list, you can just use a switch (if compiling with Java 1.7) or use an if-else ladder, which will have lower initialization overhead. Other detection methods you might want to use are an IP lookup, and checking the user's locale setting. Checking the timezone is highly inaccurate. – Lorne Laliberte Sep 15 '15 at 16:08
  • In the list Spain ("ES") it's twice. I don't understand why, in Android version, you are checking twice the TimeZone. If tz.contains("europe") == true, then previous statement it's true too (tz.contains("euro")), so it's useless, isn't it? – Sergio Viudes Sep 21 '15 at 18:22
  • Why tz.length() < 10 ? For example for Asia/Baku timezone id it will return true, which is incorrect (tested on Android emulator). – Derek K May 17 '18 at 11:31

3 Answers3

4

Below is somewhat improved Android code based on Mr. Betatester's answer using enum. I noted that Finland was missing and Spain was mistakenly entered twice. I also removed duplication of the code under /* is eu time zone id */.

private enum EUCountry {
    AT,BE,BG,HR,CY,CZ,DK,EE,FI,FR,DE,GR,HU,IE,IT,LV,LT,LU,MT,NL,PL,PT,RO,SK,SI,ES,SE,GB, //28 member states
    GF,PF,TF, //French territories French Guiana,Polynesia,Southern Territories
    EL,UK,  //alternative EU names for GR and GB
    ME,IS,AL,RS,TR,MK; //candidate countries

    public static boolean contains(String s)
    {
        for (EUCountry eucountry:values())
            if (eucountry.name().equalsIgnoreCase(s))
                return true;
        return false;
    }

};

public static boolean isEU(Activity activity)
{
    boolean error = false;

/* is eu sim */

    try {
        final TelephonyManager tm = (TelephonyManager) activity.getSystemService(Context.TELEPHONY_SERVICE);
        String simCountry = tm.getSimCountryIso();
        if (simCountry != null && simCountry.length() == 2) {
            simCountry = simCountry.toUpperCase();

            if (EUCountry.contains(simCountry)) {
                Log.v(TAG, "is EU User (sim)");
                return true;
            }
        }
    }
    catch (Exception e) { error = true; }


/* is eu network */
    try {
        final TelephonyManager tm = (TelephonyManager) activity.getSystemService(Context.TELEPHONY_SERVICE);
        if (tm.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA && tm.getPhoneType()!=TelephonyManager.PHONE_TYPE_NONE) {
            String networkCountry = tm.getNetworkCountryIso();
            if (networkCountry != null && networkCountry.length() == 2) {
                networkCountry = networkCountry.toUpperCase();

                if (EUCountry.contains(networkCountry)) {
                    Log.v(TAG, "is EU User (network)");
                    return true;
                }
            }
        }
    }
    catch (Exception e) { error = true; }

/* is eu time zone id */
    try {
        String tz = TimeZone.getDefault().getID().toLowerCase();
        if (tz.length() < 10) {
            error = true;
        } else if (tz.contains("euro")) {
            Log.v(TAG, "is EU User (time)");
            return true;
        }
    } catch (Exception e) {
        error = true;
    }


    if (error == true) {
        Log.v(TAG, "is EU User (err)");
        return true;
    }

    return false;
}
Ruyo
  • 51
  • 2
  • This code is great. I've used it on a library I've made to show the cookies dialog in my projects easily. You can check it out on [CookiesConsent for Android](https://github.com/Carlosph/CookiesConsent). Credits to both the question and the answer have been given, I hope that's fine. Regards. – Carlosph Sep 25 '15 at 12:12
0

The same function but in Swift. The only difference is that I'm using Parse's user instead NSUserDefaults.

class CookieHelper {

static func show() -> Bool {
    if PFUser.currentUser().valueForKey("cookieConsent") != nil {
        return false
    }
    let networkInfo: CTTelephonyNetworkInfo = CTTelephonyNetworkInfo.new()
    if let carrier: CTCarrier = networkInfo.subscriberCellularProvider {
        var euCountries = [String]()
        euCountries.append("AT") // Austria
        euCountries.append("BE") // Belgium
        euCountries.append("BG") // Bulgary
        euCountries.append("HR") // Croatia
        euCountries.append("CY") // Cyprus
        euCountries.append("CZ") // Czech Republic
        euCountries.append("DK") // Denmark
        euCountries.append("EE") // Estonia
        euCountries.append("FI") // Finland
        euCountries.append("FR") // France
        euCountries.append("GF") // French Guiana
        euCountries.append("PF") // French Polynesia
        euCountries.append("TF") // French Southern Territories
        euCountries.append("DE") // Germany
        euCountries.append("GR") // Greece
        euCountries.append("HU") // Hungary
        euCountries.append("IE") // Ireland
        euCountries.append("IT") // Italy
        euCountries.append("LV") // Latvia
        euCountries.append("LT") // Lithuania
        euCountries.append("LU") // Luxembourg
        euCountries.append("MT") // Malta
        euCountries.append("NL") // Netherlands
        euCountries.append("PL") // Poland
        euCountries.append("PT") // Portugal
        euCountries.append("RO") // Romania
        euCountries.append("SK") // Slovakia
        euCountries.append("SI") // Slovenia
        euCountries.append("ES") // Spain
        euCountries.append("SE") // Sweden
        euCountries.append("GB") // United Kingdom of Great Britain and Northern Ireland

        // EU Sim/Network
        let sim: String? = self.formatCountryCode(carrier.isoCountryCode)
        let net: String? = self.formatCountryCode(carrier.mobileCountryCode)
        if sim != nil || net != nil {
            for country in euCountries {
                if sim != nil {
                    if country == sim {
                        println("EU User (sim)")
                        return true
                    }
                }
                if net != nil {
                    if country == net {
                        println("EU User (net)")
                        return true
                    }
                }
            }
        }
    }

    // EU Local time zone
    if self.timeZoneContainsEurope(NSTimeZone.localTimeZone()) {
        return true
    }
    // EU Default time zone
    if self.timeZoneContainsEurope(NSTimeZone.defaultTimeZone()) {
        return true
    }

    return false
}

private static func formatCountryCode(countryCode: String?) -> String? {
    if countryCode != nil && count(countryCode!) == 2 {
        return countryCode!.uppercaseString
    } else {
        return nil
    }
}

private static func timeZoneContainsEurope(timeZone: NSTimeZone) -> Bool {
    let time: NSString = timeZone.name.lowercaseString
    if count(time as! String) > 10 {
        return time.containsString("europe")
    }
    return false
}

}

0
String ACCEPTED_COOKIES_KEY = "accepted-cookies";
final List<String> euCountries = new ArrayList<String>() {
    {
        addAll(Arrays.asList(new String[] {
            "be", "bg", "cz", "dk", "de", "ee", "ie", "el", "es", "fr", 
            "hr", "it", "cy", "lv", "lt", "lu", "hu", "mt", "nl", "at", 
            "pl", "pt", "ro", "si", "sk", "fi", "se", "uk"
            }));
    }
};


TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
    String networkCountryIso = null;

    if(telephonyManager != null) {
        networkCountryIso = telephonyManager.getNetworkCountryIso();
    }

    if(euCountries.contains(networkCountryIso)) {
        final SharedPreferences prefs = context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE);
        if (!prefs.getBoolean(ACCEPTED_COOKIES_KEY, false)) {
            new AlertDialog.Builder(context)
            .setTitle("Cookies")
            .setMessage("Your message for visitors here")
            .setNeutralButton("Close message", new OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    prefs.edit().putBoolean(ACCEPTED_COOKIES_KEY, true).commit();
                }
            }).show();
        }
    }

The above code is the best approach. It is also worth noting that:

    mTelephonyManager.getSimCountryIso()

should not be used as this would indicate the home country of the SIM provider (E.g. A Vodaphone UK SIM would return "gb", A Vodaphone Germany SIM would return "de") and not the current location (Country) of the device. This difference is significant when the user is roaming.

Ref: Reliable method to get the country the user is in?

Community
  • 1
  • 1
Younes
  • 41
  • 1
  • 6