From 905e7227dcd9b66441b9c3939662a8bc69094d1e Mon Sep 17 00:00:00 2001 From: Soonil Nagarkar Date: Tue, 1 Oct 2019 12:03:29 -0700 Subject: [PATCH] Add getCurrentLocation API to LocationManager Add new API to replace requestSingleUpdate() which is safer and more convenient for clients to use. Bug: 133356925 Test: manual (CTS tests coming in separate patch) Change-Id: Ib7f76bcbff0cd010a479bb7a02e2f7e51e65525c --- api/current.txt | 1 + api/system-current.txt | 1 + api/test-current.txt | 1 + .../android/location/ILocationManager.aidl | 8 +- .../android/location/LocationManager.java | 375 ++++++++++++++---- .../server/LocationManagerService.java | 74 +++- 6 files changed, 364 insertions(+), 96 deletions(-) diff --git a/api/current.txt b/api/current.txt index 47f10f00c4d66..bfce19e0b6acd 100644 --- a/api/current.txt +++ b/api/current.txt @@ -23142,6 +23142,7 @@ package android.location { method @Deprecated public void clearTestProviderStatus(@NonNull String); method @NonNull public java.util.List getAllProviders(); method @Nullable public String getBestProvider(@NonNull android.location.Criteria, boolean); + method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void getCurrentLocation(@NonNull String, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); method @Nullable public String getGnssHardwareModelName(); method public int getGnssYearOfHardware(); method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public android.location.GpsStatus getGpsStatus(@Nullable android.location.GpsStatus); diff --git a/api/system-current.txt b/api/system-current.txt index 117be3bb4c7ad..3270a3f22bb90 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -3430,6 +3430,7 @@ package android.location { public class LocationManager { method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void flushGnssBatch(); + method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void getCurrentLocation(@NonNull android.location.LocationRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); method @Nullable public String getExtraLocationControllerPackage(); method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public int getGnssBatchSize(); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public android.location.GnssCapabilities getGnssCapabilities(); diff --git a/api/test-current.txt b/api/test-current.txt index b9ab3755a74a6..ffb264df9c013 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1100,6 +1100,7 @@ package android.location { public class LocationManager { method @NonNull public String[] getBackgroundThrottlingWhitelist(); + method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void getCurrentLocation(@NonNull android.location.LocationRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); method @NonNull public String[] getIgnoreSettingsWhitelist(); method @Nullable @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public java.util.List getProviderPackages(@NonNull String); method @NonNull public java.util.List getTestProviderCurrentRequests(String); diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index daa2e08a6780d..91f3a20fc11d2 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -31,6 +31,7 @@ import android.location.Location; import android.location.LocationRequest; import android.location.LocationTime; import android.os.Bundle; +import android.os.ICancellationSignal; import com.android.internal.location.ProviderProperties; @@ -41,6 +42,11 @@ import com.android.internal.location.ProviderProperties; */ interface ILocationManager { + Location getLastLocation(in LocationRequest request, String packageName); + boolean getCurrentLocation(in LocationRequest request, + in ICancellationSignal cancellationSignal, in ILocationListener listener, + String packageName, String listenerIdentifier); + void requestLocationUpdates(in LocationRequest request, in ILocationListener listener, in PendingIntent intent, String packageName, String listenerIdentifier); void removeUpdates(in ILocationListener listener, in PendingIntent intent, String packageName); @@ -49,8 +55,6 @@ interface ILocationManager in PendingIntent intent, String packageName, String listenerIdentifier); void removeGeofence(in Geofence fence, in PendingIntent intent, String packageName); - Location getLastLocation(in LocationRequest request, String packageName); - boolean registerGnssStatusCallback(IGnssStatusListener callback, String packageName); void unregisterGnssStatusCallback(IGnssStatusListener callback); diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 987947b930601..9e17e9541e5c6 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.ACCESS_COARSE_LOCATION; import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.Manifest.permission.LOCATION_HARDWARE; import static android.Manifest.permission.WRITE_SECURE_SETTINGS; +import static android.app.AlarmManager.ELAPSED_REALTIME; import android.Manifest; import android.annotation.CallbackExecutor; @@ -31,17 +32,21 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; +import android.app.AlarmManager; import android.app.PendingIntent; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; import android.os.Build; import android.os.Bundle; +import android.os.CancellationSignal; import android.os.Handler; import android.os.HandlerExecutor; +import android.os.ICancellationSignal; import android.os.Looper; import android.os.Process; import android.os.RemoteException; +import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.util.ArrayMap; @@ -55,6 +60,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; +import java.util.function.Consumer; /** * This class provides access to the system location services. These services allow applications to @@ -77,22 +83,20 @@ public class LocationManager { /** * Name of the network location provider. - *

This provider determines location based on - * availability of cell tower and WiFi access points. Results are retrieved - * by means of a network lookup. + * + *

This provider determines location based on nearby of cell tower and WiFi access points. + * Results are retrieved by means of a network lookup. */ public static final String NETWORK_PROVIDER = "network"; /** - * Name of the GPS location provider. + * Name of the GNSS location provider. * - *

This provider determines location using - * satellites. Depending on conditions, this provider may take a while to return - * a location fix. Requires the permission - * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}. + *

This provider determines location using GNSS satellites. Depending on conditions, this + * provider may take a while to return a location fix. Requires the + * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission. * - *

The extras Bundle for the GPS location provider can contain the - * following key/value pairs: + *

The extras Bundle for the GPS location provider can contain the following key/value pairs: *

    *
  • satellites - the number of satellites used to derive the fix *
@@ -100,26 +104,24 @@ public class LocationManager { public static final String GPS_PROVIDER = "gps"; /** - * A special location provider for receiving locations without actually initiating - * a location fix. + * A special location provider for receiving locations without actually initiating a location + * fix. * - *

This provider can be used to passively receive location updates - * when other applications or services request them without actually requesting - * the locations yourself. This provider will return locations generated by other - * providers. You can query the {@link Location#getProvider()} method to determine - * the origin of the location update. Requires the permission - * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}, although if the GPS is - * not enabled this provider might only return coarse fixes. + *

This provider can be used to passively receive location updates when other applications or + * services request them without actually requesting the locations yourself. This provider will + * only return locations generated by other providers. You can query the + * {@link Location#getProvider()} method to determine the actual provider that supplied the + * location update. Requires the {@link android.Manifest.permission#ACCESS_FINE_LOCATION} + * permission, although there is no guarantee of fine locations. */ public static final String PASSIVE_PROVIDER = "passive"; /** - * Name of the Fused location provider. + * The fused location provider. * - *

This provider combines inputs for all possible location sources - * to provide the best possible Location fix. It is implicitly - * used for all API's that involve the {@link LocationRequest} - * object. + *

This provider combines may combine inputs from several location sources to provide the + * best possible location fix. It is implicitly used for all API's that involve the + * {@link LocationRequest} object. * * @hide */ @@ -228,6 +230,8 @@ public class LocationManager { public static final String METADATA_SETTINGS_FOOTER_STRING = "com.android.settings.location.FOOTER_STRING"; + private static final long GET_CURRENT_LOCATION_TIMEOUT_MS = 30 * 1000; + private final Context mContext; @UnsupportedAppUsage @@ -522,6 +526,13 @@ public class LocationManager { * location. The returned location may be quite old in some circumstances, so the age of the * location should always be checked. * + *

This will never activate sensors to compute a new location, and will only ever return a + * cached location. + * + *

See also {@link #getCurrentLocation(String, CancellationSignal, Executor, Consumer)} which + * will always attempt to return a current location, but will potentially use additional power + * in the course of the attempt as compared to this method. + * * @param provider the name of the provider * @return the last known location for the given provider, or null if not available * @throws SecurityException if no suitable permission is present @@ -550,36 +561,117 @@ public class LocationManager { * @return A identifying string */ private static String getListenerIdentifier(@NonNull Object listener) { - StringBuilder sb = new StringBuilder(); - - sb.append(listener.getClass().getName()); - sb.append('@'); - sb.append(Integer.toHexString(System.identityHashCode(listener))); - - return sb.toString(); + return listener.getClass().getName() + + '@' + + Integer.toHexString(System.identityHashCode(listener)); } /** - * Register for a single location update using the named provider and - * a callback. + * Asynchronously returns a single current location fix. This may activate sensors in order to + * compute a new location, unlike {@link #getLastKnownLocation(String)}, which will only return + * a cached fix if available. The given callback will be invoked once and only once, either with + * a valid location fix or with a null location fix if the provider was unable to generate a + * valid location. * - *

See {@link #requestLocationUpdates(String, long, float, LocationListener, Looper)} - * for more detail on how to use this method. + *

A client may supply an optional {@link CancellationSignal}. If this is used to cancel the + * operation, no callback should be expected after the cancellation. + * + *

This method may return locations from the very recent past (on the order of several + * seconds), but will never return older locations (for example, several minutes old or older). + * Clients may rely upon the guarantee that if this method returns a location, it will represent + * the best estimation of the location of the device in the present moment. + * + *

Clients calling this method from the background may notice that the method fails to + * determine a valid location fix more often than while in the foreground. Background + * applications may be throttled in their location accesses to some degree. + * + * @param provider the name of the provider with which to register + * @param cancellationSignal an optional signal that allows for cancelling this call + * @param executor the callback will take place on this {@link Executor} + * @param consumer the callback invoked with either a {@link Location} or null + * + * @throws IllegalArgumentException if provider is null or doesn't exist + * @throws IllegalArgumentException if executor is null + * @throws IllegalArgumentException if consumer is null + * @throws SecurityException if no suitable permission is present + */ + @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) + public void getCurrentLocation(@NonNull String provider, + @Nullable CancellationSignal cancellationSignal, + @NonNull @CallbackExecutor Executor executor, @NonNull Consumer consumer) { + getCurrentLocation(LocationRequest.createFromDeprecatedProvider(provider, 0, 0, true), + cancellationSignal, executor, consumer); + } + + /** + * Asynchronously returns a single current location fix based on the given + * {@link LocationRequest}. + * + *

See {@link #getCurrentLocation(String, CancellationSignal, Executor, Consumer)} for more + * information. + * + * @param locationRequest the location request containing location parameters + * @param cancellationSignal an optional signal that allows for cancelling this call + * @param executor the callback will take place on this {@link Executor} + * @param consumer the callback invoked with either a {@link Location} or null + * + * @throws IllegalArgumentException if provider is null or doesn't exist + * @throws IllegalArgumentException if executor is null + * @throws IllegalArgumentException if consumer is null + * @throws SecurityException if no suitable permission is present + * @hide + */ + @SystemApi + @TestApi + @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) + public void getCurrentLocation(@NonNull LocationRequest locationRequest, + @Nullable CancellationSignal cancellationSignal, + @NonNull @CallbackExecutor Executor executor, @NonNull Consumer consumer) { + LocationRequest currentLocationRequest = new LocationRequest(locationRequest) + .setNumUpdates(1).setExpireIn(GET_CURRENT_LOCATION_TIMEOUT_MS); + + GetCurrentLocationTransport listenerTransport = new GetCurrentLocationTransport(executor, + consumer); + + if (cancellationSignal != null) { + cancellationSignal.throwIfCanceled(); + } + + ICancellationSignal remoteCancellationSignal = CancellationSignal.createTransport(); + + try { + if (mService.getCurrentLocation(currentLocationRequest, remoteCancellationSignal, + listenerTransport, mContext.getPackageName(), + getListenerIdentifier(consumer))) { + listenerTransport.register(mContext.getSystemService(AlarmManager.class), + remoteCancellationSignal); + if (cancellationSignal != null) { + cancellationSignal.setOnCancelListener(listenerTransport::cancel); + } + } else { + listenerTransport.fail(); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Register for a single location update using the named provider and a callback. + * + *

See {@link #requestLocationUpdates(String, long, float, LocationListener, Looper)} for + * more detail on how to use this method. * * @param provider the name of the provider with which to register - * @param listener a {@link LocationListener} whose - * {@link LocationListener#onLocationChanged} method will be called when - * the location update is available - * @param looper a Looper object whose message queue will be used to - * implement the callback mechanism, or null to make callbacks on the calling - * thread + * @param listener the listener to receive location updates + * @param looper the looper handling listener callbacks, or null to use the looper of the + * calling thread * * @throws IllegalArgumentException if provider is null or doesn't exist * @throws IllegalArgumentException if listener is null - * @throws SecurityException if no suitable permission is present - * @deprecated This method can drain much more battery than expected if it is not possible to - * calculate location. Prefer any of the requestLocationUpdates() methods which require explicit - * removal. + * @throws SecurityException if no suitable permission is present + * @deprecated Use {@link #getCurrentLocation(String, CancellationSignal, Executor, Consumer)} + * instead as it does not carry a risk of extreme battery drain. */ @Deprecated @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) @@ -594,27 +686,21 @@ public class LocationManager { } /** - * Register for a single location update using a Criteria and - * a callback. + * Register for a single location update using a Criteria and a callback. * - *

See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)} - * for more detail on how to use this method. + *

See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)} for more detail + * on how to use this method. * - * @param criteria contains parameters for the location manager to choose the - * appropriate provider and parameters to compute the location - * @param listener a {@link LocationListener} whose - * {@link LocationListener#onLocationChanged} method will be called when - * the location update is available - * @param looper a Looper object whose message queue will be used to - * implement the callback mechanism, or null to make callbacks on the calling - * thread + * @param criteria contains parameters to choose the appropriate provider for location updates + * @param listener the listener to receive location updates + * @param looper the looper handling listener callbacks, or null to use the looper of the + * calling thread * * @throws IllegalArgumentException if criteria is null * @throws IllegalArgumentException if listener is null - * @throws SecurityException if no suitable permission is present - * @deprecated This method can drain much more battery than expected if it is not possible to - * calculate location. Prefer any of the requestLocationUpdates() methods which require explicit - * removal. + * @throws SecurityException if no suitable permission is present + * @deprecated Use {@link #getCurrentLocation(String, CancellationSignal, Executor, Consumer)} + * instead as it does not carry a risk of extreme battery drain. */ @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) public void requestSingleUpdate( @@ -632,54 +718,54 @@ public class LocationManager { /** * Register for a single location update using a named provider and pending intent. * - *

See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)} - * for more detail on how to use this method. + *

See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)} for more detail + * on how to use this method. * - * @param provider the name of the provider with which to register - * @param intent a {@link PendingIntent} to be sent for the location update + * @param provider the name of the provider with which to register + * @param pendingIntent the pending intent to send location updates * * @throws IllegalArgumentException if provider is null or doesn't exist * @throws IllegalArgumentException if intent is null - * @throws SecurityException if no suitable permission is present - * @deprecated This method can drain much more battery than expected if it is not possible to - * calculate location. Prefer any of the requestLocationUpdates() methods which require explicit - * removal. + * @throws SecurityException if no suitable permission is present + * @deprecated Use {@link #getCurrentLocation(String, CancellationSignal, Executor, Consumer)} + * instead as it does not carry a risk of extreme battery drain. */ @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) - public void requestSingleUpdate(@NonNull String provider, @NonNull PendingIntent intent) { + public void requestSingleUpdate(@NonNull String provider, + @NonNull PendingIntent pendingIntent) { Preconditions.checkArgument(provider != null, "invalid null provider"); - checkPendingIntent(intent); + checkPendingIntent(pendingIntent); LocationRequest request = LocationRequest.createFromDeprecatedProvider( provider, 0, 0, true); - requestLocationUpdates(request, intent); + requestLocationUpdates(request, pendingIntent); } /** * Register for a single location update using a Criteria and pending intent. * - *

See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)} - * for more detail on how to use this method. + *

See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)} for more detail + * on how to use this method. * - * @param criteria contains parameters for the location manager to choose the - * appropriate provider and parameters to compute the location - * @param intent a {@link PendingIntent} to be sent for the location update + * @param criteria contains parameters to choose the appropriate provider for location + * updates + * @param pendingIntent the pending intent to send location updates * * @throws IllegalArgumentException if provider is null or doesn't exist * @throws IllegalArgumentException if intent is null - * @throws SecurityException if no suitable permission is present - * @deprecated This method can drain much more battery than expected if it is not possible to - * calculate location. Prefer any of the requestLocationUpdates() methods which require explicit - * removal. + * @throws SecurityException if no suitable permission is present + * @deprecated Use {@link #getCurrentLocation(String, CancellationSignal, Executor, Consumer)} + * instead as it does not carry a risk of extreme battery drain. */ @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) - public void requestSingleUpdate(@NonNull Criteria criteria, @NonNull PendingIntent intent) { + public void requestSingleUpdate(@NonNull Criteria criteria, + @NonNull PendingIntent pendingIntent) { Preconditions.checkArgument(criteria != null, "invalid null criteria"); - checkPendingIntent(intent); + checkPendingIntent(pendingIntent); LocationRequest request = LocationRequest.createFromDeprecatedCriteria( criteria, 0, 0, true); - requestLocationUpdates(request, intent); + requestLocationUpdates(request, pendingIntent); } /** @@ -2295,6 +2381,127 @@ public class LocationManager { } } + private static class GetCurrentLocationTransport extends ILocationListener.Stub implements + AlarmManager.OnAlarmListener { + + @GuardedBy("this") + @Nullable + private Executor mExecutor; + + @GuardedBy("this") + @Nullable + private Consumer mConsumer; + + @GuardedBy("this") + @Nullable + private AlarmManager mAlarmManager; + + @GuardedBy("this") + @Nullable + private ICancellationSignal mRemoteCancellationSignal; + + private GetCurrentLocationTransport(Executor executor, Consumer consumer) { + Preconditions.checkArgument(executor != null, "illegal null executor"); + Preconditions.checkArgument(consumer != null, "illegal null consumer"); + mExecutor = executor; + mConsumer = consumer; + mAlarmManager = null; + mRemoteCancellationSignal = null; + } + + public synchronized void register(AlarmManager alarmManager, + ICancellationSignal remoteCancellationSignal) { + if (mConsumer == null) { + return; + } + + mAlarmManager = alarmManager; + mAlarmManager.set( + ELAPSED_REALTIME, + SystemClock.elapsedRealtime() + GET_CURRENT_LOCATION_TIMEOUT_MS, + "GetCurrentLocation", + this, + null); + + mRemoteCancellationSignal = remoteCancellationSignal; + } + + public synchronized void cancel() { + mExecutor = null; + mConsumer = null; + + if (mAlarmManager != null) { + mAlarmManager.cancel(this); + mAlarmManager = null; + } + + if (mRemoteCancellationSignal != null) { + try { + mRemoteCancellationSignal.cancel(); + } catch (RemoteException e) { + // ignore + } + mRemoteCancellationSignal = null; + } + } + + public void fail() { + deliverResult(null); + } + + @Override + public void onAlarm() { + synchronized (this) { + // save ourselves a pointless x-process call to cancel the alarm + mAlarmManager = null; + } + + deliverResult(null); + } + + @Override + public void onLocationChanged(Location location) { + synchronized (this) { + // save ourselves a pointless x-process call to cancel the location request + mRemoteCancellationSignal = null; + } + + deliverResult(location); + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) {} + + @Override + public void onProviderEnabled(String provider) {} + + @Override + public void onProviderDisabled(String provider) { + // in the event of the provider being disabled it is unlikely that we will get further + // locations, so fail early so the client isn't left waiting hopelessly + deliverResult(null); + } + + private synchronized void deliverResult(@Nullable Location location) { + if (mExecutor == null) { + return; + } + + mExecutor.execute(() -> { + Consumer consumer; + synchronized (GetCurrentLocationTransport.this) { + if (mConsumer == null) { + return; + } + consumer = mConsumer; + cancel(); + } + + consumer.accept(location); + }); + } + } + private class LocationListenerTransport extends ILocationListener.Stub { private final LocationListener mListener; diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index aa22feb56534d..89439e8088241 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -62,8 +62,10 @@ import android.location.LocationRequest; import android.location.LocationTime; import android.os.Binder; import android.os.Bundle; +import android.os.CancellationSignal; import android.os.Handler; import android.os.IBinder; +import android.os.ICancellationSignal; import android.os.PowerManager; import android.os.PowerManager.ServiceType; import android.os.PowerManagerInternal; @@ -119,6 +121,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; /** * The service class that manages LocationProviders and issues location @@ -145,11 +148,12 @@ public class LocationManagerService extends ILocationManager.Stub { private static final String FUSED_LOCATION_SERVICE_ACTION = "com.android.location.service.FusedLocationProvider"; - private static final long NANOS_PER_MILLI = 1000000L; - // The maximum interval a location request can have and still be considered "high power". private static final long HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000; + // maximum age of a location before it is no longer considered "current" + private static final long MAX_CURRENT_LOCATION_AGE_MS = 10 * 1000; + private static final int FOREGROUND_IMPORTANCE_CUTOFF = ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; @@ -2450,8 +2454,8 @@ public class LocationManagerService extends ILocationManager.Stub { // Don't return stale location to apps with foreground-only location permission. String op = resolutionLevelToOpStr(allowedResolutionLevel); - long locationAgeMs = SystemClock.elapsedRealtime() - - location.getElapsedRealtimeNanos() / NANOS_PER_MILLI; + long locationAgeMs = TimeUnit.NANOSECONDS.toMillis( + SystemClock.elapsedRealtime() - location.getElapsedRealtimeNanos()); if ((locationAgeMs > Settings.Global.getLong( mContext.getContentResolver(), Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS, @@ -2488,6 +2492,56 @@ public class LocationManagerService extends ILocationManager.Stub { } } + @Override + public boolean getCurrentLocation(LocationRequest locationRequest, + ICancellationSignal remoteCancellationSignal, ILocationListener listener, + String packageName, String listenerIdentifier) { + // side effect of validating locationRequest and packageName + Location lastLocation = getLastLocation(locationRequest, packageName); + if (lastLocation != null) { + long locationAgeMs = TimeUnit.NANOSECONDS.toMillis( + SystemClock.elapsedRealtimeNanos() - lastLocation.getElapsedRealtimeNanos()); + + long identity = Binder.clearCallingIdentity(); + try { + if (locationAgeMs < MAX_CURRENT_LOCATION_AGE_MS) { + try { + listener.onLocationChanged(lastLocation); + return true; + } catch (RemoteException e) { + Log.w(TAG, e); + return false; + } + } + + // packageName already validated by getLastLocation() call above + boolean foreground = LocationManagerServiceUtils.isImportanceForeground( + mActivityManager.getPackageImportance(packageName)); + if (!foreground) { + long backgroundThrottleIntervalMs = Settings.Global.getLong( + mContext.getContentResolver(), + Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS, + DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS); + if (locationAgeMs < backgroundThrottleIntervalMs) { + // not allowed to request new locations, so we can't return anything + return false; + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + requestLocationUpdates(locationRequest, listener, null, packageName, listenerIdentifier); + CancellationSignal cancellationSignal = CancellationSignal.fromTransport( + remoteCancellationSignal); + if (cancellationSignal != null) { + cancellationSignal.setOnCancelListener( + () -> removeUpdates(listener, null, packageName)); + } + return true; + } + @Override public LocationTime getGnssTimeMillis() { synchronized (mLock) { @@ -2836,9 +2890,9 @@ public class LocationManagerService extends ILocationManager.Stub { // Check whether sufficient time has passed long minTime = record.mRealRequest.getFastestInterval(); - long delta = (loc.getElapsedRealtimeNanos() - lastLoc.getElapsedRealtimeNanos()) - / NANOS_PER_MILLI; - if (delta < minTime - MAX_PROVIDER_SCHEDULING_JITTER_MS) { + long deltaMs = TimeUnit.NANOSECONDS.toMillis( + loc.getElapsedRealtimeNanos() - lastLoc.getElapsedRealtimeNanos()); + if (deltaMs < minTime - MAX_PROVIDER_SCHEDULING_JITTER_MS) { return false; } @@ -2893,9 +2947,9 @@ public class LocationManagerService extends ILocationManager.Stub { mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval); } } - long timeDiffNanos = location.getElapsedRealtimeNanos() - - lastLocationCoarseInterval.getElapsedRealtimeNanos(); - if (timeDiffNanos > LocationFudger.FASTEST_INTERVAL_MS * NANOS_PER_MILLI) { + long timeDeltaMs = TimeUnit.NANOSECONDS.toMillis(location.getElapsedRealtimeNanos() + - lastLocationCoarseInterval.getElapsedRealtimeNanos()); + if (timeDeltaMs > LocationFudger.FASTEST_INTERVAL_MS) { lastLocationCoarseInterval.set(location); } // Don't ever return a coarse location that is more recent than the allowed update