package org.briarproject.system; import static android.content.Context.LOCATION_SERVICE; import static android.content.Context.TELEPHONY_SERVICE; import java.io.IOException; import java.util.List; import java.util.Locale; import java.util.logging.Logger; import org.briarproject.api.system.LocationUtils; import android.annotation.SuppressLint; import android.app.Application; 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; class AndroidLocationUtils implements LocationUtils { private static final Logger LOG = Logger.getLogger(AndroidLocationUtils.class.getName()); private final Context appContext; @Inject public AndroidLocationUtils(Application app) { appContext = app.getApplicationContext(); } /** * This guesses the current country from the first of these sources that * succeeds (also in order of likelihood of being correct): * * * * Note: this is very similar to * this API except it seems that Google doesn't want us to useit for * some reason - both that class and {@code Context.COUNTRY_CODE} are * annotated {@code @hide}. */ @SuppressLint("DefaultLocale") public String getCurrentCountry() { String countryCode = getCountryFromPhoneNetwork(); if(!TextUtils.isEmpty(countryCode)) return countryCode.toUpperCase(); // Disabled because it involves a network call; requires // ACCESS_FINE_LOCATION // countryCode = getCountryFromLocation(); // if(!TextUtils.isEmpty(countryCode)) return countryCode; LOG.info("Falling back to SIM card country"); countryCode = getCountryFromSimCard(); if(!TextUtils.isEmpty(countryCode)) return countryCode.toUpperCase(); LOG.info("Falling back to user-defined locale"); return Locale.getDefault().getCountry(); } private String getCountryFromPhoneNetwork() { Object o = appContext.getSystemService(TELEPHONY_SERVICE); TelephonyManager tm = (TelephonyManager) o; return tm.getNetworkCountryIso(); } private String getCountryFromSimCard() { Object o = appContext.getSystemService(TELEPHONY_SERVICE); TelephonyManager tm = (TelephonyManager) o; 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. private String getCountryFromLocation() { Location location = getLastKnownLocation(); if(location == null) return null; Geocoder code = new Geocoder(appContext); try { double lat = location.getLatitude(); double lon = location.getLongitude(); List
addresses = code.getFromLocation(lat, lon, 1); if(addresses.isEmpty()) return null; return addresses.get(0).getCountryCode(); } catch(IOException e) { return null; } } /** * Returns the last location from all location providers, or null if there * is no such location. 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 * this. */ private Location getLastKnownLocation() { Object o = appContext.getSystemService(LOCATION_SERVICE); LocationManager locationManager = (LocationManager) o; 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; } }