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