[android] How can I get my Android device country code without using GPS?

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 or country name?

There isn't any need to know the exact GPS position - the country code or name is sufficient, and I am using this code:

 String locale = context.getResources().getConfiguration().locale.getCountry(Locale.getDefault());
 System.out.println("country = "+locale);

It gives me the code "US", but my device kept in India. Is there a way to find the device's current country code without using GPS or a network provider?

Because I am using a tablet.

This question is related to android locale

The answer is


I have created a utility function (tested once on a device where I was getting an incorrect country code based on locale).

Reference: CountryCodePicker.java

fun getDetectedCountry(context: Context, defaultCountryIsoCode: String): String {

    detectSIMCountry(context)?.let {
        return it
    }

    detectNetworkCountry(context)?.let {
        return it
    }

    detectLocaleCountry(context)?.let {
        return it
    }

    return defaultCountryIsoCode
}

private fun detectSIMCountry(context: Context): String? {
    try {
        val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        Log.d(TAG, "detectSIMCountry: ${telephonyManager.simCountryIso}")
        return telephonyManager.simCountryIso
    }
    catch (e: Exception) {
        e.printStackTrace()
    }
    return null
}

private fun detectNetworkCountry(context: Context): String? {
    try {
        val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        Log.d(TAG, "detectNetworkCountry: ${telephonyManager.simCountryIso}")
        return telephonyManager.networkCountryIso
    }
    catch (e: Exception) {
        e.printStackTrace()
    }
    return null
}

private fun detectLocaleCountry(context: Context): String? {
    try {
        val localeCountryISO = context.getResources().getConfiguration().locale.getCountry()
        Log.d(TAG, "detectNetworkCountry: $localeCountryISO")
        return localeCountryISO
    }
    catch (e: Exception) {
        e.printStackTrace()
    }
    return null
}

The checked answer has deprecated code. You need to implement this:

String locale;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    locale = context.getResources().getConfiguration().getLocales().get(0).getCountry();
} else {
    locale = context.getResources().getConfiguration().locale.getCountry();
}

If you wish to get the country code without asking for any permission, you can choose a tricky way.

The method simply uses an API to get the country code, and there aren't any third-party libraries to depend on. We can create one for us.

Here I have used Google Cloud Functions to write an API and it is so effortless.

Step 1: Create a Google Cloud Account, and set up billing (the free tier is enough)

Step 2: Create a cloud function to get the geo location

Copy this basic function to the code editor of index.js:

const cors = require('cors')

function _geolocation(req, res) {
  const data = {
    country_code: req.headers["x-appengine-country"],
    region: req.headers["x-appengine-region"],
    city: req.headers["x-appengine-city"],
    cityLatLong: req.headers["x-appengine-citylatlong"],
    userIP: req.headers["x-appengine-user-ip"]
  }

  res.json(data)
};

exports.geolocation = (req, res) => {
  const corsHandler = cors({ origin: true })

  return corsHandler(req, res, function() {
    return _geolocation(req, res);
  });
};

Also we need to copy the package.json definition:

{
  "name": "gfc-geolocation",
  "version": "0.0.1",
  "dependencies": {
    "cors": "^2.8.4"
  }
}

Step 3: finish, and get the URL similar to: "https://us-central1-geolocation-mods-sdde.cloudfunctions.net/geolocation"

Step 4: parse the JSON response and get the country code

The response will look like:

{
   "country": "IN",
   "region": "kl",
   "city": "kochi",
   "cityLatLong": "9.9312,76.2673",
   "userIP": "xx.xx.xx.xx"
}

Thanks and credits go to the Medium article: Free IP-based Geolocation with Google Cloud Functions


You can simply use this code,

TelephonyManager tm = (TelephonyManager)this.getSystemService(Context.TELEPHONY_SERVICE);
String countryCodeValue = tm.getNetworkCountryIso();

This will return 'US' if your current connected network is in the United States. This works without a SIM card even.


To get country code

<uses-permission android:name="android.permission.INTERNET" />

