1 /* 2 * Copyright (C) 2012 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.location.fused; 18 19 import static android.content.Intent.ACTION_USER_SWITCHED; 20 import static android.location.LocationManager.GPS_PROVIDER; 21 import static android.location.LocationManager.NETWORK_PROVIDER; 22 import static android.location.LocationRequest.QUALITY_HIGH_ACCURACY; 23 import static android.location.LocationRequest.QUALITY_LOW_POWER; 24 import static android.location.provider.ProviderProperties.ACCURACY_FINE; 25 import static android.location.provider.ProviderProperties.POWER_USAGE_LOW; 26 27 import static com.android.location.provider.ProviderRequestUnbundled.INTERVAL_DISABLED; 28 29 import android.annotation.Nullable; 30 import android.content.BroadcastReceiver; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.IntentFilter; 34 import android.location.Location; 35 import android.location.LocationListener; 36 import android.location.LocationManager; 37 import android.location.LocationRequest; 38 import android.location.flags.Flags; 39 import android.location.provider.LocationProviderBase; 40 import android.location.provider.ProviderProperties; 41 import android.location.provider.ProviderRequest; 42 import android.os.Bundle; 43 import android.util.Log; 44 import android.util.SparseArray; 45 46 import com.android.internal.annotations.GuardedBy; 47 48 import java.io.PrintWriter; 49 import java.util.Objects; 50 import java.util.concurrent.atomic.AtomicInteger; 51 52 /** Basic fused location provider implementation. */ 53 public class FusedLocationProvider extends LocationProviderBase { 54 55 private static final String TAG = "FusedLocationProvider"; 56 private static final String ATTRIBUTION_TAG = "FusedOverlayService"; 57 58 private static final ProviderProperties PROPERTIES = new ProviderProperties.Builder() 59 .setHasAltitudeSupport(true) 60 .setHasSpeedSupport(true) 61 .setHasBearingSupport(true) 62 .setPowerUsage(POWER_USAGE_LOW) 63 .setAccuracy(ACCURACY_FINE) 64 .build(); 65 66 private static final long MAX_LOCATION_COMPARISON_NS = 11 * 1000000000L; // 11 seconds 67 // Maximum request interval at which we will activate GPS (because GPS sometimes consumes 68 // excessive power with large intervals). 69 private static final long MAX_GPS_INTERVAL_MS = 5 * 1000; // 5 seconds 70 71 private final Object mLock = new Object(); 72 73 private final Context mContext; 74 private final LocationManager mLocationManager; 75 private final ChildLocationListener mGpsListener; 76 private final ChildLocationListener mNetworkListener; 77 private final BroadcastReceiver mUserChangeReceiver; 78 79 @GuardedBy("mLock") 80 boolean mGpsPresent; 81 82 @GuardedBy("mLock") 83 boolean mNlpPresent; 84 85 @GuardedBy("mLock") 86 private ProviderRequest mRequest; 87 88 @GuardedBy("mLock") 89 private @Nullable Location mFusedLocation; 90 FusedLocationProvider(Context context)91 public FusedLocationProvider(Context context) { 92 super(context, TAG, PROPERTIES); 93 if (Flags.missingAttributionTagsInOverlay()) { 94 mContext = context.createAttributionContext(ATTRIBUTION_TAG); 95 } else { 96 mContext = context; 97 } 98 mLocationManager = Objects.requireNonNull(mContext.getSystemService(LocationManager.class)); 99 100 mGpsListener = new ChildLocationListener(GPS_PROVIDER); 101 mNetworkListener = new ChildLocationListener(NETWORK_PROVIDER); 102 103 mUserChangeReceiver = new BroadcastReceiver() { 104 @Override 105 public void onReceive(Context context, Intent intent) { 106 if (!ACTION_USER_SWITCHED.equals(intent.getAction())) { 107 return; 108 } 109 110 onUserChanged(); 111 } 112 }; 113 114 mRequest = ProviderRequest.EMPTY_REQUEST; 115 } 116 start()117 void start() { 118 mContext.registerReceiver(mUserChangeReceiver, new IntentFilter(ACTION_USER_SWITCHED)); 119 } 120 stop()121 void stop() { 122 mContext.unregisterReceiver(mUserChangeReceiver); 123 124 synchronized (mLock) { 125 mRequest = ProviderRequest.EMPTY_REQUEST; 126 updateRequirementsLocked(); 127 } 128 } 129 130 @Override onSetRequest(ProviderRequest request)131 public void onSetRequest(ProviderRequest request) { 132 synchronized (mLock) { 133 mRequest = request; 134 updateRequirementsLocked(); 135 } 136 } 137 138 @Override onFlush(OnFlushCompleteCallback callback)139 public void onFlush(OnFlushCompleteCallback callback) { 140 synchronized (mLock) { 141 AtomicInteger flushCount = new AtomicInteger(0); 142 if (mGpsPresent) { 143 flushCount.incrementAndGet(); 144 } 145 if (mNlpPresent) { 146 flushCount.incrementAndGet(); 147 } 148 149 OnFlushCompleteCallback wrapper = () -> { 150 if (flushCount.decrementAndGet() == 0) { 151 callback.onFlushComplete(); 152 } 153 }; 154 155 if (mGpsPresent) { 156 mGpsListener.flush(wrapper); 157 } 158 if (mNlpPresent) { 159 mNetworkListener.flush(wrapper); 160 } 161 } 162 } 163 164 @Override onSendExtraCommand(String command, @Nullable Bundle extras)165 public void onSendExtraCommand(String command, @Nullable Bundle extras) {} 166 167 @GuardedBy("mLock") updateRequirementsLocked()168 private void updateRequirementsLocked() { 169 // it's possible there might be race conditions on device start where a provider doesn't 170 // appear to be present yet, but once a provider is present it shouldn't go away. 171 if (!mGpsPresent) { 172 mGpsPresent = mLocationManager.hasProvider(GPS_PROVIDER); 173 } 174 if (!mNlpPresent) { 175 mNlpPresent = mLocationManager.hasProvider(NETWORK_PROVIDER); 176 } 177 178 boolean requestAllowsGps = 179 Flags.limitFusedGps() 180 ? mRequest.getQuality() == QUALITY_HIGH_ACCURACY 181 && mRequest.getIntervalMillis() <= MAX_GPS_INTERVAL_MS 182 : !mNlpPresent || mRequest.getQuality() < QUALITY_LOW_POWER; 183 long gpsInterval = 184 mGpsPresent && requestAllowsGps 185 ? mRequest.getIntervalMillis() : INTERVAL_DISABLED; 186 long networkInterval = mNlpPresent ? mRequest.getIntervalMillis() : INTERVAL_DISABLED; 187 188 mGpsListener.resetProviderRequest(gpsInterval); 189 mNetworkListener.resetProviderRequest(networkInterval); 190 } 191 192 @GuardedBy("mLock") 193 void reportBestLocationLocked() { 194 Location bestLocation = chooseBestLocation(mGpsListener.getLocation(), 195 mNetworkListener.getLocation()); 196 if (bestLocation == mFusedLocation) { 197 return; 198 } 199 200 mFusedLocation = bestLocation; 201 if (mFusedLocation == null) { 202 return; 203 } 204 205 reportLocation(mFusedLocation); 206 } 207 208 void onUserChanged() { 209 // clear cached locations when the user changes to prevent leaking user information 210 synchronized (mLock) { 211 mFusedLocation = null; 212 mGpsListener.clearLocation(); 213 mNetworkListener.clearLocation(); 214 } 215 } 216 217 void dump(PrintWriter writer) { 218 synchronized (mLock) { 219 writer.println("request: " + mRequest); 220 if (mGpsListener.getInterval() != INTERVAL_DISABLED) { 221 writer.println(" gps interval: " + mGpsListener.getInterval()); 222 } 223 if (mNetworkListener.getInterval() != INTERVAL_DISABLED) { 224 writer.println(" network interval: " + mNetworkListener.getInterval()); 225 } 226 if (mGpsListener.getLocation() != null) { 227 writer.println(" last gps location: " + mGpsListener.getLocation()); 228 } 229 if (mNetworkListener.getLocation() != null) { 230 writer.println(" last network location: " + mNetworkListener.getLocation()); 231 } 232 } 233 } 234 235 @Nullable 236 private static Location chooseBestLocation( 237 @Nullable Location locationA, 238 @Nullable Location locationB) { 239 if (locationA == null) { 240 return locationB; 241 } 242 if (locationB == null) { 243 return locationA; 244 } 245 246 if (locationA.getElapsedRealtimeNanos() 247 > locationB.getElapsedRealtimeNanos() + MAX_LOCATION_COMPARISON_NS) { 248 return locationA; 249 } 250 if (locationB.getElapsedRealtimeNanos() 251 > locationA.getElapsedRealtimeNanos() + MAX_LOCATION_COMPARISON_NS) { 252 return locationB; 253 } 254 255 if (!locationA.hasAccuracy()) { 256 return locationB; 257 } 258 if (!locationB.hasAccuracy()) { 259 return locationA; 260 } 261 return locationA.getAccuracy() < locationB.getAccuracy() ? locationA : locationB; 262 } 263 264 private class ChildLocationListener implements LocationListener { 265 266 private final String mProvider; 267 private final SparseArray<OnFlushCompleteCallback> mPendingFlushes; 268 269 @GuardedBy("mLock") 270 private int mNextFlushCode = 0; 271 @GuardedBy("mLock") 272 private @Nullable Location mLocation = null; 273 @GuardedBy("mLock") 274 private long mInterval = INTERVAL_DISABLED; 275 276 ChildLocationListener(String provider) { 277 mProvider = provider; 278 mPendingFlushes = new SparseArray<>(); 279 } 280 281 @Nullable Location getLocation() { 282 synchronized (mLock) { 283 return mLocation; 284 } 285 } 286 287 long getInterval() { 288 synchronized (mLock) { 289 return mInterval; 290 } 291 } 292 293 void clearLocation() { 294 synchronized (mLock) { 295 mLocation = null; 296 } 297 } 298 299 private void resetProviderRequest(long newInterval) { 300 synchronized (mLock) { 301 if (newInterval == mInterval) { 302 return; 303 } 304 305 if (mInterval != INTERVAL_DISABLED && newInterval == INTERVAL_DISABLED) { 306 mLocationManager.removeUpdates(this); 307 } 308 309 mInterval = newInterval; 310 311 if (mInterval != INTERVAL_DISABLED) { 312 LocationRequest request = new LocationRequest.Builder(mInterval) 313 .setMaxUpdateDelayMillis(mRequest.getMaxUpdateDelayMillis()) 314 .setQuality(mRequest.getQuality()) 315 .setLowPower(mRequest.isLowPower()) 316 .setLocationSettingsIgnored(mRequest.isLocationSettingsIgnored()) 317 .setWorkSource(mRequest.getWorkSource()) 318 .setHiddenFromAppOps(true) 319 .build(); 320 321 try { 322 mLocationManager.requestLocationUpdates( 323 mProvider, request, mContext.getMainExecutor(), this); 324 } catch (IllegalArgumentException e) { 325 Log.e(TAG, "Failed to request location updates"); 326 } 327 } 328 } 329 } 330 331 void flush(OnFlushCompleteCallback callback) { 332 synchronized (mLock) { 333 int requestCode = mNextFlushCode++; 334 mPendingFlushes.put(requestCode, callback); 335 try { 336 mLocationManager.requestFlush(mProvider, this, requestCode); 337 } catch (IllegalArgumentException e) { 338 Log.e(TAG, "Failed to request flush"); 339 } 340 } 341 } 342 343 @Override 344 public void onLocationChanged(Location location) { 345 synchronized (mLock) { 346 mLocation = location; 347 reportBestLocationLocked(); 348 } 349 } 350 351 @Override 352 public void onProviderDisabled(String provider) { 353 synchronized (mLock) { 354 // if satisfying a bypass request, don't clear anything 355 if (mRequest.isActive() && mRequest.isLocationSettingsIgnored()) { 356 return; 357 } 358 359 mLocation = null; 360 } 361 } 362 363 @Override 364 public void onFlushComplete(int requestCode) { 365 synchronized (mLock) { 366 OnFlushCompleteCallback callback = mPendingFlushes.removeReturnOld(requestCode); 367 if (callback != null) { 368 callback.onFlushComplete(); 369 } 370 } 371 } 372 } 373 } 374