diff --git a/briar-android/src/org/briarproject/system/AndroidLocationUtils.java b/briar-android/src/org/briarproject/system/AndroidLocationUtils.java
index 7f3f1e110..2f5268207 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):
+ *
+ *
+ * - Phone network. This works even when no SIM card is inserted, or a
+ * foreign SIM card is inserted.
+ * Location service (GPS/WiFi/etc). 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.
+ * - SIM card. This is only an heuristic and assumes the user is not
+ * roaming.
+ * - User Locale. This is an even worse heuristic.
+ *
+ *
+ * Note: this is very similar to
+ * this API 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 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 a5f94e5a0..c0c82844d 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}.
+ *
+ * The country codes are formatted upper-case and as per ISO 3166-1 alpha 2.
+ */
String getCurrentCountry();
}