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