• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Handler;
24 import android.support.annotation.IntDef;
25 import android.support.annotation.MainThread;
26 import android.support.v4.os.UserManagerCompat;
27 import com.android.dialer.common.Assert;
28 import com.android.dialer.common.LogUtil;
29 import com.android.dialer.util.PermissionsUtil;
30 import com.google.android.gms.location.FusedLocationProviderClient;
31 import com.google.android.gms.location.LocationListener;
32 import com.google.android.gms.location.LocationRequest;
33 import com.google.android.gms.location.LocationServices;
34 import java.lang.annotation.Retention;
35 import java.lang.annotation.RetentionPolicy;
36 import java.util.ArrayList;
37 import java.util.List;
38 
39 /** Uses the Fused location service to get location and pass updates on to listeners. */
40 public class LocationHelper {
41 
42   private static final int FAST_MIN_UPDATE_INTERVAL_MS = 5 * 1000;
43   private static final int SLOW_MIN_UPDATE_INTERVAL_MS = 30 * 1000;
44   private static final int LAST_UPDATE_THRESHOLD_MS = 60 * 1000;
45   private static final int LOCATION_ACCURACY_THRESHOLD_METERS = 100;
46 
47   public static final int LOCATION_STATUS_UNKNOWN = 0;
48   public static final int LOCATION_STATUS_OK = 1;
49   public static final int LOCATION_STATUS_STALE = 2;
50   public static final int LOCATION_STATUS_INACCURATE = 3;
51   public static final int LOCATION_STATUS_NO_LOCATION = 4;
52 
53   /** Possible return values for {@code checkLocation()} */
54   @IntDef({
55     LOCATION_STATUS_UNKNOWN,
56     LOCATION_STATUS_OK,
57     LOCATION_STATUS_STALE,
58     LOCATION_STATUS_INACCURATE,
59     LOCATION_STATUS_NO_LOCATION
60   })
61   @Retention(RetentionPolicy.SOURCE)
62   public @interface LocationStatus {}
63 
64   private final LocationHelperInternal locationHelperInternal;
65   private final List<LocationListener> listeners = new ArrayList<>();
66 
67   @MainThread
LocationHelper(Context context)68   LocationHelper(Context context) {
69     Assert.isMainThread();
70     Assert.checkArgument(canGetLocation(context));
71     locationHelperInternal = new LocationHelperInternal(context);
72   }
73 
canGetLocation(Context context)74   static boolean canGetLocation(Context context) {
75     if (!PermissionsUtil.hasLocationPermissions(context)) {
76       LogUtil.i("LocationHelper.canGetLocation", "no location permissions.");
77       return false;
78     }
79 
80     // Ensure that both system location setting is on and google location services are enabled.
81     if (!GoogleLocationSettingHelper.isGoogleLocationServicesEnabled(context)
82         || !GoogleLocationSettingHelper.isSystemLocationSettingEnabled(context)) {
83       LogUtil.i("LocationHelper.canGetLocation", "location service is disabled.");
84       return false;
85     }
86 
87     if (!UserManagerCompat.isUserUnlocked(context)) {
88       LogUtil.i("LocationHelper.canGetLocation", "location unavailable in FBE mode.");
89       return false;
90     }
91 
92     return true;
93   }
94 
95   /**
96    * Check whether the location is valid. We consider it valid if it was recorded within the
97    * specified time threshold of the present and has an accuracy less than the specified distance
98    * threshold.
99    *
100    * @param location The location to determine the validity of.
101    * @return {@code LocationStatus} indicating if the location is valid or the reason its not valid
102    */
checkLocation(Location location)103   static @LocationStatus int checkLocation(Location location) {
104     if (location == null) {
105       LogUtil.i("LocationHelper.checkLocation", "no location");
106       return LOCATION_STATUS_NO_LOCATION;
107     }
108 
109     long locationTimeMs = location.getTime();
110     long elapsedTimeMs = System.currentTimeMillis() - locationTimeMs;
111     if (elapsedTimeMs > LAST_UPDATE_THRESHOLD_MS) {
112       LogUtil.i("LocationHelper.checkLocation", "stale location, age: " + elapsedTimeMs);
113       return LOCATION_STATUS_STALE;
114     }
115 
116     if (location.getAccuracy() > LOCATION_ACCURACY_THRESHOLD_METERS) {
117       LogUtil.i("LocationHelper.checkLocation", "poor accuracy: " + location.getAccuracy());
118       return LOCATION_STATUS_INACCURATE;
119     }
120 
121     return LOCATION_STATUS_OK;
122   }
123 
124   @MainThread
addLocationListener(LocationListener listener)125   void addLocationListener(LocationListener listener) {
126     Assert.isMainThread();
127     listeners.add(listener);
128   }
129 
130   @MainThread
removeLocationListener(LocationListener listener)131   void removeLocationListener(LocationListener listener) {
132     Assert.isMainThread();
133     listeners.remove(listener);
134   }
135 
136   @MainThread
close()137   void close() {
138     Assert.isMainThread();
139     LogUtil.enterBlock("LocationHelper.close");
140     listeners.clear();
141     locationHelperInternal.close();
142   }
143 
144   @MainThread
onLocationChanged(Location location, boolean isConnected)145   void onLocationChanged(Location location, boolean isConnected) {
146     Assert.isMainThread();
147     LogUtil.i("LocationHelper.onLocationChanged", "location: " + location);
148 
149     for (LocationListener listener : listeners) {
150       listener.onLocationChanged(location);
151     }
152   }
153 
154   /**
155    * This class contains all the asynchronous callbacks. It only posts location changes back to the
156    * outer class on the main thread.
157    */
158   private class LocationHelperInternal implements LocationListener {
159 
160     private final FusedLocationProviderClient locationClient;
161     private final ConnectivityManager connectivityManager;
162     private final Handler mainThreadHandler = new Handler();
163     private boolean gotGoodLocation;
164 
165     @MainThread
LocationHelperInternal(Context context)166     LocationHelperInternal(Context context) {
167       Assert.isMainThread();
168       locationClient = LocationServices.getFusedLocationProviderClient(context);
169       connectivityManager =
170           (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
171       requestUpdates();
172       getLocation();
173     }
174 
close()175     void close() {
176       LogUtil.enterBlock("LocationHelperInternal.close");
177       locationClient.removeLocationUpdates(this);
178     }
179 
requestUpdates()180     private void requestUpdates() {
181       LogUtil.enterBlock("LocationHelperInternal.requestUpdates");
182 
183       int interval = gotGoodLocation ? SLOW_MIN_UPDATE_INTERVAL_MS : FAST_MIN_UPDATE_INTERVAL_MS;
184       LocationRequest locationRequest =
185           LocationRequest.create()
186               .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
187               .setInterval(interval)
188               .setFastestInterval(interval);
189 
190       locationClient
191           .requestLocationUpdates(locationRequest, this)
192           .addOnSuccessListener(
193               result -> LogUtil.i("LocationHelperInternal.requestUpdates", "onSuccess"))
194           .addOnFailureListener(
195               e -> LogUtil.e("LocationHelperInternal.requestUpdates", "onFailure", e));
196     }
197 
getLocation()198     private void getLocation() {
199       LogUtil.enterBlock("LocationHelperInternal.getLocation");
200 
201       locationClient
202           .getLastLocation()
203           .addOnSuccessListener(
204               location -> {
205                 LogUtil.i("LocationHelperInternal.getLocation", "onSuccess");
206                 Assert.isMainThread();
207                 LocationHelper.this.onLocationChanged(location, isConnected());
208                 maybeAdjustUpdateInterval(location);
209               })
210           .addOnFailureListener(
211               e -> LogUtil.e("LocationHelperInternal.getLocation", "onFailure", e));
212     }
213 
214     @Override
onLocationChanged(Location location)215     public void onLocationChanged(Location location) {
216       // Post new location on main thread
217       mainThreadHandler.post(
218           new Runnable() {
219             @Override
220             public void run() {
221               LocationHelper.this.onLocationChanged(location, isConnected());
222               maybeAdjustUpdateInterval(location);
223             }
224           });
225     }
226 
maybeAdjustUpdateInterval(Location location)227     private void maybeAdjustUpdateInterval(Location location) {
228       if (!gotGoodLocation && checkLocation(location) == LOCATION_STATUS_OK) {
229         LogUtil.i("LocationHelperInternal.maybeAdjustUpdateInterval", "got good location");
230         gotGoodLocation = true;
231         requestUpdates();
232       }
233     }
234 
235     /** @return Whether the phone is connected to data. */
isConnected()236     private boolean isConnected() {
237       if (connectivityManager == null) {
238         return false;
239       }
240       NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
241       return networkInfo != null && networkInfo.isConnectedOrConnecting();
242     }
243   }
244 }
245