summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSoonil Nagarkar <sooniln@google.com>2020-10-25 17:23:54 -0700
committerSoonil Nagarkar <sooniln@google.com>2020-11-10 10:14:34 -0800
commitbe6ed5aa14f7418da2a23b0755158491d4ecd179 (patch)
tree121ae291776990d86a057874902464e585b50822
parentfded2b6d13331c11f9ec6b149834c519382d7dc1 (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
-rw-r--r--api/current.txt24
-rw-r--r--api/system-current.txt12
-rw-r--r--core/api/current.txt24
-rw-r--r--core/api/system-current.txt12
-rw-r--r--location/java/android/location/ILocationListener.aidl5
-rw-r--r--location/java/android/location/ILocationManager.aidl10
-rw-r--r--location/java/android/location/Location.java363
-rw-r--r--location/java/android/location/LocationListener.java23
-rw-r--r--location/java/android/location/LocationManager.java231
-rw-r--r--location/java/android/location/LocationManagerInternal.java8
-rw-r--r--location/java/android/location/LocationRequest.java45
-rw-r--r--location/java/android/location/LocationResult.aidl (renamed from location/java/android/location/IBatchedLocationCallback.aidl)16
-rw-r--r--location/java/android/location/LocationResult.java273
-rw-r--r--location/java/com/android/internal/location/ILocationProvider.aidl2
-rw-r--r--location/java/com/android/internal/location/ILocationProviderManager.aidl5
-rw-r--r--location/java/com/android/internal/location/ProviderRequest.java54
-rw-r--r--location/lib/api/current.txt7
-rw-r--r--location/lib/java/com/android/location/provider/LocationProviderBase.java75
-rw-r--r--location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java20
-rw-r--r--packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java12
-rw-r--r--services/core/java/com/android/server/location/AbstractLocationProvider.java54
-rw-r--r--services/core/java/com/android/server/location/LocationFudger.java30
-rw-r--r--services/core/java/com/android/server/location/LocationManagerService.java97
-rw-r--r--services/core/java/com/android/server/location/LocationProviderManager.java307
-rw-r--r--services/core/java/com/android/server/location/MockLocationProvider.java8
-rw-r--r--services/core/java/com/android/server/location/MockableLocationProvider.java28
-rw-r--r--services/core/java/com/android/server/location/PassiveLocationProvider.java11
-rw-r--r--services/core/java/com/android/server/location/PassiveLocationProviderManager.java6
-rw-r--r--services/core/java/com/android/server/location/ProxyLocationProvider.java69
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssBatchingProvider.java161
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssLocationProvider.java178
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssManagerService.java156
-rw-r--r--services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java3
-rw-r--r--services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java35
-rw-r--r--services/core/java/com/android/server/location/listeners/ListenerRegistration.java5
-rw-r--r--services/core/java/com/android/server/location/util/LocationEventLog.java26
-rw-r--r--services/core/jni/com_android_server_location_GnssLocationProvider.cpp49
-rw-r--r--services/robotests/src/com/android/server/location/gnss/GnssBatchingProviderTest.java120
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java140
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/LocationUtils.java20
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/MockableLocationProviderTest.java24
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java184
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java5
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/test/ProviderListenerCapture.java14
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) {}
}