diff --git a/briar-android/src/org/briarproject/system/AndroidLocationUtils.java b/briar-android/src/org/briarproject/system/AndroidLocationUtils.java index 7f3f1e110a9b348272df434742f255fbbd7db2f2..2f52682076164118e22478f53bdf93ae13a459b0 100644 --- a/briar-android/src/org/briarproject/system/AndroidLocationUtils.java +++ b/briar-android/src/org/briarproject/system/AndroidLocationUtils.java @@ -1,5 +1,7 @@ package org.briarproject.system; +import java.io.IOException; +import java.util.List; import java.util.Locale; import java.util.logging.Logger; @@ -8,6 +10,10 @@ import org.briarproject.api.system.LocationUtils; import roboguice.inject.ContextSingleton; import android.annotation.SuppressLint; import android.content.Context; +import android.location.Address; +import android.location.Geocoder; +import android.location.Location; +import android.location.LocationManager; import android.telephony.TelephonyManager; import android.text.TextUtils; @@ -26,6 +32,26 @@ class AndroidLocationUtils implements LocationUtils { this.context = context; } + /** + * This guesses the current country from the first of these sources that + * succeeds (also in order of likelihood of being correct): + * + * <ul> + * <li>Phone network. This works even when no SIM card is inserted, or a + * foreign SIM card is inserted.</li> + * <li><del>Location service (GPS/WiFi/etc).</del> <em>This is disabled for + * now, until we figure out an offline method of converting a long/lat + * into a country code, that doesn't involve a network call.</em> + * <li>SIM card. This is only an heuristic and assumes the user is not + * roaming.</li> + * <li>User Locale. This is an even worse heuristic.</li> + * </ul> + * + * Note: this is very similar to <a href="https://android.googlesource.com/platform/frameworks/base/+/cd92588%5E/location/java/android/location/CountryDetector.java"> + * this API</a> except it seems that Google doesn't want us to use it for + * some reason - both that class and {@code Context.COUNTRY_CODE} are + * annotated {@code @hide}. + */ @SuppressLint("DefaultLocale") @Override public String getCurrentCountry() { @@ -34,7 +60,17 @@ class AndroidLocationUtils implements LocationUtils { if (!TextUtils.isEmpty(countryCode)) { return countryCode.toUpperCase(); // android api gives lowercase for some reason } - LOG.warning("Could not determine current country; fall back to user-defined locale"); + // When we enable this, we will need to add ACCESS_FINE_LOCATION + //countryCode = getCountryFromLocation(); + //if (!TextUtils.isEmpty(countryCode)) { + // return countryCode; + //} + countryCode = getCountryFromSimCard(); + if (!TextUtils.isEmpty(countryCode)) { + LOG.info("Could not determine current country; fall back to SIM card country."); + return countryCode.toUpperCase(); // android api gives lowercase for some reason + } + LOG.info("Could not determine current country; fall back to user-defined locale."); return Locale.getDefault().getCountry(); } @@ -43,4 +79,48 @@ class AndroidLocationUtils implements LocationUtils { return tm.getNetworkCountryIso(); } + String getCountryFromSimCard() { + TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); + return tm.getSimCountryIso(); + } + + // TODO: this is not currently used, because it involves a network call + // it should be possible to determine country just from the long/lat, but + // this would involve something like tzdata for countries. + String getCountryFromLocation() { + Location location = getLastKnownLocation(); + if (location == null) return null; + Geocoder code = new Geocoder(context); + try { + List<Address> addresses = code.getFromLocation(location.getLatitude(), location.getLongitude(), 1); + if (addresses.isEmpty()) return null; + return addresses.get(0).getCountryCode(); + } catch (IOException e) { + return null; + } + } + + /** + * Returns the last location from all location providers. + * Since we're only checking the country, we don't care about the accuracy. + * If we ever need the accuracy, we can do something like: + * https://code.google.com/p/android-protips-location/source/browse/trunk\ + * /src/com/radioactiveyak/location_best_practices/utils/GingerbreadLastLocationFinder.java + */ + Location getLastKnownLocation() { + LocationManager locationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE); + Location bestResult = null; + long bestTime = Long.MIN_VALUE; + for (String provider: locationManager.getAllProviders()) { + Location location = locationManager.getLastKnownLocation(provider); + if (location == null) continue; + long time = location.getTime(); + if (time > bestTime) { + bestResult = location; + bestTime = time; + } + } + return bestResult; + } + } diff --git a/briar-api/src/org/briarproject/api/system/LocationUtils.java b/briar-api/src/org/briarproject/api/system/LocationUtils.java index a5f94e5a0db2a9f6fb417f121200e55a5d1f962b..c0c82844db7e3ae67b9e1c8979b55a73c076c20f 100644 --- a/briar-api/src/org/briarproject/api/system/LocationUtils.java +++ b/briar-api/src/org/briarproject/api/system/LocationUtils.java @@ -2,7 +2,12 @@ package org.briarproject.api.system; public interface LocationUtils { - /** Get the country the device is currently-located in. */ + /** Get the country the device is currently-located in, or "" if it cannot + * be determined. Should never return {@code null}. + * + * <p>The country codes are formatted upper-case and as per <a href=" + * https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2">ISO 3166-1 alpha 2</a>. + */ String getCurrentCountry(); }