142

An android mobile actually does know quite well where it is - but is there a way of retrieving the country by something like a country code?

No need of knowing the exact GPS position - the country is sufficient

I first thought of using the time zone, but actually I need more information than that since it makes a difference if the location is New York or Lima.

The background of the question: I have an application that uses temperature values, and I'd like to set the default unit either to Celsius or Fahrenheit, depending on whether the location is US or outside

DonGru
  • 13,532
  • 8
  • 45
  • 55
  • 17
    -1: You definitely don't want to know where the user _is_. You want to know the user's locale setting. The accepted answer answers just that, but is otherwise totally inappropriate here, because search will point people here who actually want to know where the user really is. – Jan Hudec Aug 01 '13 at 12:42

10 Answers10

151
/**
 * Get ISO 3166-1 alpha-2 country code for this device (or null if not available)
 * @param context Context reference to get the TelephonyManager instance from
 * @return country code or null
 */
public static String getUserCountry(Context context) {
    try {
        final TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        final String simCountry = tm.getSimCountryIso();
        if (simCountry != null && simCountry.length() == 2) { // SIM country code is available
            return simCountry.toLowerCase(Locale.US);
        }
        else if (tm.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA) { // device is not 3G (would be unreliable)
            String networkCountry = tm.getNetworkCountryIso();
            if (networkCountry != null && networkCountry.length() == 2) { // network country code is available
                return networkCountry.toLowerCase(Locale.US);
            }
        }
    }
    catch (Exception e) { }
    return null;
}
caw
  • 30,999
  • 61
  • 181
  • 291
  • 17
    Only works on phones or other devices with a sim card. When using on a WIFI tablet, you will get null. So its usefulness is limited. – Bram Aug 31 '15 at 19:38
  • 1
    @Bram This is all you can get. So it's actually quite useful, the problem is only that you have no other reliable data sources if you have a WiFi-only tablet. – caw Oct 26 '15 at 22:33
  • 13
    Note that if a user from USA is on holiday in France, `getSimCountryIso` will say `us` and `getNetworkCountryIso` will say `fr` – Tim Apr 06 '16 at 09:05
  • 2
    You may want to change the `toLowerCase()` call to `toUpperCase()`. – Etienne Lawlor May 19 '16 at 08:07
  • I need Code not code Name; Like for usa +1 not the US – Shihab Uddin Dec 03 '16 at 09:23
  • Please do not `catch (Exception e)`. – Tyler Oct 19 '17 at 19:46
  • If you don't need null you may return a country from current locale instead in order to receive at leas anything – Leo DroidCoder Dec 24 '17 at 17:05
  • @LeoDroidcoder The country from the current locale may be much less precise (or not even accurate at all). This weakens the guarantee that the method gives you, because you don't know if you've just got a country code from the SIM card or a less precise piece of information from the locale. If you're okay with that, and keep that in mind, your solution is fine. – caw Dec 28 '17 at 17:13
  • Keep in mind that `getSimCountryIso` & `getNetworkCountryIso` may return an empty string. – Aba Mar 28 '19 at 07:12
105

This will get the country code set for the phone (phones language, NOT user location):

 String locale = context.getResources().getConfiguration().locale.getCountry(); 

can also replace getCountry() with getISO3Country() to get a 3 letter ISO code for the country. This will get the country name:

 String locale = context.getResources().getConfiguration().locale.getDisplayCountry();

This seems easier than the other methods and rely upon the localisation settings on the phone, so if a US user is abroad they probably still want Fahrenheit and this will work :)

Editors note: This solution has nothing to do with the location of the phone. It is constant. When you travel to Germany locale will NOT change. In short: locale != location.

Nux
  • 9,276
  • 5
  • 59
  • 72
