summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/location
diff options
context:
space:
mode:
authorEric Erfanian <erfanian@google.com>2017-05-04 08:23:17 -0700
committerEric Erfanian <erfanian@google.com>2017-05-04 14:04:39 -0700
commit10b34a5ebf12e97ecba0caf3c8e30b476b038a96 (patch)
tree3a325b0effac02fbd228b8ddf2f96589e5df72cd /java/com/android/dialer/location
parent8369df095a73a77b3715f8ae7ba06089cebca4ce (diff)
Update Dialer to V10 RC16
This release was created following the instructions at: go/dialer-aosp-release Subsequent dialer releases will follow as O bugs are fixed, until we reach our final RC. Version: 10 Candidate: RC16 Branch: dialer-android_release_branch/153304843.1 dialer-android/dialer-android_20170416.00/dialer-android_20170416.00_RC16 This release contains the following bug fixes since RC00: Bug: 37324705 35304403 36067503 35304446 33203808 37280992 37346084 35766990 37481880 37424493 36470282 37347691 37519015 37168472 35805360 37545472 27704934 36515614 35766990 37577470 34739750 35801628 36788693 35264204 36708536 37628370 36904650 37314436 37642171 37530847 37637799 37666625 37548549 37648036 37636412 37323529 37630507 35919141 37198343 37548572 36178218 37640315 37663896 37720467 37275944 37710497 31634477 37744796 37348506 37744796 37568534 37672424 34872683 34873026 37681461 34873295 37748373 37526812 37618638 37663896 37536088 37727455 37165687 36651204 36900708 37323529 36902926 37256480 37328353 37432034 37436952 34093562 37720889 37321935 37780300 37781115 37755902 36588206 34258266 37290464 37698062 37618638 37473004 37432034 37918676 37870494 37722091 Test: make, on device Change-Id: I99e1a484ccd578c1f8a13e7a6a4b4952f0791297
Diffstat (limited to 'java/com/android/dialer/location')
-rw-r--r--java/com/android/dialer/location/AndroidManifest.xml22
-rw-r--r--java/com/android/dialer/location/CountryDetector.java262
-rw-r--r--java/com/android/dialer/location/GeoUtil.java47
3 files changed, 331 insertions, 0 deletions
diff --git a/java/com/android/dialer/location/AndroidManifest.xml b/java/com/android/dialer/location/AndroidManifest.xml
new file mode 100644
index 000000000..8f7448020
--- /dev/null
+++ b/java/com/android/dialer/location/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.contacts.common">
+
+ <application>
+ <!-- Broadcast receiver that passively listens to location updates -->
+ <receiver android:name="com.android.dialer.location.CountryDetector$LocationChangedReceiver"/>
+ </application>
+</manifest>
diff --git a/java/com/android/dialer/location/CountryDetector.java b/java/com/android/dialer/location/CountryDetector.java
new file mode 100644
index 000000000..fd93b6ae9
--- /dev/null
+++ b/java/com/android/dialer/location/CountryDetector.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.location;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.location.Address;
+import android.location.Geocoder;
+import android.location.Location;
+import android.location.LocationManager;
+import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.DialerExecutors;
+import com.android.dialer.common.concurrent.DialerExecutor.Worker;
+import com.android.dialer.util.PermissionsUtil;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * This class is used to detect the country where the user is. It is a simplified version of the
+ * country detector service in the framework. The sources of country location are queried in the
+ * following order of reliability:
+ *
+ * <ul>
+ * <li>Mobile network
+ * <li>Location manager
+ * <li>SIM's country
+ * <li>User's default locale
+ * </ul>
+ *
+ * As far as possible this class tries to replicate the behavior of the system's country detector
+ * service: 1) Order in priority of sources of country location 2) Mobile network information
+ * provided by CDMA phones is ignored 3) Location information is updated every 12 hours (instead of
+ * 24 hours in the system) 4) Location updates only uses the {@link
+ * LocationManager#PASSIVE_PROVIDER} to avoid active use of the GPS 5) If a location is successfully
+ * obtained and geocoded, we never fall back to use of the SIM's country (for the system, the
+ * fallback never happens without a reboot) 6) Location is not used if the device does not implement
+ * a {@link android.location.Geocoder}
+ */
+public class CountryDetector {
+ private static final String KEY_PREFERENCE_TIME_UPDATED = "preference_time_updated";
+ static final String KEY_PREFERENCE_CURRENT_COUNTRY = "preference_current_country";
+ // Wait 12 hours between updates
+ private static final long TIME_BETWEEN_UPDATES_MS = 1000L * 60 * 60 * 12;
+ // Minimum distance before an update is triggered, in meters. We don't need this to be too
+ // exact because all we care about is what country the user is in.
+ private static final long DISTANCE_BETWEEN_UPDATES_METERS = 5000;
+ // Used as a default country code when all the sources of country data have failed in the
+ // exceedingly rare event that the device does not have a default locale set for some reason.
+ private static final String DEFAULT_COUNTRY_ISO = "US";
+
+ @VisibleForTesting static CountryDetector sInstance;
+
+ private final TelephonyManager telephonyManager;
+ private final LocaleProvider localeProvider;
+ private final Geocoder geocoder;
+ private final Context appContext;
+
+ @VisibleForTesting
+ CountryDetector(
+ Context appContext,
+ TelephonyManager telephonyManager,
+ LocationManager locationManager,
+ LocaleProvider localeProvider,
+ Geocoder geocoder) {
+ this.telephonyManager = telephonyManager;
+ this.localeProvider = localeProvider;
+ this.appContext = appContext;
+ this.geocoder = geocoder;
+
+ // If the device does not implement Geocoder there is no point trying to get location updates
+ // because we cannot retrieve the country based on the location anyway.
+ if (Geocoder.isPresent()) {
+ registerForLocationUpdates(appContext, locationManager);
+ }
+ }
+
+ private static void registerForLocationUpdates(Context context, LocationManager locationManager) {
+ if (!PermissionsUtil.hasLocationPermissions(context)) {
+ LogUtil.w(
+ "CountryDetector.registerForLocationUpdates",
+ "no location permissions, not registering for location updates");
+ return;
+ }
+
+ LogUtil.i("CountryDetector.registerForLocationUpdates", "registering for location updates");
+
+ final Intent activeIntent = new Intent(context, LocationChangedReceiver.class);
+ final PendingIntent pendingIntent =
+ PendingIntent.getBroadcast(context, 0, activeIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ locationManager.requestLocationUpdates(
+ LocationManager.PASSIVE_PROVIDER,
+ TIME_BETWEEN_UPDATES_MS,
+ DISTANCE_BETWEEN_UPDATES_METERS,
+ pendingIntent);
+ }
+
+ /** @return the single instance of the {@link CountryDetector} */
+ public static synchronized CountryDetector getInstance(Context context) {
+ if (sInstance == null) {
+ Context appContext = context.getApplicationContext();
+ sInstance =
+ new CountryDetector(
+ appContext,
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE),
+ (LocationManager) context.getSystemService(Context.LOCATION_SERVICE),
+ Locale::getDefault,
+ new Geocoder(appContext));
+ }
+ return sInstance;
+ }
+
+ public String getCurrentCountryIso() {
+ String result = null;
+ if (isNetworkCountryCodeAvailable()) {
+ result = getNetworkBasedCountryIso();
+ }
+ if (TextUtils.isEmpty(result)) {
+ result = getLocationBasedCountryIso();
+ }
+ if (TextUtils.isEmpty(result)) {
+ result = getSimBasedCountryIso();
+ }
+ if (TextUtils.isEmpty(result)) {
+ result = getLocaleBasedCountryIso();
+ }
+ if (TextUtils.isEmpty(result)) {
+ result = DEFAULT_COUNTRY_ISO;
+ }
+ return result.toUpperCase(Locale.US);
+ }
+
+ /** @return the country code of the current telephony network the user is connected to. */
+ private String getNetworkBasedCountryIso() {
+ return telephonyManager.getNetworkCountryIso();
+ }
+
+ /** @return the geocoded country code detected by the {@link LocationManager}. */
+ private String getLocationBasedCountryIso() {
+ if (!Geocoder.isPresent() || !PermissionsUtil.hasLocationPermissions(appContext)) {
+ return null;
+ }
+ return PreferenceManager.getDefaultSharedPreferences(appContext)
+ .getString(KEY_PREFERENCE_CURRENT_COUNTRY, null);
+ }
+
+ /** @return the country code of the SIM card currently inserted in the device. */
+ private String getSimBasedCountryIso() {
+ return telephonyManager.getSimCountryIso();
+ }
+
+ /** @return the country code of the user's currently selected locale. */
+ private String getLocaleBasedCountryIso() {
+ Locale defaultLocale = localeProvider.getLocale();
+ if (defaultLocale != null) {
+ return defaultLocale.getCountry();
+ }
+ return null;
+ }
+
+ private boolean isNetworkCountryCodeAvailable() {
+ // On CDMA TelephonyManager.getNetworkCountryIso() just returns the SIM's country code.
+ // In this case, we want to ignore the value returned and fallback to location instead.
+ return telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM;
+ }
+
+ /** Interface for accessing the current locale. */
+ interface LocaleProvider {
+ Locale getLocale();
+ }
+
+ public static class LocationChangedReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(final Context context, Intent intent) {
+ if (!intent.hasExtra(LocationManager.KEY_LOCATION_CHANGED)) {
+ return;
+ }
+
+ final Location location =
+ (Location) intent.getExtras().get(LocationManager.KEY_LOCATION_CHANGED);
+
+ // TODO: rething how we access the gecoder here, right now we have to set the static instance
+ // of CountryDetector to make this work for tests which is weird
+ // (see CountryDetectorTest.locationChangedBroadcast_GeocodesLocation)
+ processLocationUpdate(context, CountryDetector.getInstance(context).geocoder, location);
+ }
+ }
+
+ private static void processLocationUpdate(
+ Context appContext, Geocoder geocoder, Location location) {
+ DialerExecutors.createNonUiTaskBuilder(new GeocodeCountryWorker(geocoder))
+ .onSuccess(
+ country -> {
+ if (country == null) {
+ return;
+ }
+
+ PreferenceManager.getDefaultSharedPreferences(appContext)
+ .edit()
+ .putLong(CountryDetector.KEY_PREFERENCE_TIME_UPDATED, System.currentTimeMillis())
+ .putString(CountryDetector.KEY_PREFERENCE_CURRENT_COUNTRY, country)
+ .apply();
+ })
+ .onFailure(
+ throwable ->
+ LogUtil.w(
+ "CountryDetector.processLocationUpdate",
+ "exception occurred when getting geocoded country from location",
+ throwable))
+ .build()
+ .executeParallel(location);
+ }
+
+ /** Worker that given a {@link Location} returns an ISO 3166-1 two letter country code. */
+ private static class GeocodeCountryWorker implements Worker<Location, String> {
+ @NonNull private final Geocoder geocoder;
+
+ GeocodeCountryWorker(@NonNull Geocoder geocoder) {
+ this.geocoder = Assert.isNotNull(geocoder);
+ }
+
+ /** @return the ISO 3166-1 two letter country code if geocoded, else null */
+ @Nullable
+ @Override
+ public String doInBackground(@Nullable Location location) throws Throwable {
+ if (location == null) {
+ return null;
+ }
+
+ List<Address> addresses =
+ geocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1);
+ if (addresses != null && !addresses.isEmpty()) {
+ return addresses.get(0).getCountryCode();
+ }
+ return null;
+ }
+ }
+}
diff --git a/java/com/android/dialer/location/GeoUtil.java b/java/com/android/dialer/location/GeoUtil.java
new file mode 100644
index 000000000..1be40544d
--- /dev/null
+++ b/java/com/android/dialer/location/GeoUtil.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.location;
+
+import android.content.Context;
+import com.google.i18n.phonenumbers.NumberParseException;
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
+import com.google.i18n.phonenumbers.Phonenumber;
+import com.google.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder;
+import java.util.Locale;
+
+/** Static methods related to Geo. */
+public class GeoUtil {
+
+ /** @return the ISO 3166-1 two letters country code of the country the user is in. */
+ public static String getCurrentCountryIso(Context context) {
+ // The {@link CountryDetector} should never return null so this is safe to return as-is.
+ return CountryDetector.getInstance(context).getCurrentCountryIso();
+ }
+
+ public static String getGeocodedLocationFor(Context context, String phoneNumber) {
+ final PhoneNumberOfflineGeocoder geocoder = PhoneNumberOfflineGeocoder.getInstance();
+ final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
+ try {
+ final Phonenumber.PhoneNumber structuredPhoneNumber =
+ phoneNumberUtil.parse(phoneNumber, getCurrentCountryIso(context));
+ final Locale locale = context.getResources().getConfiguration().locale;
+ return geocoder.getDescriptionForNumber(structuredPhoneNumber, locale);
+ } catch (NumberParseException e) {
+ return null;
+ }
+ }
+}