Newer
Older
package org.briarproject.system;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import java.util.logging.Logger;
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;
import com.google.inject.Inject;
@ContextSingleton
class AndroidLocationUtils implements LocationUtils {
private static final Logger LOG =
Logger.getLogger(AndroidLocationUtils.class.getName());
final Context context;
@Inject
public AndroidLocationUtils(Context context) {
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() {
String countryCode;
countryCode = getCountryFromPhoneNetwork();
if (!TextUtils.isEmpty(countryCode)) {
return countryCode.toUpperCase(); // android api gives lowercase for some reason
}
// 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();
}
String getCountryFromPhoneNetwork() {
TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
return tm.getNetworkCountryIso();
}
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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;
}