diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java index 9aa0c870e5126..46bd22148fb2e 100644 --- a/location/java/android/location/Location.java +++ b/location/java/android/location/Location.java @@ -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. * diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 6e597b2c1d631..ff004094ec59d 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -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. * - *

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. + *

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. * *

See {@link LocationRequest} documentation for an explanation of various request parameters * and how they can affect the received locations. * - *

If your application wants to passively observe location updates from any provider, then + *

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 { * *

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. * - *

When location callbacks are invoked, the system will hold a wakelock on your + *

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. * + *

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. + * *

To unregister for location updates, use {@link #removeUpdates(LocationListener)}. * * @param provider a provider listed by {@link #getAllProviders()} diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java index 0521b10a2530a..c57794f0f04a0 100644 --- a/location/java/android/location/LocationRequest.java +++ b/location/java/android/location/LocationRequest.java @@ -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. 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 e05bd3c22ae86..d3aa977f85b1a 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 @@ -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; diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 72734c47873eb..0329c3c78e20c 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -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; diff --git a/services/core/java/com/android/server/location/LocationProviderManager.java b/services/core/java/com/android/server/location/LocationProviderManager.java index 138301ae934d5..cd8bf4a0154d3 100644 --- a/services/core/java/com/android/server/location/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/LocationProviderManager.java @@ -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 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 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 providerRegistrations = new ArrayList<>(registrations.size()); - long intervalMs = Long.MAX_VALUE; boolean locationSettingsIgnored = false; boolean lowPower = true; ArrayList 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 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; diff --git a/services/core/java/com/android/server/location/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/PassiveLocationProviderManager.java index afeb6444c40b2..2870d41e5248f 100644 --- a/services/core/java/com/android/server/location/PassiveLocationProviderManager.java +++ b/services/core/java/com/android/server/location/PassiveLocationProviderManager.java @@ -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 registrations) { - ProviderRequest.Builder providerRequest = new ProviderRequest.Builder() - .setIntervalMillis(0); - - ArrayList 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 registrations) { + return 0; } } 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 87d668a07d703..0cda57cf1936a 100644 --- a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java +++ b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java @@ -57,6 +57,8 @@ import java.util.function.Predicate; *

  • {@link #onRegister()}
  • *
  • {@link ListenerRegistration#onRegister(Object)}
  • *
  • {@link #onRegistrationAdded(Object, ListenerRegistration)}
  • + *
  • {@link #onRegistrationReplaced(Object, ListenerRegistration, ListenerRegistration)} (only + * invoked if this registration is replacing a prior registration)
  • *
  • {@link #onActive()}
  • *
  • {@link ListenerRegistration#onActive()}
  • *
  • {@link ListenerRegistration#onInactive()}
  • @@ -182,6 +184,17 @@ public abstract class ListenerMultiplexer= 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 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); +} diff --git a/services/core/java/com/android/server/location/util/Injector.java b/services/core/java/com/android/server/location/util/Injector.java index 379b303bbfc34..d9c73ba464441 100644 --- a/services/core/java/com/android/server/location/util/Injector.java +++ b/services/core/java/com/android/server/location/util/Injector.java @@ -28,6 +28,9 @@ public interface Injector { /** Returns a UserInfoHelper. */ UserInfoHelper getUserInfoHelper(); + /** Returns an AlarmHelper. */ + AlarmHelper getAlarmHelper(); + /** Returns an AppOpsHelper. */ AppOpsHelper getAppOpsHelper(); diff --git a/services/core/java/com/android/server/location/util/SystemAlarmHelper.java b/services/core/java/com/android/server/location/util/SystemAlarmHelper.java new file mode 100644 index 0000000000000..81849794c472f --- /dev/null +++ b/services/core/java/com/android/server/location/util/SystemAlarmHelper.java @@ -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); + } +} diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml index a398961db4c3e..182fe9ae94767 100644 --- a/services/tests/mockingservicestests/AndroidManifest.xml +++ b/services/tests/mockingservicestests/AndroidManifest.xml @@ -17,7 +17,7 @@ - + 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 b4e8825c3902f..be258dc5963ec 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java @@ -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 timeoutCapture = ArgumentCaptor.forClass(Long.class); - ArgumentCaptor 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 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(); diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAlarmHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAlarmHelper.java new file mode 100644 index 0000000000000..0e3e6ef02a18c --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAlarmHelper.java @@ -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 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 it = mAlarms.iterator(); + while (it.hasNext()) { + Alarm alarm = it.next(); + alarm.delayMs -= incrementMs; + if (alarm.delayMs <= 0) { + it.remove(); + alarm.listener.onAlarm(); + } + } + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java index 1867be0b9f3b9..69f7376a43098 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java @@ -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;