Merge "Fix background request throttling + jitter calculation"

This commit is contained in:
Soonil Nagarkar
2020-09-30 21:30:57 +00:00
committed by Android (Google) Code Review
15 changed files with 621 additions and 174 deletions

View File

@@ -16,6 +16,8 @@
package android.location;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
@@ -585,6 +587,11 @@ public class Location implements Parcelable {
return mElapsedRealtimeNanos;
}
/** @hide */
public long getElapsedRealtimeMillis() {
return NANOSECONDS.toMillis(getElapsedRealtimeNanos());
}
/** @hide */
public long getElapsedRealtimeAgeNanos(long referenceRealtimeNs) {
return referenceRealtimeNs - mElapsedRealtimeNanos;
@@ -595,6 +602,11 @@ public class Location implements Parcelable {
return getElapsedRealtimeAgeNanos(SystemClock.elapsedRealtimeNanos());
}
/** @hide */
public long getElapsedRealtimeAgeMillis() {
return NANOSECONDS.toMillis(getElapsedRealtimeAgeNanos());
}
/**
* Set the time of this fix, in elapsed real-time since system boot.
*

View File

@@ -88,6 +88,16 @@ import java.util.function.Consumer;
@RequiresFeature(PackageManager.FEATURE_LOCATION)
public class LocationManager {
/**
* For apps targeting Android S and above, location clients may receive historical locations
* (from before the present time) under some circumstances.
*
* @hide
*/
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
public static final long DELIVER_HISTORICAL_LOCATIONS = 73144566L;
/**
* For apps targeting Android R and above, {@link #getProvider(String)} will no longer throw any
* security exceptions.
@@ -1256,13 +1266,15 @@ public class LocationManager {
* arguments. The same listener may be used across multiple providers with different requests
* for each provider.
*
* <p>It may take a while to receive the first location update. If an immediate location is
* required, applications may use the {@link #getLastKnownLocation(String)} method.
* <p>It may take some time to receive the first location update depending on the conditions the
* device finds itself in. In order to take advantage of cached locations, application may
* consider using {@link #getLastKnownLocation(String)} or {@link #getCurrentLocation(String,
* LocationRequest, CancellationSignal, Executor, Consumer)} instead.
*
* <p>See {@link LocationRequest} documentation for an explanation of various request parameters
* and how they can affect the received locations.
*
* <p> If your application wants to passively observe location updates from any provider, then
* <p>If your application wants to passively observe location updates from all providers, then
* use the {@link #PASSIVE_PROVIDER}. This provider does not turn on or modify active location
* providers, so you do not need to be as careful about minimum time and minimum distance
* parameters. However, if your application performs heavy work on a location update (such as
@@ -1271,13 +1283,20 @@ public class LocationManager {
*
* <p>In case the provider you have selected is disabled, location updates will cease, and a
* provider availability update will be sent. As soon as the provider is enabled again, another
* provider availability update will be sent and location updates will immediately resume.
* provider availability update will be sent and location updates will resume.
*
* <p> When location callbacks are invoked, the system will hold a wakelock on your
* <p>When location callbacks are invoked, the system will hold a wakelock on your
* application's behalf for some period of time, but not indefinitely. If your application
* requires a long running wakelock within the location callback, you should acquire it
* yourself.
*
* <p>Spamming location requests is a drain on system resources, and the system has preventative
* measures in place to ensure that this behavior will never result in more locations than could
* be achieved with a single location request with an equivalent interval that is left in place
* the whole time. As part of this amelioration, applications that target Android S and above
* may receive cached or historical locations through their listener. These locations will never
* be older than the interval of the location request.
*
* <p>To unregister for location updates, use {@link #removeUpdates(LocationListener)}.
*
* @param provider a provider listed by {@link #getAllProviders()}

View File

@@ -27,6 +27,8 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
@@ -44,6 +46,17 @@ import java.util.Objects;
*/
public final class LocationRequest implements Parcelable {
/**
* For apps targeting Android S and above, all LocationRequest objects marked as low power will
* throw exceptions if the caller does not have the LOCATION_HARDWARE permission, instead of
* silently dropping the low power part of the request.
*
* @hide
*/
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
public static final long LOW_POWER_EXCEPTIONS = 168936375L;
/**
* Represents a passive only request. Such a request will not trigger any active locations or
* power usage itself, but may receive locations generated in response to other requests.

View File

@@ -21,8 +21,6 @@ import static android.location.LocationManager.NETWORK_PROVIDER;
import static androidx.test.ext.truth.location.LocationSubject.assertThat;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.location.Criteria;
import android.location.Location;

View File

@@ -23,10 +23,10 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.location.LocationManager.FUSED_PROVIDER;
import static android.location.LocationManager.GPS_PROVIDER;
import static android.location.LocationManager.NETWORK_PROVIDER;
import static android.location.LocationRequest.LOW_POWER_EXCEPTIONS;
import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
import static com.android.server.location.LocationProviderManager.FASTEST_COARSE_INTERVAL_MS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
@@ -36,6 +36,7 @@ import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.app.compat.CompatChanges;
import android.content.Context;
import android.content.Intent;
import android.location.Criteria;
@@ -82,12 +83,12 @@ import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.location.LocationPermissions.PermissionLevel;
import com.android.server.location.LocationRequestStatistics.PackageProviderKey;
import com.android.server.location.LocationRequestStatistics.PackageStatistics;
import com.android.server.location.geofence.GeofenceManager;
import com.android.server.location.geofence.GeofenceProxy;
import com.android.server.location.gnss.GnssManagerService;
import com.android.server.location.util.AlarmHelper;
import com.android.server.location.util.AppForegroundHelper;
import com.android.server.location.util.AppOpsHelper;
import com.android.server.location.util.Injector;
@@ -97,6 +98,7 @@ import com.android.server.location.util.LocationPowerSaveModeHelper;
import com.android.server.location.util.LocationUsageLogger;
import com.android.server.location.util.ScreenInteractiveHelper;
import com.android.server.location.util.SettingsHelper;
import com.android.server.location.util.SystemAlarmHelper;
import com.android.server.location.util.SystemAppForegroundHelper;
import com.android.server.location.util.SystemAppOpsHelper;
import com.android.server.location.util.SystemLocationPermissionsHelper;
@@ -569,7 +571,7 @@ public class LocationManagerService extends ILocationManager.Stub {
new IllegalArgumentException());
}
request = validateAndSanitizeLocationRequest(request, permissionLevel);
request = validateLocationRequest(request);
LocationProviderManager manager = getLocationProviderManager(provider);
Preconditions.checkArgument(manager != null,
@@ -591,7 +593,7 @@ public class LocationManagerService extends ILocationManager.Stub {
// clients in the system process must have an attribution tag set
Preconditions.checkArgument(identity.getPid() != Process.myPid() || attributionTag != null);
request = validateAndSanitizeLocationRequest(request, permissionLevel);
request = validateLocationRequest(request);
LocationProviderManager manager = getLocationProviderManager(provider);
Preconditions.checkArgument(manager != null,
@@ -600,8 +602,7 @@ public class LocationManagerService extends ILocationManager.Stub {
manager.registerLocationRequest(request, identity, permissionLevel, pendingIntent);
}
private LocationRequest validateAndSanitizeLocationRequest(LocationRequest request,
@PermissionLevel int permissionLevel) {
private LocationRequest validateLocationRequest(LocationRequest request) {
WorkSource workSource = request.getWorkSource();
if (workSource != null && !workSource.isEmpty()) {
mContext.enforceCallingOrSelfPermission(
@@ -620,26 +621,20 @@ public class LocationManagerService extends ILocationManager.Stub {
}
LocationRequest.Builder sanitized = new LocationRequest.Builder(request);
if (mContext.checkCallingPermission(permission.LOCATION_HARDWARE) != PERMISSION_GRANTED) {
sanitized.setLowPower(false);
}
if (permissionLevel < PERMISSION_FINE) {
switch (request.getQuality()) {
case LocationRequest.ACCURACY_FINE:
sanitized.setQuality(LocationRequest.ACCURACY_BLOCK);
break;
case LocationRequest.POWER_HIGH:
sanitized.setQuality(LocationRequest.POWER_LOW);
break;
}
if (request.getIntervalMillis() < FASTEST_COARSE_INTERVAL_MS) {
sanitized.setIntervalMillis(FASTEST_COARSE_INTERVAL_MS);
if (CompatChanges.isChangeEnabled(LOW_POWER_EXCEPTIONS, Binder.getCallingUid())) {
if (request.isLowPower()) {
mContext.enforceCallingOrSelfPermission(
permission.LOCATION_HARDWARE,
"low power request requires " + permission.LOCATION_HARDWARE);
}
if (request.getMinUpdateIntervalMillis() < FASTEST_COARSE_INTERVAL_MS) {
sanitized.clearMinUpdateIntervalMillis();
} else {
if (mContext.checkCallingPermission(permission.LOCATION_HARDWARE)
!= PERMISSION_GRANTED) {
sanitized.setLowPower(false);
}
}
if (request.getWorkSource() != null) {
if (request.getWorkSource().isEmpty()) {
sanitized.setWorkSource(null);
@@ -716,7 +711,7 @@ public class LocationManagerService extends ILocationManager.Stub {
// clients in the system process must have an attribution tag set
Preconditions.checkState(identity.getPid() != Process.myPid() || attributionTag != null);
request = validateAndSanitizeLocationRequest(request, permissionLevel);
request = validateLocationRequest(request);
LocationProviderManager manager = getLocationProviderManager(provider);
Preconditions.checkArgument(manager != null,
@@ -735,7 +730,7 @@ public class LocationManagerService extends ILocationManager.Stub {
// use fine permission level to avoid creating unnecessary coarse locations
Location location = gpsManager.getLastLocationUnsafe(UserHandle.USER_ALL,
PERMISSION_FINE, false);
PERMISSION_FINE, false, Long.MAX_VALUE);
if (location == null) {
return null;
}
@@ -1237,6 +1232,7 @@ public class LocationManagerService extends ILocationManager.Stub {
private static class SystemInjector implements Injector {
private final UserInfoHelper mUserInfoHelper;
private final AlarmHelper mAlarmHelper;
private final SystemAppOpsHelper mAppOpsHelper;
private final SystemLocationPermissionsHelper mLocationPermissionsHelper;
private final SystemSettingsHelper mSettingsHelper;
@@ -1249,6 +1245,7 @@ public class LocationManagerService extends ILocationManager.Stub {
SystemInjector(Context context, UserInfoHelper userInfoHelper) {
mUserInfoHelper = userInfoHelper;
mAlarmHelper = new SystemAlarmHelper(context);
mAppOpsHelper = new SystemAppOpsHelper(context);
mLocationPermissionsHelper = new SystemLocationPermissionsHelper(context,
mAppOpsHelper);
@@ -1275,6 +1272,11 @@ public class LocationManagerService extends ILocationManager.Stub {
return mUserInfoHelper;
}
@Override
public AlarmHelper getAlarmHelper() {
return mAlarmHelper;
}
@Override
public AppOpsHelper getAppOpsHelper() {
return mAppOpsHelper;

View File

@@ -16,13 +16,14 @@
package com.android.server.location;
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
import static android.app.AlarmManager.WINDOW_EXACT;
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_LOCATION_CHANGED;
import static android.location.LocationManager.KEY_PROVIDER_ENABLED;
import static android.location.LocationManager.PASSIVE_PROVIDER;
import static android.location.LocationRequest.PASSIVE_INTERVAL;
import static android.os.IPowerManager.LOCATION_MODE_NO_CHANGE;
import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF;
import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
@@ -36,11 +37,11 @@ import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
import static com.android.server.location.LocationPermissions.PERMISSION_NONE;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.AlarmManager.OnAlarmListener;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -88,6 +89,7 @@ import com.android.server.PendingIntentUtils;
import com.android.server.location.LocationPermissions.PermissionLevel;
import com.android.server.location.listeners.ListenerMultiplexer;
import com.android.server.location.listeners.RemoteListenerRegistration;
import com.android.server.location.util.AlarmHelper;
import com.android.server.location.util.AppForegroundHelper;
import com.android.server.location.util.AppForegroundHelper.AppForegroundListener;
import com.android.server.location.util.AppOpsHelper;
@@ -118,12 +120,12 @@ class LocationProviderManager extends
LocationProviderManager.Registration, ProviderRequest> implements
AbstractLocationProvider.Listener {
// fastest interval at which clients may receive coarse locations
public static final long FASTEST_COARSE_INTERVAL_MS = 10 * 60 * 1000;
private static final String WAKELOCK_TAG = "*location*";
private static final long WAKELOCK_TIMEOUT_MS = 30 * 1000;
// fastest interval at which clients may receive coarse locations
private static final long MIN_COARSE_INTERVAL_MS = 10 * 60 * 1000;
// max interval to be considered "high power" request
private static final long MAX_HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000;
@@ -133,8 +135,15 @@ class LocationProviderManager extends
// max timeout allowed for getting the current location
private static final long GET_CURRENT_LOCATION_MAX_TIMEOUT_MS = 30 * 1000;
// max jitter allowed for fastest interval evaluation
private static final int MAX_FASTEST_INTERVAL_JITTER_MS = 100;
// max jitter allowed for min update interval as a percentage of the interval
private static final float FASTEST_INTERVAL_JITTER_PERCENTAGE = .10f;
// max absolute jitter allowed for min update interval evaluation
private static final int MAX_FASTEST_INTERVAL_JITTER_MS = 5 * 1000;
// minimum amount of request delay in order to respect the delay, below this value the request
// will just be scheduled immediately
private static final long MIN_REQUEST_DELAY_MS = 30 * 1000;
protected interface LocationTransport {
@@ -221,6 +230,7 @@ class LocationProviderManager extends
/**
* Must be implemented to return the location this operation intends to deliver.
*/
@Nullable
Location getLocation();
}
@@ -312,7 +322,7 @@ class LocationProviderManager extends
mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey());
}
onHighPowerUsageChanged();
return null;
return onProviderListenerActive();
}
@Override
@@ -325,6 +335,22 @@ class LocationProviderManager extends
if (!getRequest().isHiddenFromAppOps()) {
mLocationAttributionHelper.reportLocationStop(getIdentity(), getName(), getKey());
}
return onProviderListenerInactive();
}
/**
* Subclasses may override this instead of {@link #onActive()}.
*/
@GuardedBy("mLock")
protected LocationListenerOperation onProviderListenerActive() {
return null;
}
/**
* Subclasses may override this instead of {@link #onInactive()} ()}.
*/
@GuardedBy("mLock")
protected LocationListenerOperation onProviderListenerInactive() {
return null;
}
@@ -333,6 +359,14 @@ class LocationProviderManager extends
return mProviderLocationRequest;
}
@GuardedBy("mLock")
final void initializeLastLocation(@Nullable Location location) {
if (mLastLocation == null) {
mLastLocation = location;
}
}
@GuardedBy("mLock")
public final Location getLastDeliveredLocation() {
return mLastLocation;
}
@@ -465,9 +499,27 @@ class LocationProviderManager extends
}
private LocationRequest calculateProviderLocationRequest() {
LocationRequest.Builder builder = new LocationRequest.Builder(super.getRequest());
LocationRequest baseRequest = super.getRequest();
LocationRequest.Builder builder = new LocationRequest.Builder(baseRequest);
if (super.getRequest().isLocationSettingsIgnored()) {
if (mPermissionLevel < PERMISSION_FINE) {
switch (baseRequest.getQuality()) {
case LocationRequest.ACCURACY_FINE:
builder.setQuality(LocationRequest.ACCURACY_BLOCK);
break;
case LocationRequest.POWER_HIGH:
builder.setQuality(LocationRequest.POWER_LOW);
break;
}
if (baseRequest.getIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
builder.setIntervalMillis(MIN_COARSE_INTERVAL_MS);
}
if (baseRequest.getMinUpdateIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
builder.clearMinUpdateIntervalMillis();
}
}
if (baseRequest.isLocationSettingsIgnored()) {
// if we are not currently allowed use location settings ignored, disable it
if (!mSettingsHelper.getIgnoreSettingsPackageWhitelist().contains(
getIdentity().getPackageName()) && !mLocationManagerInternal.isProvider(
@@ -476,10 +528,10 @@ class LocationProviderManager extends
}
}
if (!super.getRequest().isLocationSettingsIgnored() && !isThrottlingExempt()) {
if (!baseRequest.isLocationSettingsIgnored() && !isThrottlingExempt()) {
// throttle in the background
if (!mForeground) {
builder.setIntervalMillis(Math.max(super.getRequest().getIntervalMillis(),
builder.setIntervalMillis(max(baseRequest.getIntervalMillis(),
mSettingsHelper.getBackgroundThrottleIntervalMs()));
}
}
@@ -534,7 +586,7 @@ class LocationProviderManager extends
}
protected abstract class LocationRegistration extends Registration implements
AlarmManager.OnAlarmListener, ProviderEnabledListener {
OnAlarmListener, ProviderEnabledListener {
private final PowerManager.WakeLock mWakeLock;
@@ -561,17 +613,15 @@ class LocationProviderManager extends
@GuardedBy("mLock")
@Override
protected final void onProviderListenerRegister() {
mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(
SystemClock.elapsedRealtime());
long registerTimeMs = SystemClock.elapsedRealtime();
mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(registerTimeMs);
// add alarm for expiration
if (mExpirationRealtimeMs < SystemClock.elapsedRealtime()) {
remove();
if (mExpirationRealtimeMs <= registerTimeMs) {
onAlarm();
} else if (mExpirationRealtimeMs < Long.MAX_VALUE) {
AlarmManager alarmManager = Objects.requireNonNull(
mContext.getSystemService(AlarmManager.class));
alarmManager.set(ELAPSED_REALTIME_WAKEUP, mExpirationRealtimeMs, WINDOW_EXACT,
0, this, FgThread.getHandler(), getWorkSource());
mAlarmHelper.setDelayedAlarm(mExpirationRealtimeMs - registerTimeMs, this,
getWorkSource());
}
// start listening for provider enabled/disabled events
@@ -594,9 +644,7 @@ class LocationProviderManager extends
// remove alarm for expiration
if (mExpirationRealtimeMs < Long.MAX_VALUE) {
AlarmManager alarmManager = Objects.requireNonNull(
mContext.getSystemService(AlarmManager.class));
alarmManager.cancel(this);
mAlarmHelper.cancel(this);
}
onLocationListenerUnregister();
@@ -614,6 +662,39 @@ class LocationProviderManager extends
@GuardedBy("mLock")
protected void onLocationListenerUnregister() {}
@GuardedBy("mLock")
@Override
protected final LocationListenerOperation onProviderListenerActive() {
// a new registration may not get a location immediately, the provider request may be
// delayed. therefore we deliver a historical location if available. since delivering an
// older location could be considered a breaking change for some applications, we only
// do so for apps targeting S+.
if (isChangeEnabled(DELIVER_HISTORICAL_LOCATIONS, getIdentity().getUid())) {
long maxLocationAgeMs = getRequest().getIntervalMillis();
Location lastDeliveredLocation = getLastDeliveredLocation();
if (lastDeliveredLocation != null) {
// ensure that location is fresher than the last delivered location
maxLocationAgeMs = min(maxLocationAgeMs,
lastDeliveredLocation.getElapsedRealtimeAgeMillis() - 1);
}
// requests are never delayed less than MIN_REQUEST_DELAY_MS, so it only makes sense
// to deliver historical locations to clients with a last location older than that
if (maxLocationAgeMs > MIN_REQUEST_DELAY_MS) {
Location lastLocation = getLastLocationUnsafe(
getIdentity().getUserId(),
PERMISSION_FINE, // acceptLocationChange() handles coarsening this
getRequest().isLocationSettingsIgnored(),
maxLocationAgeMs);
if (lastLocation != null) {
return acceptLocationChange(lastLocation);
}
}
}
return null;
}
@Override
public void onAlarm() {
if (D) {
@@ -624,6 +705,8 @@ class LocationProviderManager extends
synchronized (mLock) {
remove();
// no need to remove alarm after it's fired
mExpirationRealtimeMs = Long.MAX_VALUE;
}
}
@@ -658,11 +741,11 @@ class LocationProviderManager extends
Location lastDeliveredLocation = getLastDeliveredLocation();
if (lastDeliveredLocation != null) {
// check fastest interval
long deltaMs = NANOSECONDS.toMillis(
location.getElapsedRealtimeNanos()
- lastDeliveredLocation.getElapsedRealtimeNanos());
if (deltaMs < getRequest().getMinUpdateIntervalMillis()
- MAX_FASTEST_INTERVAL_JITTER_MS) {
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;
}
@@ -871,7 +954,7 @@ class LocationProviderManager extends
}
protected final class GetCurrentLocationListenerRegistration extends Registration implements
IBinder.DeathRecipient, ProviderEnabledListener, AlarmManager.OnAlarmListener {
IBinder.DeathRecipient, ProviderEnabledListener, OnAlarmListener {
private volatile LocationTransport mTransport;
@@ -902,15 +985,15 @@ class LocationProviderManager extends
remove();
}
mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(
SystemClock.elapsedRealtime());
long registerTimeMs = SystemClock.elapsedRealtime();
mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(registerTimeMs);
// add alarm for expiration
if (mExpirationRealtimeMs < Long.MAX_VALUE) {
AlarmManager alarmManager = Objects.requireNonNull(
mContext.getSystemService(AlarmManager.class));
alarmManager.set(ELAPSED_REALTIME_WAKEUP, mExpirationRealtimeMs, WINDOW_EXACT,
0, this, FgThread.getHandler(), getWorkSource());
if (mExpirationRealtimeMs <= registerTimeMs) {
onAlarm();
} else if (mExpirationRealtimeMs < Long.MAX_VALUE) {
mAlarmHelper.setDelayedAlarm(mExpirationRealtimeMs - registerTimeMs, this,
getWorkSource());
}
// if this request is ignoring location settings, then we don't want to immediately fail
@@ -935,9 +1018,7 @@ class LocationProviderManager extends
// remove alarm for expiration
if (mExpirationRealtimeMs < Long.MAX_VALUE) {
AlarmManager alarmManager = Objects.requireNonNull(
mContext.getSystemService(AlarmManager.class));
alarmManager.cancel(this);
mAlarmHelper.cancel(this);
}
((IBinder) getKey()).unlinkToDeath(this, 0);
@@ -953,6 +1034,8 @@ class LocationProviderManager extends
synchronized (mLock) {
deliverLocation(null);
// no need to remove alarm after it's fired
mExpirationRealtimeMs = Long.MAX_VALUE;
}
}
@@ -964,6 +1047,12 @@ class LocationProviderManager extends
Preconditions.checkState(Thread.holdsLock(mLock));
}
// check expiration time - alarm is not guaranteed to go off at the right time,
// especially for short intervals
if (SystemClock.elapsedRealtime() >= mExpirationRealtimeMs) {
fineLocation = null;
}
// lastly - note app ops
Location location;
if (fineLocation == null) {
@@ -1077,6 +1166,7 @@ class LocationProviderManager extends
protected final LocationManagerInternal mLocationManagerInternal;
protected final SettingsHelper mSettingsHelper;
protected final UserInfoHelper mUserInfoHelper;
protected final AlarmHelper mAlarmHelper;
protected final AppOpsHelper mAppOpsHelper;
protected final LocationPermissionsHelper mLocationPermissionsHelper;
protected final AppForegroundHelper mAppForegroundHelper;
@@ -1120,6 +1210,9 @@ class LocationProviderManager extends
// acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary
protected final MockableLocationProvider mProvider;
@GuardedBy("mLock")
@Nullable private OnAlarmListener mDelayedRegister;
LocationProviderManager(Context context, Injector injector, String name,
@Nullable PassiveLocationProviderManager passiveManager) {
mContext = context;
@@ -1135,6 +1228,7 @@ class LocationProviderManager extends
LocalServices.getService(LocationManagerInternal.class));
mSettingsHelper = injector.getSettingsHelper();
mUserInfoHelper = injector.getUserInfoHelper();
mAlarmHelper = injector.getAlarmHelper();
mAppOpsHelper = injector.getAppOpsHelper();
mLocationPermissionsHelper = injector.getLocationPermissionsHelper();
mAppForegroundHelper = injector.getAppForegroundHelper();
@@ -1344,7 +1438,7 @@ class LocationProviderManager extends
}
Location location = getLastLocationUnsafe(identity.getUserId(), permissionLevel,
ignoreLocationSettings);
ignoreLocationSettings, Long.MAX_VALUE);
// we don't note op here because we don't know what the client intends to do with the
// location, the client is responsible for noting if necessary
@@ -1364,13 +1458,14 @@ class LocationProviderManager extends
*/
@Nullable
public Location getLastLocationUnsafe(int userId, @PermissionLevel int permissionLevel,
boolean ignoreLocationSettings) {
boolean ignoreLocationSettings, long maximumAgeMs) {
if (userId == UserHandle.USER_ALL) {
// find the most recent location across all users
Location lastLocation = null;
final int[] runningUserIds = mUserInfoHelper.getRunningUserIds();
for (int i = 0; i < runningUserIds.length; i++) {
Location next = getLastLocationUnsafe(runningUserIds[i], permissionLevel,
ignoreLocationSettings);
ignoreLocationSettings, maximumAgeMs);
if (lastLocation == null || (next != null && next.getElapsedRealtimeNanos()
> lastLocation.getElapsedRealtimeNanos())) {
lastLocation = next;
@@ -1381,18 +1476,30 @@ class LocationProviderManager extends
Preconditions.checkArgument(userId >= 0);
Location location;
synchronized (mLock) {
LastLocation lastLocation = mLastLocations.get(userId);
if (lastLocation == null) {
return null;
location = null;
} else {
location = lastLocation.get(permissionLevel, ignoreLocationSettings);
}
return lastLocation.get(permissionLevel, ignoreLocationSettings);
}
if (location == null) {
return null;
}
if (location.getElapsedRealtimeAgeMillis() > maximumAgeMs) {
return null;
}
return location;
}
public void injectLastLocation(Location location, int userId) {
synchronized (mLock) {
if (getLastLocationUnsafe(userId, PERMISSION_FINE, false) == null) {
if (getLastLocationUnsafe(userId, PERMISSION_FINE, false, Long.MAX_VALUE) == null) {
setLastLocation(location, userId);
}
}
@@ -1455,22 +1562,14 @@ class LocationProviderManager extends
return null;
}
Location lastLocation = getLastLocationUnsafe(callerIdentity.getUserId(),
permissionLevel, request.isLocationSettingsIgnored());
Location lastLocation = getLastLocationUnsafe(
callerIdentity.getUserId(),
permissionLevel,
request.isLocationSettingsIgnored(),
MAX_CURRENT_LOCATION_AGE_MS);
if (lastLocation != null) {
long locationAgeMs = NANOSECONDS.toMillis(
SystemClock.elapsedRealtimeNanos()
- lastLocation.getElapsedRealtimeNanos());
if (locationAgeMs < MAX_CURRENT_LOCATION_AGE_MS) {
registration.deliverLocation(lastLocation);
return null;
}
if (!mAppForegroundHelper.isAppForeground(Binder.getCallingUid())
&& locationAgeMs < mSettingsHelper.getBackgroundThrottleIntervalMs()) {
registration.deliverLocation(null);
return null;
}
registration.deliverLocation(lastLocation);
return null;
}
// if last location isn't good enough then we add a location request
@@ -1627,6 +1726,16 @@ class LocationProviderManager extends
registration.isForeground());
}
@GuardedBy("mLock")
@Override
protected void onRegistrationReplaced(Object key, Registration oldRegistration,
Registration newRegistration) {
// by saving the last delivered location state we are able to potentially delay the
// resulting provider request longer and save additional power
newRegistration.initializeLastLocation(oldRegistration.getLastDeliveredLocation());
super.onRegistrationReplaced(key, oldRegistration, newRegistration);
}
@GuardedBy("mLock")
@Override
protected void onRegistrationRemoved(Object key, Registration registration) {
@@ -1652,21 +1761,61 @@ class LocationProviderManager extends
@GuardedBy("mLock")
@Override
protected boolean registerWithService(ProviderRequest mergedRequest,
protected boolean registerWithService(ProviderRequest request,
Collection<Registration> registrations) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
mProvider.setRequest(mergedRequest);
return true;
return reregisterWithService(EMPTY_REQUEST, request, registrations);
}
@GuardedBy("mLock")
@Override
protected boolean reregisterWithService(ProviderRequest oldRequest,
ProviderRequest newRequest, Collection<Registration> registrations) {
return registerWithService(newRequest, registrations);
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
if (mDelayedRegister != null) {
mAlarmHelper.cancel(mDelayedRegister);
mDelayedRegister = null;
}
// calculate how long the new request should be delayed before sending it off to the
// provider, under the assumption that once we send the request off, the provider will
// immediately attempt to deliver a new location satisfying that request.
long delayMs;
if (!oldRequest.isLocationSettingsIgnored() && newRequest.isLocationSettingsIgnored()) {
delayMs = 0;
} else if (newRequest.getIntervalMillis() > oldRequest.getIntervalMillis()) {
// if the interval has increased, tell the provider immediately, so it can save power
// (even though technically this could burn extra power in the short term by producing
// an extra location - the provider itself is free to detect an increasing interval and
// delay its own location)
delayMs = 0;
} else {
delayMs = calculateRequestDelayMillis(newRequest.getIntervalMillis(), registrations);
}
// the delay should never exceed the new interval
Preconditions.checkState(delayMs >= 0 && delayMs <= newRequest.getIntervalMillis());
if (delayMs < MIN_REQUEST_DELAY_MS) {
mProvider.setRequest(newRequest);
} else {
mDelayedRegister = new OnAlarmListener() {
@Override
public void onAlarm() {
synchronized (mLock) {
if (mDelayedRegister == this) {
mProvider.setRequest(newRequest);
mDelayedRegister = null;
}
}
}
};
mAlarmHelper.setDelayedAlarm(delayMs, mDelayedRegister, newRequest.getWorkSource());
}
return true;
}
@GuardedBy("mLock")
@@ -1733,42 +1882,40 @@ class LocationProviderManager extends
Preconditions.checkState(Thread.holdsLock(mLock));
}
ArrayList<Registration> providerRegistrations = new ArrayList<>(registrations.size());
long intervalMs = Long.MAX_VALUE;
boolean locationSettingsIgnored = false;
boolean lowPower = true;
ArrayList<LocationRequest> locationRequests = new ArrayList<>(registrations.size());
for (Registration registration : registrations) {
LocationRequest locationRequest = registration.getRequest();
// passive requests do not contribute to the provider
if (locationRequest.getIntervalMillis() == LocationRequest.PASSIVE_INTERVAL) {
for (Registration registration : registrations) {
LocationRequest request = registration.getRequest();
// passive requests do not contribute to the provider request
if (request.getIntervalMillis() == PASSIVE_INTERVAL) {
continue;
}
providerRegistrations.add(registration);
intervalMs = min(locationRequest.getIntervalMillis(), intervalMs);
locationSettingsIgnored |= locationRequest.isLocationSettingsIgnored();
lowPower &= locationRequest.isLowPower();
locationRequests.add(locationRequest);
intervalMs = min(request.getIntervalMillis(), intervalMs);
locationSettingsIgnored |= request.isLocationSettingsIgnored();
lowPower &= request.isLowPower();
locationRequests.add(request);
}
// 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
// represent differing power blame ratios).
WorkSource workSource = new WorkSource();
long thresholdIntervalMs = (intervalMs + 1000) * 3 / 2;
if (thresholdIntervalMs < 0) {
// handle overflow by setting to one below the passive interval
thresholdIntervalMs = Long.MAX_VALUE - 1;
if (thresholdIntervalMs < 0 || thresholdIntervalMs >= PASSIVE_INTERVAL) {
// check for and handle overflow by setting to one below the passive interval so passive
// requests are automatically skipped
thresholdIntervalMs = PASSIVE_INTERVAL - 1;
}
final int providerRegistrationsSize = providerRegistrations.size();
for (int i = 0; i < providerRegistrationsSize; i++) {
Registration registration = providerRegistrations.get(i);
WorkSource workSource = new WorkSource();
for (Registration registration : registrations) {
if (registration.getRequest().getIntervalMillis() <= thresholdIntervalMs) {
workSource.add(providerRegistrations.get(i).getWorkSource());
workSource.add(registration.getWorkSource());
}
}
@@ -1781,6 +1928,47 @@ class LocationProviderManager extends
.build();
}
@GuardedBy("mLock")
protected long calculateRequestDelayMillis(long newIntervalMs,
Collection<Registration> registrations) {
// calculate the minimum delay across all registrations, ensuring that it is not more than
// the requested interval
long delayMs = newIntervalMs;
for (Registration registration : registrations) {
if (delayMs == 0) {
break;
}
LocationRequest locationRequest = registration.getRequest();
Location last = registration.getLastDeliveredLocation();
if (last == null && !locationRequest.isLocationSettingsIgnored()) {
// if this request has never gotten any location and it's not ignoring location
// settings, then we pretend that this request has gotten the last applicable cached
// location for our calculations instead. this prevents spammy add/remove behavior
last = getLastLocationUnsafe(
registration.getIdentity().getUserId(),
PERMISSION_FINE,
false,
locationRequest.getIntervalMillis());
}
long registrationDelayMs;
if (last == null) {
// if this request has never gotten any location then there's no delay
registrationDelayMs = 0;
} else {
// otherwise the delay is the amount of time until the next location is expected
registrationDelayMs = max(0,
locationRequest.getIntervalMillis() - last.getElapsedRealtimeAgeMillis());
}
delayMs = min(delayMs, registrationDelayMs);
}
return delayMs;
}
private void onUserChanged(int userId, int change) {
synchronized (mLock) {
switch (change) {
@@ -2068,7 +2256,7 @@ class LocationProviderManager extends
ipw.increaseIndent();
}
ipw.print("last location=");
ipw.println(getLastLocationUnsafe(userId, PERMISSION_FINE, false));
ipw.println(getLastLocationUnsafe(userId, PERMISSION_FINE, false, Long.MAX_VALUE));
ipw.print("enabled=");
ipw.println(isEnabled(userId));
if (userIds.length != 1) {
@@ -2126,24 +2314,37 @@ class LocationProviderManager extends
}
}
public void set(Location location, Location coarseLocation) {
mFineLocation = location;
public void set(Location fineLocation, Location coarseLocation) {
mFineLocation = calculateNextFine(mFineLocation, fineLocation);
mCoarseLocation = calculateNextCoarse(mCoarseLocation, coarseLocation);
}
public void setBypass(Location location, Location coarseLocation) {
mFineBypassLocation = location;
public void setBypass(Location fineLocation, Location coarseLocation) {
mFineBypassLocation = calculateNextFine(mFineBypassLocation, fineLocation);
mCoarseBypassLocation = calculateNextCoarse(mCoarseBypassLocation, coarseLocation);
}
private Location calculateNextFine(@Nullable Location oldFine, Location newFine) {
if (oldFine == null) {
return newFine;
}
// update last fine interval only if more recent
if (newFine.getElapsedRealtimeNanos() > oldFine.getElapsedRealtimeNanos()) {
return newFine;
} else {
return oldFine;
}
}
private Location calculateNextCoarse(@Nullable Location oldCoarse, Location newCoarse) {
if (oldCoarse == null) {
return newCoarse;
}
// update last coarse interval only if enough time has passed
long timeDeltaMs = NANOSECONDS.toMillis(newCoarse.getElapsedRealtimeNanos())
- NANOSECONDS.toMillis(oldCoarse.getElapsedRealtimeNanos());
if (timeDeltaMs > FASTEST_COARSE_INTERVAL_MS) {
if (newCoarse.getElapsedRealtimeNanos() - MIN_COARSE_INTERVAL_MS
> oldCoarse.getElapsedRealtimeNanos()) {
return newCoarse;
} else {
return oldCoarse;

View File

@@ -20,14 +20,12 @@ import android.annotation.Nullable;
import android.content.Context;
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationRequest;
import android.os.Binder;
import com.android.internal.location.ProviderRequest;
import com.android.internal.util.Preconditions;
import com.android.server.location.util.Injector;
import java.util.ArrayList;
import java.util.Collection;
class PassiveLocationProviderManager extends LocationProviderManager {
@@ -65,17 +63,20 @@ class PassiveLocationProviderManager extends LocationProviderManager {
@Override
protected ProviderRequest mergeRegistrations(Collection<Registration> registrations) {
ProviderRequest.Builder providerRequest = new ProviderRequest.Builder()
.setIntervalMillis(0);
ArrayList<LocationRequest> requests = new ArrayList<>(registrations.size());
boolean locationSettingsIgnored = false;
for (Registration registration : registrations) {
requests.add(registration.getRequest());
if (registration.getRequest().isLocationSettingsIgnored()) {
providerRequest.setLocationSettingsIgnored(true);
}
locationSettingsIgnored |= registration.getRequest().isLocationSettingsIgnored();
}
return providerRequest.setLocationRequests(requests).build();
return new ProviderRequest.Builder()
.setIntervalMillis(0)
.setLocationSettingsIgnored(locationSettingsIgnored)
.build();
}
@Override
protected long calculateRequestDelayMillis(long newIntervalMs,
Collection<Registration> registrations) {
return 0;
}
}

View File

@@ -57,6 +57,8 @@ import java.util.function.Predicate;
* <li>{@link #onRegister()}</li>
* <li>{@link ListenerRegistration#onRegister(Object)}</li>
* <li>{@link #onRegistrationAdded(Object, ListenerRegistration)}</li>
* <li>{@link #onRegistrationReplaced(Object, ListenerRegistration, ListenerRegistration)} (only
* invoked if this registration is replacing a prior registration)</li>
* <li>{@link #onActive()}</li>
* <li>{@link ListenerRegistration#onActive()}</li>
* <li>{@link ListenerRegistration#onInactive()}</li>
@@ -182,6 +184,17 @@ public abstract class ListenerMultiplexer<TKey, TListener,
*/
protected void onRegistrationAdded(@NonNull TKey key, @NonNull TRegistration registration) {}
/**
* Invoked instead of {@link #onRegistrationAdded(Object, ListenerRegistration)} if a
* registration is replacing an old registration. The old registration will have already been
* unregistered. Invoked while holding the multiplexer's internal lock. The default behavior is
* simply to call into {@link #onRegistrationAdded(Object, ListenerRegistration)}.
*/
protected void onRegistrationReplaced(@NonNull TKey key, @NonNull TRegistration oldRegistration,
@NonNull TRegistration newRegistration) {
onRegistrationAdded(key, newRegistration);
}
/**
* Invoked when a registration is removed. Invoked while holding the multiplexer's internal
* lock.
@@ -227,9 +240,10 @@ public abstract class ListenerMultiplexer<TKey, TListener,
boolean wasEmpty = mRegistrations.isEmpty();
TRegistration oldRegistration = null;
int index = mRegistrations.indexOfKey(key);
if (index >= 0) {
removeRegistration(index, false);
oldRegistration = removeRegistration(index, false);
mRegistrations.setValueAt(index, registration);
} else {
mRegistrations.put(key, registration);
@@ -239,7 +253,11 @@ public abstract class ListenerMultiplexer<TKey, TListener,
onRegister();
}
registration.onRegister(key);
onRegistrationAdded(key, registration);
if (oldRegistration == null) {
onRegistrationAdded(key, registration);
} else {
onRegistrationReplaced(key, oldRegistration, registration);
}
onRegistrationActiveChanged(registration);
}
}
@@ -320,7 +338,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
}
@GuardedBy("mRegistrations")
private void removeRegistration(int index, boolean removeEntry) {
private TRegistration removeRegistration(int index, boolean removeEntry) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mRegistrations));
}
@@ -347,6 +365,8 @@ public abstract class ListenerMultiplexer<TKey, TListener,
}
}
}
return registration;
}
/**

View File

@@ -0,0 +1,47 @@
/*
* 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.util;
import android.app.AlarmManager.OnAlarmListener;
import android.os.WorkSource;
import com.android.internal.util.Preconditions;
/**
* Helps manage alarms.
*/
public abstract class AlarmHelper {
/**
* Sets a wakeup alarm that will fire after the given delay.
*/
public final void setDelayedAlarm(long delayMs, OnAlarmListener listener,
WorkSource workSource) {
// helps ensure that we're not wasting system resources by setting alarms in the past/now
Preconditions.checkArgument(delayMs > 0);
Preconditions.checkArgument(workSource != null);
setDelayedAlarmInternal(delayMs, listener, workSource);
}
protected abstract void setDelayedAlarmInternal(long delayMs, OnAlarmListener listener,
WorkSource workSource);
/**
* Cancels an alarm.
*/
public abstract void cancel(OnAlarmListener listener);
}

View File

@@ -28,6 +28,9 @@ public interface Injector {
/** Returns a UserInfoHelper. */
UserInfoHelper getUserInfoHelper();
/** Returns an AlarmHelper. */
AlarmHelper getAlarmHelper();
/** Returns an AppOpsHelper. */
AppOpsHelper getAppOpsHelper();

View File

@@ -0,0 +1,57 @@
/*
* 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.util;
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
import static android.app.AlarmManager.WINDOW_EXACT;
import android.app.AlarmManager;
import android.content.Context;
import android.os.SystemClock;
import android.os.WorkSource;
import com.android.server.FgThread;
import java.util.Objects;
/**
* Provides helpers for alarms.
*/
public class SystemAlarmHelper extends AlarmHelper {
private final Context mContext;
public SystemAlarmHelper(Context context) {
mContext = context;
}
@Override
public void setDelayedAlarmInternal(long delayMs, AlarmManager.OnAlarmListener listener,
WorkSource workSource) {
AlarmManager alarmManager = Objects.requireNonNull(
mContext.getSystemService(AlarmManager.class));
alarmManager.set(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delayMs,
WINDOW_EXACT, 0, listener, FgThread.getHandler(), workSource);
}
@Override
public void cancel(AlarmManager.OnAlarmListener listener) {
AlarmManager alarmManager = Objects.requireNonNull(
mContext.getSystemService(AlarmManager.class));
alarmManager.cancel(listener);
}
}

View File

@@ -17,7 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.mockingservicestests">
<uses-sdk android:targetSdkVersion="30" />
<uses-sdk android:targetSdkVersion="31" />
<uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
<uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>

View File

@@ -16,8 +16,6 @@
package com.android.server.location;
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
import static android.app.AlarmManager.WINDOW_EXACT;
import static android.app.AppOpsManager.OP_FINE_LOCATION;
import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
@@ -41,7 +39,6 @@ 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;
@@ -55,8 +52,6 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.MockitoAnnotations.initMocks;
import android.app.AlarmManager;
import android.app.AlarmManager.OnAlarmListener;
import android.content.Context;
import android.location.ILocationCallback;
import android.location.ILocationListener;
@@ -66,14 +61,11 @@ import android.location.LocationManagerInternal.ProviderEnabledListener;
import android.location.LocationRequest;
import android.location.util.identity.CallerIdentity;
import android.os.Bundle;
import android.os.Handler;
import android.os.ICancellationSignal;
import android.os.IRemoteCallback;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.WorkSource;
import android.platform.test.annotations.Presubmit;
import android.util.Log;
@@ -127,8 +119,6 @@ public class LocationProviderManagerTest {
@Mock
private Context mContext;
@Mock
private AlarmManager mAlarmManager;
@Mock
private PowerManager mPowerManager;
@Mock
private PowerManager.WakeLock mWakeLock;
@@ -151,7 +141,6 @@ public class LocationProviderManagerTest {
LocalServices.addService(LocationManagerInternal.class, mInternal);
doReturn("android").when(mContext).getPackageName();
doReturn(mAlarmManager).when(mContext).getSystemService(AlarmManager.class);
doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
@@ -505,19 +494,8 @@ public class LocationProviderManagerTest {
ILocationListener listener = createMockLocationListener();
LocationRequest request = new LocationRequest.Builder(0).setDurationMillis(5000).build();
mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
long baseTimeMs = SystemClock.elapsedRealtime();
ArgumentCaptor<Long> timeoutCapture = ArgumentCaptor.forClass(Long.class);
ArgumentCaptor<OnAlarmListener> listenerCapture = ArgumentCaptor.forClass(
OnAlarmListener.class);
verify(mAlarmManager).set(eq(ELAPSED_REALTIME_WAKEUP), timeoutCapture.capture(),
eq(WINDOW_EXACT), eq(0L), listenerCapture.capture(), any(Handler.class),
any(WorkSource.class));
assertThat(timeoutCapture.getValue()).isAtLeast(baseTimeMs + 4000);
assertThat(timeoutCapture.getValue()).isAtMost(baseTimeMs + 5000);
listenerCapture.getValue().onAlarm();
mInjector.getAlarmHelper().incrementAlarmTime(5000);
mProvider.setProviderLocation(createLocation(NAME, mRandom));
verify(listener, never()).onLocationChanged(any(Location.class),
nullable(IRemoteCallback.class));
@@ -684,13 +662,7 @@ public class LocationProviderManagerTest {
LocationRequest locationRequest = new LocationRequest.Builder(0).build();
mManager.getCurrentLocation(locationRequest, IDENTITY, PERMISSION_FINE, listener);
ArgumentCaptor<OnAlarmListener> listenerCapture = ArgumentCaptor.forClass(
OnAlarmListener.class);
verify(mAlarmManager).set(eq(ELAPSED_REALTIME_WAKEUP), anyLong(),
eq(WINDOW_EXACT), eq(0L), listenerCapture.capture(), any(Handler.class),
any(WorkSource.class));
listenerCapture.getValue().onAlarm();
mInjector.getAlarmHelper().incrementAlarmTime(60000);
verify(listener, times(1)).onLocation(isNull());
}
@@ -768,6 +740,40 @@ public class LocationProviderManagerTest {
assertThat(mProvider.getRequest().getLocationRequests()).isEmpty();
}
@Test
public void testProviderRequest_DelayedRequest() throws Exception {
mProvider.setProviderLocation(createLocation(NAME, mRandom));
ILocationListener listener1 = createMockLocationListener();
LocationRequest request1 = new LocationRequest.Builder(60000).build();
mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
verify(listener1).onLocationChanged(any(Location.class), nullable(IRemoteCallback.class));
assertThat(mProvider.getRequest().isActive()).isFalse();
mInjector.getAlarmHelper().incrementAlarmTime(60000);
assertThat(mProvider.getRequest().isActive()).isTrue();
assertThat(mProvider.getRequest().getIntervalMillis()).isEqualTo(60000);
}
@Test
public void testProviderRequest_SpamRequesting() {
mProvider.setProviderLocation(createLocation(NAME, mRandom));
ILocationListener listener1 = createMockLocationListener();
LocationRequest request1 = new LocationRequest.Builder(60000).build();
mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
assertThat(mProvider.getRequest().isActive()).isFalse();
mManager.unregisterLocationRequest(listener1);
assertThat(mProvider.getRequest().isActive()).isFalse();
mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
assertThat(mProvider.getRequest().isActive()).isFalse();
mManager.unregisterLocationRequest(listener1);
assertThat(mProvider.getRequest().isActive()).isFalse();
}
@Test
public void testProviderRequest_BackgroundThrottle() {
ILocationListener listener1 = createMockLocationListener();

View File

@@ -0,0 +1,61 @@
/*
* 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.util;
import android.app.AlarmManager.OnAlarmListener;
import android.os.WorkSource;
import java.util.ArrayList;
import java.util.Iterator;
public class FakeAlarmHelper extends AlarmHelper {
private static class Alarm {
public long delayMs;
public final OnAlarmListener listener;
Alarm(long delayMs, OnAlarmListener listener) {
this.delayMs = delayMs;
this.listener = listener;
}
}
private final ArrayList<Alarm> mAlarms = new ArrayList<>();
@Override
public void setDelayedAlarmInternal(long delayMs, OnAlarmListener listener,
WorkSource workSource) {
mAlarms.add(new Alarm(delayMs, listener));
}
@Override
public void cancel(OnAlarmListener listener) {
mAlarms.removeIf(alarm -> alarm.listener == listener);
}
public void incrementAlarmTime(long incrementMs) {
Iterator<Alarm> it = mAlarms.iterator();
while (it.hasNext()) {
Alarm alarm = it.next();
alarm.delayMs -= incrementMs;
if (alarm.delayMs <= 0) {
it.remove();
alarm.listener.onAlarm();
}
}
}
}

View File

@@ -21,6 +21,7 @@ import com.android.server.location.LocationRequestStatistics;
public class TestInjector implements Injector {
private final FakeUserInfoHelper mUserInfoHelper;
private final FakeAlarmHelper mAlarmHelper;
private final FakeAppOpsHelper mAppOpsHelper;
private final FakeLocationPermissionsHelper mLocationPermissionsHelper;
private final FakeSettingsHelper mSettingsHelper;
@@ -33,6 +34,7 @@ public class TestInjector implements Injector {
public TestInjector() {
mUserInfoHelper = new FakeUserInfoHelper();
mAlarmHelper = new FakeAlarmHelper();
mAppOpsHelper = new FakeAppOpsHelper();
mLocationPermissionsHelper = new FakeLocationPermissionsHelper(mAppOpsHelper);
mSettingsHelper = new FakeSettingsHelper();
@@ -49,6 +51,11 @@ public class TestInjector implements Injector {
return mUserInfoHelper;
}
@Override
public FakeAlarmHelper getAlarmHelper() {
return mAlarmHelper;
}
@Override
public FakeAppOpsHelper getAppOpsHelper() {
return mAppOpsHelper;