diff options
Diffstat (limited to 'services/java/com/android/server/LocationManagerService.java')
| -rw-r--r-- | services/java/com/android/server/LocationManagerService.java | 327 |
1 files changed, 219 insertions, 108 deletions
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index 63eeeb3..37dee19 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -23,8 +23,11 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.pm.Signature; import android.content.res.Resources; import android.database.ContentObserver; import android.location.Address; @@ -90,10 +93,13 @@ public class LocationManagerService extends ILocationManager.Stub implements Run private static final String WAKELOCK_KEY = TAG; private static final String THREAD_NAME = TAG; - private static final String ACCESS_FINE_LOCATION = - android.Manifest.permission.ACCESS_FINE_LOCATION; - private static final String ACCESS_COARSE_LOCATION = - android.Manifest.permission.ACCESS_COARSE_LOCATION; + // Location resolution level: no location data whatsoever + private static final int RESOLUTION_LEVEL_NONE = 0; + // Location resolution level: coarse location data only + private static final int RESOLUTION_LEVEL_COARSE = 1; + // Location resolution level: fine location data + private static final int RESOLUTION_LEVEL_FINE = 2; + private static final String ACCESS_MOCK_LOCATION = android.Manifest.permission.ACCESS_MOCK_LOCATION; private static final String ACCESS_LOCATION_EXTRA_COMMANDS = @@ -246,6 +252,74 @@ public class LocationManagerService extends ILocationManager.Stub implements Run updateProvidersLocked(); } + private void ensureFallbackFusedProviderPresentLocked(ArrayList<String> pkgs) { + PackageManager pm = mContext.getPackageManager(); + String systemPackageName = mContext.getPackageName(); + ArrayList<HashSet<Signature>> sigSets = ServiceWatcher.getSignatureSets(mContext, pkgs); + + List<ResolveInfo> rInfos = pm.queryIntentServicesAsUser( + new Intent(FUSED_LOCATION_SERVICE_ACTION), + PackageManager.GET_META_DATA, mCurrentUserId); + for (ResolveInfo rInfo : rInfos) { + String packageName = rInfo.serviceInfo.packageName; + + // Check that the signature is in the list of supported sigs. If it's not in + // this list the standard provider binding logic won't bind to it. + try { + PackageInfo pInfo; + pInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); + if (!ServiceWatcher.isSignatureMatch(pInfo.signatures, sigSets)) { + Log.w(TAG, packageName + " resolves service " + FUSED_LOCATION_SERVICE_ACTION + + ", but has wrong signature, ignoring"); + continue; + } + } catch (NameNotFoundException e) { + Log.e(TAG, "missing package: " + packageName); + continue; + } + + // Get the version info + if (rInfo.serviceInfo.metaData == null) { + Log.w(TAG, "Found fused provider without metadata: " + packageName); + continue; + } + + int version = rInfo.serviceInfo.metaData.getInt( + ServiceWatcher.EXTRA_SERVICE_VERSION, -1); + if (version == 0) { + // This should be the fallback fused location provider. + + // Make sure it's in the system partition. + if ((rInfo.serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { + if (D) Log.d(TAG, "Fallback candidate not in /system: " + packageName); + continue; + } + + // Check that the fallback is signed the same as the OS + // as a proxy for coreApp="true" + if (pm.checkSignatures(systemPackageName, packageName) + != PackageManager.SIGNATURE_MATCH) { + if (D) Log.d(TAG, "Fallback candidate not signed the same as system: " + + packageName); + continue; + } + + // Found a valid fallback. + if (D) Log.d(TAG, "Found fallback provider: " + packageName); + return; + } else { + if (D) Log.d(TAG, "Fallback candidate not version 0: " + packageName); + } + } + + throw new IllegalStateException("Unable to find a fused location provider that is in the " + + "system partition with version 0 and signed with the platform certificate. " + + "Such a package is needed to provide a default fused location provider in the " + + "event that no other fused location provider has been installed or is currently " + + "available. For example, coreOnly boot mode when decrypting the data " + + "partition. The fallback must also be marked coreApp=\"true\" in the manifest"); + } + private void loadProvidersLocked() { // create a passive location provider, which is always enabled PassiveProvider passiveProvider = new PassiveProvider(this); @@ -275,14 +349,13 @@ public class LocationManagerService extends ILocationManager.Stub implements Run */ Resources resources = mContext.getResources(); ArrayList<String> providerPackageNames = new ArrayList<String>(); - String[] pkgs1 = resources.getStringArray( + String[] pkgs = resources.getStringArray( com.android.internal.R.array.config_locationProviderPackageNames); - String[] pkgs2 = resources.getStringArray( - com.android.internal.R.array.config_overlay_locationProviderPackageNames); - if (D) Log.d(TAG, "built-in location providers: " + Arrays.toString(pkgs1)); - if (D) Log.d(TAG, "overlay location providers: " + Arrays.toString(pkgs2)); - if (pkgs1 != null) providerPackageNames.addAll(Arrays.asList(pkgs1)); - if (pkgs2 != null) providerPackageNames.addAll(Arrays.asList(pkgs2)); + if (D) Log.d(TAG, "certificates for location providers pulled from: " + + Arrays.toString(pkgs)); + if (pkgs != null) providerPackageNames.addAll(Arrays.asList(pkgs)); + + ensureFallbackFusedProviderPresentLocked(providerPackageNames); // bind to network provider LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind( @@ -347,7 +420,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run final int mUid; // uid of receiver final int mPid; // pid of receiver final String mPackageName; // package name of receiver - final String mPermission; // best permission that receiver has + final int mAllowedResolutionLevel; // resolution level allowed to receiver final ILocationListener mListener; final PendingIntent mPendingIntent; @@ -366,7 +439,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } else { mKey = intent; } - mPermission = checkPermission(); + mAllowedResolutionLevel = getAllowedResolutionLevel(pid, uid); mUid = uid; mPid = pid; mPackageName = packageName; @@ -440,7 +513,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() mPendingIntent.send(mContext, 0, statusChanged, this, mLocationHandler, - mPermission); + getResolutionPermission(mAllowedResolutionLevel)); // call this after broadcasting so we do not increment // if we throw an exeption. incrementPendingBroadcastsLocked(); @@ -474,7 +547,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() mPendingIntent.send(mContext, 0, locationChanged, this, mLocationHandler, - mPermission); + getResolutionPermission(mAllowedResolutionLevel)); // call this after broadcasting so we do not increment // if we throw an exeption. incrementPendingBroadcastsLocked(); @@ -512,7 +585,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() mPendingIntent.send(mContext, 0, providerIntent, this, mLocationHandler, - mPermission); + getResolutionPermission(mAllowedResolutionLevel)); // call this after broadcasting so we do not increment // if we throw an exeption. incrementPendingBroadcastsLocked(); @@ -609,51 +682,76 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } /** - * Returns the best permission available to the caller. + * Returns the permission string associated with the specified resolution level. + * + * @param resolutionLevel the resolution level + * @return the permission string */ - private String getBestCallingPermission() { - if (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) == - PackageManager.PERMISSION_GRANTED) { - return ACCESS_FINE_LOCATION; - } else if (mContext.checkCallingOrSelfPermission(ACCESS_COARSE_LOCATION) == - PackageManager.PERMISSION_GRANTED) { - return ACCESS_COARSE_LOCATION; + private String getResolutionPermission(int resolutionLevel) { + switch (resolutionLevel) { + case RESOLUTION_LEVEL_FINE: + return android.Manifest.permission.ACCESS_FINE_LOCATION; + case RESOLUTION_LEVEL_COARSE: + return android.Manifest.permission.ACCESS_COARSE_LOCATION; + default: + return null; } - return null; } /** - * Throw SecurityException if caller has neither COARSE or FINE. - * Otherwise, return the best permission. + * Returns the resolution level allowed to the given PID/UID pair. + * + * @param pid the PID + * @param uid the UID + * @return resolution level allowed to the pid/uid pair */ - private String checkPermission() { - String perm = getBestCallingPermission(); - if (perm == null) { - throw new SecurityException("Location requires either ACCESS_COARSE_LOCATION or" + - " ACCESS_FINE_LOCATION permission"); + private int getAllowedResolutionLevel(int pid, int uid) { + if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, + pid, uid) == PackageManager.PERMISSION_GRANTED) { + return RESOLUTION_LEVEL_FINE; + } else if (mContext.checkPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION, + pid, uid) == PackageManager.PERMISSION_GRANTED) { + return RESOLUTION_LEVEL_COARSE; + } else { + return RESOLUTION_LEVEL_NONE; } - return perm; } /** - * Throw SecurityException if caller lacks permission to use Geofences. + * Returns the resolution level allowed to the caller + * + * @return resolution level allowed to caller + */ + private int getCallerAllowedResolutionLevel() { + return getAllowedResolutionLevel(Binder.getCallingPid(), Binder.getCallingUid()); + } + + /** + * Throw SecurityException if specified resolution level is insufficient to use geofences. + * + * @param allowedResolutionLevel resolution level allowed to caller */ - private void checkGeofencePermission() { - if (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) != - PackageManager.PERMISSION_GRANTED) { + private void checkResolutionLevelIsSufficientForGeofenceUse(int allowedResolutionLevel) { + if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) { throw new SecurityException("Geofence usage requires ACCESS_FINE_LOCATION permission"); } } - private String getMinimumPermissionForProvider(String provider) { + /** + * Return the minimum resolution level required to use the specified location provider. + * + * @param provider the name of the location provider + * @return minimum resolution level required for provider + */ + private int getMinimumResolutionLevelForProviderUse(String provider) { if (LocationManager.GPS_PROVIDER.equals(provider) || LocationManager.PASSIVE_PROVIDER.equals(provider)) { // gps and passive providers require FINE permission - return ACCESS_FINE_LOCATION; + return RESOLUTION_LEVEL_FINE; } else if (LocationManager.NETWORK_PROVIDER.equals(provider) || LocationManager.FUSED_PROVIDER.equals(provider)) { // network and fused providers are ok with COARSE or FINE - return ACCESS_COARSE_LOCATION; + return RESOLUTION_LEVEL_COARSE; } else { // mock providers LocationProviderInterface lp = mMockProviders.get(provider); @@ -662,41 +760,38 @@ public class LocationManagerService extends ILocationManager.Stub implements Run if (properties != null) { if (properties.mRequiresSatellite) { // provider requiring satellites require FINE permission - return ACCESS_FINE_LOCATION; + return RESOLUTION_LEVEL_FINE; } else if (properties.mRequiresNetwork || properties.mRequiresCell) { // provider requiring network and or cell require COARSE or FINE - return ACCESS_COARSE_LOCATION; + return RESOLUTION_LEVEL_COARSE; } } } } - - return null; + return RESOLUTION_LEVEL_FINE; // if in doubt, require FINE } - private boolean isPermissionSufficient(String perm, String minPerm) { - if (ACCESS_FINE_LOCATION.equals(minPerm)) { - return ACCESS_FINE_LOCATION.equals(perm); - } else if (ACCESS_COARSE_LOCATION.equals(minPerm)) { - return ACCESS_FINE_LOCATION.equals(perm) || - ACCESS_COARSE_LOCATION.equals(perm); - } else { - return false; - } - } - - private void checkPermissionForProvider(String perm, String provider) { - String minPerm = getMinimumPermissionForProvider(provider); - if (!isPermissionSufficient(perm, minPerm)) { - if (ACCESS_FINE_LOCATION.equals(minPerm)) { - throw new SecurityException("Location provider \"" + provider + - "\" requires ACCESS_FINE_LOCATION permission."); - } else if (ACCESS_COARSE_LOCATION.equals(minPerm)) { - throw new SecurityException("Location provider \"" + provider + - "\" requires ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission."); - } else { - throw new SecurityException("Insufficient permission for location provider \"" + - provider + "\"."); + /** + * Throw SecurityException if specified resolution level is insufficient to use the named + * location provider. + * + * @param allowedResolutionLevel resolution level allowed to caller + * @param providerName the name of the location provider + */ + private void checkResolutionLevelIsSufficientForProviderUse(int allowedResolutionLevel, + String providerName) { + int requiredResolutionLevel = getMinimumResolutionLevelForProviderUse(providerName); + if (allowedResolutionLevel < requiredResolutionLevel) { + switch (requiredResolutionLevel) { + case RESOLUTION_LEVEL_FINE: + throw new SecurityException("\"" + providerName + "\" location provider " + + "requires ACCESS_FINE_LOCATION permission."); + case RESOLUTION_LEVEL_COARSE: + throw new SecurityException("\"" + providerName + "\" location provider " + + "requires ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission."); + default: + throw new SecurityException("Insufficient permission for \"" + providerName + + "\" location provider."); } } } @@ -731,8 +826,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run */ @Override public List<String> getProviders(Criteria criteria, boolean enabledOnly) { + int allowedResolutionLevel = getCallerAllowedResolutionLevel(); ArrayList<String> out; - String perm = getBestCallingPermission(); int callingUserId = UserHandle.getCallingUserId(); long identity = Binder.clearCallingIdentity(); try { @@ -743,7 +838,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run if (LocationManager.FUSED_PROVIDER.equals(name)) { continue; } - if (isPermissionSufficient(perm, getMinimumPermissionForProvider(name))) { + if (allowedResolutionLevel >= getMinimumResolutionLevelForProviderUse(name)) { if (enabledOnly && !isAllowedBySettingsLocked(name, callingUserId)) { continue; } @@ -803,8 +898,6 @@ public class LocationManagerService extends ILocationManager.Stub implements Run @Override public boolean providerMeetsCriteria(String provider, Criteria criteria) { - checkPermission(); - LocationProviderInterface p = mProvidersByName.get(provider); if (p == null) { throw new IllegalArgumentException("provider=" + provider); @@ -1010,33 +1103,41 @@ public class LocationManagerService extends ILocationManager.Stub implements Run return receiver; } - private String checkPermissionAndRequest(LocationRequest request) { - String perm = getBestCallingPermission(); - String provider = request.getProvider(); - checkPermissionForProvider(perm, provider); - - if (ACCESS_COARSE_LOCATION.equals(perm)) { - switch (request.getQuality()) { + /** + * Creates a LocationRequest based upon the supplied LocationRequest that to meets resolution + * and consistency requirements. + * + * @param request the LocationRequest from which to create a sanitized version + * @param shouldBeCoarse whether the sanitized version should be held to coarse resolution + * constraints + * @param fastestCoarseIntervalMS minimum interval allowed for coarse resolution + * @return a version of request that meets the given resolution and consistency requirements + * @hide + */ + private LocationRequest createSanitizedRequest(LocationRequest request, int resolutionLevel) { + LocationRequest sanitizedRequest = new LocationRequest(request); + if (resolutionLevel < RESOLUTION_LEVEL_FINE) { + switch (sanitizedRequest.getQuality()) { case LocationRequest.ACCURACY_FINE: - request.setQuality(LocationRequest.ACCURACY_BLOCK); + sanitizedRequest.setQuality(LocationRequest.ACCURACY_BLOCK); break; case LocationRequest.POWER_HIGH: - request.setQuality(LocationRequest.POWER_LOW); + sanitizedRequest.setQuality(LocationRequest.POWER_LOW); break; } // throttle - if (request.getInterval() < LocationFudger.FASTEST_INTERVAL_MS) { - request.setInterval(LocationFudger.FASTEST_INTERVAL_MS); + if (sanitizedRequest.getInterval() < LocationFudger.FASTEST_INTERVAL_MS) { + sanitizedRequest.setInterval(LocationFudger.FASTEST_INTERVAL_MS); } - if (request.getFastestInterval() < LocationFudger.FASTEST_INTERVAL_MS) { - request.setFastestInterval(LocationFudger.FASTEST_INTERVAL_MS); + if (sanitizedRequest.getFastestInterval() < LocationFudger.FASTEST_INTERVAL_MS) { + sanitizedRequest.setFastestInterval(LocationFudger.FASTEST_INTERVAL_MS); } } // make getFastestInterval() the minimum of interval and fastest interval - if (request.getFastestInterval() > request.getInterval()) { + if (sanitizedRequest.getFastestInterval() > sanitizedRequest.getInterval()) { request.setFastestInterval(request.getInterval()); } - return perm; + return sanitizedRequest; } private void checkPackageName(String packageName) { @@ -1079,7 +1180,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run PendingIntent intent, String packageName) { if (request == null) request = DEFAULT_LOCATION_REQUEST; checkPackageName(packageName); - checkPermissionAndRequest(request); + int allowedResolutionLevel = getCallerAllowedResolutionLevel(); + checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel, + request.getProvider()); + LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel); final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); @@ -1089,7 +1193,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { - requestLocationUpdatesLocked(request, recevier, pid, uid, packageName); + requestLocationUpdatesLocked(sanitizedRequest, recevier, pid, uid, packageName); } } finally { Binder.restoreCallingIdentity(identity); @@ -1132,7 +1236,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run public void removeUpdates(ILocationListener listener, PendingIntent intent, String packageName) { checkPackageName(packageName); - checkPermission(); + final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); Receiver receiver = checkListenerOrIntent(listener, intent, pid, uid, packageName); @@ -1188,8 +1292,11 @@ public class LocationManagerService extends ILocationManager.Stub implements Run public Location getLastLocation(LocationRequest request, String packageName) { if (D) Log.d(TAG, "getLastLocation: " + request); if (request == null) request = DEFAULT_LOCATION_REQUEST; - String perm = checkPermissionAndRequest(request); + int allowedResolutionLevel = getCallerAllowedResolutionLevel(); checkPackageName(packageName); + checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel, + request.getProvider()); + // no need to sanitize this request, as only the provider name is used long identity = Binder.clearCallingIdentity(); try { @@ -1213,13 +1320,13 @@ public class LocationManagerService extends ILocationManager.Stub implements Run if (location == null) { return null; } - if (ACCESS_FINE_LOCATION.equals(perm)) { - return location; - } else { + if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) { Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION); if (noGPSLocation != null) { return mLocationFudger.getOrCreate(noGPSLocation); } + } else { + return location; } } return null; @@ -1232,18 +1339,21 @@ public class LocationManagerService extends ILocationManager.Stub implements Run public void requestGeofence(LocationRequest request, Geofence geofence, PendingIntent intent, String packageName) { if (request == null) request = DEFAULT_LOCATION_REQUEST; - checkGeofencePermission(); - checkPermissionAndRequest(request); + int allowedResolutionLevel = getCallerAllowedResolutionLevel(); + checkResolutionLevelIsSufficientForGeofenceUse(allowedResolutionLevel); checkPendingIntent(intent); checkPackageName(packageName); + checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel, + request.getProvider()); + LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel); - if (D) Log.d(TAG, "requestGeofence: " + request + " " + geofence + " " + intent); + if (D) Log.d(TAG, "requestGeofence: " + sanitizedRequest + " " + geofence + " " + intent); // geo-fence manager uses the public location API, need to clear identity int uid = Binder.getCallingUid(); long identity = Binder.clearCallingIdentity(); try { - mGeofenceManager.addFence(request, geofence, intent, uid, packageName); + mGeofenceManager.addFence(sanitizedRequest, geofence, intent, uid, packageName); } finally { Binder.restoreCallingIdentity(identity); } @@ -1251,7 +1361,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run @Override public void removeGeofence(Geofence geofence, PendingIntent intent, String packageName) { - checkGeofencePermission(); + checkResolutionLevelIsSufficientForGeofenceUse(getCallerAllowedResolutionLevel()); checkPendingIntent(intent); checkPackageName(packageName); @@ -1272,10 +1382,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run if (mGpsStatusProvider == null) { return false; } - if (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) != - PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires ACCESS_FINE_LOCATION permission"); - } + checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(), + LocationManager.GPS_PROVIDER); try { mGpsStatusProvider.addGpsStatusListener(listener); @@ -1303,8 +1411,9 @@ public class LocationManagerService extends ILocationManager.Stub implements Run // throw NullPointerException to remain compatible with previous implementation throw new NullPointerException(); } + checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(), + provider); - checkPermission(); // and check for ACCESS_LOCATION_EXTRA_COMMANDS if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS) != PackageManager.PERMISSION_GRANTED)) { @@ -1344,7 +1453,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run return null; } - checkPermissionForProvider(getBestCallingPermission(), provider); + checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(), + provider); LocationProviderInterface p; synchronized (mLock) { @@ -1357,7 +1467,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run @Override public boolean isProviderEnabled(String provider) { - checkPermissionForProvider(getBestCallingPermission(), provider); + checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(), + provider); if (LocationManager.FUSED_PROVIDER.equals(provider)) return false; long identity = Binder.clearCallingIdentity(); @@ -1522,10 +1633,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } Location notifyLocation = null; - if (ACCESS_FINE_LOCATION.equals(receiver.mPermission)) { - notifyLocation = lastLocation; // use fine location + if (receiver.mAllowedResolutionLevel < RESOLUTION_LEVEL_FINE) { + notifyLocation = coarseLocation; // use coarse location } else { - notifyLocation = coarseLocation; // use coarse location if available + notifyLocation = lastLocation; // use fine location } if (notifyLocation != null) { Location lastLoc = r.mLastFixBroadcast; |
