From 48e5d5123e93966c52b82330e954a23d3f8f5dad Mon Sep 17 00:00:00 2001
From: Ximin Luo <infinity0@pwned.gg>
Date: Thu, 6 Feb 2014 19:18:19 +0000
Subject: [PATCH] add documentation and pointer to hidden Google API - fallback
 to SIM card before phone locale - add disabled code to lookup the country
 from GPS, disabled because it requires a network call

---
 .../system/AndroidLocationUtils.java          | 82 ++++++++++++++++++-
 .../api/system/LocationUtils.java             |  7 +-
 2 files changed, 87 insertions(+), 2 deletions(-)

diff --git a/briar-android/src/org/briarproject/system/AndroidLocationUtils.java b/briar-android/src/org/briarproject/system/AndroidLocationUtils.java
index 7f3f1e110a..2f52682076 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 a5f94e5a0d..c0c82844db 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();
 
 }
-- 
GitLab