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 23 import android.annotation.Nullable; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.location.Criteria; 29 import android.location.Location; 30 import android.location.LocationListener; 31 import android.location.LocationManager; 32 import android.location.LocationRequest; 33 import android.os.Bundle; 34 import android.os.Looper; 35 import android.os.Parcelable; 36 import android.os.WorkSource; 37 38 import com.android.internal.annotations.GuardedBy; 39 import com.android.internal.location.ProviderRequest; 40 import com.android.location.provider.LocationProviderBase; 41 import com.android.location.provider.LocationRequestUnbundled; 42 import com.android.location.provider.ProviderPropertiesUnbundled; 43 import com.android.location.provider.ProviderRequestUnbundled; 44 45 import java.io.PrintWriter; 46 47 /** Basic fused location provider implementation. */ 48 public class FusedLocationProvider extends LocationProviderBase { 49 50 private static final String TAG = "FusedLocationProvider"; 51 52 private static final ProviderPropertiesUnbundled PROPERTIES = 53 ProviderPropertiesUnbundled.create( 54 /* requiresNetwork = */ false, 55 /* requiresSatellite = */ false, 56 /* requiresCell = */ false, 57 /* hasMonetaryCost = */ false, 58 /* supportsAltitude = */ true, 59 /* supportsSpeed = */ true, 60 /* supportsBearing = */ true, 61 Criteria.POWER_LOW, 62 Criteria.ACCURACY_FINE 63 ); 64 65 private static final long MAX_LOCATION_COMPARISON_NS = 11 * 1000000000L; // 11 seconds 66 67 private final Object mLock = new Object(); 68 69 private final Context mContext; 70 private final LocationManager mLocationManager; 71 private final LocationListener mGpsListener; 72 private final LocationListener mNetworkListener; 73 private final BroadcastReceiver mUserChangeReceiver; 74 75 @GuardedBy("mLock") 76 private ProviderRequestUnbundled mRequest; 77 @GuardedBy("mLock") 78 private WorkSource mWorkSource; 79 @GuardedBy("mLock") 80 private long mGpsInterval; 81 @GuardedBy("mLock") 82 private long mNetworkInterval; 83 84 @GuardedBy("mLock") 85 @Nullable private Location mFusedLocation; 86 @GuardedBy("mLock") 87 @Nullable private Location mGpsLocation; 88 @GuardedBy("mLock") 89 @Nullable private Location mNetworkLocation; 90 FusedLocationProvider(Context context)91 public FusedLocationProvider(Context context) { 92 super(TAG, PROPERTIES); 93 mContext = context; 94 mLocationManager = context.getSystemService(LocationManager.class); 95 96 mGpsListener = new LocationListener() { 97 @Override 98 public void onLocationChanged(Location location) { 99 synchronized (mLock) { 100 mGpsLocation = location; 101 reportBestLocationLocked(); 102 } 103 } 104 105 @Override 106 public void onProviderDisabled(String provider) { 107 synchronized (mLock) { 108 // if satisfying a bypass request, don't clear anything 109 if (mRequest.getReportLocation() && mRequest.isLocationSettingsIgnored()) { 110 return; 111 } 112 113 mGpsLocation = null; 114 } 115 } 116 }; 117 118 mNetworkListener = new LocationListener() { 119 @Override 120 public void onLocationChanged(Location location) { 121 synchronized (mLock) { 122 mNetworkLocation = location; 123 reportBestLocationLocked(); 124 } 125 } 126 127 @Override 128 public void onProviderDisabled(String provider) { 129 synchronized (mLock) { 130 // if satisfying a bypass request, don't clear anything 131 if (mRequest.getReportLocation() && mRequest.isLocationSettingsIgnored()) { 132 return; 133 } 134 135 mNetworkLocation = null; 136 } 137 } 138 }; 139 140 mUserChangeReceiver = new BroadcastReceiver() { 141 @Override 142 public void onReceive(Context context, Intent intent) { 143 if (!ACTION_USER_SWITCHED.equals(intent.getAction())) { 144 return; 145 } 146 147 onUserChanged(); 148 } 149 }; 150 151 mRequest = new ProviderRequestUnbundled(ProviderRequest.EMPTY_REQUEST); 152 mWorkSource = new WorkSource(); 153 mGpsInterval = Long.MAX_VALUE; 154 mNetworkInterval = Long.MAX_VALUE; 155 } 156 start()157 void start() { 158 mContext.registerReceiver(mUserChangeReceiver, new IntentFilter(ACTION_USER_SWITCHED)); 159 } 160 stop()161 void stop() { 162 mContext.unregisterReceiver(mUserChangeReceiver); 163 164 synchronized (mLock) { 165 mRequest = new ProviderRequestUnbundled(ProviderRequest.EMPTY_REQUEST); 166 updateRequirementsLocked(); 167 } 168 } 169 170 @Override onSetRequest(ProviderRequestUnbundled request, WorkSource workSource)171 public void onSetRequest(ProviderRequestUnbundled request, WorkSource workSource) { 172 synchronized (mLock) { 173 mRequest = request; 174 mWorkSource = workSource; 175 updateRequirementsLocked(); 176 } 177 } 178 179 @GuardedBy("mLock") updateRequirementsLocked()180 private void updateRequirementsLocked() { 181 long gpsInterval = Long.MAX_VALUE; 182 long networkInterval = Long.MAX_VALUE; 183 if (mRequest.getReportLocation()) { 184 for (LocationRequestUnbundled request : mRequest.getLocationRequests()) { 185 switch (request.getQuality()) { 186 case LocationRequestUnbundled.ACCURACY_FINE: 187 case LocationRequestUnbundled.POWER_HIGH: 188 if (request.getInterval() < gpsInterval) { 189 gpsInterval = request.getInterval(); 190 } 191 if (request.getInterval() < networkInterval) { 192 networkInterval = request.getInterval(); 193 } 194 break; 195 case LocationRequestUnbundled.ACCURACY_BLOCK: 196 case LocationRequestUnbundled.ACCURACY_CITY: 197 case LocationRequestUnbundled.POWER_LOW: 198 if (request.getInterval() < networkInterval) { 199 networkInterval = request.getInterval(); 200 } 201 break; 202 } 203 } 204 } 205 206 if (gpsInterval != mGpsInterval) { 207 resetProviderRequestLocked(GPS_PROVIDER, mGpsInterval, gpsInterval, mGpsListener); 208 mGpsInterval = gpsInterval; 209 } 210 if (networkInterval != mNetworkInterval) { 211 resetProviderRequestLocked(NETWORK_PROVIDER, mNetworkInterval, networkInterval, 212 mNetworkListener); 213 mNetworkInterval = networkInterval; 214 } 215 } 216 217 @GuardedBy("mLock") resetProviderRequestLocked(String provider, long oldInterval, long newInterval, LocationListener listener)218 private void resetProviderRequestLocked(String provider, long oldInterval, long newInterval, 219 LocationListener listener) { 220 if (oldInterval != Long.MAX_VALUE) { 221 mLocationManager.removeUpdates(listener); 222 } 223 if (newInterval != Long.MAX_VALUE) { 224 LocationRequest request = LocationRequest.createFromDeprecatedProvider( 225 provider, newInterval, 0, false); 226 if (mRequest.isLocationSettingsIgnored()) { 227 request.setLocationSettingsIgnored(true); 228 } 229 request.setWorkSource(mWorkSource); 230 mLocationManager.requestLocationUpdates(request, listener, Looper.getMainLooper()); 231 } 232 } 233 234 @GuardedBy("mLock") reportBestLocationLocked()235 private void reportBestLocationLocked() { 236 Location bestLocation = chooseBestLocation(mGpsLocation, mNetworkLocation); 237 if (bestLocation == mFusedLocation) { 238 return; 239 } 240 241 mFusedLocation = bestLocation; 242 if (mFusedLocation == null) { 243 return; 244 } 245 246 // copy NO_GPS_LOCATION extra from mNetworkLocation into mFusedLocation 247 if (mNetworkLocation != null) { 248 Bundle srcExtras = mNetworkLocation.getExtras(); 249 if (srcExtras != null) { 250 Parcelable srcParcelable = 251 srcExtras.getParcelable(LocationProviderBase.EXTRA_NO_GPS_LOCATION); 252 if (srcParcelable instanceof Location) { 253 Bundle dstExtras = mFusedLocation.getExtras(); 254 if (dstExtras == null) { 255 dstExtras = new Bundle(); 256 mFusedLocation.setExtras(dstExtras); 257 } 258 dstExtras.putParcelable(LocationProviderBase.EXTRA_NO_GPS_LOCATION, 259 srcParcelable); 260 } 261 } 262 } 263 264 reportLocation(mFusedLocation); 265 } 266 onUserChanged()267 private void onUserChanged() { 268 // clear cached locations when the user changes to prevent leaking user information 269 synchronized (mLock) { 270 mFusedLocation = null; 271 mGpsLocation = null; 272 mNetworkLocation = null; 273 } 274 } 275 dump(PrintWriter writer)276 void dump(PrintWriter writer) { 277 synchronized (mLock) { 278 writer.println("request: " + mRequest); 279 if (mGpsInterval != Long.MAX_VALUE) { 280 writer.println(" gps interval: " + mGpsInterval); 281 } 282 if (mNetworkInterval != Long.MAX_VALUE) { 283 writer.println(" network interval: " + mNetworkInterval); 284 } 285 if (mGpsLocation != null) { 286 writer.println(" last gps location: " + mGpsLocation); 287 } 288 if (mNetworkLocation != null) { 289 writer.println(" last network location: " + mNetworkLocation); 290 } 291 } 292 } 293 294 @Nullable chooseBestLocation( @ullable Location locationA, @Nullable Location locationB)295 private static Location chooseBestLocation( 296 @Nullable Location locationA, 297 @Nullable Location locationB) { 298 if (locationA == null) { 299 return locationB; 300 } 301 if (locationB == null) { 302 return locationA; 303 } 304 305 if (locationA.getElapsedRealtimeNanos() 306 > locationB.getElapsedRealtimeNanos() + MAX_LOCATION_COMPARISON_NS) { 307 return locationA; 308 } 309 if (locationB.getElapsedRealtimeNanos() 310 > locationA.getElapsedRealtimeNanos() + MAX_LOCATION_COMPARISON_NS) { 311 return locationB; 312 } 313 314 if (!locationA.hasAccuracy()) { 315 return locationB; 316 } 317 if (!locationB.hasAccuracy()) { 318 return locationA; 319 } 320 return locationA.getAccuracy() < locationB.getAccuracy() ? locationA : locationB; 321 } 322 } 323