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
This commit is contained in:
Ram Periathiruvadi
2017-12-08 18:35:10 -08:00
parent d93d206cfe
commit 8671feab63
3 changed files with 117 additions and 17 deletions

View File

@@ -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<String> getAllProviders();

View File

@@ -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.
*
* <p>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.</p>
*
* <p> 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) {

View File

@@ -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);