stealthcopter
  • 13,964
  • 13
  • 65
  • 83
  • 92
    This won't work in countries for which Android does not have a Locale. For example, in Switzerland, the language is likely to be set to German or French. This method will give you Germany or France, not Switzerland. Better to use the LocationManager or TelephonyManager approach. – MathewI Nov 21 '11 at 16:53
  • 5
    Doesn't work well. I have to differentiate user country for all the Latin American countries, all the testing phones have returned USA even when the phone is in English or Spanish lang. – htafoya Jun 26 '12 at 20:59
  • 23
    This **does not answer** the **title** of the question. I may have the phone set to English and be about anywhere in the world (my native language is _not_ English, but I _do_ have my phone set to (British) English. It however **does answer the actual question**, because user wants US units if he is USAian, wherever he actually happens to be at the moment. – Jan Hudec Aug 01 '13 at 12:40
  • 1
    This won't work in countries such as Switzerland like Mathewl noted. – DNax Nov 07 '13 at 15:54
  • 2
    Not the feasible solution to get Country name – Aamirkhan Jan 27 '14 at 10:26
  • This is not a good questions. Precisely if I want to know the country is because I want to know if the user has move or if it´s not in the usual location... – Sotti May 11 '14 at 19:08
  • 4
    It's a good answer in general but it only returns the country of the locale, not the actual country. – dst Jun 04 '14 at 05:40
  • This code is returning `US` but it's not giving the current country name/code – silverFoxA Apr 15 '15 at 10:49
  • This code get phone configuration not user location – Davide Feb 12 '16 at 09:31
  • This doesn't work all the time. I'm getting an empty string in some devices – Chisko Sep 15 '16 at 17:58
  • 1
    context.getResources().getConfiguration().locale is deprecated – Mike6679 Mar 29 '17 at 15:19
  • Unreliable, not universally valid and simply bad practice. Please do not use this code. – Firzen May 15 '19 at 15:20
  • 1
    this doesn't work, it return the code based on the language of the device – user2934536 May 27 '19 at 00:31
  • Thanks. i tried this one also but it always return "US – Manideep Mar 03 '21 at 12:30
83

Use this link http://ip-api.com/json ,this will provide all the information as json. From this json you can get the country easily. This site works using your current IP,it automatically detects the IP and sendback details.

Docs http://ip-api.com/docs/api:json Hope it helps.

Example json

{
  "status": "success",
  "country": "United States",
  "countryCode": "US",
  "region": "CA",
  "regionName": "California",
  "city": "San Francisco",
  "zip": "94105",
  "lat": "37.7898",
  "lon": "-122.3942",
  "timezone": "America/Los_Angeles",
  "isp": "Wikimedia Foundation",
  "org": "Wikimedia Foundation",
  "as": "AS14907 Wikimedia US network",
  "query": "208.80.152.201"
}

note : As this is a 3rd party solution, only use if others didn't work.

shine_joseph
  • 2,922
  • 4
  • 22
  • 44
  • 1
    I'm not sure this is exactly what the OP wanted, but I really like the answer anyway. – JohnnyLambada Jan 16 '15 at 00:06
  • 1
    Like the answer since this would work on tablets (without sim) as well. Telephony solution works just on phones with active sims. Dual sim phones is another problem scenario as they might be from different countries. So probably it makes more sense to rely on IP solution mentioned above. – Manish Kataria Apr 22 '15 at 15:00
  • 1
    Even though, as you pointed out, this may not be the best approach to the problem, it actually provides a valid solution. +1 from me and thank you! – Lampione Apr 28 '16 at 06:29
  • 2
    It's always bad to use third parties, you can never know how stable there are. for instance - Parse. – Nativ May 22 '16 at 10:57
  • Yeah. That's why this is not a recommended solution. But is an easy approach. – shine_joseph May 23 '16 at 06:36
  • 2
    but it has limitations. their system will automatically ban any IP addresses doing over 150 requests per minute. – Ram Mandal Jul 15 '16 at 09:44
  • I like the approach but how can i test this?? – Arpit Patel Sep 03 '16 at 06:49
  • What do you mean by test? you can implement this as normal webservices and parse the data. But as this is a 3rd party solution, you must try all other options mentioned above. – shine_joseph Sep 05 '16 at 05:31
  • This is what I wanted – Harvinder Singh Sep 27 '19 at 13:53
  • It's nice but you need internet connection for this to work. – Mohit Atray Feb 20 '20 at 10:24
  • Thanks, it works, but has limitations. They do not allow commercial use of the free endpoint, their endpoints are limited to 45 HTTP requests per minute from an IP address (else requests will be throttled (HTTP 429) until your rate limit window is reset). – CoolMind Mar 04 '21 at 08:05
  • How they know if we use free tier as commercial use? – Aaditya Paliwal Mar 29 '21 at 04:49
  • This wont work for people using VPNs, but still not a bad backup strategy – PPartisan Jan 31 '23 at 13:12
66

Actually I just found out that there is even one more way of getting a country code, using the getSimCountryIso() method of TelephoneManager:

TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
String countryCode = tm.getSimCountryIso();

Since it is the sim code it also should not change when traveling to other countries.

DonGru
  • 13,532
  • 8
  • 45
  • 55
41

First, get the LocationManager. Then, call LocationManager.getLastKnownPosition. Then create a GeoCoder and call GeoCoder.getFromLocation. Do this is in a separate thread!! This will give you a list of Address objects. Call Address.getCountryName and you got it.

Keep in mind that the last known position can be a bit stale, so if the user just crossed the border, you may not know about it for a while.

Sharp Edge
  • 4,144
  • 2
  • 27
  • 41
EboMike
  • 76,846
  • 14
  • 164
  • 167
  • For this application changing the default unit would not be desired when moving countries as the user's preference of Celsius or Fahrenheit will not change as he ventures to new lands. – stealthcopter Sep 07 '10 at 15:53
  • I need to know in which country my user is for an API call. The call returns locations in the surrounding area. Therefore I am only interested in the real country code the user is in right now. Just to give an insight what the above approach could be needed for. – vinzenzweber Mar 07 '11 at 07:58
  • This is highly under rated ans, but the fact is this is the most reliable solution. Most of the ans above don't work for wifi network. The one provided by @shine_joseph is looking good but the api provided is not for commercial use. – Gem Jul 04 '16 at 06:27
  • Use with caution. Your device will give you an location even if not networked, but the Geocoder will typically return a null for the addresses array if wifi is off, raising an exception that you will need to try/catch. – Mike Critchley Dec 08 '18 at 17:46
  • 2
    But one catch is we need to request runtime permission for accessing COARSE_LOCATION right? – Tianyao 'Till' Chen Dec 14 '20 at 15:47
17

You could use getNetworkCountryIso() from TelephonyManager to get the country the phone is currently in (although apparently this is unreliable on CDMA networks).

Pedro Loureiro
  • 11,436
  • 2
  • 31
  • 37
David Webb
  • 190,537
  • 57
  • 313
  • 299
  • 1
    that sounds pretty straight forward and worked well at the emulator in first instance - could you just explain why it's unreliable on CDMA networks? – DonGru Sep 07 '10 at 15:33
  • ah okay, I got it - because the documentation says so :) – DonGru Sep 07 '10 at 15:50
  • Because of the application this wouldn't be the best solution as when a user travels abroad their default unit would change. It would make more sense to have a static default unit based on the phones locale settings, which then can of course be changed in a setting somewhere. – stealthcopter Sep 07 '10 at 15:51
