Home | History | Annotate | Download | only in impl
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.incallui.calllocation.impl;
     18 
     19 import android.content.Context;
     20 import android.location.Location;
     21 import android.net.ConnectivityManager;
     22 import android.net.NetworkInfo;
     23 import android.os.Bundle;
     24 import android.os.Handler;
     25 import android.support.annotation.IntDef;
     26 import android.support.annotation.MainThread;
     27 import android.support.v4.os.UserManagerCompat;
     28 import com.android.dialer.common.Assert;
     29 import com.android.dialer.common.LogUtil;
     30 import com.android.dialer.util.PermissionsUtil;
     31 import com.google.android.gms.common.ConnectionResult;
     32 import com.google.android.gms.common.api.GoogleApiClient;
     33 import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
     34 import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
     35 import com.google.android.gms.common.api.ResultCallback;
     36 import com.google.android.gms.common.api.Status;
     37 import com.google.android.gms.location.LocationListener;
     38 import com.google.android.gms.location.LocationRequest;
     39 import com.google.android.gms.location.LocationServices;
     40 import java.lang.annotation.Retention;
     41 import java.lang.annotation.RetentionPolicy;
     42 import java.util.ArrayList;
     43 import java.util.List;
     44 
     45 /** Uses the Fused location service to get location and pass updates on to listeners. */
     46 public class LocationHelper {
     47 
     48   private static final int MIN_UPDATE_INTERVAL_MS = 30 * 1000;
     49   private static final int LAST_UPDATE_THRESHOLD_MS = 60 * 1000;
     50   private static final int LOCATION_ACCURACY_THRESHOLD_METERS = 100;
     51 
     52   public static final int LOCATION_STATUS_UNKNOWN = 0;
     53   public static final int LOCATION_STATUS_OK = 1;
     54   public static final int LOCATION_STATUS_STALE = 2;
     55   public static final int LOCATION_STATUS_INACCURATE = 3;
     56   public static final int LOCATION_STATUS_NO_LOCATION = 4;
     57 
     58   /** Possible return values for {@code checkLocation()} */
     59   @IntDef({
     60     LOCATION_STATUS_UNKNOWN,
     61     LOCATION_STATUS_OK,
     62     LOCATION_STATUS_STALE,
     63     LOCATION_STATUS_INACCURATE,
     64     LOCATION_STATUS_NO_LOCATION
     65   })
     66   @Retention(RetentionPolicy.SOURCE)
     67   public @interface LocationStatus {}
     68 
     69   private final LocationHelperInternal locationHelperInternal;
     70   private final List<LocationListener> listeners = new ArrayList<>();
     71 
     72   @MainThread
     73   LocationHelper(Context context) {
     74     Assert.isMainThread();
     75     Assert.checkArgument(canGetLocation(context));
     76     locationHelperInternal = new LocationHelperInternal(context);
     77   }
     78 
     79   static boolean canGetLocation(Context context) {
     80     if (!PermissionsUtil.hasLocationPermissions(context)) {
     81       LogUtil.i("LocationHelper.canGetLocation", "no location permissions.");
     82       return false;
     83     }
     84 
     85     // Ensure that both system location setting is on and google location services are enabled.
     86     if (!GoogleLocationSettingHelper.isGoogleLocationServicesEnabled(context)
     87         || !GoogleLocationSettingHelper.isSystemLocationSettingEnabled(context)) {
     88       LogUtil.i("LocationHelper.canGetLocation", "location service is disabled.");
     89       return false;
     90     }
     91 
     92     if (!UserManagerCompat.isUserUnlocked(context)) {
     93       LogUtil.i("LocationHelper.canGetLocation", "location unavailable in FBE mode.");
     94       return false;
     95     }
     96 
     97     return true;
     98   }
     99 
    100   /**
    101    * Check whether the location is valid. We consider it valid if it was recorded within the
    102    * specified time threshold of the present and has an accuracy less than the specified distance
    103    * threshold.
    104    *
    105    * @param location The location to determine the validity of.
    106    * @return {@code LocationStatus} indicating if the location is valid or the reason its not valid
    107    */
    108   static @LocationStatus int checkLocation(Location location) {
    109     if (location == null) {
    110       LogUtil.i("LocationHelper.checkLocation", "no location");
    111       return LOCATION_STATUS_NO_LOCATION;
    112     }
    113 
    114     long locationTimeMs = location.getTime();
    115     long elapsedTimeMs = System.currentTimeMillis() - locationTimeMs;
    116     if (elapsedTimeMs > LAST_UPDATE_THRESHOLD_MS) {
    117       LogUtil.i("LocationHelper.checkLocation", "stale location, age: " + elapsedTimeMs);
    118       return LOCATION_STATUS_STALE;
    119     }
    120 
    121     if (location.getAccuracy() > LOCATION_ACCURACY_THRESHOLD_METERS) {
    122       LogUtil.i("LocationHelper.checkLocation", "poor accuracy: " + location.getAccuracy());
    123       return LOCATION_STATUS_INACCURATE;
    124     }
    125 
    126     return LOCATION_STATUS_OK;
    127   }
    128 
    129   @MainThread
    130   void addLocationListener(LocationListener listener) {
    131     Assert.isMainThread();
    132     listeners.add(listener);
    133   }
    134 
    135   @MainThread
    136   void removeLocationListener(LocationListener listener) {
    137     Assert.isMainThread();
    138     listeners.remove(listener);
    139   }
    140 
    141   @MainThread
    142   void close() {
    143     Assert.isMainThread();
    144     LogUtil.enterBlock("LocationHelper.close");
    145     listeners.clear();
    146 
    147     if (locationHelperInternal != null) {
    148       locationHelperInternal.close();
    149     }
    150   }
    151 
    152   @MainThread
    153   void onLocationChanged(Location location, boolean isConnected) {
    154     Assert.isMainThread();
    155     LogUtil.i("LocationHelper.onLocationChanged", "location: " + location);
    156 
    157     for (LocationListener listener : listeners) {
    158       listener.onLocationChanged(location);
    159     }
    160   }
    161 
    162   /**
    163    * This class contains all the asynchronous callbacks. It only posts location changes back to the
    164    * outer class on the main thread.
    165    */
    166   private class LocationHelperInternal
    167       implements ConnectionCallbacks, OnConnectionFailedListener, LocationListener {
    168 
    169     private final GoogleApiClient apiClient;
    170     private final ConnectivityManager connectivityManager;
    171     private final Handler mainThreadHandler = new Handler();
    172 
    173     @MainThread
    174     LocationHelperInternal(Context context) {
    175       Assert.isMainThread();
    176       apiClient =
    177           new GoogleApiClient.Builder(context)
    178               .addApi(LocationServices.API)
    179               .addConnectionCallbacks(this)
    180               .addOnConnectionFailedListener(this)
    181               .build();
    182 
    183       LogUtil.i("LocationHelperInternal", "Connecting to location service...");
    184       apiClient.connect();
    185 
    186       connectivityManager =
    187           (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    188     }
    189 
    190     void close() {
    191       if (apiClient.isConnected()) {
    192         LogUtil.i("LocationHelperInternal", "disconnecting");
    193         LocationServices.FusedLocationApi.removeLocationUpdates(apiClient, this);
    194         apiClient.disconnect();
    195       }
    196     }
    197 
    198     @Override
    199     public void onConnected(Bundle bundle) {
    200       LogUtil.enterBlock("LocationHelperInternal.onConnected");
    201       LocationRequest locationRequest =
    202           LocationRequest.create()
    203               .setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY)
    204               .setInterval(MIN_UPDATE_INTERVAL_MS)
    205               .setFastestInterval(MIN_UPDATE_INTERVAL_MS);
    206 
    207       LocationServices.FusedLocationApi.requestLocationUpdates(apiClient, locationRequest, this)
    208           .setResultCallback(
    209               new ResultCallback<Status>() {
    210                 @Override
    211                 public void onResult(Status status) {
    212                   if (status.getStatus().isSuccess()) {
    213                     onLocationChanged(LocationServices.FusedLocationApi.getLastLocation(apiClient));
    214                   }
    215                 }
    216               });
    217     }
    218 
    219     @Override
    220     public void onConnectionSuspended(int i) {
    221       // Do nothing.
    222     }
    223 
    224     @Override
    225     public void onConnectionFailed(ConnectionResult result) {
    226       // Do nothing.
    227     }
    228 
    229     @Override
    230     public void onLocationChanged(Location location) {
    231       // Post new location on main thread
    232       mainThreadHandler.post(
    233           new Runnable() {
    234             @Override
    235             public void run() {
    236               LocationHelper.this.onLocationChanged(location, isConnected());
    237             }
    238           });
    239     }
    240 
    241     /** @return Whether the phone is connected to data. */
    242     private boolean isConnected() {
    243       if (connectivityManager == null) {
    244         return false;
    245       }
    246       NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
    247       return networkInfo != null && networkInfo.isConnectedOrConnecting();
    248     }
    249   }
    250 }
    251