diff options
author | Soonil Nagarkar <sooniln@google.com> | 2020-10-25 17:23:54 -0700 |
---|---|---|
committer | Soonil Nagarkar <sooniln@google.com> | 2020-11-10 10:14:34 -0800 |
commit | be6ed5aa14f7418da2a23b0755158491d4ecd179 (patch) | |
tree | 121ae291776990d86a057874902464e585b50822 | |
parent | fded2b6d13331c11f9ec6b149834c519382d7dc1 (diff) |
Add batching APIs and Location.equals()
-Moves batching APIs from SystemApi to Public, and makes them
multi-client safe.
-Adds equals/hashcode to Location.
Bug: 171512333
Test: manual + presubmit
Change-Id: I6ee28f8229fdf49386cd370ea785de63b97e7cde
44 files changed, 1686 insertions, 1265 deletions
diff --git a/api/current.txt b/api/current.txt index 88e09d5e128e..7f6137142bda 100644 --- a/api/current.txt +++ b/api/current.txt @@ -23964,7 +23964,7 @@ package android.location { method public float getBearingAccuracyDegrees(); method public long getElapsedRealtimeNanos(); method public double getElapsedRealtimeUncertaintyNanos(); - method public android.os.Bundle getExtras(); + method @Deprecated public android.os.Bundle getExtras(); method public double getLatitude(); method public double getLongitude(); method public String getProvider(); @@ -23993,7 +23993,7 @@ package android.location { method public void setBearingAccuracyDegrees(float); method public void setElapsedRealtimeNanos(long); method public void setElapsedRealtimeUncertaintyNanos(double); - method public void setExtras(android.os.Bundle); + method @Deprecated public void setExtras(android.os.Bundle); method public void setLatitude(double); method public void setLongitude(double); method public void setProvider(String); @@ -24009,7 +24009,9 @@ package android.location { } public interface LocationListener { + method public default void onFlushComplete(int); method public void onLocationChanged(@NonNull android.location.Location); + method public default void onLocationChanged(@NonNull android.location.LocationResult); method public default void onProviderDisabled(@NonNull String); method public default void onProviderEnabled(@NonNull String); method @Deprecated public default void onStatusChanged(String, int, android.os.Bundle); @@ -24057,6 +24059,8 @@ package android.location { method public void removeTestProvider(@NonNull String); method @RequiresPermission(anyOf={"android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"}, apis="..22") public void removeUpdates(@NonNull android.location.LocationListener); method public void removeUpdates(@NonNull android.app.PendingIntent); + method public void requestFlush(@NonNull String, @NonNull android.location.LocationListener, int); + method public void requestFlush(@NonNull String, @NonNull android.app.PendingIntent, int); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull String, long, float, @NonNull android.location.LocationListener); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull String, long, float, @NonNull android.location.LocationListener, @Nullable android.os.Looper); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull String, long, float, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener); @@ -24082,7 +24086,9 @@ package android.location { field public static final String EXTRA_PROVIDER_ENABLED = "android.location.extra.PROVIDER_ENABLED"; field public static final String EXTRA_PROVIDER_NAME = "android.location.extra.PROVIDER_NAME"; field public static final String GPS_PROVIDER = "gps"; + field public static final String KEY_FLUSH_COMPLETE = "flushComplete"; field public static final String KEY_LOCATION_CHANGED = "location"; + field public static final String KEY_LOCATION_RESULT = "locationResult"; field public static final String KEY_PROVIDER_ENABLED = "providerEnabled"; field public static final String KEY_PROXIMITY_ENTERING = "entering"; field @Deprecated public static final String KEY_STATUS_CHANGED = "status"; @@ -24113,6 +24119,7 @@ package android.location { method public int describeContents(); method @IntRange(from=1) public long getDurationMillis(); method @IntRange(from=0) public long getIntervalMillis(); + method @IntRange(from=0) public long getMaxUpdateDelayMillis(); method @IntRange(from=1, to=java.lang.Integer.MAX_VALUE) public int getMaxUpdates(); method @FloatRange(from=0, to=java.lang.Float.MAX_VALUE) public float getMinUpdateDistanceMeters(); method @IntRange(from=0) public long getMinUpdateIntervalMillis(); @@ -24132,12 +24139,25 @@ package android.location { method @NonNull public android.location.LocationRequest.Builder clearMinUpdateIntervalMillis(); method @NonNull public android.location.LocationRequest.Builder setDurationMillis(@IntRange(from=1) long); method @NonNull public android.location.LocationRequest.Builder setIntervalMillis(@IntRange(from=0) long); + method @NonNull public android.location.LocationRequest.Builder setMaxUpdateDelayMillis(@IntRange(from=0) long); method @NonNull public android.location.LocationRequest.Builder setMaxUpdates(@IntRange(from=1, to=java.lang.Integer.MAX_VALUE) int); method @NonNull public android.location.LocationRequest.Builder setMinUpdateDistanceMeters(@FloatRange(from=0, to=java.lang.Float.MAX_VALUE) float); method @NonNull public android.location.LocationRequest.Builder setMinUpdateIntervalMillis(@IntRange(from=0) long); method @NonNull public android.location.LocationRequest.Builder setQuality(int); } + public final class LocationResult implements android.os.Parcelable { + method @NonNull public java.util.List<android.location.Location> asList(); + method @NonNull public static android.location.LocationResult create(@NonNull android.location.Location); + method @NonNull public static android.location.LocationResult create(@NonNull java.util.List<android.location.Location>); + method public int describeContents(); + method @NonNull public android.location.Location get(@IntRange(from=0) int); + method @NonNull public android.location.Location getLastLocation(); + method @IntRange(from=1) public int size(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.LocationResult> CREATOR; + } + public interface OnNmeaMessageListener { method public void onNmeaMessage(String, long); } diff --git a/api/system-current.txt b/api/system-current.txt index 1e9e1398d745..39922d8f46b2 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4164,17 +4164,17 @@ package android.location { } public class LocationManager { - method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void flushGnssBatch(); + method @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void flushGnssBatch(); method @Deprecated @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<android.location.Location>); method @Nullable public String getExtraLocationControllerPackage(); - method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public int getGnssBatchSize(); + method @Deprecated public int getGnssBatchSize(); method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void injectGnssMeasurementCorrections(@NonNull android.location.GnssMeasurementCorrections); method public boolean isExtraLocationControllerPackageEnabled(); method public boolean isLocationEnabledForUser(@NonNull android.os.UserHandle); method public boolean isProviderEnabledForUser(@NonNull String, @NonNull android.os.UserHandle); method @Deprecated @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public boolean isProviderPackage(@NonNull String); method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public boolean isProviderPackage(@Nullable String, @NonNull String); - method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean registerGnssBatchedLocationCallback(long, boolean, @NonNull android.location.BatchedLocationCallback, @Nullable android.os.Handler); + method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.UPDATE_APP_OPS_STATS}) public boolean registerGnssBatchedLocationCallback(long, boolean, @NonNull android.location.BatchedLocationCallback, @Nullable android.os.Handler); method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.LOCATION_HARDWARE}) public boolean registerGnssMeasurementsCallback(@NonNull android.location.GnssRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.GnssMeasurementsEvent.Callback); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener); @@ -4183,7 +4183,7 @@ package android.location { method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void setExtraLocationControllerPackageEnabled(boolean); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setLocationEnabledForUser(boolean, @NonNull android.os.UserHandle); method @Deprecated @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setProviderEnabledForUser(@NonNull String, boolean, @NonNull android.os.UserHandle); - method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean unregisterGnssBatchedLocationCallback(@NonNull android.location.BatchedLocationCallback); + method @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean unregisterGnssBatchedLocationCallback(@NonNull android.location.BatchedLocationCallback); } public final class LocationRequest implements android.os.Parcelable { @@ -4230,6 +4230,10 @@ package android.location { method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.location.LocationRequest.Builder setWorkSource(@Nullable android.os.WorkSource); } + public final class LocationResult implements android.os.Parcelable { + method @NonNull public static android.location.LocationResult wrap(@NonNull android.location.Location); + } + } package android.media { diff --git a/core/api/current.txt b/core/api/current.txt index 434705d1c546..a3ffee7ed307 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -23946,7 +23946,7 @@ package android.location { method public float getBearingAccuracyDegrees(); method public long getElapsedRealtimeNanos(); method public double getElapsedRealtimeUncertaintyNanos(); - method public android.os.Bundle getExtras(); + method @Deprecated public android.os.Bundle getExtras(); method public double getLatitude(); method public double getLongitude(); method public String getProvider(); @@ -23975,7 +23975,7 @@ package android.location { method public void setBearingAccuracyDegrees(float); method public void setElapsedRealtimeNanos(long); method public void setElapsedRealtimeUncertaintyNanos(double); - method public void setExtras(android.os.Bundle); + method @Deprecated public void setExtras(android.os.Bundle); method public void setLatitude(double); method public void setLongitude(double); method public void setProvider(String); @@ -23991,7 +23991,9 @@ package android.location { } public interface LocationListener { + method public default void onFlushComplete(int); method public void onLocationChanged(@NonNull android.location.Location); + method public default void onLocationChanged(@NonNull android.location.LocationResult); method public default void onProviderDisabled(@NonNull String); method public default void onProviderEnabled(@NonNull String); method @Deprecated public default void onStatusChanged(String, int, android.os.Bundle); @@ -24039,6 +24041,8 @@ package android.location { method public void removeTestProvider(@NonNull String); method @RequiresPermission(anyOf={"android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"}, apis="..22") public void removeUpdates(@NonNull android.location.LocationListener); method public void removeUpdates(@NonNull android.app.PendingIntent); + method public void requestFlush(@NonNull String, @NonNull android.location.LocationListener, int); + method public void requestFlush(@NonNull String, @NonNull android.app.PendingIntent, int); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull String, long, float, @NonNull android.location.LocationListener); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull String, long, float, @NonNull android.location.LocationListener, @Nullable android.os.Looper); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull String, long, float, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener); @@ -24064,7 +24068,9 @@ package android.location { field public static final String EXTRA_PROVIDER_ENABLED = "android.location.extra.PROVIDER_ENABLED"; field public static final String EXTRA_PROVIDER_NAME = "android.location.extra.PROVIDER_NAME"; field public static final String GPS_PROVIDER = "gps"; + field public static final String KEY_FLUSH_COMPLETE = "flushComplete"; field public static final String KEY_LOCATION_CHANGED = "location"; + field public static final String KEY_LOCATION_RESULT = "locationResult"; field public static final String KEY_PROVIDER_ENABLED = "providerEnabled"; field public static final String KEY_PROXIMITY_ENTERING = "entering"; field @Deprecated public static final String KEY_STATUS_CHANGED = "status"; @@ -24095,6 +24101,7 @@ package android.location { method public int describeContents(); method @IntRange(from=1) public long getDurationMillis(); method @IntRange(from=0) public long getIntervalMillis(); + method @IntRange(from=0) public long getMaxUpdateDelayMillis(); method @IntRange(from=1, to=java.lang.Integer.MAX_VALUE) public int getMaxUpdates(); method @FloatRange(from=0, to=java.lang.Float.MAX_VALUE) public float getMinUpdateDistanceMeters(); method @IntRange(from=0) public long getMinUpdateIntervalMillis(); @@ -24114,12 +24121,25 @@ package android.location { method @NonNull public android.location.LocationRequest.Builder clearMinUpdateIntervalMillis(); method @NonNull public android.location.LocationRequest.Builder setDurationMillis(@IntRange(from=1) long); method @NonNull public android.location.LocationRequest.Builder setIntervalMillis(@IntRange(from=0) long); + method @NonNull public android.location.LocationRequest.Builder setMaxUpdateDelayMillis(@IntRange(from=0) long); method @NonNull public android.location.LocationRequest.Builder setMaxUpdates(@IntRange(from=1, to=java.lang.Integer.MAX_VALUE) int); method @NonNull public android.location.LocationRequest.Builder setMinUpdateDistanceMeters(@FloatRange(from=0, to=java.lang.Float.MAX_VALUE) float); method @NonNull public android.location.LocationRequest.Builder setMinUpdateIntervalMillis(@IntRange(from=0) long); method @NonNull public android.location.LocationRequest.Builder setQuality(int); } + public final class LocationResult implements android.os.Parcelable { + method @NonNull public java.util.List<android.location.Location> asList(); + method @NonNull public static android.location.LocationResult create(@NonNull android.location.Location); + method @NonNull public static android.location.LocationResult create(@NonNull java.util.List<android.location.Location>); + method public int describeContents(); + method @NonNull public android.location.Location get(@IntRange(from=0) int); + method @NonNull public android.location.Location getLastLocation(); + method @IntRange(from=1) public int size(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.LocationResult> CREATOR; + } + public interface OnNmeaMessageListener { method public void onNmeaMessage(String, long); } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 40dd713d3324..18bdeb6a4747 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -4104,17 +4104,17 @@ package android.location { } public class LocationManager { - method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void flushGnssBatch(); + method @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void flushGnssBatch(); method @Deprecated @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<android.location.Location>); method @Nullable public String getExtraLocationControllerPackage(); - method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public int getGnssBatchSize(); + method @Deprecated public int getGnssBatchSize(); method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void injectGnssMeasurementCorrections(@NonNull android.location.GnssMeasurementCorrections); method public boolean isExtraLocationControllerPackageEnabled(); method public boolean isLocationEnabledForUser(@NonNull android.os.UserHandle); method public boolean isProviderEnabledForUser(@NonNull String, @NonNull android.os.UserHandle); method @Deprecated @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public boolean isProviderPackage(@NonNull String); method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public boolean isProviderPackage(@Nullable String, @NonNull String); - method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean registerGnssBatchedLocationCallback(long, boolean, @NonNull android.location.BatchedLocationCallback, @Nullable android.os.Handler); + method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.UPDATE_APP_OPS_STATS}) public boolean registerGnssBatchedLocationCallback(long, boolean, @NonNull android.location.BatchedLocationCallback, @Nullable android.os.Handler); method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.LOCATION_HARDWARE}) public boolean registerGnssMeasurementsCallback(@NonNull android.location.GnssRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.GnssMeasurementsEvent.Callback); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener); @@ -4123,7 +4123,7 @@ package android.location { method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void setExtraLocationControllerPackageEnabled(boolean); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setLocationEnabledForUser(boolean, @NonNull android.os.UserHandle); method @Deprecated @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setProviderEnabledForUser(@NonNull String, boolean, @NonNull android.os.UserHandle); - method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean unregisterGnssBatchedLocationCallback(@NonNull android.location.BatchedLocationCallback); + method @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean unregisterGnssBatchedLocationCallback(@NonNull android.location.BatchedLocationCallback); } public final class LocationRequest implements android.os.Parcelable { @@ -4170,6 +4170,10 @@ package android.location { method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.location.LocationRequest.Builder setWorkSource(@Nullable android.os.WorkSource); } + public final class LocationResult implements android.os.Parcelable { + method @NonNull public static android.location.LocationResult wrap(@NonNull android.location.Location); + } + } package android.media { diff --git a/location/java/android/location/ILocationListener.aidl b/location/java/android/location/ILocationListener.aidl index 29b483af8721..ce92661cb87c 100644 --- a/location/java/android/location/ILocationListener.aidl +++ b/location/java/android/location/ILocationListener.aidl @@ -16,7 +16,7 @@ package android.location; -import android.location.Location; +import android.location.LocationResult; import android.os.IRemoteCallback; /** @@ -24,6 +24,7 @@ import android.os.IRemoteCallback; */ oneway interface ILocationListener { - void onLocationChanged(in Location location, in @nullable IRemoteCallback onCompleteCallback); + void onLocationChanged(in LocationResult locationResult, in @nullable IRemoteCallback onCompleteCallback); void onProviderEnabledChanged(String provider, boolean enabled); + void onFlushComplete(int requestCode); } diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index 3905e0b2b878..d3dc3b32e15d 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -23,7 +23,6 @@ import android.location.GeocoderParams; import android.location.Geofence; import android.location.GnssMeasurementCorrections; import android.location.GnssRequest; -import android.location.IBatchedLocationCallback; import android.location.IGeocodeListener; import android.location.IGnssAntennaInfoListener; import android.location.IGnssMeasurementsListener; @@ -53,10 +52,13 @@ interface ILocationManager void unregisterLocationListener(in ILocationListener listener); void registerLocationPendingIntent(String provider, in LocationRequest request, in PendingIntent pendingIntent, String packageName, String attributionTag); - void unregisterLocationPendingIntent(in PendingIntent intent); + void unregisterLocationPendingIntent(in PendingIntent pendingIntent); void injectLocation(in Location location); + void requestListenerFlush(String provider, in ILocationListener listener, int requestCode); + void requestPendingIntentFlush(String provider, in PendingIntent pendingIntent, int requestCode); + void requestGeofence(in Geofence geofence, in PendingIntent intent, String packageName, String attributionTag); void removeGeofence(in PendingIntent intent); @@ -86,9 +88,7 @@ interface ILocationManager void removeGnssNavigationMessageListener(in IGnssNavigationMessageListener listener); int getGnssBatchSize(); - void setGnssBatchingCallback(in IBatchedLocationCallback callback, String packageName, String attributionTag); - void removeGnssBatchingCallback(); - void startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName, String attributionTag); + void startGnssBatch(long periodNanos, in ILocationListener listener, String packageName, String attributionTag, String listenerId); void flushGnssBatch(); void stopGnssBatch(); diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java index 20175d70e735..b4392b1d29b4 100644 --- a/location/java/android/location/Location.java +++ b/location/java/android/location/Location.java @@ -18,6 +18,7 @@ package android.location; import static java.util.concurrent.TimeUnit.NANOSECONDS; +import android.annotation.NonNull; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; @@ -29,20 +30,22 @@ import android.util.Printer; import android.util.TimeUtils; import java.text.DecimalFormat; +import java.util.Locale; +import java.util.Objects; import java.util.StringTokenizer; /** * A data class representing a geographic location. * - * <p>A location can consist of a latitude, longitude, timestamp, - * and other information such as bearing, altitude and velocity. + * <p>A location may consist of a latitude, longitude, timestamp, and other information such as + * bearing, altitude and velocity. * - * <p>All locations generated by the {@link LocationManager} are - * guaranteed to have a valid latitude, longitude, and timestamp - * (both UTC time and elapsed real-time since boot), all other + * <p>All locations generated through {@link LocationManager} are guaranteed to have a valid + * latitude, longitude, and timestamp (both UTC time and elapsed real-time since boot). All other * parameters are optional. */ public class Location implements Parcelable { + /** * Constant used to specify formatting of a latitude or longitude * in the form "[+-]DDD.DDDDD where D indicates degrees. @@ -78,58 +81,26 @@ public class Location implements Parcelable { @Deprecated public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation"; - /** - * Bit mask for mFieldsMask indicating the presence of mAltitude. - */ - private static final int HAS_ALTITUDE_MASK = 1; - /** - * Bit mask for mFieldsMask indicating the presence of mSpeed. - */ - private static final int HAS_SPEED_MASK = 2; - /** - * Bit mask for mFieldsMask indicating the presence of mBearing. - */ - private static final int HAS_BEARING_MASK = 4; - /** - * Bit mask for mFieldsMask indicating the presence of mHorizontalAccuracy. - */ - private static final int HAS_HORIZONTAL_ACCURACY_MASK = 8; - /** - * Bit mask for mFieldsMask indicating location is from a mock provider. - */ - private static final int HAS_MOCK_PROVIDER_MASK = 16; - /** - * Bit mask for mFieldsMask indicating the presence of mVerticalAccuracy. - */ - private static final int HAS_VERTICAL_ACCURACY_MASK = 32; - /** - * Bit mask for mFieldsMask indicating the presence of mSpeedAccuracy. - */ - private static final int HAS_SPEED_ACCURACY_MASK = 64; - /** - * Bit mask for mFieldsMask indicating the presence of mBearingAccuracy. - */ - private static final int HAS_BEARING_ACCURACY_MASK = 128; - /** - * Bit mask for mFieldsMask indicating the presence of mElapsedRealtimeUncertaintyNanos. - */ - private static final int HAS_ELAPSED_REALTIME_UNCERTAINTY_MASK = 256; + private static final int HAS_ALTITUDE_MASK = 1 << 0; + private static final int HAS_SPEED_MASK = 1 << 1; + private static final int HAS_BEARING_MASK = 1 << 2; + private static final int HAS_HORIZONTAL_ACCURACY_MASK = 1 << 3; + private static final int HAS_MOCK_PROVIDER_MASK = 1 << 4; + private static final int HAS_VERTICAL_ACCURACY_MASK = 1 << 5; + private static final int HAS_SPEED_ACCURACY_MASK = 1 << 6; + private static final int HAS_BEARING_ACCURACY_MASK = 1 << 7; + private static final int HAS_ELAPSED_REALTIME_UNCERTAINTY_MASK = 1 << 8; // Cached data to make bearing/distance computations more efficient for the case // where distanceTo and bearingTo are called in sequence. Assume this typically happens // on the same thread for caching purposes. - private static ThreadLocal<BearingDistanceCache> sBearingDistanceCache - = new ThreadLocal<BearingDistanceCache>() { - @Override - protected BearingDistanceCache initialValue() { - return new BearingDistanceCache(); - } - }; + private static final ThreadLocal<BearingDistanceCache> sBearingDistanceCache = + ThreadLocal.withInitial(BearingDistanceCache::new); - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private String mProvider; private long mTime = 0; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, + publicAlternatives = "{@link #getElapsedRealtimeNanos()}") private long mElapsedRealtimeNanos = 0; // Estimate of the relative precision of the alignment of this SystemClock // timestamp, with the reported measurements in nanoseconds (68% confidence). @@ -227,8 +198,7 @@ public class Location implements Parcelable { * FORMAT_DEGREES, FORMAT_MINUTES, or FORMAT_SECONDS. */ public static String convert(double coordinate, int outputType) { - if (coordinate < -180.0 || coordinate > 180.0 || - Double.isNaN(coordinate)) { + if (coordinate < -180.0 || coordinate > 180.0 || Double.isNaN(coordinate)) { throw new IllegalArgumentException("coordinate=" + coordinate); } if ((outputType != FORMAT_DEGREES) && @@ -374,10 +344,10 @@ public class Location implements Parcelable { double sigma = 0.0; double deltaSigma = 0.0; - double cosSqAlpha = 0.0; - double cos2SM = 0.0; - double cosSigma = 0.0; - double sinSigma = 0.0; + double cosSqAlpha; + double cos2SM; + double cosSigma; + double sinSigma; double cosLambda = 0.0; double sinLambda = 0.0; @@ -428,8 +398,7 @@ public class Location implements Parcelable { } } - float distance = (float) (b * A * (sigma - deltaSigma)); - results.mDistance = distance; + results.mDistance = (float) (b * A * (sigma - deltaSigma)); float initialBearing = (float) Math.atan2(cosU2 * sinLambda, cosU1 * sinU2 - sinU1 * cosU2 * cosLambda); initialBearing *= 180.0 / Math.PI; @@ -536,29 +505,26 @@ public class Location implements Parcelable { } /** - * Return the UTC time of this fix, in milliseconds since January 1, 1970. + * Return the UTC time of this location fix, in milliseconds since epoch (January 1, 1970). * - * <p>Note that the UTC time on a device is not monotonic: it - * can jump forwards or backwards unpredictably. So always use - * {@link #getElapsedRealtimeNanos} when calculating time deltas. + * <p>Note that the UTC time on a device is not monotonic; it can jump forwards or backwards + * unpredictably, so this time should not be used to calculate time deltas between locations. + * Instead prefer {@link #getElapsedRealtimeNanos} for that purpose. * - * <p>On the other hand, {@link #getTime} is useful for presenting - * a human readable time to the user, or for carefully comparing - * location fixes across reboot or across devices. + * <p>On the other hand, this method is useful for presenting a human readable time to the user, + * or for carefully comparing location fixes across reboot or across devices. * - * <p>All locations generated by the {@link LocationManager} - * are guaranteed to have a valid UTC time, however remember that - * the system time may have changed since the location was generated. + * <p>All locations generated by the {@link LocationManager} are guaranteed to have a UTC time, + * however remember that the system time may have changed since the location was generated. * - * @return time of fix, in milliseconds since January 1, 1970. + * @return UTC time of fix, in milliseconds since January 1, 1970. */ public long getTime() { return mTime; } /** - * Set the UTC time of this fix, in milliseconds since January 1, - * 1970. + * Set the UTC time of this fix, in milliseconds since epoch (January 1, 1970). * * @param time UTC time of this fix, in milliseconds since January 1, 1970 */ @@ -886,18 +852,15 @@ public class Location implements Parcelable { /** * Get the estimated vertical accuracy of this location, in meters. * - * <p>We define vertical accuracy at 68% confidence. Specifically, as 1-side of the - * 2-sided range above and below the estimated altitude reported by {@link #getAltitude()}, - * within which there is a 68% probability of finding the true altitude. + * <p>We define vertical accuracy at 68% confidence. Specifically, as 1-side of the 2-sided + * range above and below the estimated altitude reported by {@link #getAltitude()}, within which + * there is a 68% probability of finding the true altitude. * * <p>In the case where the underlying distribution is assumed Gaussian normal, this would be * considered 1 standard deviation. * - * <p>For example, if {@link #getAltitude()} returns 150, and - * {@link #getVerticalAccuracyMeters()} returns 20 then there is a 68% probability - * of the true altitude being between 130 and 170 meters. - * - * <p>If this location does not have a vertical accuracy, then 0.0 is returned. + * <p>For example, if {@link #getAltitude()} returns 150m, and this method returns 20m then + * there is a 68% probability of the true altitude being between 130m and 170m. */ public float getVerticalAccuracyMeters() { return mVerticalAccuracyMeters; @@ -940,22 +903,19 @@ public class Location implements Parcelable { /** * Get the estimated speed accuracy of this location, in meters per second. * - * <p>We define speed accuracy at 68% confidence. Specifically, as 1-side of the - * 2-sided range above and below the estimated speed reported by {@link #getSpeed()}, - * within which there is a 68% probability of finding the true speed. - * - * <p>In the case where the underlying - * distribution is assumed Gaussian normal, this would be considered 1 standard deviation. + * <p>We define speed accuracy at 68% confidence. Specifically, as 1-side of the 2-sided range + * above and below the estimated speed reported by {@link #getSpeed()}, within which there is a + * 68% probability of finding the true speed. * - * <p>For example, if {@link #getSpeed()} returns 5, and - * {@link #getSpeedAccuracyMetersPerSecond()} returns 1, then there is a 68% probability of - * the true speed being between 4 and 6 meters per second. + * <p>In the case where the underlying distribution is assumed Gaussian normal, this would be + * considered 1 standard deviation. * - * <p>Note that the speed and speed accuracy is often better than would be obtained simply from - * differencing sequential positions, such as when the Doppler measurements from GNSS satellites - * are used. + * <p>For example, if {@link #getSpeed()} returns 5m/s, and this method returns 1m/s, then there + * is a 68% probability of the true speed being between 4m/s and 6m/s. * - * <p>If this location does not have a speed accuracy, then 0.0 is returned. + * <p>Note that the speed and speed accuracy may be more accurate than would be obtained simply + * from differencing sequential positions, such as when the Doppler measurements from GNSS + * satellites are taken into account. */ public float getSpeedAccuracyMetersPerSecond() { return mSpeedAccuracyMetersPerSecond; @@ -998,18 +958,15 @@ public class Location implements Parcelable { /** * Get the estimated bearing accuracy of this location, in degrees. * - * <p>We define bearing accuracy at 68% confidence. Specifically, as 1-side of the - * 2-sided range on each side of the estimated bearing reported by {@link #getBearing()}, - * within which there is a 68% probability of finding the true bearing. + * <p>We define bearing accuracy at 68% confidence. Specifically, as 1-side of the 2-sided range + * on each side of the estimated bearing reported by {@link #getBearing()}, within which there + * is a 68% probability of finding the true bearing. * * <p>In the case where the underlying distribution is assumed Gaussian normal, this would be * considered 1 standard deviation. * - * <p>For example, if {@link #getBearing()} returns 60, and - * {@link #getBearingAccuracyDegrees()} returns 10, then there is a 68% probability of the - * true bearing being between 50 and 70 degrees. - * - * <p>If this location does not have a bearing accuracy, then 0.0 is returned. + * <p>For example, if {@link #getBearing()} returns 60°, and this method returns 10°, then there + * is a 68% probability of the true bearing being between 50° and 70°. */ public float getBearingAccuracyDegrees() { return mBearingAccuracyDegrees; @@ -1056,11 +1013,7 @@ public class Location implements Parcelable { */ @SystemApi public boolean isComplete() { - if (mProvider == null) return false; - if (!hasAccuracy()) return false; - if (mTime == 0) return false; - if (mElapsedRealtimeNanos == 0) return false; - return true; + return mProvider != null && hasAccuracy() && mTime != 0 && mElapsedRealtimeNanos != 0; } /** @@ -1084,10 +1037,9 @@ public class Location implements Parcelable { } /** - * Returns additional provider-specific information about the - * location fix as a Bundle. The keys and values are determined - * by the provider. If no additional information is available, - * null is returned. + * Returns additional provider-specific information about the location fix as a Bundle. The keys + * and values are determined by the provider. If no additional information is available, null + * is returned. * * <p> A number of common key/value pairs are listed * below. Providers that use any of the keys on this list must @@ -1096,53 +1048,105 @@ public class Location implements Parcelable { * <ul> * <li> satellites - the number of satellites used to derive the fix * </ul> + * + * @deprecated Do not use. For GNSS related information, prefer listening for GNSS status + * information via {@link LocationManager}. */ + @Deprecated public Bundle getExtras() { return mExtras; } /** - * Sets the extra information associated with this fix to the - * given Bundle. + * Sets the extra information associated with this fix to the given Bundle. * * <p>Note this stores a copy of the given extras, so any changes to extras after calling this * method won't be reflected in the location bundle. + * + * @deprecated Do not use. */ + @Deprecated public void setExtras(Bundle extras) { mExtras = (extras == null) ? null : new Bundle(extras); } @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Location location = (Location) o; + return mTime == location.mTime + && mElapsedRealtimeNanos == location.mElapsedRealtimeNanos + && hasElapsedRealtimeUncertaintyNanos() + == location.hasElapsedRealtimeUncertaintyNanos() + && (!hasElapsedRealtimeUncertaintyNanos() || Double.compare( + location.mElapsedRealtimeUncertaintyNanos, mElapsedRealtimeUncertaintyNanos) == 0) + && Double.compare(location.mLatitude, mLatitude) == 0 + && Double.compare(location.mLongitude, mLongitude) == 0 + && hasAltitude() == location.hasAltitude() + && (!hasAltitude() || Double.compare(location.mAltitude, mAltitude) == 0) + && hasSpeed() == location.hasSpeed() + && (!hasSpeed() || Float.compare(location.mSpeed, mSpeed) == 0) + && hasBearing() == location.hasBearing() + && (!hasBearing() || Float.compare(location.mBearing, mBearing) == 0) + && hasAccuracy() == location.hasAccuracy() + && (!hasAccuracy() || Float.compare(location.mHorizontalAccuracyMeters, + mHorizontalAccuracyMeters) == 0) + && hasVerticalAccuracy() == location.hasVerticalAccuracy() + && (!hasVerticalAccuracy() || Float.compare(location.mVerticalAccuracyMeters, + mVerticalAccuracyMeters) == 0) + && hasSpeedAccuracy() == location.hasSpeedAccuracy() + && (!hasSpeedAccuracy() || Float.compare(location.mSpeedAccuracyMetersPerSecond, + mSpeedAccuracyMetersPerSecond) == 0) + && hasBearingAccuracy() == location.hasBearingAccuracy() + && (!hasBearingAccuracy() || Float.compare(location.mBearingAccuracyDegrees, + mBearingAccuracyDegrees) == 0) + && Objects.equals(mProvider, location.mProvider) + && Objects.equals(mExtras, location.mExtras); + } + + @Override + public int hashCode() { + return Objects.hash(mProvider, mElapsedRealtimeNanos, mLatitude, mLongitude); + } + + @Override public String toString() { StringBuilder s = new StringBuilder(); s.append("Location["); s.append(mProvider); - s.append(String.format(" %.6f,%.6f", mLatitude, mLongitude)); - if (hasAccuracy()) s.append(String.format(" hAcc=%.0f", mHorizontalAccuracyMeters)); - else s.append(" hAcc=???"); - if (mTime == 0) { - s.append(" t=?!?"); + s.append(" ").append(String.format(Locale.ROOT, "%.6f,%.6f", mLatitude, mLongitude)); + if (hasAccuracy()) { + s.append(" hAcc=").append(mHorizontalAccuracyMeters); } - if (mElapsedRealtimeNanos == 0) { - s.append(" et=?!?"); - } else { - s.append(" et="); - TimeUtils.formatDuration(mElapsedRealtimeNanos / 1000000L, s); + s.append(" et="); + TimeUtils.formatDuration(getElapsedRealtimeMillis(), s); + if (hasAltitude()) { + s.append(" alt=").append(mAltitude); + if (hasVerticalAccuracy()) { + s.append(" vAcc=").append(mVerticalAccuracyMeters); + } } - if (hasElapsedRealtimeUncertaintyNanos()) { - s.append(" etAcc="); - TimeUtils.formatDuration((long) (mElapsedRealtimeUncertaintyNanos / 1000000), s); + if (hasSpeed()) { + s.append(" vel=").append(mSpeed); + if (hasSpeedAccuracy()) { + s.append(" sAcc=").append(mSpeedAccuracyMetersPerSecond); + } + } + if (hasBearing()) { + s.append(" bear=").append(mBearing); + if (hasBearingAccuracy()) { + s.append(" bAcc=").append(mBearingAccuracyDegrees); + } + } + if (isFromMockProvider()) { + s.append(" mock"); } - if (hasAltitude()) s.append(" alt=").append(mAltitude); - if (hasSpeed()) s.append(" vel=").append(mSpeed); - if (hasBearing()) s.append(" bear=").append(mBearing); - if (hasVerticalAccuracy()) s.append(String.format(" vAcc=%.0f", mVerticalAccuracyMeters)); - else s.append(" vAcc=???"); - if (hasSpeedAccuracy()) s.append(String.format(" sAcc=%.0f", mSpeedAccuracyMetersPerSecond)); - else s.append(" sAcc=???"); - if (hasBearingAccuracy()) s.append(String.format(" bAcc=%.0f", mBearingAccuracyDegrees)); - else s.append(" bAcc=???"); - if (isFromMockProvider()) s.append(" mock"); if (mExtras != null) { s.append(" {").append(mExtras).append('}'); @@ -1155,25 +1159,40 @@ public class Location implements Parcelable { pw.println(prefix + toString()); } - public static final @android.annotation.NonNull Parcelable.Creator<Location> CREATOR = + public static final @NonNull Parcelable.Creator<Location> CREATOR = new Parcelable.Creator<Location>() { @Override public Location createFromParcel(Parcel in) { - String provider = in.readString(); - Location l = new Location(provider); + Location l = new Location(in.readString()); + l.mFieldsMask = in.readInt(); l.mTime = in.readLong(); l.mElapsedRealtimeNanos = in.readLong(); - l.mElapsedRealtimeUncertaintyNanos = in.readDouble(); - l.mFieldsMask = in.readInt(); + if (l.hasElapsedRealtimeUncertaintyNanos()) { + l.mElapsedRealtimeUncertaintyNanos = in.readDouble(); + } l.mLatitude = in.readDouble(); l.mLongitude = in.readDouble(); - l.mAltitude = in.readDouble(); - l.mSpeed = in.readFloat(); - l.mBearing = in.readFloat(); - l.mHorizontalAccuracyMeters = in.readFloat(); - l.mVerticalAccuracyMeters = in.readFloat(); - l.mSpeedAccuracyMetersPerSecond = in.readFloat(); - l.mBearingAccuracyDegrees = in.readFloat(); + if (l.hasAltitude()) { + l.mAltitude = in.readDouble(); + } + if (l.hasSpeed()) { + l.mSpeed = in.readFloat(); + } + if (l.hasBearing()) { + l.mBearing = in.readFloat(); + } + if (l.hasAccuracy()) { + l.mHorizontalAccuracyMeters = in.readFloat(); + } + if (l.hasVerticalAccuracy()) { + l.mVerticalAccuracyMeters = in.readFloat(); + } + if (l.hasSpeedAccuracy()) { + l.mSpeedAccuracyMetersPerSecond = in.readFloat(); + } + if (l.hasBearingAccuracy()) { + l.mBearingAccuracyDegrees = in.readFloat(); + } l.mExtras = Bundle.setDefusable(in.readBundle(), true); return l; } @@ -1192,38 +1211,36 @@ public class Location implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeString(mProvider); + parcel.writeInt(mFieldsMask); parcel.writeLong(mTime); parcel.writeLong(mElapsedRealtimeNanos); - parcel.writeDouble(mElapsedRealtimeUncertaintyNanos); - parcel.writeInt(mFieldsMask); + if (hasElapsedRealtimeUncertaintyNanos()) { + parcel.writeDouble(mElapsedRealtimeUncertaintyNanos); + } parcel.writeDouble(mLatitude); parcel.writeDouble(mLongitude); - parcel.writeDouble(mAltitude); - parcel.writeFloat(mSpeed); - parcel.writeFloat(mBearing); - parcel.writeFloat(mHorizontalAccuracyMeters); - parcel.writeFloat(mVerticalAccuracyMeters); - parcel.writeFloat(mSpeedAccuracyMetersPerSecond); - parcel.writeFloat(mBearingAccuracyDegrees); - parcel.writeBundle(mExtras); - } - - /** - * Returns one of the optional extra {@link Location}s that can be attached - * to this Location. - * - * @param key the key associated with the desired extra Location - * @return the extra Location, or null if unavailable - * @hide - */ - public Location getExtraLocation(String key) { - if (mExtras != null) { - Parcelable value = mExtras.getParcelable(key); - if (value instanceof Location) { - return (Location) value; - } + if (hasAltitude()) { + parcel.writeDouble(mAltitude); + } + if (hasSpeed()) { + parcel.writeFloat(mSpeed); + } + if (hasBearing()) { + parcel.writeFloat(mBearing); + } + if (hasAccuracy()) { + parcel.writeFloat(mHorizontalAccuracyMeters); } - return null; + if (hasVerticalAccuracy()) { + parcel.writeFloat(mVerticalAccuracyMeters); + } + if (hasSpeedAccuracy()) { + parcel.writeFloat(mSpeedAccuracyMetersPerSecond); + } + if (hasBearingAccuracy()) { + parcel.writeFloat(mBearingAccuracyDegrees); + } + parcel.writeBundle(mExtras); } /** diff --git a/location/java/android/location/LocationListener.java b/location/java/android/location/LocationListener.java index 0ff0a723237b..523117b39762 100644 --- a/location/java/android/location/LocationListener.java +++ b/location/java/android/location/LocationListener.java @@ -46,6 +46,29 @@ public interface LocationListener { void onLocationChanged(@NonNull Location location); /** + * Called when the location has changed and locations are being delivered in batches. The + * default implementation calls through to ({@link #onLocationChanged(Location)} with all + * locations in the batch, from earliest to latest. + * + * @see LocationRequest#getMaxUpdateDelayMillis() + * @param locationResult the location result list + */ + default void onLocationChanged(@NonNull LocationResult locationResult) { + final int size = locationResult.size(); + for (int i = 0; i < size; ++i) { + onLocationChanged(locationResult.get(i)); + } + } + + /** + * Invoked when a flush operation is complete and after flushed locations have been delivered. + * + * @param requestCode the request code passed into + * {@link LocationManager#requestFlush(String, LocationListener, int)} + */ + default void onFlushComplete(int requestCode) {} + + /** * This callback will never be invoked on Android Q and above, and providers can be considered * as always in the {@link LocationProvider#AVAILABLE} state. * diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index b61b79e07736..4fbcfe16feb7 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -33,6 +33,7 @@ import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; @@ -65,8 +66,8 @@ import com.android.internal.util.Preconditions; import java.lang.ref.WeakReference; import java.util.Collection; +import java.util.Collections; import java.util.List; -import java.util.Objects; import java.util.WeakHashMap; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -238,6 +239,22 @@ public class LocationManager { public static final String KEY_LOCATION_CHANGED = "location"; /** + * Key used for an extra holding a {@link LocationResult} value when a location change is sent + * using a PendingIntent. + * + * @see #requestLocationUpdates(String, LocationRequest, PendingIntent) + */ + public static final String KEY_LOCATION_RESULT = "locationResult"; + + /** + * Key used for an extra holding an integer request code when location flush completion is sent + * using a PendingIntent. + * + * @see #requestFlush(String, PendingIntent, int) + */ + public static final String KEY_FLUSH_COMPLETE = "flushComplete"; + + /** * Broadcast intent action when the set of enabled location providers changes. To check the * status of a provider, use {@link #isProviderEnabled(String)}. From Android Q and above, will * include a string intent extra, {@link #EXTRA_PROVIDER_NAME}, with the name of the provider @@ -376,9 +393,6 @@ public class LocationManager { @GuardedBy("mLock") @Nullable private GnssAntennaInfoTransportMultiplexer mGnssAntennaInfoTransportMultiplexer; - @GuardedBy("mLock") - @Nullable private BatchedLocationCallbackTransport mBatchedLocationCallbackTransport; - /** * @hide */ @@ -1432,8 +1446,70 @@ public class LocationManager { } /** + * Requests that the given provider flush any batched locations to listeners. The given listener + * (registered with the provider) will have {@link LocationListener#onFlushComplete(int)} + * invoked with the given result code after any locations that were flushed have been delivered. + * If {@link #removeUpdates(LocationListener)} is invoked before the flush callback is executed, + * then the flush callback will never be executed. + * + * @param provider a provider listed by {@link #getAllProviders()} + * @param listener a listener registered under the provider + * @param requestCode an arbitrary integer passed through to + * {@link LocationListener#onFlushComplete(int)} + * + * @throws IllegalArgumentException if provider is null or doesn't exist + * @throws IllegalArgumentException if listener is null or is not registered under the provider + */ + @SuppressLint("SamShouldBeLast") + public void requestFlush(@NonNull String provider, @NonNull LocationListener listener, + @SuppressLint("ListenerLast") int requestCode) { + Preconditions.checkArgument(provider != null, "invalid null provider"); + Preconditions.checkArgument(listener != null, "invalid null listener"); + + synchronized (sLocationListeners) { + WeakReference<LocationListenerTransport> ref = sLocationListeners.get(listener); + LocationListenerTransport transport = ref != null ? ref.get() : null; + + Preconditions.checkArgument(transport != null, + "unregistered listener cannot be flushed"); + + try { + mService.requestListenerFlush(provider, transport, requestCode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Requests that the given provider flush any batched locations to listeners. The given + * PendingIntent (registered with the provider) will be sent with {@link #KEY_FLUSH_COMPLETE} + * present in the extra keys, and {@code requestCode} as the corresponding value. + * + * @param provider a provider listed by {@link #getAllProviders()} + * @param pendingIntent a pendingIntent registered under the provider + * @param requestCode an arbitrary integer that will be passed back as the extra value for + * {@link #KEY_FLUSH_COMPLETE} + * + * @throws IllegalArgumentException if provider is null or doesn't exist + * @throws IllegalArgumentException if pending intent is null or is not registered under the + * provider + */ + public void requestFlush(@NonNull String provider, @NonNull PendingIntent pendingIntent, + int requestCode) { + Preconditions.checkArgument(provider != null, "invalid null provider"); + Preconditions.checkArgument(pendingIntent != null, "invalid null pending intent"); + + try { + mService.requestPendingIntentFlush(provider, pendingIntent, requestCode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Removes location updates for the specified {@link LocationListener}. Following this call, - * the listener will no longer receive location updates. + * the listener will not receive any more invocations of any kind. * * @param listener listener that no longer needs location updates * @@ -2435,10 +2511,11 @@ public class LocationManager { * interface. * * @return Maximum number of location objects that can be returned + * @deprecated Do not use * @hide */ + @Deprecated @SystemApi - @RequiresPermission(Manifest.permission.LOCATION_HARDWARE) public int getGnssBatchSize() { try { return mService.getGnssBatchSize(); @@ -2458,48 +2535,47 @@ public class LocationManager { * * @param periodNanos Time interval, in nanoseconds, that the GNSS locations are requested * within the batch - * @param wakeOnFifoFull True if the hardware batching should flush the locations in a - * a callback to the listener, when it's internal buffer is full. If - * set to false, the oldest location information is, instead, - * dropped when the buffer is full. + * @param wakeOnFifoFull ignored * @param callback The listener on which to return the batched locations * @param handler The handler on which to process the callback * - * @return True if batching was successfully started + * @return True always + * @deprecated Use {@link LocationRequest.Builder#setMaxUpdateDelayMillis(long)} instead. * @hide */ + @Deprecated @SystemApi - @RequiresPermission(Manifest.permission.LOCATION_HARDWARE) + @RequiresPermission(allOf = {Manifest.permission.LOCATION_HARDWARE, + Manifest.permission.UPDATE_APP_OPS_STATS}) public boolean registerGnssBatchedLocationCallback(long periodNanos, boolean wakeOnFifoFull, @NonNull BatchedLocationCallback callback, @Nullable Handler handler) { if (handler == null) { handler = new Handler(); } - BatchedLocationCallbackTransport transport = new BatchedLocationCallbackTransport(callback, - handler); - - synchronized (mLock) { - try { - mService.setGnssBatchingCallback(transport, mContext.getPackageName(), - mContext.getAttributionTag()); - mBatchedLocationCallbackTransport = transport; - mService.startGnssBatch(periodNanos, wakeOnFifoFull, - mContext.getPackageName(), mContext.getFeatureId()); - return true; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + try { + mService.startGnssBatch( + periodNanos, + new BatchedLocationCallbackTransport(callback, handler), + mContext.getPackageName(), + mContext.getAttributionTag(), + AppOpsManager.toReceiverId(callback)); + return true; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } } /** - * Flush the batched GNSS locations. - * All GNSS locations currently ready in the batch are returned via the callback sent in - * startGnssBatch(), and the buffer containing the batched locations is cleared. + * Flush the batched GNSS locations. All GNSS locations currently ready in the batch are + * returned via the callback sent in startGnssBatch(), and the buffer containing the batched + * locations is cleared. * * @hide + * @deprecated Use {@link #requestFlush(String, LocationListener, int)} or + * {@link #requestFlush(String, PendingIntent, int)} instead. */ + @Deprecated @SystemApi @RequiresPermission(Manifest.permission.LOCATION_HARDWARE) public void flushGnssBatch() { @@ -2511,29 +2587,25 @@ public class LocationManager { } /** - * Stop batching locations. This API is primarily used when the AP is - * asleep and the device can batch locations in the hardware. + * Stop batching locations. This API is primarily used when the AP is asleep and the device can + * batch locations in the hardware. * - * @param callback the specific callback class to remove from the transport layer + * @param callback ignored * * @return True always + * @deprecated Use {@link LocationRequest.Builder#setMaxUpdateDelayMillis(long)} instead. * @hide */ + @Deprecated @SystemApi @RequiresPermission(Manifest.permission.LOCATION_HARDWARE) public boolean unregisterGnssBatchedLocationCallback( @NonNull BatchedLocationCallback callback) { - synchronized (mLock) { - if (callback == mBatchedLocationCallbackTransport.getCallback()) { - try { - mBatchedLocationCallbackTransport = null; - mService.removeGnssBatchingCallback(); - mService.stopGnssBatch(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } + try { + mService.stopGnssBatch(); return true; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } } @@ -2541,10 +2613,7 @@ public class LocationManager { ListenerExecutor, CancellationSignal.OnCancelListener { private final Executor mExecutor; - - @GuardedBy("this") - @Nullable - private Consumer<Location> mConsumer; + private volatile @Nullable Consumer<Location> mConsumer; GetCurrentLocationTransport(Executor executor, Consumer<Location> consumer, @Nullable CancellationSignal cancellationSignal) { @@ -2560,20 +2629,22 @@ public class LocationManager { @Override public void onCancel() { - synchronized (this) { - mConsumer = null; - } + mConsumer = null; } @Override public void onLocation(@Nullable Location location) { - Consumer<Location> consumer; - synchronized (this) { - consumer = mConsumer; - mConsumer = null; - } + executeSafely(mExecutor, () -> mConsumer, new ListenerOperation<Consumer<Location>>() { + @Override + public void operate(Consumer<Location> consumer) { + consumer.accept(location); + } - executeSafely(mExecutor, () -> consumer, listener -> listener.accept(location)); + @Override + public void onPostExecute(boolean success) { + mConsumer = null; + } + }); } } @@ -2581,7 +2652,7 @@ public class LocationManager { ListenerExecutor { private Executor mExecutor; - @Nullable private volatile LocationListener mListener; + private volatile @Nullable LocationListener mListener; LocationListenerTransport(LocationListener listener, Executor executor) { Preconditions.checkArgument(listener != null, "invalid null listener"); @@ -2603,12 +2674,12 @@ public class LocationManager { } @Override - public void onLocationChanged(Location location, + public void onLocationChanged(LocationResult locationResult, @Nullable IRemoteCallback onCompleteCallback) { executeSafely(mExecutor, () -> mListener, new ListenerOperation<LocationListener>() { @Override public void operate(LocationListener listener) { - listener.onLocationChanged(location); + listener.onLocationChanged(locationResult); } @Override @@ -2625,6 +2696,12 @@ public class LocationManager { } @Override + public void onFlushComplete(int requestCode) { + executeSafely(mExecutor, () -> mListener, + listener -> listener.onFlushComplete(requestCode)); + } + + @Override public void onProviderEnabledChanged(String provider, boolean enabled) { executeSafely(mExecutor, () -> mListener, listener -> { if (enabled) { @@ -2913,39 +2990,29 @@ public class LocationManager { } } - private static class BatchedLocationCallbackTransport extends IBatchedLocationCallback.Stub { + private static class BatchedLocationCallbackWrapper implements LocationListener { - private final Handler mHandler; - private volatile @Nullable BatchedLocationCallback mCallback; + private final BatchedLocationCallback mCallback; - BatchedLocationCallbackTransport(BatchedLocationCallback callback, Handler handler) { - mCallback = Objects.requireNonNull(callback); - mHandler = Objects.requireNonNull(handler); - } - - @Nullable - public BatchedLocationCallback getCallback() { - return mCallback; + BatchedLocationCallbackWrapper(BatchedLocationCallback callback) { + mCallback = callback; } - public void unregister() { - mCallback = null; + @Override + public void onLocationChanged(@NonNull Location location) { + mCallback.onLocationBatch(Collections.singletonList(location)); } @Override - public void onLocationBatch(List<Location> locations) { - if (mCallback == null) { - return; - } + public void onLocationChanged(@NonNull LocationResult locationResult) { + mCallback.onLocationBatch(locationResult.asList()); + } + } - mHandler.post(() -> { - BatchedLocationCallback callback = mCallback; - if (callback == null) { - return; - } + private static class BatchedLocationCallbackTransport extends LocationListenerTransport { - callback.onLocationBatch(locations); - }); + BatchedLocationCallbackTransport(BatchedLocationCallback callback, Handler handler) { + super(new BatchedLocationCallbackWrapper(callback), new HandlerExecutor(handler)); } } diff --git a/location/java/android/location/LocationManagerInternal.java b/location/java/android/location/LocationManagerInternal.java index ef68814bce84..1027f9c0a7be 100644 --- a/location/java/android/location/LocationManagerInternal.java +++ b/location/java/android/location/LocationManagerInternal.java @@ -21,8 +21,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.location.util.identity.CallerIdentity; -import java.util.List; - /** * Location manager local system service interface. * @@ -82,10 +80,4 @@ public abstract class LocationManagerInternal { */ // TODO: there is no reason for this to exist as part of any API. move all the logic into gnss public abstract void sendNiResponse(int notifId, int userResponse); - - /** - * Should only be used by GNSS code. - */ - // TODO: there is no reason for this to exist as part of any API. create a real batching API - public abstract void reportGnssBatchLocations(List<Location> locations); } diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java index 1adb2b47a884..323e740ee8e3 100644 --- a/location/java/android/location/LocationRequest.java +++ b/location/java/android/location/LocationRequest.java @@ -191,6 +191,7 @@ public final class LocationRequest implements Parcelable { private long mDurationMillis; private int mMaxUpdates; private float mMinUpdateDistanceMeters; + private final long mMaxUpdateDelayMillis; private boolean mHideFromAppOps; private boolean mLocationSettingsIgnored; private boolean mLowPower; @@ -285,6 +286,7 @@ public final class LocationRequest implements Parcelable { int maxUpdates, long minUpdateIntervalMillis, float minUpdateDistanceMeters, + long maxUpdateDelayMillis, boolean hiddenFromAppOps, boolean locationSettingsIgnored, boolean lowPower, @@ -297,6 +299,7 @@ public final class LocationRequest implements Parcelable { mDurationMillis = durationMillis; mMaxUpdates = maxUpdates; mMinUpdateDistanceMeters = minUpdateDistanceMeters; + mMaxUpdateDelayMillis = maxUpdateDelayMillis; mHideFromAppOps = hiddenFromAppOps; mLowPower = lowPower; mLocationSettingsIgnored = locationSettingsIgnored; @@ -592,6 +595,24 @@ public final class LocationRequest implements Parcelable { } /** + * Returns the maximum time any location update may be delayed, and thus grouped with following + * updates to enable location batching. If the maximum update delay is equal to or greater than + * twice the interval, then location providers may provide batched results. The maximum batch + * size is the maximum update delay divided by the interval. Not all devices or location + * providers support batching, and use of this parameter does not guarantee that the client will + * see batched results, or that batched results will always be of the maximum size. + * + * When available, batching can provide substantial power savings to the device, and clients are + * encouraged to take advantage where appropriate for the use case. + * + * @see LocationListener#onLocationChanged(LocationResult) + * @return the maximum time by which a location update may be delayed + */ + public @IntRange(from = 0) long getMaxUpdateDelayMillis() { + return mMaxUpdateDelayMillis; + } + + /** * @hide * @deprecated LocationRequests should be treated as immutable. */ @@ -725,6 +746,7 @@ public final class LocationRequest implements Parcelable { /* maxUpdates= */ in.readInt(), /* minUpdateIntervalMillis= */ in.readLong(), /* minUpdateDistanceMeters= */ in.readFloat(), + /* maxUpdateDelayMillis= */ in.readLong(), /* hiddenFromAppOps= */ in.readBoolean(), /* locationSettingsIgnored= */ in.readBoolean(), /* lowPower= */ in.readBoolean(), @@ -752,6 +774,7 @@ public final class LocationRequest implements Parcelable { parcel.writeInt(mMaxUpdates); parcel.writeLong(mMinUpdateIntervalMillis); parcel.writeFloat(mMinUpdateDistanceMeters); + parcel.writeLong(mMaxUpdateDelayMillis); parcel.writeBoolean(mHideFromAppOps); parcel.writeBoolean(mLocationSettingsIgnored); parcel.writeBoolean(mLowPower); @@ -775,6 +798,7 @@ public final class LocationRequest implements Parcelable { && mMaxUpdates == that.mMaxUpdates && mMinUpdateIntervalMillis == that.mMinUpdateIntervalMillis && Float.compare(that.mMinUpdateDistanceMeters, mMinUpdateDistanceMeters) == 0 + && mMaxUpdateDelayMillis == that.mMaxUpdateDelayMillis && mHideFromAppOps == that.mHideFromAppOps && mLocationSettingsIgnored == that.mLocationSettingsIgnored && mLowPower == that.mLowPower @@ -831,6 +855,10 @@ public final class LocationRequest implements Parcelable { if (mMinUpdateDistanceMeters > 0.0) { s.append(", minUpdateDistance=").append(mMinUpdateDistanceMeters); } + if (mMaxUpdateDelayMillis / 2 > mInterval) { + s.append(", maxUpdateDelay="); + TimeUtils.formatDuration(mMaxUpdateDelayMillis, s); + } if (mLowPower) { s.append(", lowPower"); } @@ -858,6 +886,7 @@ public final class LocationRequest implements Parcelable { private int mMaxUpdates; private long mMinUpdateIntervalMillis; private float mMinUpdateDistanceMeters; + private long mMaxUpdateDelayMillis; private boolean mHiddenFromAppOps; private boolean mLocationSettingsIgnored; private boolean mLowPower; @@ -876,6 +905,7 @@ public final class LocationRequest implements Parcelable { mMaxUpdates = Integer.MAX_VALUE; mMinUpdateIntervalMillis = IMPLICIT_MIN_UPDATE_INTERVAL; mMinUpdateDistanceMeters = 0; + mMaxUpdateDelayMillis = 0; mHiddenFromAppOps = false; mLocationSettingsIgnored = false; mLowPower = false; @@ -892,6 +922,7 @@ public final class LocationRequest implements Parcelable { mMaxUpdates = locationRequest.mMaxUpdates; mMinUpdateIntervalMillis = locationRequest.mMinUpdateIntervalMillis; mMinUpdateDistanceMeters = locationRequest.mMinUpdateDistanceMeters; + mMaxUpdateDelayMillis = locationRequest.mMaxUpdateDelayMillis; mHiddenFromAppOps = locationRequest.mHideFromAppOps; mLocationSettingsIgnored = locationRequest.mLocationSettingsIgnored; mLowPower = locationRequest.mLowPower; @@ -1027,6 +1058,19 @@ public final class LocationRequest implements Parcelable { } /** + * Sets the maximum time any location update may be delayed, and thus grouped with following + * updates to enable location batching. If the maximum update delay is equal to or greater + * than twice the interval, then location providers may provide batched results. Defaults to + * 0, which represents no batching allowed. + */ + public @NonNull Builder setMaxUpdateDelayMillis( + @IntRange(from = 0) long maxUpdateDelayMillis) { + mMaxUpdateDelayMillis = Preconditions.checkArgumentInRange(maxUpdateDelayMillis, 0, + Long.MAX_VALUE, "maxUpdateDelayMillis"); + return this; + } + + /** * If set to true, indicates that app ops should not be updated with location usage due to * this request. This implies that someone else (usually the creator of the location * request) is responsible for updating app ops as appropriate. Defaults to false. @@ -1121,6 +1165,7 @@ public final class LocationRequest implements Parcelable { mMaxUpdates, min(mMinUpdateIntervalMillis, mIntervalMillis), mMinUpdateDistanceMeters, + mMaxUpdateDelayMillis, mHiddenFromAppOps, mLocationSettingsIgnored, mLowPower, diff --git a/location/java/android/location/IBatchedLocationCallback.aidl b/location/java/android/location/LocationResult.aidl index dce9f964c010..3bb894f17503 100644 --- a/location/java/android/location/IBatchedLocationCallback.aidl +++ b/location/java/android/location/LocationResult.aidl @@ -1,11 +1,11 @@ /* - * Copyright (C) 2017, The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -16,14 +16,4 @@ package android.location; -import android.location.Location; - -import java.util.List; - -/** - * {@hide} - */ -oneway interface IBatchedLocationCallback -{ - void onLocationBatch(in List<Location> locations); -} +parcelable LocationResult; diff --git a/location/java/android/location/LocationResult.java b/location/java/android/location/LocationResult.java new file mode 100644 index 000000000000..79a000c23484 --- /dev/null +++ b/location/java/android/location/LocationResult.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * A location result representing a list of locations, ordered from earliest to latest. + */ +public final class LocationResult implements Parcelable { + + /** + * Creates a new LocationResult from the given location. + */ + public static @NonNull LocationResult create(@NonNull Location location) { + ArrayList<Location> locations = new ArrayList<>(1); + locations.add(new Location(Objects.requireNonNull(location))); + return new LocationResult(locations); + } + + /** + * Creates a new LocationResult from the given locations. Locations must be ordered in the same + * order they were derived (earliest to latest). + */ + public static @NonNull LocationResult create(@NonNull List<Location> locations) { + Preconditions.checkArgument(!locations.isEmpty()); + ArrayList<Location> locationsCopy = new ArrayList<>(locations.size()); + for (Location location : locations) { + locationsCopy.add(new Location(Objects.requireNonNull(location))); + } + return new LocationResult(locationsCopy); + } + + /** + * Creates a new LocationResult that takes ownership of the given location without copying it. + * Callers must ensure the given location is never mutated after this method is called. + * + * @hide + */ + @SystemApi + public static @NonNull LocationResult wrap(@NonNull Location location) { + ArrayList<Location> locations = new ArrayList<>(1); + locations.add(Objects.requireNonNull(location)); + return new LocationResult(locations); + } + + private final ArrayList<Location> mLocations; + + private LocationResult(ArrayList<Location> locations) { + Preconditions.checkArgument(!locations.isEmpty()); + mLocations = locations; + } + + /** + * Throws an IllegalArgumentException if the ordering of locations does not appear to generally + * be from earliest to latest, or if any individual location is incomplete. + * + * @hide + */ + public @NonNull LocationResult validate() { + long prevElapsedRealtimeNs = 0; + final int size = mLocations.size(); + for (int i = 0; i < size; ++i) { + Location location = mLocations.get(i); + if (!location.isComplete()) { + throw new IllegalArgumentException( + "incomplete location at index " + i + ": " + mLocations); + } + if (location.getElapsedRealtimeNanos() < prevElapsedRealtimeNs) { + throw new IllegalArgumentException( + "incorrectly ordered location at index " + i + ": " + mLocations); + } + prevElapsedRealtimeNs = location.getElapsedRealtimeNanos(); + } + + return this; + } + + /** + * Returns the latest location in this location result, ie, the location at the highest index. + */ + public @NonNull Location getLastLocation() { + return mLocations.get(mLocations.size() - 1); + } + + /** + * Returns the numer of locations in this location result. + */ + public @IntRange(from = 1) int size() { + return mLocations.size(); + } + + /** + * Returns the location at the given index, from 0 to {@link #size()} - 1. Locations at lower + * indices are from earlier in time than location at higher indices. + */ + public @NonNull Location get(@IntRange(from = 0) int i) { + return mLocations.get(i); + } + + /** + * Returns an unmodifiable list of locations in this location result. + */ + public @NonNull List<Location> asList() { + return Collections.unmodifiableList(mLocations); + } + + /** + * Returns a deep copy of this LocationResult. + * + * @hide + */ + public @NonNull LocationResult deepCopy() { + ArrayList<Location> copy = new ArrayList<>(mLocations.size()); + final int size = mLocations.size(); + for (int i = 0; i < size; ++i) { + copy.add(new Location(mLocations.get(i))); + } + return new LocationResult(copy); + } + + /** + * Returns a LocationResult with only the last location from this location result. + * + * @hide + */ + public @NonNull LocationResult asLastLocationResult() { + if (mLocations.size() == 1) { + return this; + } else { + return LocationResult.wrap(getLastLocation()); + } + } + + /** + * Returns a LocationResult with only locations that pass the given predicate. This + * implementation will avoid allocations when no locations are filtered out. The predicate is + * guaranteed to be invoked once per location, in order from earliest to latest. If all + * locations are filtered out a null value is returned instead of an empty LocationResult. + * + * @hide + */ + public @Nullable LocationResult filter(Predicate<Location> predicate) { + ArrayList<Location> filtered = mLocations; + final int size = mLocations.size(); + for (int i = 0; i < size; ++i) { + if (!predicate.test(mLocations.get(i))) { + if (filtered == mLocations) { + filtered = new ArrayList<>(mLocations.size() - 1); + for (int j = 0; j < i; ++j) { + filtered.add(mLocations.get(j)); + } + } + } else if (filtered != mLocations) { + filtered.add(mLocations.get(i)); + } + } + + if (filtered == mLocations) { + return this; + } else if (filtered.isEmpty()) { + return null; + } else { + return new LocationResult(filtered); + } + } + + /** + * Returns a LocationResult with locations mapped to other locations. This implementation will + * avoid allocations when all locations are mapped to the same location. The function is + * guaranteed to be invoked once per location, in order from earliest to latest. + * + * @hide + */ + public @NonNull LocationResult map(Function<Location, Location> function) { + ArrayList<Location> mapped = mLocations; + final int size = mLocations.size(); + for (int i = 0; i < size; ++i) { + Location location = mLocations.get(i); + Location newLocation = function.apply(location); + if (mapped != mLocations) { + mapped.add(newLocation); + } else if (newLocation != location) { + mapped = new ArrayList<>(mLocations.size()); + for (int j = 0; j < i; ++j) { + mapped.add(mLocations.get(j)); + } + mapped.add(newLocation); + } + } + + if (mapped == mLocations) { + return this; + } else { + return new LocationResult(mapped); + } + } + + public static final @NonNull Parcelable.Creator<LocationResult> CREATOR = + new Parcelable.Creator<LocationResult>() { + @Override + public LocationResult createFromParcel(Parcel in) { + return new LocationResult( + Objects.requireNonNull(in.createTypedArrayList(Location.CREATOR))); + } + + @Override + public LocationResult[] newArray(int size) { + return new LocationResult[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeTypedList(mLocations); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + LocationResult that = (LocationResult) o; + return mLocations.equals(that.mLocations); + } + + @Override + public int hashCode() { + return Objects.hash(mLocations); + } + + @Override + public String toString() { + return mLocations.toString(); + } +} diff --git a/location/java/com/android/internal/location/ILocationProvider.aidl b/location/java/com/android/internal/location/ILocationProvider.aidl index 01570dc4415d..dac08ff77938 100644 --- a/location/java/com/android/internal/location/ILocationProvider.aidl +++ b/location/java/com/android/internal/location/ILocationProvider.aidl @@ -35,6 +35,8 @@ interface ILocationProvider { @UnsupportedAppUsage(maxTargetSdk = 30, publicAlternatives = "{@code Do not use}") oneway void setRequest(in ProviderRequest request, in WorkSource ws); + oneway void flush(); + @UnsupportedAppUsage(maxTargetSdk = 30, publicAlternatives = "{@code Do not use}") oneway void sendExtraCommand(String command, in Bundle extras); } diff --git a/location/java/com/android/internal/location/ILocationProviderManager.aidl b/location/java/com/android/internal/location/ILocationProviderManager.aidl index 2a6cef812db1..a74538b0334e 100644 --- a/location/java/com/android/internal/location/ILocationProviderManager.aidl +++ b/location/java/com/android/internal/location/ILocationProviderManager.aidl @@ -16,7 +16,7 @@ package com.android.internal.location; -import android.location.Location; +import android.location.LocationResult; import com.android.internal.location.ProviderProperties; @@ -28,5 +28,6 @@ interface ILocationProviderManager { void onSetIdentity(@nullable String packageName, @nullable String attributionTag); void onSetAllowed(boolean allowed); void onSetProperties(in ProviderProperties properties); - void onReportLocation(in Location location); + void onReportLocation(in LocationResult locationResult); + void onFlushComplete(); } diff --git a/location/java/com/android/internal/location/ProviderRequest.java b/location/java/com/android/internal/location/ProviderRequest.java index 00ba5523b8d4..545ea528826b 100644 --- a/location/java/com/android/internal/location/ProviderRequest.java +++ b/location/java/com/android/internal/location/ProviderRequest.java @@ -46,7 +46,7 @@ public final class ProviderRequest implements Parcelable { public static final long INTERVAL_DISABLED = Long.MAX_VALUE; public static final ProviderRequest EMPTY_REQUEST = new ProviderRequest( - INTERVAL_DISABLED, QUALITY_BALANCED_POWER_ACCURACY, false, false, new WorkSource()); + INTERVAL_DISABLED, QUALITY_BALANCED_POWER_ACCURACY, 0, false, false, new WorkSource()); @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "{@link " + "ProviderRequest}") @@ -55,6 +55,7 @@ public final class ProviderRequest implements Parcelable { + "ProviderRequest}") public final long interval; private final @Quality int mQuality; + private final long mMaxUpdateDelayMillis; private final boolean mLowPower; private final boolean mLocationSettingsIgnored; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "{@link " @@ -62,11 +63,17 @@ public final class ProviderRequest implements Parcelable { public final List<LocationRequest> locationRequests; private final WorkSource mWorkSource; - private ProviderRequest(long intervalMillis, @Quality int quality, boolean lowPower, - boolean locationSettingsIgnored, @NonNull WorkSource workSource) { + private ProviderRequest( + long intervalMillis, + @Quality int quality, + long maxUpdateDelayMillis, + boolean lowPower, + boolean locationSettingsIgnored, + @NonNull WorkSource workSource) { reportLocation = intervalMillis != INTERVAL_DISABLED; interval = intervalMillis; mQuality = quality; + mMaxUpdateDelayMillis = maxUpdateDelayMillis; mLowPower = lowPower; mLocationSettingsIgnored = locationSettingsIgnored; if (intervalMillis != INTERVAL_DISABLED) { @@ -108,6 +115,17 @@ public final class ProviderRequest implements Parcelable { } /** + * The maximum time any location update may be delayed, and thus grouped with following updates + * to enable location batching. If the maximum update delay is equal to or greater than + * twice the interval, then the provider may provide batched results if possible. The maximum + * batch size a provider is allowed to return is the maximum update delay divided by the + * interval. + */ + public @IntRange(from = 0) long getMaxUpdateDelayMillis() { + return mMaxUpdateDelayMillis; + } + + /** * Whether any applicable hardware low power modes should be used to satisfy this request. */ public boolean isLowPower() { @@ -141,6 +159,7 @@ public final class ProviderRequest implements Parcelable { return new ProviderRequest( intervalMillis, /* quality= */ in.readInt(), + /* maxUpdateDelayMillis= */ in.readLong(), /* lowPower= */ in.readBoolean(), /* locationSettingsIgnored= */ in.readBoolean(), /* workSource= */ in.readTypedObject(WorkSource.CREATOR)); @@ -163,6 +182,7 @@ public final class ProviderRequest implements Parcelable { parcel.writeLong(interval); if (interval != INTERVAL_DISABLED) { parcel.writeInt(mQuality); + parcel.writeLong(mMaxUpdateDelayMillis); parcel.writeBoolean(mLowPower); parcel.writeBoolean(mLocationSettingsIgnored); parcel.writeTypedObject(mWorkSource, flags); @@ -184,6 +204,7 @@ public final class ProviderRequest implements Parcelable { } else { return interval == that.interval && mQuality == that.mQuality + && mMaxUpdateDelayMillis == that.mMaxUpdateDelayMillis && mLowPower == that.mLowPower && mLocationSettingsIgnored == that.mLocationSettingsIgnored && mWorkSource.equals(that.mWorkSource); @@ -209,6 +230,10 @@ public final class ProviderRequest implements Parcelable { s.append(", LOW_POWER"); } } + if (mMaxUpdateDelayMillis / 2 > interval) { + s.append(", maxUpdateDelay="); + TimeUtils.formatDuration(mMaxUpdateDelayMillis, s); + } if (mLowPower) { s.append(", lowPower"); } @@ -231,6 +256,7 @@ public final class ProviderRequest implements Parcelable { public static class Builder { private long mIntervalMillis = INTERVAL_DISABLED; private int mQuality = QUALITY_BALANCED_POWER_ACCURACY; + private long mMaxUpdateDelayMillis = 0; private boolean mLowPower; private boolean mLocationSettingsIgnored; private WorkSource mWorkSource = new WorkSource(); @@ -260,6 +286,19 @@ public final class ProviderRequest implements Parcelable { } /** + * Sets the maximum time any location update may be delayed, and thus grouped with following + * updates to enable location batching. If the maximum update delay is equal to or greater + * than twice the interval, then location providers may provide batched results. Defaults to + * 0. + */ + public @NonNull Builder setMaxUpdateDelayMillis( + @IntRange(from = 0) long maxUpdateDelayMillis) { + mMaxUpdateDelayMillis = Preconditions.checkArgumentInRange(maxUpdateDelayMillis, 0, + Long.MAX_VALUE, "maxUpdateDelayMillis"); + return this; + } + + /** * Sets whether hardware low power mode should be used. False by default. */ public @NonNull Builder setLowPower(boolean lowPower) { @@ -290,8 +329,13 @@ public final class ProviderRequest implements Parcelable { if (mIntervalMillis == INTERVAL_DISABLED) { return EMPTY_REQUEST; } else { - return new ProviderRequest(mIntervalMillis, mQuality, mLowPower, - mLocationSettingsIgnored, mWorkSource); + return new ProviderRequest( + mIntervalMillis, + mQuality, + mMaxUpdateDelayMillis, + mLowPower, + mLocationSettingsIgnored, + mWorkSource); } } } diff --git a/location/lib/api/current.txt b/location/lib/api/current.txt index 80636c665a1a..d4f7558bf990 100644 --- a/location/lib/api/current.txt +++ b/location/lib/api/current.txt @@ -15,12 +15,14 @@ package com.android.location.provider { method @Deprecated protected void onDisable(); method @Deprecated protected void onDump(java.io.FileDescriptor, java.io.PrintWriter, String[]); method @Deprecated protected void onEnable(); + method protected void onFlush(com.android.location.provider.LocationProviderBase.OnFlushCompleteCallback); method @Deprecated protected int onGetStatus(android.os.Bundle); method @Deprecated protected long onGetStatusUpdateTime(); method protected void onInit(); method protected boolean onSendExtraCommand(@Nullable String, @Nullable android.os.Bundle); method protected abstract void onSetRequest(com.android.location.provider.ProviderRequestUnbundled, android.os.WorkSource); method public void reportLocation(android.location.Location); + method public void reportLocation(android.location.LocationResult); method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.Q) public void setAdditionalProviderPackages(java.util.List<java.lang.String>); method @RequiresApi(android.os.Build.VERSION_CODES.R) public void setAllowed(boolean); method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.Q) public void setEnabled(boolean); @@ -29,6 +31,10 @@ package com.android.location.provider { field public static final String FUSED_PROVIDER = "fused"; } + protected static interface LocationProviderBase.OnFlushCompleteCallback { + method public void onFlushComplete(); + } + @Deprecated public final class LocationRequestUnbundled { method @Deprecated public long getFastestInterval(); method @Deprecated public long getInterval(); @@ -50,6 +56,7 @@ package com.android.location.provider { public final class ProviderRequestUnbundled { method public long getInterval(); method @Deprecated @NonNull public java.util.List<com.android.location.provider.LocationRequestUnbundled> getLocationRequests(); + method @RequiresApi(android.os.Build.VERSION_CODES.S) public long getMaxUpdateDelayMillis(); method @android.location.LocationRequest.Quality @RequiresApi(android.os.Build.VERSION_CODES.S) public int getQuality(); method public boolean getReportLocation(); method @NonNull @RequiresApi(android.os.Build.VERSION_CODES.S) public android.os.WorkSource getWorkSource(); diff --git a/location/lib/java/com/android/location/provider/LocationProviderBase.java b/location/lib/java/com/android/location/provider/LocationProviderBase.java index 32ac374b33c2..54d806671e86 100644 --- a/location/lib/java/com/android/location/provider/LocationProviderBase.java +++ b/location/lib/java/com/android/location/provider/LocationProviderBase.java @@ -22,6 +22,7 @@ import android.location.ILocationManager; import android.location.Location; import android.location.LocationManager; import android.location.LocationProvider; +import android.location.LocationResult; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.IBinder; @@ -61,6 +62,18 @@ import java.util.List; public abstract class LocationProviderBase { /** + * Callback to be invoked when a flush operation is complete and all flushed locations have been + * reported. + */ + protected interface OnFlushCompleteCallback { + + /** + * Should be invoked once the flush is complete. + */ + void onFlushComplete(); + } + + /** * Bundle key for a version of the location containing no GPS data. * Allows location providers to flag locations as being safe to * feed to LocationFudger. @@ -235,23 +248,33 @@ public abstract class LocationProviderBase { * Reports a new location from this provider. */ public void reportLocation(Location location) { + reportLocation(LocationResult.create(location)); + } + + /** + * Reports a new location from this provider. + */ + public void reportLocation(LocationResult locationResult) { ILocationProviderManager manager = mManager; if (manager != null) { - // remove deprecated extras to save on serialization - Bundle extras = location.getExtras(); - if (extras != null && (extras.containsKey("noGPSLocation") - || extras.containsKey("coarseLocation"))) { - location = new Location(location); - extras = location.getExtras(); - extras.remove("noGPSLocation"); - extras.remove("coarseLocation"); - if (extras.isEmpty()) { - location.setExtras(null); + locationResult = locationResult.map(location -> { + // remove deprecated extras to save on serialization costs + Bundle extras = location.getExtras(); + if (extras != null && (extras.containsKey("noGPSLocation") + || extras.containsKey("coarseLocation"))) { + location = new Location(location); + extras = location.getExtras(); + extras.remove("noGPSLocation"); + extras.remove("coarseLocation"); + if (extras.isEmpty()) { + location.setExtras(null); + } } - } + return location; + }); try { - manager.onReportLocation(location); + manager.onReportLocation(locationResult); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (RuntimeException e) { @@ -292,6 +315,16 @@ public abstract class LocationProviderBase { protected abstract void onSetRequest(ProviderRequestUnbundled request, WorkSource source); /** + * Requests a flush of any pending batched locations. The callback must always be invoked once + * per invocation, and should be invoked after {@link #reportLocation(LocationResult)} has been + * invoked with any flushed locations. The callback may be invoked immediately if no locations + * are flushed. + */ + protected void onFlush(OnFlushCompleteCallback callback) { + callback.onFlushComplete(); + } + + /** * @deprecated This callback will never be invoked on Android Q and above. This method may be * removed in the future. Prefer to dump provider state via the containing service instead. */ @@ -362,6 +395,24 @@ public abstract class LocationProviderBase { } @Override + public void flush() { + onFlush(this::onFlushComplete); + } + + private void onFlushComplete() { + ILocationProviderManager manager = mManager; + if (manager != null) { + try { + manager.onFlushComplete(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (RuntimeException e) { + Log.w(mTag, e); + } + } + } + + @Override public void sendExtraCommand(String command, Bundle extras) { onSendExtraCommand(command, extras); } diff --git a/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java b/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java index f7bac74bf92d..b464fca6f596 100644 --- a/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java +++ b/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java @@ -57,6 +57,14 @@ public final class ProviderRequestUnbundled { } /** + * The interval at which a provider should report location. Will return + * {@link #INTERVAL_DISABLED} for an inactive request. + */ + public long getInterval() { + return mRequest.getIntervalMillis(); + } + + /** * The quality hint for this location request. The quality hint informs the provider how it * should attempt to manage any accuracy vs power tradeoffs while attempting to satisfy this * provider request. @@ -67,11 +75,15 @@ public final class ProviderRequestUnbundled { } /** - * The interval at which a provider should report location. Will return - * {@link #INTERVAL_DISABLED} for an inactive request. + * The maximum time any location update may be delayed, and thus grouped with following updates + * to enable location batching. If the maximum update delay is equal to or greater than + * twice the interval, then the provider may provide batched results if possible. The maximum + * batch size a provider is allowed to return is the maximum update delay divided by the + * interval. */ - public long getInterval() { - return mRequest.getIntervalMillis(); + @RequiresApi(Build.VERSION_CODES.S) + public long getMaxUpdateDelayMillis() { + return mRequest.getMaxUpdateDelayMillis(); } /** diff --git a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java index 61349d9bb8e8..f9f1607dfc95 100644 --- a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java +++ b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java @@ -26,6 +26,7 @@ import android.location.Criteria; import android.location.Location; import android.location.LocationManager; import android.location.LocationRequest; +import android.location.LocationResult; import android.os.ParcelFileDescriptor; import android.os.SystemClock; import android.os.WorkSource; @@ -167,8 +168,15 @@ public class FusedLocationServiceTest { } @Override - public void onReportLocation(Location location) { - mLocations.add(location); + public void onReportLocation(LocationResult locationResult) { + for (int i = 0; i < locationResult.size(); i++) { + mLocations.add(locationResult.get(i)); + } + } + + @Override + public void onFlushComplete() { + } public Location getNextLocation(long timeoutMs) throws InterruptedException { diff --git a/services/core/java/com/android/server/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java index bd2676e60825..26bf6c15d21b 100644 --- a/services/core/java/com/android/server/location/AbstractLocationProvider.java +++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java @@ -17,7 +17,7 @@ package com.android.server.location; import android.annotation.Nullable; -import android.location.Location; +import android.location.LocationResult; import android.location.util.identity.CallerIdentity; import android.os.Binder; import android.os.Bundle; @@ -27,8 +27,6 @@ import com.android.internal.location.ProviderRequest; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; @@ -56,13 +54,7 @@ public abstract class AbstractLocationProvider { * Called when a provider has a new location available. May be invoked from any thread. Will * be invoked with a cleared binder identity. */ - void onReportLocation(Location location); - - /** - * Called when a provider has a new location available. May be invoked from any thread. Will - * be invoked with a cleared binder identity. - */ - void onReportLocation(List<Location> locations); + void onReportLocation(LocationResult locationResult); } /** @@ -302,33 +294,12 @@ public abstract class AbstractLocationProvider { /** * Call this method to report a new location. */ - protected void reportLocation(Location location) { - Listener listener = mInternalState.get().listener; - if (listener != null) { - final long identity = Binder.clearCallingIdentity(); - try { - // copy location so if provider makes further changes they do not propagate - listener.onReportLocation(new Location(location)); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - } - - /** - * Call this method to report a new location. - */ - protected void reportLocation(List<Location> locations) { + protected void reportLocation(LocationResult locationResult) { Listener listener = mInternalState.get().listener; if (listener != null) { final long identity = Binder.clearCallingIdentity(); try { - // copy location so if provider makes further changes they do not propagate - ArrayList<Location> copy = new ArrayList<>(locations.size()); - for (Location location : locations) { - copy.add(new Location(location)); - } - listener.onReportLocation(copy); + listener.onReportLocation(Objects.requireNonNull(locationResult)); } finally { Binder.restoreCallingIdentity(identity); } @@ -349,6 +320,23 @@ public abstract class AbstractLocationProvider { protected abstract void onSetRequest(ProviderRequest request); /** + * Requests that any applicable locations are flushed. Normally only relevant for batched + * locations. + */ + public final void flush(Runnable listener) { + // all calls into the provider must be moved onto the provider thread to prevent deadlock + mExecutor.execute(() -> onFlush(listener)); + } + + /** + * Always invoked on the provider executor. The callback must always be invoked exactly once + * for every invocation, and should only be invoked after + * {@link #reportLocation(LocationResult)} has been called for every flushed location. If no + * locations are flushed, the callback may be invoked immediately. + */ + protected abstract void onFlush(Runnable callback); + + /** * Sends an extra command to the provider for it to interpret as it likes. */ public final void sendExtraCommand(int uid, int pid, String command, Bundle extras) { diff --git a/services/core/java/com/android/server/location/LocationFudger.java b/services/core/java/com/android/server/location/LocationFudger.java index ecdd429cd0c8..c2bfc5114e5a 100644 --- a/services/core/java/com/android/server/location/LocationFudger.java +++ b/services/core/java/com/android/server/location/LocationFudger.java @@ -18,6 +18,7 @@ package com.android.server.location; import android.annotation.Nullable; import android.location.Location; +import android.location.LocationResult; import android.os.SystemClock; import com.android.internal.annotations.GuardedBy; @@ -77,6 +78,11 @@ public class LocationFudger { @GuardedBy("this") @Nullable private Location mCachedCoarseLocation; + @GuardedBy("this") + @Nullable private LocationResult mCachedFineLocationResult; + @GuardedBy("this") + @Nullable private LocationResult mCachedCoarseLocationResult; + public LocationFudger(float accuracyM) { this(accuracyM, SystemClock.elapsedRealtimeClock(), new SecureRandom()); } @@ -100,6 +106,28 @@ public class LocationFudger { } /** + * Coarsens a LocationResult by coarsening every location within the location result with + * {@link #createCoarse(Location)}. + */ + public LocationResult createCoarse(LocationResult fineLocationResult) { + synchronized (this) { + if (fineLocationResult == mCachedFineLocationResult + || fineLocationResult == mCachedCoarseLocationResult) { + return mCachedCoarseLocationResult; + } + } + + LocationResult coarseLocationResult = fineLocationResult.map(this::createCoarse); + + synchronized (this) { + mCachedFineLocationResult = fineLocationResult; + mCachedCoarseLocationResult = coarseLocationResult; + } + + return coarseLocationResult; + } + + /** * Create a coarse location using two technique, random offsets and snap-to-grid. * * First we add a random offset to mitigate against detecting grid transitions. Without a random @@ -154,7 +182,7 @@ public class LocationFudger { mCachedCoarseLocation = coarse; } - return mCachedCoarseLocation; + return coarse; } /** diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 9d3855e6dd75..d225f968d593 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -32,6 +32,7 @@ import static com.android.server.location.LocationPermissions.PERMISSION_FINE; import static java.util.concurrent.TimeUnit.NANOSECONDS; +import android.Manifest; import android.Manifest.permission; import android.annotation.NonNull; import android.annotation.Nullable; @@ -47,7 +48,6 @@ import android.location.Geofence; import android.location.GnssCapabilities; import android.location.GnssMeasurementCorrections; import android.location.GnssRequest; -import android.location.IBatchedLocationCallback; import android.location.IGeocodeListener; import android.location.IGnssAntennaInfoListener; import android.location.IGnssMeasurementsListener; @@ -219,6 +219,10 @@ public class LocationManagerService extends ILocationManager.Stub { private volatile @Nullable GnssManagerService mGnssManagerService = null; private GeocoderProxy mGeocodeProvider; + private final Object mDeprecatedGnssBatchingLock = new Object(); + @GuardedBy("mDeprecatedGnssBatchingLock") + private @Nullable ILocationListener mDeprecatedGnssBatchingListener; + @GuardedBy("mLock") private String mExtraLocationControllerPackage; @GuardedBy("mLock") @@ -442,40 +446,63 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override - public void setGnssBatchingCallback(IBatchedLocationCallback callback, String packageName, - String attributionTag) { - if (mGnssManagerService != null) { - mGnssManagerService.setGnssBatchingCallback(callback, packageName, attributionTag); - } - } + public void startGnssBatch(long periodNanos, ILocationListener listener, String packageName, + String attributionTag, String listenerId) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null); - @Override - public void removeGnssBatchingCallback() { - if (mGnssManagerService != null) { - mGnssManagerService.removeGnssBatchingCallback(); + if (mGnssManagerService == null) { + return; } - } - @Override - public void startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName, - String attributionTag) { - if (mGnssManagerService != null) { - mGnssManagerService.startGnssBatch(periodNanos, wakeOnFifoFull, packageName, - attributionTag); + long intervalMs = NANOSECONDS.toMillis(periodNanos); + + synchronized (mDeprecatedGnssBatchingLock) { + stopGnssBatch(); + + registerLocationListener( + GPS_PROVIDER, + new LocationRequest.Builder(intervalMs) + .setMaxUpdateDelayMillis( + intervalMs * mGnssManagerService.getGnssBatchSize()) + .setHiddenFromAppOps(true) + .build(), + listener, + packageName, + attributionTag, + listenerId); + mDeprecatedGnssBatchingListener = listener; } } @Override public void flushGnssBatch() { - if (mGnssManagerService != null) { - mGnssManagerService.flushGnssBatch(); + mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null); + + if (mGnssManagerService == null) { + return; + } + + synchronized (mDeprecatedGnssBatchingLock) { + if (mDeprecatedGnssBatchingListener != null) { + requestListenerFlush(GPS_PROVIDER, mDeprecatedGnssBatchingListener, 0); + } } } @Override public void stopGnssBatch() { - if (mGnssManagerService != null) { - mGnssManagerService.stopGnssBatch(); + mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null); + + if (mGnssManagerService == null) { + return; + } + + synchronized (mDeprecatedGnssBatchingLock) { + if (mDeprecatedGnssBatchingListener != null) { + ILocationListener listener = mDeprecatedGnssBatchingListener; + mDeprecatedGnssBatchingListener = null; + unregisterLocationListener(listener); + } } } @@ -673,6 +700,25 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override + public void requestListenerFlush(String provider, ILocationListener listener, int requestCode) { + LocationProviderManager manager = getLocationProviderManager(provider); + Preconditions.checkArgument(manager != null, + "provider \"" + provider + "\" does not exist"); + + manager.flush(Objects.requireNonNull(listener), requestCode); + } + + @Override + public void requestPendingIntentFlush(String provider, PendingIntent pendingIntent, + int requestCode) { + LocationProviderManager manager = getLocationProviderManager(provider); + Preconditions.checkArgument(manager != null, + "provider \"" + provider + "\" does not exist"); + + manager.flush(Objects.requireNonNull(pendingIntent), requestCode); + } + + @Override public void unregisterLocationListener(ILocationListener listener) { for (LocationProviderManager manager : mProviderManagers) { manager.unregisterLocationRequest(listener); @@ -1218,13 +1264,6 @@ public class LocationManagerService extends ILocationManager.Stub { mGnssManagerService.sendNiResponse(notifId, userResponse); } } - - @Override - public void reportGnssBatchLocations(List<Location> locations) { - if (mGnssManagerService != null) { - mGnssManagerService.onReportLocation(locations); - } - } } private static class SystemInjector implements Injector { diff --git a/services/core/java/com/android/server/location/LocationProviderManager.java b/services/core/java/com/android/server/location/LocationProviderManager.java index fc0704a15359..b0b5575da359 100644 --- a/services/core/java/com/android/server/location/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/LocationProviderManager.java @@ -20,7 +20,9 @@ import static android.app.compat.CompatChanges.isChangeEnabled; import static android.location.LocationManager.DELIVER_HISTORICAL_LOCATIONS; import static android.location.LocationManager.FUSED_PROVIDER; import static android.location.LocationManager.GPS_PROVIDER; +import static android.location.LocationManager.KEY_FLUSH_COMPLETE; import static android.location.LocationManager.KEY_LOCATION_CHANGED; +import static android.location.LocationManager.KEY_LOCATION_RESULT; import static android.location.LocationManager.KEY_PROVIDER_ENABLED; import static android.location.LocationManager.PASSIVE_PROVIDER; import static android.os.IPowerManager.LOCATION_MODE_NO_CHANGE; @@ -52,6 +54,7 @@ import android.location.LocationManager; import android.location.LocationManagerInternal; import android.location.LocationManagerInternal.ProviderEnabledListener; import android.location.LocationRequest; +import android.location.LocationResult; import android.location.util.identity.CallerIdentity; import android.os.Binder; import android.os.Build; @@ -112,8 +115,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collection; -import java.util.List; import java.util.Objects; +import java.util.function.Predicate; class LocationProviderManager extends ListenerMultiplexer<Object, LocationProviderManager.LocationTransport, @@ -155,8 +158,9 @@ class LocationProviderManager extends protected interface LocationTransport { - void deliverOnLocationChanged(Location location, @Nullable Runnable onCompleteCallback) - throws Exception; + void deliverOnLocationChanged(LocationResult locationResult, + @Nullable Runnable onCompleteCallback) throws Exception; + void deliverOnFlushComplete(int requestCode) throws Exception; } protected interface ProviderTransport { @@ -174,9 +178,14 @@ class LocationProviderManager extends } @Override - public void deliverOnLocationChanged(Location location, + public void deliverOnLocationChanged(LocationResult locationResult, @Nullable Runnable onCompleteCallback) throws RemoteException { - mListener.onLocationChanged(location, SingleUseCallback.wrap(onCompleteCallback)); + mListener.onLocationChanged(locationResult, SingleUseCallback.wrap(onCompleteCallback)); + } + + @Override + public void deliverOnFlushComplete(int requestCode) throws RemoteException { + mListener.onFlushComplete(requestCode); } @Override @@ -198,12 +207,26 @@ class LocationProviderManager extends } @Override - public void deliverOnLocationChanged(Location location, + public void deliverOnLocationChanged(LocationResult locationResult, @Nullable Runnable onCompleteCallback) throws PendingIntent.CanceledException { - mPendingIntent.send(mContext, 0, new Intent().putExtra(KEY_LOCATION_CHANGED, location), + mPendingIntent.send( + mContext, + 0, + new Intent() + .putExtra(KEY_LOCATION_CHANGED, locationResult.getLastLocation()) + .putExtra(KEY_LOCATION_RESULT, locationResult), onCompleteCallback != null ? (pI, i, rC, rD, rE) -> onCompleteCallback.run() - : null, null, null, + : null, + null, + null, + PendingIntentUtils.createDontSendToRestrictedAppsBundle(null)); + } + + @Override + public void deliverOnFlushComplete(int requestCode) throws PendingIntent.CanceledException { + mPendingIntent.send(mContext, 0, new Intent().putExtra(KEY_FLUSH_COMPLETE, requestCode), + null, null, null, PendingIntentUtils.createDontSendToRestrictedAppsBundle(null)); } @@ -225,13 +248,20 @@ class LocationProviderManager extends } @Override - public void deliverOnLocationChanged(Location location, + public void deliverOnLocationChanged(@Nullable LocationResult locationResult, @Nullable Runnable onCompleteCallback) throws RemoteException { // ILocationCallback doesn't currently support completion callbacks Preconditions.checkState(onCompleteCallback == null); - mCallback.onLocation(location); + if (locationResult != null) { + mCallback.onLocation(locationResult.getLastLocation()); + } else { + mCallback.onLocation(null); + } } + + @Override + public void deliverOnFlushComplete(int requestCode) {} } protected abstract class Registration extends RemoteListenerRegistration<LocationRequest, @@ -377,6 +407,14 @@ class LocationProviderManager extends return mPermitted; } + public final void flush(int requestCode) { + // when the flush callback is invoked, we are guaranteed that locations have been + // queued on our executor, so by running the listener callback on the same executor it + // should be guaranteed that those locations will be delivered before the flush callback + mProvider.flush(() -> executeOperation( + listener -> listener.deliverOnFlushComplete(requestCode))); + } + @Override protected final LocationProviderManager getOwner() { return LocationProviderManager.this; @@ -539,7 +577,7 @@ class LocationProviderManager extends @GuardedBy("mLock") abstract @Nullable ListenerOperation<LocationTransport> acceptLocationChange( - Location fineLocation); + LocationResult fineLocationResult); @Override public String toString() { @@ -668,7 +706,7 @@ class LocationProviderManager extends getRequest().isLocationSettingsIgnored(), maxLocationAgeMs); if (lastLocation != null) { - executeOperation(acceptLocationChange(lastLocation)); + executeOperation(acceptLocationChange(LocationResult.wrap(lastLocation))); } } } @@ -691,7 +729,7 @@ class LocationProviderManager extends @GuardedBy("mLock") @Override @Nullable ListenerOperation<LocationTransport> acceptLocationChange( - Location fineLocation) { + LocationResult fineLocationResult) { if (Build.IS_DEBUGGABLE) { Preconditions.checkState(Thread.holdsLock(mLock)); } @@ -707,26 +745,44 @@ class LocationProviderManager extends return null; } - Location location = Objects.requireNonNull( - getPermittedLocation(fineLocation, getPermissionLevel())); + LocationResult permittedLocationResult = Objects.requireNonNull( + getPermittedLocationResult(fineLocationResult, getPermissionLevel())); + + LocationResult locationResult = permittedLocationResult.filter( + new Predicate<Location>() { + private Location mPreviousLocation = getLastDeliveredLocation(); + + @Override + public boolean test(Location location) { + if (mPreviousLocation != null) { + // check fastest interval + long deltaMs = location.getElapsedRealtimeMillis() + - mPreviousLocation.getElapsedRealtimeMillis(); + long maxJitterMs = min((long) (FASTEST_INTERVAL_JITTER_PERCENTAGE + * getRequest().getIntervalMillis()), + MAX_FASTEST_INTERVAL_JITTER_MS); + if (deltaMs + < getRequest().getMinUpdateIntervalMillis() - maxJitterMs) { + return false; + } + + // check smallest displacement + double smallestDisplacementM = + getRequest().getMinUpdateDistanceMeters(); + if (smallestDisplacementM > 0.0 && location.distanceTo( + mPreviousLocation) + <= smallestDisplacementM) { + return false; + } + } - Location lastDeliveredLocation = getLastDeliveredLocation(); - if (lastDeliveredLocation != null) { - // check fastest interval - long deltaMs = location.getElapsedRealtimeMillis() - - lastDeliveredLocation.getElapsedRealtimeMillis(); - long maxJitterMs = min((long) (FASTEST_INTERVAL_JITTER_PERCENTAGE - * getRequest().getIntervalMillis()), MAX_FASTEST_INTERVAL_JITTER_MS); - if (deltaMs < getRequest().getMinUpdateIntervalMillis() - maxJitterMs) { - return null; - } + mPreviousLocation = location; + return true; + } + }); - // check smallest displacement - double smallestDisplacementM = getRequest().getMinUpdateDistanceMeters(); - if (smallestDisplacementM > 0.0 && location.distanceTo(lastDeliveredLocation) - <= smallestDisplacementM) { - return null; - } + if (locationResult == null) { + return null; } // note app ops @@ -745,10 +801,17 @@ class LocationProviderManager extends @Override public void onPreExecute() { - mUseWakeLock = !location.isFromMockProvider(); + mUseWakeLock = false; + final int size = locationResult.size(); + for (int i = 0; i < size; ++i) { + if (!locationResult.get(i).isFromMockProvider()) { + mUseWakeLock = true; + break; + } + } // update last delivered location - setLastDeliveredLocation(location); + setLastDeliveredLocation(locationResult.getLastLocation()); // don't acquire a wakelock for mock locations to prevent abuse if (mUseWakeLock) { @@ -760,16 +823,17 @@ class LocationProviderManager extends public void operate(LocationTransport listener) throws Exception { // if delivering to the same process, make a copy of the location first (since // location is mutable) - Location deliveryLocation; + LocationResult deliverLocationResult; if (getIdentity().getPid() == Process.myPid()) { - deliveryLocation = new Location(location); + deliverLocationResult = locationResult.deepCopy(); } else { - deliveryLocation = location; + deliverLocationResult = locationResult; } - listener.deliverOnLocationChanged(deliveryLocation, + listener.deliverOnLocationChanged(deliverLocationResult, mUseWakeLock ? mWakeLock::release : null); - mLocationEventLog.logProviderDeliveredLocation(mName, getIdentity()); + mLocationEventLog.logProviderDeliveredLocations(mName, locationResult.size(), + getIdentity()); } @Override @@ -987,7 +1051,7 @@ class LocationProviderManager extends getRequest().isLocationSettingsIgnored(), MAX_CURRENT_LOCATION_AGE_MS); if (lastLocation != null) { - executeOperation(acceptLocationChange(lastLocation)); + executeOperation(acceptLocationChange(LocationResult.wrap(lastLocation))); } } @@ -1021,7 +1085,7 @@ class LocationProviderManager extends @GuardedBy("mLock") @Override @Nullable ListenerOperation<LocationTransport> acceptLocationChange( - @Nullable Location fineLocation) { + @Nullable LocationResult fineLocationResult) { if (Build.IS_DEBUGGABLE) { Preconditions.checkState(Thread.holdsLock(mLock)); } @@ -1033,35 +1097,53 @@ class LocationProviderManager extends Log.d(TAG, mName + " provider registration " + getIdentity() + " expired at " + TimeUtils.formatRealtime(mExpirationRealtimeMs)); } - fineLocation = null; + fineLocationResult = null; } // lastly - note app ops - if (fineLocation != null && !mAppOpsHelper.noteOpNoThrow( + if (fineLocationResult != null && !mAppOpsHelper.noteOpNoThrow( LocationPermissions.asAppOp(getPermissionLevel()), getIdentity())) { if (D) { Log.w(TAG, "noteOp denied for " + getIdentity()); } - fineLocation = null; + fineLocationResult = null; } - Location location = getPermittedLocation(fineLocation, getPermissionLevel()); + if (fineLocationResult != null) { + fineLocationResult = fineLocationResult.asLastLocationResult(); + } + + LocationResult locationResult = getPermittedLocationResult(fineLocationResult, + getPermissionLevel()); // deliver location - return listener -> { - // if delivering to the same process, make a copy of the location first (since - // location is mutable) - Location deliveryLocation = location; - if (getIdentity().getPid() == Process.myPid() && location != null) { - deliveryLocation = new Location(location); - } + return new ListenerOperation<LocationTransport>() { + @Override + public void operate(LocationTransport listener) throws Exception { + // if delivering to the same process, make a copy of the location first (since + // location is mutable) + LocationResult deliverLocationResult; + if (getIdentity().getPid() == Process.myPid() && locationResult != null) { + deliverLocationResult = locationResult.deepCopy(); + } else { + deliverLocationResult = locationResult; + } - // we currently don't hold a wakelock for getCurrentLocation deliveries - listener.deliverOnLocationChanged(deliveryLocation, null); - mLocationEventLog.logProviderDeliveredLocation(mName, getIdentity()); + // we currently don't hold a wakelock for getCurrentLocation deliveries + listener.deliverOnLocationChanged(deliverLocationResult, null); + mLocationEventLog.logProviderDeliveredLocations(mName, + locationResult != null ? locationResult.size() : 0, getIdentity()); + } - synchronized (mLock) { - remove(); + @Override + public void onPostExecute(boolean success) { + // on failure we're automatically removed anyways, no need to attempt removal + // again + if (success) { + synchronized (mLock) { + remove(); + } + } } }; } @@ -1584,6 +1666,41 @@ class LocationProviderManager extends } } + public void flush(ILocationListener listener, int requestCode) { + synchronized (mLock) { + final long identity = Binder.clearCallingIdentity(); + try { + boolean flushed = updateRegistration(listener.asBinder(), registration -> { + registration.flush(requestCode); + return false; + }); + if (!flushed) { + throw new IllegalArgumentException("unregistered listener cannot be flushed"); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + public void flush(PendingIntent pendingIntent, int requestCode) { + synchronized (mLock) { + final long identity = Binder.clearCallingIdentity(); + try { + boolean flushed = updateRegistration(pendingIntent, registration -> { + registration.flush(requestCode); + return false; + }); + if (!flushed) { + throw new IllegalArgumentException( + "unregistered pending intent cannot be flushed"); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + public void unregisterLocationRequest(ILocationListener listener) { synchronized (mLock) { Preconditions.checkState(mState != STATE_STOPPED); @@ -1832,19 +1949,22 @@ class LocationProviderManager extends long intervalMs = ProviderRequest.INTERVAL_DISABLED; int quality = LocationRequest.QUALITY_LOW_POWER; + long maxUpdateDelayMs = Long.MAX_VALUE; boolean locationSettingsIgnored = false; boolean lowPower = true; for (Registration registration : registrations) { LocationRequest request = registration.getRequest(); - // passive requests do not contribute to the provider request + // passive requests do not contribute to the provider request, and passive requests + // must handle the batching parameters of non-passive requests if (request.getIntervalMillis() == LocationRequest.PASSIVE_INTERVAL) { continue; } intervalMs = min(request.getIntervalMillis(), intervalMs); quality = min(request.getQuality(), quality); + maxUpdateDelayMs = min(request.getMaxUpdateDelayMillis(), maxUpdateDelayMs); locationSettingsIgnored |= request.isLocationSettingsIgnored(); lowPower &= request.isLowPower(); } @@ -1853,6 +1973,11 @@ class LocationProviderManager extends return ProviderRequest.EMPTY_REQUEST; } + if (maxUpdateDelayMs / 2 < intervalMs) { + // reduces churn if only the batching parameter has changed + maxUpdateDelayMs = 0; + } + // calculate who to blame for power in a somewhat arbitrary fashion. we pick a threshold // interval slightly higher that the minimum interval, and spread the blame across all // contributing registrations under that threshold (since worksource does not allow us to @@ -1876,6 +2001,7 @@ class LocationProviderManager extends return new ProviderRequest.Builder() .setIntervalMillis(intervalMs) .setQuality(quality) + .setMaxUpdateDelayMillis(maxUpdateDelayMs) .setLocationSettingsIgnored(locationSettingsIgnored) .setLowPower(lowPower) .setWorkSource(workSource) @@ -2038,54 +2164,55 @@ class LocationProviderManager extends @GuardedBy("mLock") @Override - public void onReportLocation(Location location) { + public void onReportLocation(LocationResult locationResult) { if (Build.IS_DEBUGGABLE) { Preconditions.checkState(Thread.holdsLock(mLock)); } - // don't validate mock locations - if (!location.isFromMockProvider()) { - if (location.getLatitude() == 0 && location.getLongitude() == 0) { - Log.w(TAG, "blocking 0,0 location from " + mName + " provider"); + LocationResult filtered; + if (mPassiveManager != null) { + filtered = locationResult.filter(location -> { + if (!location.isFromMockProvider()) { + if (location.getLatitude() == 0 && location.getLongitude() == 0) { + Log.w(TAG, "blocking 0,0 location from " + mName + " provider"); + return false; + } + } + + if (!location.isComplete()) { + Log.w(TAG, "blocking incomplete location from " + mName + " provider"); + return false; + } + + return true; + }); + + if (filtered == null) { return; } - } - - if (!location.isComplete()) { - Log.w(TAG, "blocking incomplete location from " + mName + " provider"); - return; - } - if (mPassiveManager != null) { // don't log location received for passive provider because it's spammy - mLocationEventLog.logProviderReceivedLocation(mName); + mLocationEventLog.logProviderReceivedLocations(mName, filtered.size()); + } else { + // passive provider should get already filtered results as input + filtered = locationResult; } // update last location - setLastLocation(location, UserHandle.USER_ALL); + setLastLocation(filtered.getLastLocation(), UserHandle.USER_ALL); // attempt listener delivery deliverToListeners(registration -> { - return registration.acceptLocationChange(location); + return registration.acceptLocationChange(filtered); }); // notify passive provider if (mPassiveManager != null) { - mPassiveManager.updateLocation(location); + mPassiveManager.updateLocation(filtered); } } @GuardedBy("mLock") - @Override - public void onReportLocation(List<Location> locations) { - if (!GPS_PROVIDER.equals(mName)) { - return; - } - - mLocationManagerInternal.reportGnssBatchLocations(locations); - } - - @GuardedBy("mLock") private void onUserStarted(int userId) { if (Build.IS_DEBUGGABLE) { Preconditions.checkState(Thread.holdsLock(mLock)); @@ -2218,6 +2345,20 @@ class LocationProviderManager extends } } + private @Nullable LocationResult getPermittedLocationResult( + @Nullable LocationResult fineLocationResult, @PermissionLevel int permissionLevel) { + switch (permissionLevel) { + case PERMISSION_FINE: + return fineLocationResult; + case PERMISSION_COARSE: + return fineLocationResult != null ? mLocationFudger.createCoarse(fineLocationResult) + : null; + default: + // shouldn't be possible to have a client added without location permissions + throw new AssertionError(); + } + } + public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) { synchronized (mLock) { ipw.print(mName); diff --git a/services/core/java/com/android/server/location/MockLocationProvider.java b/services/core/java/com/android/server/location/MockLocationProvider.java index e8067283fb8c..505a858e0c63 100644 --- a/services/core/java/com/android/server/location/MockLocationProvider.java +++ b/services/core/java/com/android/server/location/MockLocationProvider.java @@ -20,6 +20,7 @@ import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import android.annotation.Nullable; import android.location.Location; +import android.location.LocationResult; import android.location.util.identity.CallerIdentity; import android.os.Bundle; @@ -54,13 +55,18 @@ public class MockLocationProvider extends AbstractLocationProvider { Location location = new Location(l); location.setIsFromMockProvider(true); mLocation = location; - reportLocation(location); + reportLocation(LocationResult.wrap(location)); } @Override public void onSetRequest(ProviderRequest request) {} @Override + protected void onFlush(Runnable callback) { + callback.run(); + } + + @Override protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {} @Override diff --git a/services/core/java/com/android/server/location/MockableLocationProvider.java b/services/core/java/com/android/server/location/MockableLocationProvider.java index 6744d2a3dd6d..fa5339ed0b6f 100644 --- a/services/core/java/com/android/server/location/MockableLocationProvider.java +++ b/services/core/java/com/android/server/location/MockableLocationProvider.java @@ -20,6 +20,7 @@ import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import android.annotation.Nullable; import android.location.Location; +import android.location.LocationResult; import android.os.Bundle; import com.android.internal.annotations.GuardedBy; @@ -28,7 +29,6 @@ import com.android.internal.util.Preconditions; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.List; /** * Represents a location provider that may switch between a mock implementation and a real @@ -215,6 +215,17 @@ public class MockableLocationProvider extends AbstractLocationProvider { } @Override + protected void onFlush(Runnable callback) { + synchronized (mOwnerLock) { + if (mProvider != null) { + mProvider.flush(callback); + } else { + callback.run(); + } + } + } + + @Override protected void onExtraCommand(int uid, int pid, String command, Bundle extras) { synchronized (mOwnerLock) { if (mProvider != null) { @@ -272,24 +283,13 @@ public class MockableLocationProvider extends AbstractLocationProvider { } @Override - public final void onReportLocation(Location location) { - synchronized (mOwnerLock) { - if (mListenerProvider != mProvider) { - return; - } - - reportLocation(location); - } - } - - @Override - public final void onReportLocation(List<Location> locations) { + public final void onReportLocation(LocationResult locationResult) { synchronized (mOwnerLock) { if (mListenerProvider != mProvider) { return; } - reportLocation(locations); + reportLocation(locationResult); } } } diff --git a/services/core/java/com/android/server/location/PassiveLocationProvider.java b/services/core/java/com/android/server/location/PassiveLocationProvider.java index fba9a89e5ea2..ddae4ed5fb0c 100644 --- a/services/core/java/com/android/server/location/PassiveLocationProvider.java +++ b/services/core/java/com/android/server/location/PassiveLocationProvider.java @@ -20,7 +20,7 @@ import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import android.content.Context; import android.location.Criteria; -import android.location.Location; +import android.location.LocationResult; import android.location.util.identity.CallerIdentity; import android.os.Bundle; @@ -61,14 +61,19 @@ public class PassiveLocationProvider extends AbstractLocationProvider { /** * Pass a location into the passive provider. */ - public void updateLocation(Location location) { - reportLocation(location); + public void updateLocation(LocationResult locationResult) { + reportLocation(locationResult); } @Override public void onSetRequest(ProviderRequest request) {} @Override + protected void onFlush(Runnable callback) { + callback.run(); + } + + @Override protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {} @Override diff --git a/services/core/java/com/android/server/location/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/PassiveLocationProviderManager.java index e390138bafab..343379a55060 100644 --- a/services/core/java/com/android/server/location/PassiveLocationProviderManager.java +++ b/services/core/java/com/android/server/location/PassiveLocationProviderManager.java @@ -18,8 +18,8 @@ package com.android.server.location; import android.annotation.Nullable; import android.content.Context; -import android.location.Location; import android.location.LocationManager; +import android.location.LocationResult; import android.os.Binder; import com.android.internal.location.ProviderRequest; @@ -47,14 +47,14 @@ class PassiveLocationProviderManager extends LocationProviderManager { } } - public void updateLocation(Location location) { + public void updateLocation(LocationResult locationResult) { synchronized (mLock) { PassiveLocationProvider passive = (PassiveLocationProvider) mProvider.getProvider(); Preconditions.checkState(passive != null); final long identity = Binder.clearCallingIdentity(); try { - passive.updateLocation(location); + passive.updateLocation(locationResult); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/core/java/com/android/server/location/ProxyLocationProvider.java b/services/core/java/com/android/server/location/ProxyLocationProvider.java index 5ab7abd24b46..ce9b10d19363 100644 --- a/services/core/java/com/android/server/location/ProxyLocationProvider.java +++ b/services/core/java/com/android/server/location/ProxyLocationProvider.java @@ -21,7 +21,7 @@ import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; -import android.location.Location; +import android.location.LocationResult; import android.location.util.identity.CallerIdentity; import android.os.Binder; import android.os.Bundle; @@ -38,6 +38,7 @@ import com.android.server.ServiceWatcher; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Objects; /** @@ -67,6 +68,9 @@ public class ProxyLocationProvider extends AbstractLocationProvider { final ServiceWatcher mServiceWatcher; @GuardedBy("mLock") + final ArrayList<Runnable> mFlushListeners = new ArrayList<>(0); + + @GuardedBy("mLock") Proxy mProxy; @GuardedBy("mLock") @Nullable ComponentName mService; @@ -107,10 +111,18 @@ public class ProxyLocationProvider extends AbstractLocationProvider { } private void onUnbind() { + Runnable[] flushListeners; synchronized (mLock) { mProxy = null; mService = null; setState(State.EMPTY_STATE); + flushListeners = mFlushListeners.toArray(new Runnable[0]); + mFlushListeners.clear(); + } + + final int size = flushListeners.length; + for (int i = 0; i < size; ++i) { + flushListeners[i].run(); } } @@ -124,6 +136,38 @@ public class ProxyLocationProvider extends AbstractLocationProvider { } @Override + protected void onFlush(Runnable callback) { + mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() { + @Override + public void run(IBinder binder) throws RemoteException { + ILocationProvider provider = ILocationProvider.Stub.asInterface(binder); + + // at first glance it would be more straightforward to pass the flush callback + // through to the provider and allow it to be invoked directly. however, in this + // case the binder calls 1) provider delivering flushed locations 2) provider + // delivering flush complete, while correctly ordered within the provider, would + // be invoked on different binder objects and thus would have no defined order + // on the system server side. thus, we ensure that both (1) and (2) are invoked + // on the same binder object (the ILocationProviderManager) and have a well + // defined ordering, so that the flush callback will always happen after + // location delivery. + synchronized (mLock) { + mFlushListeners.add(callback); + } + provider.flush(); + } + + @Override + public void onError() { + synchronized (mLock) { + mFlushListeners.remove(callback); + } + callback.run(); + } + }); + } + + @Override public void onExtraCommand(int uid, int pid, String command, Bundle extras) { mServiceWatcher.runOnBinder(binder -> { ILocationProvider provider = ILocationProvider.Stub.asInterface(binder); @@ -209,12 +253,31 @@ public class ProxyLocationProvider extends AbstractLocationProvider { // executed on binder thread @Override - public void onReportLocation(Location location) { + public void onReportLocation(LocationResult locationResult) { synchronized (mLock) { if (mProxy != this) { return; } - reportLocation(location); + + reportLocation(locationResult.validate()); + } + } + + // executed on binder thread + @Override + public void onFlushComplete() { + Runnable callback = null; + synchronized (mLock) { + if (mProxy != this) { + return; + } + if (!mFlushListeners.isEmpty()) { + callback = mFlushListeners.remove(0); + } + } + + if (callback != null) { + callback.run(); } } } diff --git a/services/core/java/com/android/server/location/gnss/GnssBatchingProvider.java b/services/core/java/com/android/server/location/gnss/GnssBatchingProvider.java deleted file mode 100644 index 057b17886b10..000000000000 --- a/services/core/java/com/android/server/location/gnss/GnssBatchingProvider.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.location.gnss; - -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; - -/** - * Manages GNSS Batching operations. - * - * <p>This class is not thread safe (It's client's responsibility to make sure calls happen on - * the same thread). - */ -public class GnssBatchingProvider { - - private static final String TAG = "GnssBatchingProvider"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - - private final GnssBatchingProviderNative mNative; - private boolean mEnabled; - private boolean mStarted; - private long mPeriodNanos; - private boolean mWakeOnFifoFull; - - GnssBatchingProvider() { - this(new GnssBatchingProviderNative()); - } - - @VisibleForTesting - GnssBatchingProvider(GnssBatchingProviderNative gnssBatchingProviderNative) { - mNative = gnssBatchingProviderNative; - } - - /** - * Returns the GNSS batching size - */ - public int getBatchSize() { - return mNative.getBatchSize(); - } - - /** Enable GNSS batching. */ - public void enable() { - mEnabled = mNative.initBatching(); - if (!mEnabled) { - Log.e(TAG, "Failed to initialize GNSS batching"); - } - } - - /** - * Starts the hardware batching operation - */ - public boolean start(long periodNanos, boolean wakeOnFifoFull) { - if (!mEnabled) { - throw new IllegalStateException(); - } - if (periodNanos <= 0) { - Log.e(TAG, "Invalid periodNanos " + periodNanos - + " in batching request, not started"); - return false; - } - mStarted = mNative.startBatch(periodNanos, wakeOnFifoFull); - if (mStarted) { - mPeriodNanos = periodNanos; - mWakeOnFifoFull = wakeOnFifoFull; - } - return mStarted; - } - - /** - * Forces a flush of existing locations from the hardware batching - */ - public void flush() { - if (!mStarted) { - Log.w(TAG, "Cannot flush since GNSS batching has not started."); - return; - } - mNative.flushBatch(); - } - - /** - * Stops the batching operation - */ - public boolean stop() { - boolean stopped = mNative.stopBatch(); - if (stopped) { - mStarted = false; - } - return stopped; - } - - /** Disable GNSS batching. */ - public void disable() { - stop(); - mNative.cleanupBatching(); - mEnabled = false; - } - - // TODO(b/37460011): Use this with death recovery logic. - void resumeIfStarted() { - if (DEBUG) { - Log.d(TAG, "resumeIfStarted"); - } - if (mStarted) { - mNative.startBatch(mPeriodNanos, mWakeOnFifoFull); - } - } - - @VisibleForTesting - static class GnssBatchingProviderNative { - public int getBatchSize() { - return native_get_batch_size(); - } - - public boolean startBatch(long periodNanos, boolean wakeOnFifoFull) { - return native_start_batch(periodNanos, wakeOnFifoFull); - } - - public void flushBatch() { - native_flush_batch(); - } - - public boolean stopBatch() { - return native_stop_batch(); - } - - public boolean initBatching() { - return native_init_batching(); - } - - public void cleanupBatching() { - native_cleanup_batching(); - } - } - - private static native int native_get_batch_size(); - - private static native boolean native_start_batch(long periodNanos, boolean wakeOnFifoFull); - - private static native void native_flush_batch(); - - private static native boolean native_stop_batch(); - - private static native boolean native_init_batching(); - - private static native void native_cleanup_batching(); -} diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index 0c1e91d9bf24..740fd9bba83d 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -16,6 +16,8 @@ package com.android.server.location.gnss; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + import android.app.AlarmManager; import android.app.AppOpsManager; import android.app.PendingIntent; @@ -39,6 +41,7 @@ import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.location.LocationRequest; +import android.location.LocationResult; import android.location.util.identity.CallerIdentity; import android.os.AsyncTask; import android.os.BatteryStats; @@ -298,6 +301,9 @@ public class GnssLocationProvider extends AbstractLocationProvider implements @GuardedBy("mLock") private boolean mGpsEnabled; + @GuardedBy("mLock") + private boolean mBatchingEnabled; + private boolean mShutdown; @GuardedBy("mLock") @@ -315,6 +321,9 @@ public class GnssLocationProvider extends AbstractLocationProvider implements // true if we started navigation in the HAL, only change value of this in setStarted private boolean mStarted; + // true if batching is being used + private boolean mBatchingStarted; + // for logging of latest change, and warning of ongoing location after a stop private long mStartedChangedElapsedRealtime; @@ -371,7 +380,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements private final LocationChangeListener mNetworkLocationListener = new NetworkLocationListener(); private final LocationChangeListener mFusedLocationListener = new FusedLocationListener(); private final NtpTimeHelper mNtpTimeHelper; - private final GnssBatchingProvider mGnssBatchingProvider; private final GnssGeofenceProvider mGnssGeofenceProvider; private final GnssCapabilitiesProvider mGnssCapabilitiesProvider; private final GnssSatelliteBlacklistHelper mGnssSatelliteBlacklistHelper; @@ -421,6 +429,9 @@ public class GnssLocationProvider extends AbstractLocationProvider implements private volatile boolean mItarSpeedLimitExceeded = false; + @GuardedBy("mLock") + private final ArrayList<Runnable> mFlushListeners = new ArrayList<>(0); + // GNSS Metrics private GnssMetrics mGnssMetrics; @@ -625,7 +636,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements mGnssSatelliteBlacklistHelper = new GnssSatelliteBlacklistHelper(mContext, mLooper, this); - mGnssBatchingProvider = new GnssBatchingProvider(); mGnssGeofenceProvider = new GnssGeofenceProvider(); setProperties(PROPERTIES); @@ -922,7 +932,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements mC2KServerHost, mC2KServerPort); } - mGnssBatchingProvider.enable(); + mBatchingEnabled = native_init_batching() && native_get_batch_size() > 1; if (mGnssVisibilityControl != null) { mGnssVisibilityControl.onGpsEnabledChanged(/* isEnabled= */ true); } @@ -938,13 +948,11 @@ public class GnssLocationProvider extends AbstractLocationProvider implements setGpsEnabled(false); updateClientUids(new WorkSource()); stopNavigating(); - mAlarmManager.cancel(mWakeupIntent); - mAlarmManager.cancel(mTimeoutIntent); + stopBatching(); if (mGnssVisibilityControl != null) { mGnssVisibilityControl.onGpsEnabledChanged(/* isEnabled= */ false); } - mGnssBatchingProvider.disable(); // do this before releasing wakelock native_cleanup(); } @@ -957,7 +965,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements // ... but disable if PowerManager overrides enabled &= !mDisableGpsForPowerManager; - // .. but enable anyway, if there's an active settings-ignored request (e.g. ELS) + // .. but enable anyway, if there's an active settings-ignored request enabled |= (mProviderRequest != null && mProviderRequest.isActive() && mProviderRequest.isLocationSettingsIgnored()); @@ -982,6 +990,30 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } } + /** + * Returns the hardware batch size available in this hardware implementation. If the available + * size is variable, for example, based on other operations consuming memory, this is the + * minimum size guaranteed to be available for batching operations. + */ + public int getBatchSize() { + return native_get_batch_size(); + } + + @Override + protected void onFlush(Runnable listener) { + boolean added = false; + synchronized (mLock) { + if (mBatchingEnabled) { + added = mFlushListeners.add(listener); + } + } + if (!added) { + listener.run(); + } else { + native_flush_batch(); + } + } + @Override public void onSetRequest(ProviderRequest request) { sendMessage(SET_REQUEST, 0, request); @@ -1011,46 +1043,64 @@ public class GnssLocationProvider extends AbstractLocationProvider implements Log.w(TAG, "interval overflow: " + mProviderRequest.getIntervalMillis()); mFixInterval = Integer.MAX_VALUE; } + // requested batch size, or zero to disable batching + int batchSize; + try { + batchSize = mBatchingEnabled ? Math.toIntExact( + mProviderRequest.getMaxUpdateDelayMillis() / mFixInterval) : 0; + } catch (ArithmeticException e) { + batchSize = Integer.MAX_VALUE; + } + + if (batchSize < getBatchSize()) { + batchSize = 0; + } // apply request to GPS engine - if (mStarted && hasCapability(GPS_CAPABILITY_SCHEDULING)) { - // change period and/or lowPowerMode - if (!setPositionMode(mPositionMode, GPS_POSITION_RECURRENCE_PERIODIC, - mFixInterval, 0, 0, mLowPowerMode)) { - Log.e(TAG, "set_position_mode failed in updateRequirements"); - } - } else if (!mStarted) { - // start GPS - startNavigating(); + if (batchSize > 0) { + stopNavigating(); + startBatching(); } else { - // GNSS Engine is already ON, but no GPS_CAPABILITY_SCHEDULING - mAlarmManager.cancel(mTimeoutIntent); - if (mFixInterval >= NO_FIX_TIMEOUT) { - // set timer to give up if we do not receive a fix within NO_FIX_TIMEOUT - // and our fix interval is not short - mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, - SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT, mTimeoutIntent); + stopBatching(); + + if (mStarted && hasCapability(GPS_CAPABILITY_SCHEDULING)) { + // change period and/or lowPowerMode + if (!setPositionMode(mPositionMode, GPS_POSITION_RECURRENCE_PERIODIC, + mFixInterval, mLowPowerMode)) { + Log.e(TAG, "set_position_mode failed in updateRequirements"); + } + } else if (!mStarted) { + // start GPS + startNavigating(); + } else { + // GNSS Engine is already ON, but no GPS_CAPABILITY_SCHEDULING + mAlarmManager.cancel(mTimeoutIntent); + if (mFixInterval >= NO_FIX_TIMEOUT) { + // set timer to give up if we do not receive a fix within NO_FIX_TIMEOUT + // and our fix interval is not short + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT, mTimeoutIntent); + } } } } else { updateClientUids(new WorkSource()); stopNavigating(); - mAlarmManager.cancel(mWakeupIntent); - mAlarmManager.cancel(mTimeoutIntent); + stopBatching(); } } private boolean setPositionMode(int mode, int recurrence, int minInterval, - int preferredAccuracy, int preferredTime, boolean lowPowerMode) { + boolean lowPowerMode) { GnssPositionMode positionMode = new GnssPositionMode(mode, recurrence, minInterval, - preferredAccuracy, preferredTime, lowPowerMode); + 0, 0, lowPowerMode); if (mLastPositionMode != null && mLastPositionMode.equals(positionMode)) { return true; } boolean result = native_set_position_mode(mode, recurrence, minInterval, - preferredAccuracy, preferredTime, lowPowerMode); + 0, 0, lowPowerMode); if (result) { mLastPositionMode = positionMode; } else { @@ -1210,7 +1260,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements int interval = (hasCapability(GPS_CAPABILITY_SCHEDULING) ? mFixInterval : 1000); mLowPowerMode = mProviderRequest.isLowPower(); if (!setPositionMode(mPositionMode, GPS_POSITION_RECURRENCE_PERIODIC, - interval, 0, 0, mLowPowerMode)) { + interval, mLowPowerMode)) { setStarted(false); Log.e(TAG, "set_position_mode failed in startNavigating()"); return; @@ -1247,6 +1297,27 @@ public class GnssLocationProvider extends AbstractLocationProvider implements // reset SV count to zero mLocationExtras.reset(); } + mAlarmManager.cancel(mTimeoutIntent); + mAlarmManager.cancel(mWakeupIntent); + } + + private void startBatching() { + if (DEBUG) { + Log.d(TAG, "startBatching " + mFixInterval); + } + if (native_start_batch(MILLISECONDS.toNanos(mFixInterval), true)) { + mBatchingStarted = true; + } else { + Log.e(TAG, "native_start_batch failed in startBatching()"); + } + } + + private void stopBatching() { + if (DEBUG) Log.d(TAG, "stopBatching"); + if (mBatchingStarted) { + native_stop_batch(); + mBatchingStarted = false; + } } private void setStarted(boolean started) { @@ -1259,8 +1330,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements private void hibernate() { // stop GPS until our next fix interval arrives stopNavigating(); - mAlarmManager.cancel(mTimeoutIntent); - mAlarmManager.cancel(mWakeupIntent); long now = SystemClock.elapsedRealtime(); mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, now + mFixInterval, mWakeupIntent); } @@ -1291,7 +1360,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements location.setExtras(mLocationExtras.getBundle()); - reportLocation(location); + reportLocation(LocationResult.wrap(location).validate()); if (mStarted) { mGnssMetrics.logReceivedLocationStatus(hasLatLong); @@ -1474,7 +1543,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements Log.i(TAG, "restartRequests"); restartLocationRequest(); - mGnssBatchingProvider.resumeIfStarted(); mGnssGeofenceProvider.resumeIfStarted(); } @@ -1545,13 +1613,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } /** - * @hide - */ - public GnssBatchingProvider getGnssBatchingProvider() { - return mGnssBatchingProvider; - } - - /** * Interface for GnssMetrics methods. */ public interface GnssMetricsProvider { @@ -1575,12 +1636,24 @@ public class GnssLocationProvider extends AbstractLocationProvider implements return mGnssCapabilitiesProvider; } - void reportLocationBatch(Location[] locationArray) { - List<Location> locations = new ArrayList<>(Arrays.asList(locationArray)); + void reportLocationBatch(Location[] locations) { if (DEBUG) { - Log.d(TAG, "Location batch of size " + locationArray.length + " reported"); + Log.d(TAG, "Location batch of size " + locations.length + " reported"); + } + + Runnable[] listeners; + synchronized (mLock) { + listeners = mFlushListeners.toArray(new Runnable[0]); + mFlushListeners.clear(); + } + + if (locations.length > 0) { + reportLocation(LocationResult.create(Arrays.asList(locations)).validate()); + } + + for (Runnable listener : listeners) { + listener.run(); } - reportLocation(locations); } void downloadPsdsData(int psdsType) { @@ -2032,6 +2105,9 @@ public class GnssLocationProvider extends AbstractLocationProvider implements TimeUtils.formatDuration(SystemClock.elapsedRealtime() - mStartedChangedElapsedRealtime, s); s.append(" ago)").append('\n'); + s.append("mBatchingEnabled=").append(mBatchingEnabled).append('\n'); + s.append("mBatchingStarted=").append(mBatchingStarted).append('\n'); + s.append("mBatchSize=").append(getBatchSize()).append('\n'); s.append("mFixInterval=").append(mFixInterval).append('\n'); s.append("mLowPowerMode=").append(mLowPowerMode).append('\n'); s.append("mDisableGpsForPowerManager=").append(mDisableGpsForPowerManager).append('\n'); @@ -2067,7 +2143,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } // preallocated to avoid memory allocation in reportNmea() - private byte[] mNmeaBuffer = new byte[120]; + private final byte[] mNmeaBuffer = new byte[120]; private static native boolean native_is_gnss_visibility_control_supported(); @@ -2119,4 +2195,16 @@ public class GnssLocationProvider extends AbstractLocationProvider implements int lac, int cid); private native void native_agps_set_id(int type, String setid); + + private static native boolean native_init_batching(); + + private static native void native_cleanup_batching(); + + private static native int native_get_batch_size(); + + private static native boolean native_start_batch(long periodNanos, boolean wakeOnFifoFull); + + private static native void native_flush_batch(); + + private static native boolean native_stop_batch(); } diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java index 2bf6af25422a..47c528b68fb9 100644 --- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java +++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java @@ -16,18 +16,14 @@ package com.android.server.location.gnss; -import static android.location.LocationManager.GPS_PROVIDER; - import android.Manifest; import android.annotation.Nullable; -import android.app.AppOpsManager; import android.content.Context; import android.location.GnssAntennaInfo; import android.location.GnssMeasurementCorrections; import android.location.GnssMeasurementsEvent; import android.location.GnssNavigationMessage; import android.location.GnssRequest; -import android.location.IBatchedLocationCallback; import android.location.IGnssAntennaInfoListener; import android.location.IGnssMeasurementsListener; import android.location.IGnssNavigationMessageListener; @@ -37,12 +33,10 @@ import android.location.INetInitiatedListener; import android.location.Location; import android.location.LocationManagerInternal; import android.location.util.identity.CallerIdentity; -import android.os.Binder; import android.os.RemoteException; import android.util.IndentingPrintWriter; import android.util.Log; -import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import com.android.server.LocalServices; @@ -77,21 +71,9 @@ public class GnssManagerService implements GnssNative.Callbacks { private final GnssLocationProvider.GnssSystemInfoProvider mGnssSystemInfoProvider; private final GnssLocationProvider.GnssMetricsProvider mGnssMetricsProvider; private final GnssCapabilitiesProvider mGnssCapabilitiesProvider; - private final GnssBatchingProvider mGnssBatchingProvider; private final INetInitiatedListener mNetInitiatedListener; private final IGpsGeofenceHardware mGpsGeofenceProxy; - private final Object mGnssBatchingLock = new Object(); - - @GuardedBy("mGnssBatchingLock") - @Nullable private IBatchedLocationCallback mGnssBatchingCallback; - @GuardedBy("mGnssBatchingLock") - @Nullable private CallerIdentity mGnssBatchingIdentity; - @GuardedBy("mGnssBatchingLock") - @Nullable private Binder.DeathRecipient mGnssBatchingDeathRecipient; - @GuardedBy("mGnssBatchingLock") - private boolean mGnssBatchingInProgress = false; - public GnssManagerService(Context context, Injector injector) { this(context, injector, null); } @@ -121,7 +103,6 @@ public class GnssManagerService implements GnssNative.Callbacks { mGnssSystemInfoProvider = mGnssLocationProvider.getGnssSystemInfoProvider(); mGnssMetricsProvider = mGnssLocationProvider.getGnssMetricsProvider(); mGnssCapabilitiesProvider = mGnssLocationProvider.getGnssCapabilitiesProvider(); - mGnssBatchingProvider = mGnssLocationProvider.getGnssBatchingProvider(); mNetInitiatedListener = mGnssLocationProvider.getNetInitiatedListener(); mGpsGeofenceProxy = mGnssLocationProvider.getGpsGeofenceProxy(); @@ -171,108 +152,7 @@ public class GnssManagerService implements GnssNative.Callbacks { * Get size of GNSS batch (GNSS location results are batched together for power savings). */ public int getGnssBatchSize() { - mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null); - - synchronized (mGnssBatchingLock) { - return mGnssBatchingProvider.getBatchSize(); - } - } - - /** - * Starts GNSS batch collection. GNSS positions are collected in a batch before being delivered - * as a collection. - */ - public boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName, - String attributionTag) { - mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null); - mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION, null); - - CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag); - if (!mAppOpsHelper.checkOpNoThrow(AppOpsManager.OP_FINE_LOCATION, identity)) { - return false; - } - - synchronized (mGnssBatchingLock) { - if (mGnssBatchingInProgress) { - // Current design does not expect multiple starts to be called repeatedly - Log.e(TAG, "startGnssBatch unexpectedly called w/o stopping prior batch"); - stopGnssBatch(); - } - - mGnssBatchingInProgress = true; - return mGnssBatchingProvider.start(periodNanos, wakeOnFifoFull); - } - } - - /** - * Adds a GNSS batching callback for delivering GNSS location batch results. - */ - public boolean setGnssBatchingCallback(IBatchedLocationCallback callback, String packageName, - @Nullable String attributionTag) { - mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null); - mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION, null); - - CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag); - - synchronized (mGnssBatchingLock) { - Binder.DeathRecipient deathRecipient = () -> { - synchronized (mGnssBatchingLock) { - stopGnssBatch(); - removeGnssBatchingCallback(); - } - }; - - try { - callback.asBinder().linkToDeath(mGnssBatchingDeathRecipient, 0); - mGnssBatchingCallback = callback; - mGnssBatchingIdentity = identity; - mGnssBatchingDeathRecipient = deathRecipient; - return true; - } catch (RemoteException e) { - return false; - } - } - } - - /** - * Force flush GNSS location results from batch. - */ - public void flushGnssBatch() { - mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null); - - synchronized (mGnssBatchingLock) { - mGnssBatchingProvider.flush(); - } - } - - /** - * Removes GNSS batching callback. - */ - public void removeGnssBatchingCallback() { - mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null); - - synchronized (mGnssBatchingLock) { - if (mGnssBatchingCallback == null) { - return; - } - - mGnssBatchingCallback.asBinder().unlinkToDeath(mGnssBatchingDeathRecipient, 0); - mGnssBatchingCallback = null; - mGnssBatchingIdentity = null; - mGnssBatchingDeathRecipient = null; - } - } - - /** - * Stop GNSS batch collection. - */ - public boolean stopGnssBatch() { - mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null); - - synchronized (mGnssBatchingLock) { - mGnssBatchingInProgress = false; - return mGnssBatchingProvider.stop(); - } + return mGnssLocationProvider.getBatchSize(); } /** @@ -377,34 +257,6 @@ public class GnssManagerService implements GnssNative.Callbacks { } /** - * Report location results to GNSS batching listener. - */ - public void onReportLocation(List<Location> locations) { - IBatchedLocationCallback callback; - CallerIdentity identity; - synchronized (mGnssBatchingLock) { - callback = mGnssBatchingCallback; - identity = mGnssBatchingIdentity; - } - - if (callback == null || identity == null) { - return; - } - - if (!mLocationManagerInternal.isProviderEnabledForUser(GPS_PROVIDER, - identity.getUserId())) { - Log.w(TAG, "reportLocationBatch() called without user permission"); - return; - } - - try { - callback.onLocationBatch(locations); - } catch (RemoteException e) { - // ignore - } - } - - /** * Dump info for debugging. */ public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) { @@ -434,12 +286,6 @@ public class GnssManagerService implements GnssNative.Callbacks { ipw.increaseIndent(); mGnssStatusProvider.dump(fd, ipw, args); ipw.decreaseIndent(); - - synchronized (mGnssBatchingLock) { - if (mGnssBatchingInProgress) { - ipw.println("GNSS batching in progress"); - } - } } // all native callbacks - to be funneled to various locations as appropriate diff --git a/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java b/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java index d6b179bab5a2..709e23615b14 100644 --- a/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java +++ b/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java @@ -32,8 +32,7 @@ import android.util.Log; * @param <TListener> listener type */ public abstract class BinderListenerRegistration<TRequest, TListener> extends - RemoteListenerRegistration<TRequest, TListener> implements - Binder.DeathRecipient { + RemoteListenerRegistration<TRequest, TListener> implements Binder.DeathRecipient { /** * Interface to allow binder retrieval when keys are not themselves IBinders. diff --git a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java index 6b936169c82f..33b08d41c07d 100644 --- a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java +++ b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java @@ -79,8 +79,7 @@ import java.util.function.Predicate; * @param <TMergedRegistration> merged registration type */ public abstract class ListenerMultiplexer<TKey, TListener, - TRegistration extends ListenerRegistration<TListener>, - TMergedRegistration> { + TRegistration extends ListenerRegistration<TListener>, TMergedRegistration> { @GuardedBy("mRegistrations") private final ArrayMap<TKey, TRegistration> mRegistrations = new ArrayMap<>(); @@ -484,6 +483,38 @@ public abstract class ListenerMultiplexer<TKey, TListener, } } + /** + * Evaluates the predicate on a registration with the given key. The predicate should return + * true if the active state of the registration may have changed as a result. If the active + * state of the registration has changed, {@link #updateService()} will automatically be invoked + * to handle the resulting changes. Returns true if there is a registration with the given key + * (and thus the predicate was invoked), and false otherwise. + */ + protected final boolean updateRegistration(@NonNull Object key, + @NonNull Predicate<TRegistration> predicate) { + synchronized (mRegistrations) { + // since updating a registration can invoke a variety of callbacks, we need to ensure + // those callbacks themselves do not re-enter, as this could lead to out-of-order + // callbacks. note that try-with-resources ordering is meaningful here as well. we want + // to close the reentrancy guard first, as this may generate additional service updates, + // then close the update service buffer. + try (UpdateServiceBuffer ignored1 = mUpdateServiceBuffer.acquire(); + ReentrancyGuard ignored2 = mReentrancyGuard.acquire()) { + + int index = mRegistrations.indexOfKey(key); + if (index < 0) { + return false; + } + + TRegistration registration = mRegistrations.valueAt(index); + if (predicate.test(registration)) { + onRegistrationActiveChanged(registration); + } + return true; + } + } + } + @GuardedBy("mRegistrations") private void onRegistrationActiveChanged(TRegistration registration) { if (Build.IS_DEBUGGABLE) { diff --git a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java index fa21b3a8e369..711dde89ef13 100644 --- a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java +++ b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java @@ -16,7 +16,6 @@ package com.android.server.location.listeners; - import android.annotation.Nullable; import com.android.internal.listeners.ListenerExecutor; @@ -108,8 +107,8 @@ public class ListenerRegistration<TListener> implements ListenerExecutor { * May be overridden by subclasses to handle listener operation failures. The default behavior * is to further propagate any exceptions. Will always be invoked on the executor thread. */ - protected void onOperationFailure(ListenerOperation<TListener> operation, Exception e) { - throw new AssertionError(e); + protected void onOperationFailure(ListenerOperation<TListener> operation, Exception exception) { + throw new AssertionError(exception); } /** diff --git a/services/core/java/com/android/server/location/util/LocationEventLog.java b/services/core/java/com/android/server/location/util/LocationEventLog.java index e4c354b1c070..ff4503e215e7 100644 --- a/services/core/java/com/android/server/location/util/LocationEventLog.java +++ b/services/core/java/com/android/server/location/util/LocationEventLog.java @@ -90,14 +90,14 @@ public class LocationEventLog extends LocalEventLog { } /** Logs a new incoming location for a location provider. */ - public synchronized void logProviderReceivedLocation(String provider) { - addLogEvent(EVENT_PROVIDER_RECEIVE_LOCATION, provider); + public synchronized void logProviderReceivedLocations(String provider, int numLocations) { + addLogEvent(EVENT_PROVIDER_RECEIVE_LOCATION, provider, numLocations); } /** Logs a location deliver for a client of a location provider. */ - public synchronized void logProviderDeliveredLocation(String provider, + public synchronized void logProviderDeliveredLocations(String provider, int numLocations, CallerIdentity identity) { - addLogEvent(EVENT_PROVIDER_DELIVER_LOCATION, provider, identity); + addLogEvent(EVENT_PROVIDER_DELIVER_LOCATION, provider, numLocations, identity); } /** Logs that the location power save mode has changed. */ @@ -126,10 +126,11 @@ public class LocationEventLog extends LocalEventLog { return new ProviderUpdateEvent(timeDelta, (String) args[0], (ProviderRequest) args[1]); case EVENT_PROVIDER_RECEIVE_LOCATION: - return new ProviderReceiveLocationEvent(timeDelta, (String) args[0]); + return new ProviderReceiveLocationEvent(timeDelta, (String) args[0], + (Integer) args[1]); case EVENT_PROVIDER_DELIVER_LOCATION: return new ProviderDeliverLocationEvent(timeDelta, (String) args[0], - (CallerIdentity) args[1]); + (Integer) args[1], (CallerIdentity) args[2]); case EVENT_LOCATION_POWER_SAVE_MODE_CHANGE: return new LocationPowerSaveModeEvent(timeDelta, (Integer) args[0]); default: @@ -226,33 +227,38 @@ public class LocationEventLog extends LocalEventLog { private static class ProviderReceiveLocationEvent extends LogEvent { private final String mProvider; + private final int mNumLocations; - private ProviderReceiveLocationEvent(long timeDelta, String provider) { + private ProviderReceiveLocationEvent(long timeDelta, String provider, int numLocations) { super(timeDelta); mProvider = provider; + mNumLocations = numLocations; } @Override public String getLogString() { - return mProvider + " provider received location"; + return mProvider + " provider received location[" + mNumLocations + "]"; } } private static class ProviderDeliverLocationEvent extends LogEvent { private final String mProvider; + private final int mNumLocations; @Nullable private final CallerIdentity mIdentity; - private ProviderDeliverLocationEvent(long timeDelta, String provider, + private ProviderDeliverLocationEvent(long timeDelta, String provider, int numLocations, @Nullable CallerIdentity identity) { super(timeDelta); mProvider = provider; + mNumLocations = numLocations; mIdentity = identity; } @Override public String getLogString() { - return mProvider + " provider delivered location to " + mIdentity; + return mProvider + " provider delivered location[" + mNumLocations + "] to " + + mIdentity; } } diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index e9d048a69966..91b809f08a11 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -3532,7 +3532,7 @@ static jboolean android_location_GnssConfiguration_set_es_extension_sec( return gnssConfigurationIface->setEsExtensionSec(emergencyExtensionSeconds); } -static jint android_location_GnssBatchingProvider_get_batch_size(JNIEnv*, jclass) { +static jint android_location_GnssLocationProvider_get_batch_size(JNIEnv*, jclass) { if (gnssBatchingIface == nullptr) { return 0; // batching not supported, size = 0 } @@ -3544,7 +3544,7 @@ static jint android_location_GnssBatchingProvider_get_batch_size(JNIEnv*, jclass return static_cast<jint>(result); } -static jboolean android_location_GnssBatchingProvider_init_batching(JNIEnv*, jclass) { +static jboolean android_location_GnssLocationProvider_init_batching(JNIEnv*, jclass) { if (gnssBatchingIface_V2_0 != nullptr) { sp<IGnssBatchingCallback_V2_0> gnssBatchingCbIface_V2_0 = new GnssBatchingCallback_V2_0(); auto result = gnssBatchingIface_V2_0->init_2_0(gnssBatchingCbIface_V2_0); @@ -3558,7 +3558,7 @@ static jboolean android_location_GnssBatchingProvider_init_batching(JNIEnv*, jcl } } -static void android_location_GnssBatchingProvider_cleanup_batching(JNIEnv*, jclass) { +static void android_location_GnssLocationProvider_cleanup_batching(JNIEnv*, jclass) { if (gnssBatchingIface == nullptr) { return; // batching not supported } @@ -3566,8 +3566,9 @@ static void android_location_GnssBatchingProvider_cleanup_batching(JNIEnv*, jcla checkHidlReturn(result, "IGnssBatching cleanup() failed."); } -static jboolean android_location_GnssBatchingProvider_start_batch(JNIEnv*, jclass, - jlong periodNanos, jboolean wakeOnFifoFull) { +static jboolean android_location_GnssLocationProvider_start_batch(JNIEnv*, jclass, + jlong periodNanos, + jboolean wakeOnFifoFull) { if (gnssBatchingIface == nullptr) { return JNI_FALSE; // batching not supported } @@ -3584,7 +3585,7 @@ static jboolean android_location_GnssBatchingProvider_start_batch(JNIEnv*, jclas return checkHidlReturn(result, "IGnssBatching start() failed."); } -static void android_location_GnssBatchingProvider_flush_batch(JNIEnv*, jclass) { +static void android_location_GnssLocationProvider_flush_batch(JNIEnv*, jclass) { if (gnssBatchingIface == nullptr) { return; // batching not supported } @@ -3592,7 +3593,7 @@ static void android_location_GnssBatchingProvider_flush_batch(JNIEnv*, jclass) { checkHidlReturn(result, "IGnssBatching flush() failed."); } -static jboolean android_location_GnssBatchingProvider_stop_batch(JNIEnv*, jclass) { +static jboolean android_location_GnssLocationProvider_stop_batch(JNIEnv*, jclass) { if (gnssBatchingIface == nullptr) { return JNI_FALSE; // batching not supported } @@ -3670,25 +3671,19 @@ static const JNINativeMethod sLocationProviderMethods[] = { }; static const JNINativeMethod sMethodsBatching[] = { - /* name, signature, funcPtr */ - {"native_get_batch_size", - "()I", - reinterpret_cast<void *>(android_location_GnssBatchingProvider_get_batch_size)}, - {"native_start_batch", - "(JZ)Z", - reinterpret_cast<void *>(android_location_GnssBatchingProvider_start_batch)}, - {"native_flush_batch", - "()V", - reinterpret_cast<void *>(android_location_GnssBatchingProvider_flush_batch)}, - {"native_stop_batch", - "()Z", - reinterpret_cast<void *>(android_location_GnssBatchingProvider_stop_batch)}, - {"native_init_batching", - "()Z", - reinterpret_cast<void *>(android_location_GnssBatchingProvider_init_batching)}, - {"native_cleanup_batching", - "()V", - reinterpret_cast<void *>(android_location_GnssBatchingProvider_cleanup_batching)}, + /* name, signature, funcPtr */ + {"native_get_batch_size", "()I", + reinterpret_cast<void*>(android_location_GnssLocationProvider_get_batch_size)}, + {"native_start_batch", "(JZ)Z", + reinterpret_cast<void*>(android_location_GnssLocationProvider_start_batch)}, + {"native_flush_batch", "()V", + reinterpret_cast<void*>(android_location_GnssLocationProvider_flush_batch)}, + {"native_stop_batch", "()Z", + reinterpret_cast<void*>(android_location_GnssLocationProvider_stop_batch)}, + {"native_init_batching", "()Z", + reinterpret_cast<void*>(android_location_GnssLocationProvider_init_batching)}, + {"native_cleanup_batching", "()V", + reinterpret_cast<void*>(android_location_GnssLocationProvider_cleanup_batching)}, }; static const JNINativeMethod sAntennaInfoMethods[] = { @@ -3817,7 +3812,7 @@ static const JNINativeMethod sVisibilityControlMethods[] = { int register_android_server_location_GnssLocationProvider(JNIEnv* env) { jniRegisterNativeMethods(env, "com/android/server/location/gnss/GnssAntennaInfoProvider", sAntennaInfoMethods, NELEM(sAntennaInfoMethods)); - jniRegisterNativeMethods(env, "com/android/server/location/gnss/GnssBatchingProvider", + jniRegisterNativeMethods(env, "com/android/server/location/gnss/GnssLocationProvider", sMethodsBatching, NELEM(sMethodsBatching)); jniRegisterNativeMethods(env, "com/android/server/location/gnss/GnssGeofenceProvider", sGeofenceMethods, NELEM(sGeofenceMethods)); diff --git a/services/robotests/src/com/android/server/location/gnss/GnssBatchingProviderTest.java b/services/robotests/src/com/android/server/location/gnss/GnssBatchingProviderTest.java deleted file mode 100644 index fa324e8c60ee..000000000000 --- a/services/robotests/src/com/android/server/location/gnss/GnssBatchingProviderTest.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.location.gnss; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.platform.test.annotations.Presubmit; - -import com.android.server.location.gnss.GnssBatchingProvider.GnssBatchingProviderNative; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; - -/** - * Unit tests for {@link GnssBatchingProvider}. - */ -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class GnssBatchingProviderTest { - - private static final long PERIOD_NANOS = (long) 1e9; - private static final boolean WAKE_ON_FIFO_FULL = true; - private static final int BATCH_SIZE = 3; - @Mock - private GnssBatchingProviderNative mMockNative; - private GnssBatchingProvider mTestProvider; - - /** - * Mocks native methods and starts GnssBatchingProvider. - */ - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - when(mMockNative.initBatching()).thenReturn(true); - when(mMockNative.startBatch(anyLong(), anyBoolean())).thenReturn(true); - when(mMockNative.stopBatch()).thenReturn(true); - when(mMockNative.getBatchSize()).thenReturn(BATCH_SIZE); - mTestProvider = new GnssBatchingProvider(mMockNative); - mTestProvider.enable(); - mTestProvider.start(PERIOD_NANOS, WAKE_ON_FIFO_FULL); - } - - /** - * Test native start method is called. - */ - @Test - public void start_nativeStarted() { - verify(mMockNative).startBatch(eq(PERIOD_NANOS), eq(WAKE_ON_FIFO_FULL)); - } - - /** - * Verify native stop method is called. - */ - @Test - public void stop_nativeStopped() { - mTestProvider.stop(); - verify(mMockNative).stopBatch(); - } - - /** - * Verify native flush method is called. - */ - @Test - public void flush_nativeFlushed() { - mTestProvider.flush(); - verify(mMockNative).flushBatch(); - } - - /** - * Verify getBatchSize returns value from native batch size method. - */ - @Test - public void getBatchSize_nativeGetBatchSize() { - assertThat(mTestProvider.getBatchSize()).isEqualTo(BATCH_SIZE); - } - - /** - * Verify resumeIfStarted method will call native start method a second time. - */ - @Test - public void started_resume_started() { - mTestProvider.resumeIfStarted(); - verify(mMockNative, times(2)).startBatch(eq(PERIOD_NANOS), eq(WAKE_ON_FIFO_FULL)); - } - - /** - * Verify that if batching is stopped, resume will not call native start method. - */ - @Test - public void stopped_resume_notStarted() { - mTestProvider.stop(); - mTestProvider.resumeIfStarted(); - verify(mMockNative, times(1)).startBatch(eq(PERIOD_NANOS), eq(WAKE_ON_FIFO_FULL)); - } -} diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java index cc46d9d867ef..b55da07b0618 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java @@ -31,6 +31,7 @@ import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import static com.android.server.location.LocationPermissions.PERMISSION_COARSE; import static com.android.server.location.LocationPermissions.PERMISSION_FINE; import static com.android.server.location.LocationUtils.createLocation; +import static com.android.server.location.LocationUtils.createLocationResult; import static com.android.server.location.listeners.RemoteListenerRegistration.IN_PROCESS_EXECUTOR; import static com.google.common.truth.Truth.assertThat; @@ -40,10 +41,12 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.after; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -52,6 +55,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.MockitoAnnotations.initMocks; +import static org.testng.Assert.assertThrows; import android.content.Context; import android.location.ILocationCallback; @@ -60,6 +64,7 @@ import android.location.Location; import android.location.LocationManagerInternal; import android.location.LocationManagerInternal.ProviderEnabledListener; import android.location.LocationRequest; +import android.location.LocationResult; import android.location.util.identity.CallerIdentity; import android.os.Bundle; import android.os.ICancellationSignal; @@ -85,11 +90,12 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; import org.mockito.Mock; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Collections; import java.util.Random; import java.util.concurrent.CountDownLatch; @@ -339,13 +345,9 @@ public class LocationProviderManagerTest { LocationRequest request = new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build(); mPassive.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener); - Location loc = createLocation(NAME, mRandom); + LocationResult loc = createLocationResult(NAME, mRandom); mProvider.setProviderLocation(loc); - - ArgumentCaptor<Location> locationCaptor = ArgumentCaptor.forClass(Location.class); - verify(listener).onLocationChanged(locationCaptor.capture(), - nullable(IRemoteCallback.class)); - assertThat(locationCaptor.getValue()).isEqualTo(loc); + verify(listener).onLocationChanged(eq(loc), nullable(IRemoteCallback.class)); } @Test @@ -358,8 +360,6 @@ public class LocationProviderManagerTest { @Test public void testRegisterListener() throws Exception { - ArgumentCaptor<Location> locationCaptor = ArgumentCaptor.forClass(Location.class); - ILocationListener listener = createMockLocationListener(); mManager.registerLocationRequest( new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build(), @@ -367,17 +367,15 @@ public class LocationProviderManagerTest { PERMISSION_FINE, listener); - Location loc = createLocation(NAME, mRandom); + LocationResult loc = createLocationResult(NAME, mRandom); mProvider.setProviderLocation(loc); - verify(listener, times(1)).onLocationChanged(locationCaptor.capture(), - nullable(IRemoteCallback.class)); - assertThat(locationCaptor.getValue()).isEqualTo(loc); + verify(listener).onLocationChanged(eq(loc), nullable(IRemoteCallback.class)); mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER); verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, false); - loc = createLocation(NAME, mRandom); + loc = createLocationResult(NAME, mRandom); mProvider.setProviderLocation(loc); - verify(listener, times(1)).onLocationChanged(any(Location.class), + verify(listener, times(1)).onLocationChanged(any(LocationResult.class), nullable(IRemoteCallback.class)); mInjector.getSettingsHelper().setLocationEnabled(true, CURRENT_USER); @@ -385,25 +383,21 @@ public class LocationProviderManagerTest { mProvider.setAllowed(false); verify(listener, timeout(TIMEOUT_MS).times(2)).onProviderEnabledChanged(NAME, false); - loc = createLocation(NAME, mRandom); + loc = createLocationResult(NAME, mRandom); mProvider.setProviderLocation(loc); - verify(listener, times(1)).onLocationChanged(any(Location.class), + verify(listener, times(1)).onLocationChanged(any(LocationResult.class), nullable(IRemoteCallback.class)); mProvider.setAllowed(true); verify(listener, timeout(TIMEOUT_MS).times(2)).onProviderEnabledChanged(NAME, true); - loc = createLocation(NAME, mRandom); + loc = createLocationResult(NAME, mRandom); mProvider.setProviderLocation(loc); - verify(listener, times(2)).onLocationChanged(locationCaptor.capture(), - nullable(IRemoteCallback.class)); - assertThat(locationCaptor.getValue()).isEqualTo(loc); + verify(listener).onLocationChanged(eq(loc), nullable(IRemoteCallback.class)); } @Test public void testRegisterListener_SameProcess() throws Exception { - ArgumentCaptor<Location> locationCaptor = ArgumentCaptor.forClass(Location.class); - CallerIdentity identity = CallerIdentity.forTest(CURRENT_USER, Process.myPid(), "mypackage", "attribution"); @@ -414,11 +408,10 @@ public class LocationProviderManagerTest { PERMISSION_FINE, listener); - Location loc = createLocation(NAME, mRandom); + LocationResult loc = createLocationResult(NAME, mRandom); mProvider.setProviderLocation(loc); - verify(listener, timeout(TIMEOUT_MS).times(1)).onLocationChanged(locationCaptor.capture(), + verify(listener, timeout(TIMEOUT_MS).times(1)).onLocationChanged(eq(loc), nullable(IRemoteCallback.class)); - assertThat(locationCaptor.getValue()).isEqualTo(loc); } @Test @@ -432,7 +425,7 @@ public class LocationProviderManagerTest { mManager.unregisterLocationRequest(listener); mProvider.setProviderLocation(createLocation(NAME, mRandom)); - verify(listener, never()).onLocationChanged(any(Location.class), + verify(listener, never()).onLocationChanged(any(LocationResult.class), nullable(IRemoteCallback.class)); mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER); @@ -463,7 +456,7 @@ public class LocationProviderManagerTest { mProvider.setProviderLocation(createLocation(NAME, mRandom)); mManager.unregisterLocationRequest(listener); blocker.countDown(); - verify(listener, after(TIMEOUT_MS).never()).onLocationChanged(any(Location.class), + verify(listener, after(TIMEOUT_MS).never()).onLocationChanged(any(LocationResult.class), nullable(IRemoteCallback.class)); } @@ -483,7 +476,7 @@ public class LocationProviderManagerTest { mProvider.setProviderLocation(createLocation(NAME, mRandom)); mProvider.setProviderLocation(createLocation(NAME, mRandom)); - verify(listener, times(5)).onLocationChanged(any(Location.class), + verify(listener, times(5)).onLocationChanged(any(LocationResult.class), nullable(IRemoteCallback.class)); } @@ -498,7 +491,7 @@ public class LocationProviderManagerTest { mInjector.getAlarmHelper().incrementAlarmTime(5000); mProvider.setProviderLocation(createLocation(NAME, mRandom)); - verify(listener, never()).onLocationChanged(any(Location.class), + verify(listener, never()).onLocationChanged(any(LocationResult.class), nullable(IRemoteCallback.class)); } @@ -514,7 +507,7 @@ public class LocationProviderManagerTest { Thread.sleep(25); mProvider.setProviderLocation(createLocation(NAME, mRandom)); - verify(listener, never()).onLocationChanged(any(Location.class), + verify(listener, never()).onLocationChanged(any(LocationResult.class), nullable(IRemoteCallback.class)); } @@ -530,8 +523,8 @@ public class LocationProviderManagerTest { mProvider.setProviderLocation(createLocation(NAME, mRandom)); mProvider.setProviderLocation(createLocation(NAME, mRandom)); - verify(listener, times(1)).onLocationChanged(any(Location.class), - nullable(IRemoteCallback.class)); + verify(listener, times(1)).onLocationChanged( + any(LocationResult.class), nullable(IRemoteCallback.class)); } @Test @@ -547,8 +540,8 @@ public class LocationProviderManagerTest { mProvider.setProviderLocation(loc); mProvider.setProviderLocation(loc); - verify(listener, times(1)).onLocationChanged(any(Location.class), - nullable(IRemoteCallback.class)); + verify(listener, times(1)).onLocationChanged( + any(LocationResult.class), nullable(IRemoteCallback.class)); } @Test @@ -562,7 +555,7 @@ public class LocationProviderManagerTest { mProvider.setProviderLocation(createLocation(NAME, mRandom)); - verify(listener, never()).onLocationChanged(any(Location.class), + verify(listener, never()).onLocationChanged(any(LocationResult.class), nullable(IRemoteCallback.class)); } @@ -592,7 +585,7 @@ public class LocationProviderManagerTest { verify(mWakeLock, never()).release(); blocker.countDown(); - verify(listener, timeout(TIMEOUT_MS)).onLocationChanged(any(Location.class), + verify(listener, timeout(TIMEOUT_MS)).onLocationChanged(any(LocationResult.class), nullable(IRemoteCallback.class)); verify(mWakeLock).acquire(anyLong()); verify(mWakeLock, timeout(TIMEOUT_MS)).release(); @@ -610,7 +603,7 @@ public class LocationProviderManagerTest { mProvider.setProviderLocation(createLocation(NAME, mRandom)); mProvider.setProviderLocation(createLocation(NAME, mRandom)); verify(listener, times(1)) - .onLocationChanged(any(Location.class), nullable(IRemoteCallback.class)); + .onLocationChanged(any(LocationResult.class), nullable(IRemoteCallback.class)); } @Test @@ -627,13 +620,11 @@ public class LocationProviderManagerTest { mProvider.setProviderLocation(createLocation(NAME, mRandom)); mProvider.setProviderLocation(createLocation(NAME, mRandom)); verify(listener, times(1)) - .onLocationChanged(any(Location.class), nullable(IRemoteCallback.class)); + .onLocationChanged(any(LocationResult.class), nullable(IRemoteCallback.class)); } @Test public void testGetCurrentLocation() throws Exception { - ArgumentCaptor<Location> locationCaptor = ArgumentCaptor.forClass(Location.class); - ILocationCallback listener = createMockGetCurrentLocationListener(); LocationRequest request = new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build(); mManager.getCurrentLocation(request, IDENTITY, PERMISSION_FINE, listener); @@ -641,9 +632,7 @@ public class LocationProviderManagerTest { Location loc = createLocation(NAME, mRandom); mProvider.setProviderLocation(loc); mProvider.setProviderLocation(createLocation(NAME, mRandom)); - - verify(listener, times(1)).onLocation(locationCaptor.capture()); - assertThat(locationCaptor.getValue()).isEqualTo(loc); + verify(listener, times(1)).onLocation(loc); } @Test @@ -655,7 +644,6 @@ public class LocationProviderManagerTest { cancellationSignal.cancel(); mProvider.setProviderLocation(createLocation(NAME, mRandom)); - verify(listener, never()).onLocation(nullable(Location.class)); } @@ -686,17 +674,13 @@ public class LocationProviderManagerTest { @Test public void testGetCurrentLocation_LastLocation() throws Exception { - ArgumentCaptor<Location> locationCaptor = ArgumentCaptor.forClass(Location.class); - Location loc = createLocation(NAME, mRandom); mProvider.setProviderLocation(loc); ILocationCallback listener = createMockGetCurrentLocationListener(); LocationRequest request = new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build(); mManager.getCurrentLocation(request, IDENTITY, PERMISSION_FINE, listener); - - verify(listener, times(1)).onLocation(locationCaptor.capture()); - assertThat(locationCaptor.getValue()).isEqualTo(loc); + verify(listener, times(1)).onLocation(eq(loc)); } @Test @@ -710,6 +694,32 @@ public class LocationProviderManagerTest { } @Test + public void testFlush() throws Exception { + ILocationListener listener = createMockLocationListener(); + mManager.registerLocationRequest( + new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build(), + IDENTITY, + PERMISSION_FINE, + listener); + + mManager.flush(listener, 99); + + LocationResult loc = createLocationResult(NAME, mRandom); + mProvider.setProviderLocation(loc); + mProvider.completeFlushes(); + + InOrder inOrder = inOrder(listener); + inOrder.verify(listener).onLocationChanged(eq(loc), any(IRemoteCallback.class)); + inOrder.verify(listener).onFlushComplete(99); + } + + @Test + public void testFlush_UnknownKey() { + assertThrows(IllegalArgumentException.class, + () -> mManager.flush(createMockLocationListener(), 0)); + } + + @Test public void testLocationMonitoring() { assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION, IDENTITY.getPackageName())).isFalse(); @@ -791,7 +801,8 @@ public class LocationProviderManagerTest { .build(); mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1); - verify(listener1).onLocationChanged(any(Location.class), nullable(IRemoteCallback.class)); + verify(listener1).onLocationChanged(any(LocationResult.class), + nullable(IRemoteCallback.class)); assertThat(mProvider.getRequest().isActive()).isFalse(); @@ -941,7 +952,8 @@ public class LocationProviderManagerTest { private ILocationListener createMockLocationListener() { return spy(new ILocationListener.Stub() { @Override - public void onLocationChanged(Location location, IRemoteCallback onCompleteCallback) { + public void onLocationChanged(LocationResult location, + IRemoteCallback onCompleteCallback) { if (onCompleteCallback != null) { try { onCompleteCallback.sendResult(null); @@ -952,6 +964,10 @@ public class LocationProviderManagerTest { } @Override + public void onFlushComplete(int requestCode) { + } + + @Override public void onProviderEnabledChanged(String provider, boolean enabled) { } }); @@ -969,6 +985,8 @@ public class LocationProviderManagerTest { private ProviderRequest mProviderRequest = ProviderRequest.EMPTY_REQUEST; + private final ArrayList<Runnable> mFlushCallbacks = new ArrayList<>(); + TestProvider(ProviderProperties properties, CallerIdentity identity) { super(DIRECT_EXECUTOR, identity); setProperties(properties); @@ -979,7 +997,18 @@ public class LocationProviderManagerTest { } public void setProviderLocation(Location l) { - reportLocation(new Location(l)); + reportLocation(LocationResult.create(new Location(l))); + } + + public void setProviderLocation(LocationResult l) { + reportLocation(l); + } + + public void completeFlushes() { + for (Runnable r : mFlushCallbacks) { + r.run(); + } + mFlushCallbacks.clear(); } public ProviderRequest getRequest() { @@ -992,6 +1021,11 @@ public class LocationProviderManagerTest { } @Override + protected void onFlush(Runnable callback) { + mFlushCallbacks.add(callback); + } + + @Override protected void onExtraCommand(int uid, int pid, String command, Bundle extras) { } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationUtils.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationUtils.java index decb3a6e334a..593e62a76f41 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/LocationUtils.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationUtils.java @@ -17,8 +17,10 @@ package com.android.server.location; import android.location.Location; +import android.location.LocationResult; import android.os.SystemClock; +import java.util.Arrays; import java.util.Random; public final class LocationUtils { @@ -38,6 +40,19 @@ public final class LocationUtils { MIN_ACCURACY + random.nextFloat() * (MAX_ACCURACY - MIN_ACCURACY)); } + public static LocationResult createLocationResult(String provider, Random random) { + return LocationResult.wrap(createLocation(provider, random)); + } + + public static LocationResult createLocationResult(String provider, Random random, + int numLocations) { + Location[] locations = new Location[numLocations]; + for (int i = 0; i < numLocations; i++) { + locations[i] = createLocation(provider, random); + } + return LocationResult.create(Arrays.asList(locations)); + } + public static Location createLocation(String provider, double latitude, double longitude, float accuracy) { Location location = new Location(provider); @@ -48,4 +63,9 @@ public final class LocationUtils { location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); return location; } + + public static LocationResult createLocationResult(String provider, double latitude, + double longitude, float accuracy) { + return LocationResult.wrap(createLocation(provider, latitude, longitude, accuracy)); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/MockableLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/MockableLocationProviderTest.java index 85cb7a0befe3..28fd70aea22f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/MockableLocationProviderTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/MockableLocationProviderTest.java @@ -21,12 +21,14 @@ import static com.android.internal.location.ProviderRequest.EMPTY_REQUEST; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.location.Criteria; import android.location.Location; +import android.location.LocationResult; import android.location.util.identity.CallerIdentity; import android.platform.test.annotations.Presubmit; @@ -117,6 +119,20 @@ public class MockableLocationProviderTest { } @Test + public void testFlush() { + Runnable listener = mock(Runnable.class); + mProvider.flush(listener); + verify(mRealProvider).onFlush(listener); + verify(listener).run(); + + listener = mock(Runnable.class); + mProvider.setMockProvider(mMockProvider); + mProvider.flush(listener); + verify(mMockProvider).onFlush(listener); + verify(listener).run(); + } + + @Test public void testSendExtraCommand() { mProvider.sendExtraCommand(0, 0, "command", null); verify(mRealProvider, times(1)).onExtraCommand(0, 0, "command", null); @@ -158,15 +174,15 @@ public class MockableLocationProviderTest { @Test public void testReportLocation() { - Location realLocation = new Location("real"); - Location mockLocation = new Location("mock"); + LocationResult realLocation = LocationResult.create(new Location("real")); + LocationResult mockLocation = LocationResult.create(new Location("mock")); mRealProvider.reportLocation(realLocation); - assertThat(mListener.getNextLocation()).isEqualTo(realLocation); + assertThat(mListener.getNextLocationResult()).isEqualTo(realLocation); mProvider.setMockProvider(mMockProvider); mRealProvider.reportLocation(realLocation); mMockProvider.reportLocation(mockLocation); - assertThat(mListener.getNextLocation()).isEqualTo(mockLocation); + assertThat(mListener.getNextLocationResult()).isEqualTo(mockLocation); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java index 879b76793863..1df2854028e7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java @@ -23,7 +23,6 @@ import static android.location.LocationManager.GPS_PROVIDER; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; @@ -48,13 +47,11 @@ import android.location.GnssMeasurementsEvent; import android.location.GnssNavigationMessage; import android.location.GnssRequest; import android.location.GnssSingleSatCorrection; -import android.location.IBatchedLocationCallback; import android.location.IGnssAntennaInfoListener; import android.location.IGnssMeasurementsListener; import android.location.IGnssNavigationMessageListener; import android.location.IGnssStatusListener; import android.location.INetInitiatedListener; -import android.location.Location; import android.location.LocationManagerInternal; import android.os.Handler; import android.os.IBinder; @@ -99,7 +96,6 @@ public class GnssManagerServiceTest { @Mock private LocationManagerInternal mLocationManagerInternal; @Mock private GnssNative.GnssNativeInitNative mGnssInitNative; @Mock private GnssLocationProvider mMockGnssLocationProvider; - @Mock private GnssBatchingProvider mMockGnssBatchingProvider; @Mock private GnssLocationProvider.GnssSystemInfoProvider mMockGnssSystemInfoProvider; @Mock private GnssCapabilitiesProvider mMockGnssCapabilitiesProvider; @Mock private GnssMeasurementCorrectionsProvider mMockGnssMeasurementCorrectionsProvider; @@ -148,8 +144,6 @@ public class GnssManagerServiceTest { // Setup GnssLocationProvider to return providers when(mMockGnssLocationProvider.getGnssStatusProvider()).thenReturn( mTestGnssStatusProvider); - when(mMockGnssLocationProvider.getGnssBatchingProvider()).thenReturn( - mMockGnssBatchingProvider); when(mMockGnssLocationProvider.getGnssCapabilitiesProvider()).thenReturn( mMockGnssCapabilitiesProvider); when(mMockGnssLocationProvider.getGnssSystemInfoProvider()).thenReturn( @@ -165,10 +159,6 @@ public class GnssManagerServiceTest { when(mMockGnssLocationProvider.getGnssAntennaInfoProvider()).thenReturn( mTestGnssAntennaInfoProvider); - // Setup GnssBatching provider - when(mMockGnssBatchingProvider.start(anyLong(), anyBoolean())).thenReturn(true); - when(mMockGnssBatchingProvider.stop()).thenReturn(true); - // Create GnssManagerService mGnssManagerService = new GnssManagerService(mMockContext, mInjector, mMockGnssLocationProvider); @@ -204,12 +194,6 @@ public class GnssManagerServiceTest { return mockListener; } - private IBatchedLocationCallback createMockBatchedLocationCallback() { - IBatchedLocationCallback mockedCallback = mock(IBatchedLocationCallback.class); - overrideAsBinder(mockedCallback); - return mockedCallback; - } - private IGnssNavigationMessageListener createMockGnssNavigationMessageListener() { IGnssNavigationMessageListener mockListener = mock(IGnssNavigationMessageListener.class); overrideAsBinder(mockListener); @@ -359,174 +343,6 @@ public class GnssManagerServiceTest { } @Test - public void getGnssBatchSizeWithoutPermissionsTest() { - disableLocationPermissions(); - - assertThrows(SecurityException.class, - () -> mGnssManagerService.getGnssBatchSize()); - } - - @Test - public void getGnssBatchSizeWithPermissionsTest() { - final int gnssBatchSize = 10; - when(mMockGnssBatchingProvider.getBatchSize()).thenReturn(gnssBatchSize); - enableLocationPermissions(); - - assertThat(mGnssManagerService.getGnssBatchSize()).isEqualTo( - gnssBatchSize); - } - - @Test - public void startGnssBatchWithoutPermissionsTest() { - final long periodNanos = 100L; - final boolean wakeOnFifoFull = true; - - disableLocationPermissions(); - - assertThrows(SecurityException.class, - () -> mGnssManagerService.startGnssBatch(periodNanos, wakeOnFifoFull, - TEST_PACKAGE, null)); - verify(mMockGnssBatchingProvider, times(0)).start(periodNanos, wakeOnFifoFull); - } - - @Test - public void startGnssBatchWithPermissionsTest() { - final long periodNanos = 100L; - final boolean wakeOnFifoFull = true; - - enableLocationPermissions(); - - assertThat(mGnssManagerService.startGnssBatch(periodNanos, wakeOnFifoFull, - TEST_PACKAGE, null)) - .isEqualTo( - true); - verify(mMockGnssBatchingProvider, times(1)).start(100L, true); - } - - @Test - public void addGnssBatchCallbackWithoutPermissionsTest() throws RemoteException { - IBatchedLocationCallback mockBatchedLocationCallback = createMockBatchedLocationCallback(); - List<Location> mockLocationList = new ArrayList<>(); - - disableLocationPermissions(); - - assertThrows(SecurityException.class, () -> mGnssManagerService.setGnssBatchingCallback( - mockBatchedLocationCallback, TEST_PACKAGE, null)); - - mGnssManagerService.onReportLocation(mockLocationList); - - verify(mockBatchedLocationCallback, times(0)).onLocationBatch(mockLocationList); - } - - @Test - public void addGnssBatchCallbackWithPermissionsTest() throws RemoteException { - IBatchedLocationCallback mockBatchedLocationCallback = createMockBatchedLocationCallback(); - List<Location> mockLocationList = new ArrayList<>(); - - enableLocationPermissions(); - - assertThat(mGnssManagerService.setGnssBatchingCallback( - mockBatchedLocationCallback, TEST_PACKAGE, null)) - .isEqualTo(true); - - mGnssManagerService.onReportLocation(mockLocationList); - - verify(mockBatchedLocationCallback, times(1)).onLocationBatch(mockLocationList); - } - - @Test - public void replaceGnssBatchCallbackTest() throws RemoteException { - IBatchedLocationCallback mockBatchedLocationCallback1 = createMockBatchedLocationCallback(); - IBatchedLocationCallback mockBatchedLocationCallback2 = createMockBatchedLocationCallback(); - List<Location> mockLocationList = new ArrayList<>(); - - enableLocationPermissions(); - - assertThat(mGnssManagerService.setGnssBatchingCallback( - mockBatchedLocationCallback1, TEST_PACKAGE, null)) - .isEqualTo(true); - assertThat(mGnssManagerService.setGnssBatchingCallback( - mockBatchedLocationCallback2, TEST_PACKAGE, null)) - .isEqualTo(true); - - mGnssManagerService.onReportLocation(mockLocationList); - - verify(mockBatchedLocationCallback1, times(0)).onLocationBatch(mockLocationList); - verify(mockBatchedLocationCallback2, times(1)).onLocationBatch(mockLocationList); - } - - @Test - public void flushGnssBatchWithoutPermissionsTest() { - disableLocationPermissions(); - - assertThrows(SecurityException.class, - () -> mGnssManagerService.flushGnssBatch()); - verify(mMockGnssBatchingProvider, times(0)).flush(); - } - - @Test - public void flushGnssBatchWithPermissionsTest() { - enableLocationPermissions(); - mGnssManagerService.flushGnssBatch(); - - verify(mMockGnssBatchingProvider, times(1)).flush(); - } - - @Test - public void removeGnssBatchingCallbackWithoutPermissionsTest() throws RemoteException { - IBatchedLocationCallback mockBatchedLocationCallback = createMockBatchedLocationCallback(); - List<Location> mockLocationList = new ArrayList<>(); - - enableLocationPermissions(); - - mGnssManagerService.setGnssBatchingCallback(mockBatchedLocationCallback, - TEST_PACKAGE, null); - - disableLocationPermissions(); - - assertThrows(SecurityException.class, - () -> mGnssManagerService.removeGnssBatchingCallback()); - - enableLocationPermissions(); - mGnssManagerService.onReportLocation(mockLocationList); - - verify(mockBatchedLocationCallback, times(1)).onLocationBatch(mockLocationList); - } - - @Test - public void removeGnssBatchingCallbackWithPermissionsTest() throws RemoteException { - IBatchedLocationCallback mockBatchedLocationCallback = createMockBatchedLocationCallback(); - List<Location> mockLocationList = new ArrayList<>(); - - enableLocationPermissions(); - - mGnssManagerService.setGnssBatchingCallback(mockBatchedLocationCallback, - TEST_PACKAGE, null); - - mGnssManagerService.removeGnssBatchingCallback(); - - mGnssManagerService.onReportLocation(mockLocationList); - - verify(mockBatchedLocationCallback, times(0)).onLocationBatch(mockLocationList); - } - - @Test - public void stopGnssBatchWithoutPermissionsTest() { - disableLocationPermissions(); - - assertThrows(SecurityException.class, () -> mGnssManagerService.stopGnssBatch()); - verify(mMockGnssBatchingProvider, times(0)).stop(); - } - - @Test - public void stopGnssBatchWithPermissionsTest() { - enableLocationPermissions(); - - assertThat(mGnssManagerService.stopGnssBatch()).isEqualTo(true); - verify(mMockGnssBatchingProvider, times(1)).stop(); - } - - @Test public void registerGnssStatusCallbackWithoutPermissionsTest() throws RemoteException { final int timeToFirstFix = 20000; IGnssStatusListener mockGnssStatusListener = createMockGnssStatusListener(); diff --git a/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java b/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java index 2f1a20b2cbb1..7a1a76278bcc 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java @@ -34,6 +34,11 @@ public class FakeProvider extends AbstractLocationProvider { protected void onSetRequest(ProviderRequest request) {} @Override + protected void onFlush(Runnable callback) { + callback.run(); + } + + @Override protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {} @Override diff --git a/services/tests/mockingservicestests/src/com/android/server/location/test/ProviderListenerCapture.java b/services/tests/mockingservicestests/src/com/android/server/location/test/ProviderListenerCapture.java index 5e5ed11bd0e7..c0c45e41d4b1 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/test/ProviderListenerCapture.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/test/ProviderListenerCapture.java @@ -18,18 +18,17 @@ package com.android.server.location.test; import static com.google.common.truth.Truth.assertThat; -import android.location.Location; +import android.location.LocationResult; import com.android.server.location.AbstractLocationProvider; import java.util.LinkedList; -import java.util.List; public class ProviderListenerCapture implements AbstractLocationProvider.Listener { private final Object mLock; private final LinkedList<AbstractLocationProvider.State> mNewStates = new LinkedList<>(); - private final LinkedList<Location> mLocations = new LinkedList<>(); + private final LinkedList<LocationResult> mLocations = new LinkedList<>(); public ProviderListenerCapture(Object lock) { mLock = lock; @@ -47,15 +46,12 @@ public class ProviderListenerCapture implements AbstractLocationProvider.Listene } @Override - public void onReportLocation(Location location) { + public void onReportLocation(LocationResult locationResult) { assertThat(Thread.holdsLock(mLock)).isTrue(); - mLocations.add(location); + mLocations.add(locationResult); } - public Location getNextLocation() { + public LocationResult getNextLocationResult() { return mLocations.poll(); } - - @Override - public void onReportLocation(List<Location> locations) {} } |