17

Here is a complete solution based on the LocationManager and as fallbacks the TelephonyManager and the Network Provider's locations. I used the above answer from @Marco W. for the fallback part(great answer as itself!).

Note: the code contains PreferencesManager, this is a helper class that saves and loads data from SharedPrefrences. I'm using it to save the country to S"P, I'm only getting the country if it is empty. For my product I don't really care for all the edge cases(user travels abroad and so on).

public static String getCountry(Context context) {
    String country = PreferencesManager.getInstance(context).getString(COUNTRY);
    if (country != null) {
        return country;
    }

    LocationManager locationManager = (LocationManager) PiplApp.getInstance().getSystemService(Context.LOCATION_SERVICE);
    if (locationManager != null) {
        Location location = locationManager
                .getLastKnownLocation(LocationManager.GPS_PROVIDER);
        if (location == null) {
            location = locationManager
                    .getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
        }
        if (location == null) {
            log.w("Couldn't get location from network and gps providers")
            return
        }
        Geocoder gcd = new Geocoder(context, Locale.getDefault());
        List<Address> addresses;
        try {
            addresses = gcd.getFromLocation(location.getLatitude(),
                    location.getLongitude(), 1);

            if (addresses != null && !addresses.isEmpty()) {
                country = addresses.get(0).getCountryName();
                if (country != null) {
                    PreferencesManager.getInstance(context).putString(COUNTRY, country);
                    return country;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    country = getCountryBasedOnSimCardOrNetwork(context);
    if (country != null) {
        PreferencesManager.getInstance(context).putString(COUNTRY, country);
        return country;
    }
    return null;
}


/**
 * Get ISO 3166-1 alpha-2 country code for this device (or null if not available)
 *
 * @param context Context reference to get the TelephonyManager instance from
 * @return country code or null
 */
private static String getCountryBasedOnSimCardOrNetwork(Context context) {
    try {
        final TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        final String simCountry = tm.getSimCountryIso();
        if (simCountry != null && simCountry.length() == 2) { // SIM country code is available
            return simCountry.toLowerCase(Locale.US);
        } else if (tm.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA) { // device is not 3G (would be unreliable)
            String networkCountry = tm.getNetworkCountryIso();
            if (networkCountry != null && networkCountry.length() == 2) { // network country code is available
                return networkCountry.toLowerCase(Locale.US);
            }
        }
    } catch (Exception e) {
    }
    return null;
}
Nativ
  • 3,092
  • 6
  • 38
  • 69
  • 4
    Sorry, but your answer really needs to be rewritten. The code contains lots of unusable code. – ichalos Apr 12 '17 at 09:27
  • @ichalos or you didn't understand the logic. Please give me 1 example of unused code in this snippet – Nativ Apr 12 '17 at 09:55
  • 2
    Of course I undestood the logic and please don't take it the wrong way. I ment that PipplApp, PrefernecesManager etc could have been removed and the offered solution could be a bit more clear to the readers. – ichalos Apr 12 '17 at 09:59
  • @ichalos about the PiplApp I agree 100% with you, I just remove it – Nativ Apr 12 '17 at 10:08
  • There is `country = addresses.get(0).getCountryName();` in your code, which will put full name of country into variable `country`. At same time in method `getCountryBasedOnSimCardOrNetwork(Context context)` you are returning the ISO code of country. I believe that you wanted to write `country = addresses.get(0).getCountryCode();` :-) – Firzen May 15 '19 at 21:46
  • @Nativ The location that you set with the network provider is never used – Charles Aug 21 '19 at 17:16
  • @Charles it is used..it assigns value to location variable which later on in the code is being used – Nativ Aug 22 '19 at 08:01
  • @Nativ but that's the thing. Strictly looking at the given method above, location is a local variable to the method. Therefore it cannot be used later on in the code. For further clarification, I am not talking about the first assignation of the variable. I am talking about the assignation in the if condition. You check if location is null. If it is, attempt to set it again. Then only in the else you apply some logic with the location. therefore, if the first assignation of location resulted in a null, the second assignation may work but will never be used – Charles Aug 22 '19 at 15:53
6
String locale = context.getResources().getConfiguration().locale.getCountry(); 

Is deprecated. Use this instead:

Locale locale;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    locale = context.getResources().getConfiguration().getLocales().get(0);
} else {
    locale = context.getResources().getConfiguration().locale;
}
4gus71n
  • 3,717
  • 3
  • 39
  • 66
6

For some devices, if the default language is set different (an indian can set English (US)) then

context.getResources().getConfiguration().locale.getDisplayCountry();

will give wrong value .So this method is non reliable

Also, getNetworkCountryIso() method of TelephonyManager will not work on devices which don't have SIM card (WIFI tablets).

If a device doesn't have SIM then we can use Time Zone to get the country. For countries like India, this method will work

sample code used to check the country is India or not (Time zone id : asia/calcutta)

private void checkCountry() {


    TelephonyManager telMgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
    if (telMgr == null)
        return;

    int simState = telMgr.getSimState();

    switch (simState) {
        //if sim is not available then country is find out using timezone id
        case TelephonyManager.SIM_STATE_ABSENT:
            TimeZone tz = TimeZone.getDefault();
            String timeZoneId = tz.getID();
            if (timeZoneId.equalsIgnoreCase(Constants.INDIA_TIME_ZONE_ID)) {
               //do something
            } else {
               //do something
            }
            break;

            //if sim is available then telephony manager network country info is used
        case TelephonyManager.SIM_STATE_READY:

           TelephonyManager tm = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);
            if (tm != null) {
                String countryCodeValue = tm.getNetworkCountryIso();
                //check if the network country code is "in"
                if (countryCodeValue.equalsIgnoreCase(Constants.NETWORK_INDIA_CODE)) {
                   //do something
                }

                else {
                   //do something
                }

            }
            break;

    }
}
MarGin
  • 2,078
  • 1
  • 17
  • 28
  • 1
    nice idea but Time zone is not a country specific feature you know that right? For every 15 degree of meridians there is another time zone etc. – Gunhan Feb 13 '19 at 14:55
  • 1
    @Gunhan, the time zones returned by Android mostly don't cross the country borders. See [the list on wikipedia](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). However, there are [some exceptions](https://stackoverflow.com/a/29371634/986216) that make the mapping tricky – riwnodennyk Jan 08 '21 at 01:29
6

Java (which is what Android uses) allows for one to retrieve the current TZ (Timezone) database name. Although your question mentioned that time zones may not be specific enough, using this method, you can get the country (and even city in some cases) of the user without needing location permissions.

A sample TZ Database Name:

Europe/Zurich reveals that the user is in Switzerland, while Asia/Seoul shows that the user is in South Korea.
(the user may not be in Zurich or Seoul respectively though, maybe in other states/provinces)
Here is a list of all available TZ Database Time Zones: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones

So you can get the TZ Database name using:

public String usersCountryByTzDbName() {
    return ZoneId.systemDefault().getId();
}

You can map these to the countries of your choice. The advantages of this method:

  • Unlike context.getResources().getConfiguration().locale.getCountry() like others have suggested, this would work regardless of the user's locale. Imagine if the user lived in Japan and set the language to en_US, you'd detect that the user is in USA instead of Japan.
  • Works on Wi-Fi only devices (which would not work if you used the telephony manager API)

Reference: Java 8 - tz database time zones
Note that according to this SO answer, TZ Database Time Zones may change from time to time, so you should expect new timezones that you have not previously encountered. Also, if the user happens to travel to another country, the other country would be reflected in this method.

Thanks!

LCZ
  • 589
  • 1
  • 9
  • 15