public String makeServiceCall() {
    String response = null;
    String reqUrl = "https://ipinfo.io/country";
    try {
        URL url = new URL(reqUrl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        // read the response
        InputStream in = new BufferedInputStream(conn.getInputStream());
        response = convertStreamToString(in);
    } catch (MalformedURLException e) {
        Log.e(TAG, "MalformedURLException: " + e.getMessage());
    } catch (ProtocolException e) {
        Log.e(TAG, "ProtocolException: " + e.getMessage());
    } catch (IOException e) {
        Log.e(TAG, "IOException: " + e.getMessage());
    } catch (Exception e) {
        Log.e(TAG, "Exception: " + e.getMessage());
    }
    return response;
}

Here is a complete example. It tries to get the country code from TelephonyManager (from SIM or CDMA devices), and if not available, tries to get it from the local configuration.

private static String getDeviceCountryCode(Context context) {
    String countryCode;

    // Try to get country code from TelephonyManager service
    TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
    if(tm != null) {
        // Query first getSimCountryIso()
        countryCode = tm.getSimCountryIso();
        if (countryCode != null && countryCode.length() == 2)
            return countryCode.toLowerCase();

        if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
            // Special case for CDMA Devices
            countryCode = getCDMACountryIso();
        }
        else {
            // For 3G devices (with SIM) query getNetworkCountryIso()
            countryCode = tm.getNetworkCountryIso();
        }

        if (countryCode != null && countryCode.length() == 2)
            return countryCode.toLowerCase();
    }

    // If network country not available (tablets maybe), get country code from Locale class
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        countryCode = context.getResources().getConfiguration().getLocales().get(0).getCountry();
    }
    else {
        countryCode = context.getResources().getConfiguration().locale.getCountry();
    }

    if (countryCode != null && countryCode.length() == 2)
        return  countryCode.toLowerCase();

    // General fallback to "us"
    return "us";
}

@SuppressLint("PrivateApi")
private static String getCDMACountryIso() {
    try {
        // Try to get country code from SystemProperties private class
        Class<?> systemProperties = Class.forName("android.os.SystemProperties");
        Method get = systemProperties.getMethod("get", String.class);

        // Get homeOperator that contain MCC + MNC
        String homeOperator = ((String) get.invoke(systemProperties,
                "ro.cdma.home.operator.numeric"));

        // First three characters (MCC) from homeOperator represents the country code
        int mcc = Integer.parseInt(homeOperator.substring(0, 3));

        // Mapping just countries that actually use CDMA networks
        switch (mcc) {
            case 330: return "PR";
            case 310: return "US";
            case 311: return "US";
            case 312: return "US";
            case 316: return "US";
            case 283: return "AM";
            case 460: return "CN";
            case 455: return "MO";
            case 414: return "MM";
            case 619: return "SL";
            case 450: return "KR";
            case 634: return "SD";
            case 434: return "UZ";
            case 232: return "AT";
            case 204: return "NL";
            case 262: return "DE";
            case 247: return "LV";
            case 255: return "UA";
        }
    }
    catch (ClassNotFoundException ignored) {
    }
    catch (NoSuchMethodException ignored) {
    }
    catch (IllegalAccessException ignored) {
    }
    catch (InvocationTargetException ignored) {
    }
    catch (NullPointerException ignored) {
    }

    return null;
}

Also another idea is to try an API request like in this answer.

References are here and here.


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

Documentation

This is what I got:

{
"as": "AS55410 C48 Okhla Industrial Estate, New Delhi-110020",
"city": "Kochi",
"country": "India",
"countryCode": "IN",
"isp": "Vodafone India",
"lat": 9.9667,
"lon": 76.2333,
"org": "Vodafone India",
"query": "123.63.81.162",
"region": "KL",
"regionName": "Kerala",
"status": "success",
"timezone": "Asia/Kolkata",
"zip": ""
}

N.B. - As this is a third-party API, do not use it as the primary solution. And also I am not sure whether it's free or not.


There isn't any need to call any API. You can get the country code from your device where it is located. Just use this function:

 fun getUserCountry(context: Context): String? {
    try {
        val tm = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        val simCountry = tm.simCountryIso
        if (simCountry != null && simCountry.length == 2) { // SIM country code is available
            return simCountry.toLowerCase(Locale.US)
        } 
        else if (tm.phoneType != TelephonyManager.PHONE_TYPE_CDMA) { // Device is not 3G (would be unreliable)
            val networkCountry = tm.networkCountryIso
            if (networkCountry != null && networkCountry.length == 2) { // network country code is available
                return networkCountry.toLowerCase(Locale.US)
            }
        }
    }
    catch (e: Exception) {
    }
    return null
}

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

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

will give the wrong value. So this method is not reliable.

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

If a device doesn't have a SIM card then we can use the 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.

Use the below values in your constants file

(Constants.INDIA_TIME_ZONE_ID: "asia/calcutta", Constants.NETWORK_INDIA_CODE :"in")

And in your activity, add the following code:

private void checkCountry() {

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

    int simState = telMgr.getSimState();

    switch (simState) {

        // If a SIM card is not available then the
        // country is found using the 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 a SIM card is available then the telephony
        // manager network country information is used
        case TelephonyManager.SIM_STATE_READY:

            if (telMgr != 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;
    }
}