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