From 8671feab630c1938691f44502ff7be9c7b03e7c4 Mon Sep 17 00:00:00 2001 From: Ram Periathiruvadi Date: Fri, 8 Dec 2017 18:35:10 -0800 Subject: [PATCH] Add a hidden API to inject location. New API allows system clients with LOCATION_HARDWARE and ACCESS_FINE_LOCATION to inject a location to the Location Manager. This is useful in products like Auto, where the location needs to be cached across reboots. Bug: b/64125396 Test: Foll. were tested: 1. location can be injected when lastLocation is not available 2. location cannot be injected when lastLocation is already available Also, tested location availability on different devices for sanity. Change-Id: I1fd060caafed0431074ae64439ac52e99e0d6032 --- .../android/location/ILocationManager.aidl | 1 + .../android/location/LocationManager.java | 29 +++++ .../server/LocationManagerService.java | 104 +++++++++++++++--- 3 files changed, 117 insertions(+), 17 deletions(-) diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index f9075cfd10d99..0990dcccaecb0 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -79,6 +79,7 @@ interface ILocationManager boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName); void flushGnssBatch(String packageName); boolean stopGnssBatch(); + boolean injectLocation(in Location location); // --- deprecated --- List getAllProviders(); diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 4802b23579069..f0b2774a42762 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -42,6 +42,7 @@ import java.util.List; import static android.Manifest.permission.ACCESS_COARSE_LOCATION; import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static android.Manifest.permission.LOCATION_HARDWARE; /** * This class provides access to the system location services. These @@ -882,6 +883,34 @@ public class LocationManager { requestLocationUpdates(request, null, null, intent); } + /** + * Set the last known location with a new location. + * + *

A privileged client can inject a {@link Location} if it has a better estimate of what + * the recent location is. This is especially useful when the device boots up and the GPS + * chipset is in the process of getting the first fix. If the client has cached the location, + * it can inject the {@link Location}, so if an app requests for a {@link Location} from {@link + * #getLastKnownLocation(String)}, the location information is still useful before getting + * the first fix.

+ * + *

Useful in products like Auto. + * + * @param newLocation newly available {@link Location} object + * @return true if update was successful, false if not + * + * @throws SecurityException if no suitable permission is present + * + * @hide + */ + @RequiresPermission(allOf = {LOCATION_HARDWARE, ACCESS_FINE_LOCATION}) + public boolean injectLocation(Location newLocation) { + try { + return mService.injectLocation(newLocation); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private ListenerTransport wrapListener(LocationListener listener, Looper looper) { if (listener == null) return null; synchronized (mListeners) { diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 57c992fdcf487..ea748db159e1f 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -2254,6 +2254,64 @@ public class LocationManagerService extends ILocationManager.Stub { } } + /** + * Provides an interface to inject and set the last location if location is not available + * currently. + * + * This helps in cases where the product (Cars for example) has saved the last known location + * before powering off. This interface lets the client inject the saved location while the GPS + * chipset is getting its first fix, there by improving user experience. + * + * @param location - Location object to inject + * @return true if update was successful, false if not + */ + @Override + public boolean injectLocation(Location location) { + mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, + "Location Hardware permission not granted to inject location"); + mContext.enforceCallingPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, + "Access Fine Location permission not granted to inject Location"); + + if (location == null) { + if (D) { + Log.d(TAG, "injectLocation(): called with null location"); + } + return false; + } + LocationProviderInterface p = null; + String provider = location.getProvider(); + if (provider != null) { + p = mProvidersByName.get(provider); + } + if (p == null) { + if (D) { + Log.d(TAG, "injectLocation(): unknown provider"); + } + return false; + } + synchronized (mLock) { + if (!isAllowedByCurrentUserSettingsLocked(provider)) { + if (D) { + Log.d(TAG, "Location disabled in Settings for current user:" + mCurrentUserId); + } + return false; + } else { + // NOTE: If last location is already available, location is not injected. If + // provider's normal source (like a GPS chipset) have already provided an output, + // there is no need to inject this location. + if (mLastLocation.get(provider) == null) { + updateLastLocationLocked(location, provider); + } else { + if (D) { + Log.d(TAG, "injectLocation(): Location exists. Not updating"); + } + return false; + } + } + } + return true; + } + @Override public void requestGeofence(LocationRequest request, Geofence geofence, PendingIntent intent, String packageName) { @@ -2614,30 +2672,18 @@ public class LocationManagerService extends ILocationManager.Stub { private void handleLocationChangedLocked(Location location, boolean passive) { if (D) Log.d(TAG, "incoming location: " + location); - long now = SystemClock.elapsedRealtime(); String provider = (passive ? LocationManager.PASSIVE_PROVIDER : location.getProvider()); - // Skip if the provider is unknown. LocationProviderInterface p = mProvidersByName.get(provider); if (p == null) return; - - // Update last known locations - Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION); - Location lastNoGPSLocation; + updateLastLocationLocked(location, provider); + // mLastLocation should have been updated from the updateLastLocationLocked call above. Location lastLocation = mLastLocation.get(provider); if (lastLocation == null) { - lastLocation = new Location(provider); - mLastLocation.put(provider, lastLocation); - } else { - lastNoGPSLocation = lastLocation.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION); - if (noGPSLocation == null && lastNoGPSLocation != null) { - // New location has no no-GPS location: adopt last no-GPS location. This is set - // directly into location because we do not want to notify COARSE clients. - location.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, lastNoGPSLocation); - } + Log.e(TAG, "handleLocationChangedLocked() updateLastLocation failed"); + return; } - lastLocation.set(location); // Update last known coarse interval location if enough time has passed. Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(provider); @@ -2653,7 +2699,7 @@ public class LocationManagerService extends ILocationManager.Stub { // Don't ever return a coarse location that is more recent than the allowed update // interval (i.e. don't allow an app to keep registering and unregistering for // location updates to overcome the minimum interval). - noGPSLocation = + Location noGPSLocation = lastLocationCoarseInterval.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION); // Skip if there are no UpdateRecords for this provider. @@ -2778,6 +2824,30 @@ public class LocationManagerService extends ILocationManager.Stub { } } + /** + * Updates last location with the given location + * + * @param location new location to update + * @param provider Location provider to update for + */ + private void updateLastLocationLocked(Location location, String provider) { + Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION); + Location lastNoGPSLocation; + Location lastLocation = mLastLocation.get(provider); + if (lastLocation == null) { + lastLocation = new Location(provider); + mLastLocation.put(provider, lastLocation); + } else { + lastNoGPSLocation = lastLocation.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION); + if (noGPSLocation == null && lastNoGPSLocation != null) { + // New location has no no-GPS location: adopt last no-GPS location. This is set + // directly into location because we do not want to notify COARSE clients. + location.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, lastNoGPSLocation); + } + } + lastLocation.set(location); + } + private class LocationWorkerHandler extends Handler { public LocationWorkerHandler(Looper looper) { super(looper, null, true);