summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/com/android/internal/listeners/ListenerExecutor.java16
-rw-r--r--location/java/android/location/GpsStatus.java10
-rw-r--r--location/java/android/location/ILocationListener.aidl2
-rw-r--r--location/java/android/location/ILocationManager.aidl2
-rw-r--r--location/java/android/location/LocationListener.java15
-rw-r--r--location/java/android/location/LocationManager.java51
-rw-r--r--services/core/java/com/android/server/location/LocationManagerService.java88
-rw-r--r--services/core/java/com/android/server/location/PassiveProvider.java12
-rw-r--r--services/core/java/com/android/server/location/geofence/GeofenceManager.java84
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java73
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssManagerService.java12
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java8
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java8
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssStatusProvider.java11
-rw-r--r--services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java80
-rw-r--r--services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java2
-rw-r--r--services/core/java/com/android/server/location/util/AppOpsHelper.java84
-rw-r--r--services/core/java/com/android/server/location/util/Injector.java17
-rw-r--r--services/core/java/com/android/server/location/util/LocationAttributionHelper.java24
-rw-r--r--services/core/java/com/android/server/location/util/LocationPermissionsHelper.java107
-rw-r--r--services/core/java/com/android/server/location/util/LocationPowerSaveModeHelper.java71
-rw-r--r--services/core/java/com/android/server/location/util/ScreenInteractiveHelper.java67
-rw-r--r--services/core/java/com/android/server/location/util/SystemAppOpsHelper.java10
-rw-r--r--services/core/java/com/android/server/location/util/SystemLocationPermissionsHelper.java65
-rw-r--r--services/core/java/com/android/server/location/util/SystemLocationPowerSaveModeHelper.java87
-rw-r--r--services/core/java/com/android/server/location/util/SystemScreenInteractiveHelper.java85
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/util/FakeAppOpsHelper.java10
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPermissionsHelper.java56
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPowerSaveModeHelper.java49
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/util/FakeScreenInteractiveHelper.java42
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/util/FakeSettingsHelper.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/util/FakeUserInfoHelper.java15
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/util/LocationAttributionHelperTest.java56
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/util/SystemAppOpsHelperTest.java73
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/util/SystemLocationPowerSaveModeHelperTest.java170
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java38
36 files changed, 1247 insertions, 355 deletions
diff --git a/core/java/com/android/internal/listeners/ListenerExecutor.java b/core/java/com/android/internal/listeners/ListenerExecutor.java
index e78e32b829b3..9979e6056f50 100644
--- a/core/java/com/android/internal/listeners/ListenerExecutor.java
+++ b/core/java/com/android/internal/listeners/ListenerExecutor.java
@@ -40,7 +40,7 @@ public interface ListenerExecutor {
/**
* Called before this operation is to be run. Some operations may be canceled before they
* are run, in which case this method may not be called. {@link #onPostExecute(boolean)}
- * will only be run if this method was run.
+ * will only be run if this method was run. This callback is invoked on the calling thread.
*/
default void onPreExecute() {}
@@ -49,7 +49,7 @@ public interface ListenerExecutor {
* RuntimeException, which will propagate normally. Implementations of
* {@link ListenerExecutor} have the option to override
* {@link ListenerExecutor#onOperationFailure(ListenerOperation, Exception)} instead to
- * intercept failures at the class level.
+ * intercept failures at the class level. This callback is invoked on the executor thread.
*/
default void onFailure(Exception e) {
// implementations should handle any exceptions that may be thrown
@@ -59,21 +59,24 @@ public interface ListenerExecutor {
/**
* Called after the operation is run. This method will always be called if
* {@link #onPreExecute()} is called. Success implies that the operation was run to
- * completion with no failures.
+ * completion with no failures. This callback may be invoked on the calling thread or
+ * executor thread.
*/
default void onPostExecute(boolean success) {}
/**
* Called after this operation is complete (which does not imply that it was necessarily
* run). Will always be called once per operation, no matter if the operation was run or
- * not. Success implies that the operation was run to completion with no failures.
+ * not. Success implies that the operation was run to completion with no failures. This
+ * callback may be invoked on the calling thread or executor thread.
*/
default void onComplete(boolean success) {}
}
/**
* May be override to handle operation failures at a class level. Will not be invoked in the
- * event of a RuntimeException, which will propagate normally.
+ * event of a RuntimeException, which will propagate normally. This callback is invoked on the
+ * executor thread.
*/
default <TListener> void onOperationFailure(ListenerOperation<TListener> operation,
Exception exception) {
@@ -83,7 +86,8 @@ public interface ListenerExecutor {
/**
* Executes the given listener operation on the given executor, using the provided listener
* supplier. If the supplier returns a null value, or a value during the operation that does not
- * match the value prior to the operation, then the operation is considered canceled.
+ * match the value prior to the operation, then the operation is considered canceled. If a null
+ * operation is supplied, nothing happens.
*/
default <TListener> void executeSafely(Executor executor, Supplier<TListener> listenerSupplier,
@Nullable ListenerOperation<TListener> operation) {
diff --git a/location/java/android/location/GpsStatus.java b/location/java/android/location/GpsStatus.java
index 496885cd1f37..997339eb2a80 100644
--- a/location/java/android/location/GpsStatus.java
+++ b/location/java/android/location/GpsStatus.java
@@ -151,6 +151,16 @@ public final class GpsStatus {
return status;
}
+ /**
+ * Builds an empty GpsStatus. Should only be used for legacy reasons.
+ *
+ * @hide
+ */
+ @NonNull
+ static GpsStatus createEmpty() {
+ return new GpsStatus();
+ }
+
private GpsStatus() {
}
diff --git a/location/java/android/location/ILocationListener.aidl b/location/java/android/location/ILocationListener.aidl
index 6e7f6a52d669..29b483af8721 100644
--- a/location/java/android/location/ILocationListener.aidl
+++ b/location/java/android/location/ILocationListener.aidl
@@ -24,6 +24,6 @@ import android.os.IRemoteCallback;
*/
oneway interface ILocationListener
{
- void onLocationChanged(in Location location, in IRemoteCallback onCompleteCallback);
+ void onLocationChanged(in Location location, in @nullable IRemoteCallback onCompleteCallback);
void onProviderEnabledChanged(String provider, boolean enabled);
}
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index bb8f81dfaa32..0e7eaa21888e 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -113,7 +113,7 @@ interface ILocationManager
List<LocationRequest> getTestProviderCurrentRequests(String provider);
LocationTime getGnssTimeMillis();
- boolean sendExtraCommand(String provider, String command, inout Bundle extras);
+ void sendExtraCommand(String provider, String command, inout Bundle extras);
// used by gts tests to verify whitelists
String[] getBackgroundThrottlingWhitelist();
diff --git a/location/java/android/location/LocationListener.java b/location/java/android/location/LocationListener.java
index 8df08345c79b..2738ff4ff38c 100644
--- a/location/java/android/location/LocationListener.java
+++ b/location/java/android/location/LocationListener.java
@@ -36,7 +36,9 @@ import android.os.Bundle;
public interface LocationListener {
/**
- * Called when the location has changed.
+ * Called when the location has changed. A wakelock is held on behalf on the listener for some
+ * brief amount of time as this callback executes. If this callback performs long running
+ * operations, it is the client's responsibility to obtain their own wakelock.
*
* @param location the updated location
*/
@@ -52,18 +54,17 @@ public interface LocationListener {
default void onStatusChanged(String provider, int status, Bundle extras) {}
/**
- * Called when the provider is enabled by the user.
+ * Called when a provider this listener is registered with becomes enabled.
*
- * @param provider the name of the location provider that has become enabled
+ * @param provider the name of the location provider
*/
default void onProviderEnabled(@NonNull String provider) {}
/**
- * Called when the provider is disabled by the user. If requestLocationUpdates
- * is called on an already disabled provider, this method is called
- * immediately.
+ * Called when the provider this listener is registered with becomes disabled. If a provider is
+ * disabled when this listener is registered, this callback will be invoked immediately.
*
- * @param provider the name of the location provider that has become disabled
+ * @param provider the name of the location provider
*/
default void onProviderDisabled(@NonNull String provider) {}
}
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index c0b8e1bf3bbe..1803027743f6 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -303,7 +303,6 @@ public class LocationManager {
public static final String METADATA_SETTINGS_FOOTER_STRING =
"com.android.settings.location.FOOTER_STRING";
-
private static final long MAX_SINGLE_LOCATION_TIMEOUT_MS = 30 * 1000;
@GuardedBy("sLocationListeners")
@@ -311,7 +310,9 @@ public class LocationManager {
sLocationListeners = new WeakHashMap<>();
final Context mContext;
- @UnsupportedAppUsage
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "{@link "
+ + "LocationManager}")
final ILocationManager mService;
private final Object mLock = new Object();
@@ -421,8 +422,7 @@ public class LocationManager {
try {
return mService.getExtraLocationControllerPackage();
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
- return null;
+ throw e.rethrowFromSystemServer();
}
}
@@ -437,7 +437,7 @@ public class LocationManager {
try {
mService.setExtraLocationControllerPackage(packageName);
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ throw e.rethrowFromSystemServer();
}
}
@@ -452,7 +452,7 @@ public class LocationManager {
try {
mService.setExtraLocationControllerPackageEnabled(enabled);
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ throw e.rethrowFromSystemServer();
}
}
@@ -466,8 +466,7 @@ public class LocationManager {
try {
return mService.isExtraLocationControllerPackageEnabled();
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
- return false;
+ throw e.rethrowFromSystemServer();
}
}
@@ -485,7 +484,7 @@ public class LocationManager {
try {
mService.setExtraLocationControllerPackage(packageName);
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ throw e.rethrowFromSystemServer();
}
}
@@ -503,7 +502,7 @@ public class LocationManager {
try {
mService.setExtraLocationControllerPackageEnabled(enabled);
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ throw e.rethrowFromSystemServer();
}
}
@@ -1199,7 +1198,7 @@ public class LocationManager {
mContext.getPackageName(), mContext.getAttributionTag(),
AppOpsManager.toReceiverId(listener));
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ throw e.rethrowFromSystemServer();
}
}
}
@@ -1301,7 +1300,7 @@ public class LocationManager {
// unregistration is complete.
mService.unregisterLocationListener(transport);
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ throw e.rethrowFromSystemServer();
}
}
}
@@ -1517,7 +1516,8 @@ public class LocationManager {
Preconditions.checkArgument(command != null, "invalid null command");
try {
- return mService.sendExtraCommand(provider, command, extras);
+ mService.sendExtraCommand(provider, command, extras);
+ return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1835,6 +1835,10 @@ public class LocationManager {
} else {
status.setStatus(gnssStatus, ttff);
}
+ } else if (status == null) {
+ // even though this method is marked as nullable, legacy behavior was to never return
+ // a null result, and there are applications that rely on this behavior.
+ status = GpsStatus.createEmpty();
}
return status;
}
@@ -2424,7 +2428,7 @@ public class LocationManager {
try {
cancellationSignal.cancel();
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ throw e.rethrowFromSystemServer();
}
}
}
@@ -2464,7 +2468,8 @@ public class LocationManager {
}
@Override
- public void onLocationChanged(Location location, IRemoteCallback onCompleteCallback) {
+ public void onLocationChanged(Location location,
+ @Nullable IRemoteCallback onCompleteCallback) {
executeSafely(mExecutor, () -> mListener, new ListenerOperation<LocationListener>() {
@Override
public void operate(LocationListener listener) {
@@ -2473,7 +2478,13 @@ public class LocationManager {
@Override
public void onComplete(boolean success) {
- markComplete(onCompleteCallback);
+ if (onCompleteCallback != null) {
+ try {
+ onCompleteCallback.sendResult(null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
});
}
@@ -2488,14 +2499,6 @@ public class LocationManager {
}
});
}
-
- private void markComplete(IRemoteCallback onCompleteCallback) {
- try {
- onCompleteCallback.sendResult(null);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- }
}
private static class NmeaAdapter extends GnssStatus.Callback implements OnNmeaMessageListener {
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index f69c8239762d..d933c109b27d 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -17,6 +17,9 @@
package com.android.server.location;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.app.AppOpsManager.OP_MOCK_LOCATION;
+import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
+import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -120,10 +123,16 @@ import com.android.server.location.util.AppForegroundHelper;
import com.android.server.location.util.AppOpsHelper;
import com.android.server.location.util.Injector;
import com.android.server.location.util.LocationAttributionHelper;
+import com.android.server.location.util.LocationPermissionsHelper;
+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.SystemAppForegroundHelper;
import com.android.server.location.util.SystemAppOpsHelper;
+import com.android.server.location.util.SystemLocationPermissionsHelper;
+import com.android.server.location.util.SystemLocationPowerSaveModeHelper;
+import com.android.server.location.util.SystemScreenInteractiveHelper;
import com.android.server.location.util.SystemSettingsHelper;
import com.android.server.location.util.SystemUserInfoHelper;
import com.android.server.location.util.UserInfoHelper;
@@ -173,7 +182,7 @@ public class LocationManagerService extends ILocationManager.Stub {
publishBinderService(Context.LOCATION_SERVICE, mService);
// client caching behavior is only enabled after seeing the first invalidate
- invalidateLocalLocationEnabledCaches();
+ LocationManager.invalidateLocalLocationEnabledCaches();
// disable caching for our own process
Objects.requireNonNull(mService.mContext.getSystemService(LocationManager.class))
.disableLocalLocationEnabledCaches();
@@ -486,7 +495,7 @@ public class LocationManagerService extends ILocationManager.Stub {
private void onLocationModeChanged(int userId) {
boolean enabled = mSettingsHelper.isLocationEnabled(userId);
- invalidateLocalLocationEnabledCaches();
+ LocationManager.invalidateLocalLocationEnabledCaches();
if (D) {
Log.d(TAG, "[u" + userId + "] location enabled = " + enabled);
@@ -1232,19 +1241,20 @@ public class LocationManagerService extends ILocationManager.Stub {
if (!currentlyMonitoring) {
if (allowMonitoring) {
if (!highPower) {
- return mAppOpsHelper.startLocationMonitoring(mCallerIdentity);
+ return mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, mCallerIdentity);
} else {
- return mAppOpsHelper.startHighPowerLocationMonitoring(mCallerIdentity);
+ return mAppOpsHelper.startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION,
+ mCallerIdentity);
}
}
} else {
- if (!allowMonitoring || !mAppOpsHelper.checkLocationAccess(mCallerIdentity,
+ if (!allowMonitoring || !mAppOpsHelper.checkOpNoThrow(LocationPermissions.asAppOp(
LocationPermissions.getPermissionLevel(mContext, mCallerIdentity.getUid(),
- mCallerIdentity.getPid()))) {
+ mCallerIdentity.getPid())), mCallerIdentity)) {
if (!highPower) {
- mAppOpsHelper.stopLocationMonitoring(mCallerIdentity);
+ mAppOpsHelper.finishOp(OP_MONITOR_LOCATION, mCallerIdentity);
} else {
- mAppOpsHelper.stopHighPowerLocationMonitoring(mCallerIdentity);
+ mAppOpsHelper.finishOp(OP_MONITOR_HIGH_POWER_LOCATION, mCallerIdentity);
}
return false;
}
@@ -1589,8 +1599,9 @@ public class LocationManagerService extends ILocationManager.Stub {
continue;
}
- if (!mAppOpsHelper.checkLocationAccess(identity,
- record.mRequest.isCoarse() ? PERMISSION_COARSE : PERMISSION_FINE)) {
+ if (!mAppOpsHelper.checkOpNoThrow(LocationPermissions.asAppOp(
+ record.mRequest.isCoarse() ? PERMISSION_COARSE : PERMISSION_FINE),
+ identity)) {
continue;
}
final boolean isBatterySaverDisablingLocation = shouldThrottleRequests
@@ -2118,7 +2129,8 @@ public class LocationManagerService extends ILocationManager.Stub {
}
// appops check should always be right before delivery
- if (!mAppOpsHelper.noteLocationAccess(identity, permissionLevel)) {
+ if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel),
+ identity)) {
return null;
}
@@ -2179,7 +2191,8 @@ public class LocationManagerService extends ILocationManager.Stub {
if (locationAgeMs < MAX_CURRENT_LOCATION_AGE_MS) {
// appops check should always be right before delivery
- if (mAppOpsHelper.noteLocationAccess(identity, permissionLevel)) {
+ if (mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel),
+ identity)) {
transport.deliverResult(lastLocation);
} else {
transport.deliverResult(null);
@@ -2329,7 +2342,7 @@ public class LocationManagerService extends ILocationManager.Stub {
}
@Override
- public boolean sendExtraCommand(String provider, String command, Bundle extras) {
+ public void sendExtraCommand(String provider, String command, Bundle extras) {
LocationPermissions.enforceCallingOrSelfLocationPermission(mContext, PERMISSION_COARSE);
mContext.enforceCallingOrSelfPermission(
permission.ACCESS_LOCATION_EXTRA_COMMANDS, null);
@@ -2350,8 +2363,6 @@ public class LocationManagerService extends ILocationManager.Stub {
LocationStatsEnums.USAGE_ENDED,
LocationStatsEnums.API_SEND_EXTRA_COMMAND,
provider);
-
- return true;
}
@Override
@@ -2553,7 +2564,8 @@ public class LocationManagerService extends ILocationManager.Stub {
r.mLastFixBroadcast = location;
// appops check should always be right before delivery
- if (!mAppOpsHelper.noteLocationAccess(receiver.mCallerIdentity, permissionLevel)) {
+ if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel),
+ receiver.mCallerIdentity)) {
continue;
}
@@ -2644,7 +2656,7 @@ public class LocationManagerService extends ILocationManager.Stub {
// unsafe is ok because app ops will verify the package name
CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName,
attributionTag);
- if (!mAppOpsHelper.noteMockLocationAccess(identity)) {
+ if (!mAppOpsHelper.noteOp(OP_MOCK_LOCATION, identity)) {
return;
}
@@ -2664,7 +2676,7 @@ public class LocationManagerService extends ILocationManager.Stub {
// unsafe is ok because app ops will verify the package name
CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName,
attributionTag);
- if (!mAppOpsHelper.noteMockLocationAccess(identity)) {
+ if (!mAppOpsHelper.noteOp(OP_MOCK_LOCATION, identity)) {
return;
}
@@ -2687,7 +2699,7 @@ public class LocationManagerService extends ILocationManager.Stub {
// unsafe is ok because app ops will verify the package name
CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName,
attributionTag);
- if (!mAppOpsHelper.noteMockLocationAccess(identity)) {
+ if (!mAppOpsHelper.noteOp(OP_MOCK_LOCATION, identity)) {
return;
}
@@ -2708,7 +2720,7 @@ public class LocationManagerService extends ILocationManager.Stub {
// unsafe is ok because app ops will verify the package name
CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName,
attributionTag);
- if (!mAppOpsHelper.noteMockLocationAccess(identity)) {
+ if (!mAppOpsHelper.noteOp(OP_MOCK_LOCATION, identity)) {
return;
}
@@ -2926,24 +2938,36 @@ public class LocationManagerService extends ILocationManager.Stub {
private final UserInfoHelper mUserInfoHelper;
private final SystemAppOpsHelper mAppOpsHelper;
+ private final SystemLocationPermissionsHelper mLocationPermissionsHelper;
private final SystemSettingsHelper mSettingsHelper;
private final SystemAppForegroundHelper mAppForegroundHelper;
- private final LocationUsageLogger mLocationUsageLogger;
+ private final SystemLocationPowerSaveModeHelper mLocationPowerSaveModeHelper;
+ private final SystemScreenInteractiveHelper mScreenInteractiveHelper;
private final LocationAttributionHelper mLocationAttributionHelper;
+ private final LocationUsageLogger mLocationUsageLogger;
+ private final LocationRequestStatistics mLocationRequestStatistics;
SystemInjector(Context context, UserInfoHelper userInfoHelper) {
mUserInfoHelper = userInfoHelper;
mAppOpsHelper = new SystemAppOpsHelper(context);
+ mLocationPermissionsHelper = new SystemLocationPermissionsHelper(context,
+ mAppOpsHelper);
mSettingsHelper = new SystemSettingsHelper(context);
mAppForegroundHelper = new SystemAppForegroundHelper(context);
- mLocationUsageLogger = new LocationUsageLogger();
+ mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context);
+ mScreenInteractiveHelper = new SystemScreenInteractiveHelper(context);
mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper);
+ mLocationUsageLogger = new LocationUsageLogger();
+ mLocationRequestStatistics = new LocationRequestStatistics();
}
void onSystemReady() {
mAppOpsHelper.onSystemReady();
+ mLocationPermissionsHelper.onSystemReady();
mSettingsHelper.onSystemReady();
mAppForegroundHelper.onSystemReady();
+ mLocationPowerSaveModeHelper.onSystemReady();
+ mScreenInteractiveHelper.onSystemReady();
}
@Override
@@ -2957,6 +2981,11 @@ public class LocationManagerService extends ILocationManager.Stub {
}
@Override
+ public LocationPermissionsHelper getLocationPermissionsHelper() {
+ return mLocationPermissionsHelper;
+ }
+
+ @Override
public SettingsHelper getSettingsHelper() {
return mSettingsHelper;
}
@@ -2972,8 +3001,23 @@ public class LocationManagerService extends ILocationManager.Stub {
}
@Override
+ public LocationPowerSaveModeHelper getLocationPowerSaveModeHelper() {
+ return mLocationPowerSaveModeHelper;
+ }
+
+ @Override
+ public ScreenInteractiveHelper getScreenInteractiveHelper() {
+ return mScreenInteractiveHelper;
+ }
+
+ @Override
public LocationAttributionHelper getLocationAttributionHelper() {
return mLocationAttributionHelper;
}
+
+ @Override
+ public LocationRequestStatistics getLocationRequestStatistics() {
+ return mLocationRequestStatistics;
+ }
}
}
diff --git a/services/core/java/com/android/server/location/PassiveProvider.java b/services/core/java/com/android/server/location/PassiveProvider.java
index f37992a456ac..f6896b86f9b9 100644
--- a/services/core/java/com/android/server/location/PassiveProvider.java
+++ b/services/core/java/com/android/server/location/PassiveProvider.java
@@ -50,14 +50,10 @@ public class PassiveProvider extends AbstractLocationProvider {
Criteria.POWER_LOW,
Criteria.ACCURACY_COARSE);
- private volatile boolean mReportLocation;
-
public PassiveProvider(Context context) {
// using a direct executor is ok because this class has no locks that could deadlock
super(DIRECT_EXECUTOR, CallerIdentity.fromContext(context));
- mReportLocation = false;
-
setProperties(PROPERTIES);
setAllowed(true);
}
@@ -66,15 +62,11 @@ public class PassiveProvider extends AbstractLocationProvider {
* Pass a location into the passive provider.
*/
public void updateLocation(Location location) {
- if (mReportLocation) {
- reportLocation(location);
- }
+ reportLocation(location);
}
@Override
- public void onSetRequest(ProviderRequest request) {
- mReportLocation = request.reportLocation;
- }
+ public void onSetRequest(ProviderRequest request) {}
@Override
protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {}
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
index c855a12606b2..2d9734ef0553 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
@@ -16,7 +16,6 @@
package com.android.server.location.geofence;
-import static android.Manifest.permission;
import static android.location.LocationManager.KEY_PROXIMITY_ENTERING;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
@@ -44,8 +43,8 @@ import com.android.server.PendingIntentUtils;
import com.android.server.location.LocationPermissions;
import com.android.server.location.listeners.ListenerMultiplexer;
import com.android.server.location.listeners.PendingIntentListenerRegistration;
-import com.android.server.location.util.AppOpsHelper;
import com.android.server.location.util.Injector;
+import com.android.server.location.util.LocationPermissionsHelper;
import com.android.server.location.util.LocationUsageLogger;
import com.android.server.location.util.SettingsHelper;
import com.android.server.location.util.UserInfoHelper;
@@ -86,7 +85,7 @@ public class GeofenceManager extends
// we store these values because we don't trust the listeners not to give us dupes, not to
// spam us, and because checking the values may be more expensive
- private boolean mAppOpsAllowed;
+ private boolean mPermitted;
private @Nullable Location mCachedLocation;
private float mCachedLocationDistanceM;
@@ -101,7 +100,7 @@ public class GeofenceManager extends
mWakeLock = Objects.requireNonNull(mContext.getSystemService(PowerManager.class))
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
- TAG + ":" + identity.getPackageName());
+ TAG + ":" + identity.getPackageName());
mWakeLock.setReferenceCounted(true);
mWakeLock.setWorkSource(identity.addToWorkSource(null));
}
@@ -114,7 +113,8 @@ public class GeofenceManager extends
@Override
protected void onPendingIntentListenerRegister() {
mGeofenceState = STATE_UNKNOWN;
- mAppOpsAllowed = mAppOpsHelper.checkLocationAccess(getIdentity(), PERMISSION_FINE);
+ mPermitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
+ getIdentity());
}
@Override
@@ -127,18 +127,32 @@ public class GeofenceManager extends
}
}
- boolean isAppOpsAllowed() {
- return mAppOpsAllowed;
+ boolean isPermitted() {
+ return mPermitted;
}
- boolean onAppOpsChanged(String packageName) {
+ boolean onLocationPermissionsChanged(String packageName) {
if (getIdentity().getPackageName().equals(packageName)) {
- boolean appOpsAllowed = mAppOpsHelper.checkLocationAccess(getIdentity(),
- PERMISSION_FINE);
- if (appOpsAllowed != mAppOpsAllowed) {
- mAppOpsAllowed = appOpsAllowed;
- return true;
- }
+ return onLocationPermissionsChanged();
+ }
+
+ return false;
+ }
+
+ boolean onLocationPermissionsChanged(int uid) {
+ if (getIdentity().getUid() == uid) {
+ return onLocationPermissionsChanged();
+ }
+
+ return false;
+ }
+
+ private boolean onLocationPermissionsChanged() {
+ boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
+ getIdentity());
+ if (permitted != mPermitted) {
+ mPermitted = permitted;
+ return true;
}
return false;
@@ -186,10 +200,10 @@ public class GeofenceManager extends
mWakeLock.acquire(WAKELOCK_TIMEOUT_MS);
try {
- pendingIntent.send(mContext, 0, intent,
- (pI, i, rC, rD, rE) -> mWakeLock.release(),
- null, permission.ACCESS_FINE_LOCATION,
- PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
+ // send() only enforces permissions for broadcast intents, but since clients can
+ // select any kind of pending intent we do not rely on send() to enforce permissions
+ pendingIntent.send(mContext, 0, intent, (pI, i, rC, rD, rE) -> mWakeLock.release(),
+ null, null, PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
} catch (PendingIntent.CanceledException e) {
mWakeLock.release();
removeRegistration(new GeofenceKey(pendingIntent, getRequest()), this);
@@ -202,7 +216,7 @@ public class GeofenceManager extends
builder.append(getIdentity());
ArraySet<String> flags = new ArraySet<>(1);
- if (!mAppOpsAllowed) {
+ if (!mPermitted) {
flags.add("na");
}
if (!flags.isEmpty()) {
@@ -224,10 +238,22 @@ public class GeofenceManager extends
private final SettingsHelper.UserSettingChangedListener
mLocationPackageBlacklistChangedListener =
this::onLocationPackageBlacklistChanged;
- private final AppOpsHelper.LocationAppOpListener mAppOpsChangedListener = this::onAppOpsChanged;
+ private final LocationPermissionsHelper.LocationPermissionsListener
+ mLocationPermissionsListener =
+ new LocationPermissionsHelper.LocationPermissionsListener() {
+ @Override
+ public void onLocationPermissionsChanged(String packageName) {
+ GeofenceManager.this.onLocationPermissionsChanged(packageName);
+ }
+
+ @Override
+ public void onLocationPermissionsChanged(int uid) {
+ GeofenceManager.this.onLocationPermissionsChanged(uid);
+ }
+ };
protected final UserInfoHelper mUserInfoHelper;
- protected final AppOpsHelper mAppOpsHelper;
+ protected final LocationPermissionsHelper mLocationPermissionsHelper;
protected final SettingsHelper mSettingsHelper;
protected final LocationUsageLogger mLocationUsageLogger;
@@ -241,7 +267,7 @@ public class GeofenceManager extends
mContext = context.createAttributionContext(ATTRIBUTION_TAG);
mUserInfoHelper = injector.getUserInfoHelper();
mSettingsHelper = injector.getSettingsHelper();
- mAppOpsHelper = injector.getAppOpsHelper();
+ mLocationPermissionsHelper = injector.getLocationPermissionsHelper();
mLocationUsageLogger = injector.getLocationUsageLogger();
}
@@ -281,7 +307,7 @@ public class GeofenceManager extends
@Override
protected boolean isActive(GeofenceRegistration registration) {
CallerIdentity identity = registration.getIdentity();
- return registration.isAppOpsAllowed()
+ return registration.isPermitted()
&& mUserInfoHelper.isCurrentUserId(identity.getUserId())
&& mSettingsHelper.isLocationEnabled(identity.getUserId())
&& !mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
@@ -294,7 +320,7 @@ public class GeofenceManager extends
mSettingsHelper.addOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
mSettingsHelper.addOnLocationPackageBlacklistChangedListener(
mLocationPackageBlacklistChangedListener);
- mAppOpsHelper.addListener(mAppOpsChangedListener);
+ mLocationPermissionsHelper.addListener(mLocationPermissionsListener);
}
@Override
@@ -303,7 +329,7 @@ public class GeofenceManager extends
mSettingsHelper.removeOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
mSettingsHelper.removeOnLocationPackageBlacklistChangedListener(
mLocationPackageBlacklistChangedListener);
- mAppOpsHelper.removeListener(mAppOpsChangedListener);
+ mLocationPermissionsHelper.removeListener(mLocationPermissionsListener);
}
@Override
@@ -434,7 +460,11 @@ public class GeofenceManager extends
updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
}
- private void onAppOpsChanged(String packageName) {
- updateRegistrations(registration -> registration.onAppOpsChanged(packageName));
+ private void onLocationPermissionsChanged(String packageName) {
+ updateRegistrations(registration -> registration.onLocationPermissionsChanged(packageName));
+ }
+
+ private void onLocationPermissionsChanged(int uid) {
+ updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid));
}
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
index 53e660ad6475..0b7968be484b 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -31,8 +31,8 @@ import com.android.server.LocalServices;
import com.android.server.location.listeners.BinderListenerRegistration;
import com.android.server.location.listeners.ListenerMultiplexer;
import com.android.server.location.util.AppForegroundHelper;
-import com.android.server.location.util.AppOpsHelper;
import com.android.server.location.util.Injector;
+import com.android.server.location.util.LocationPermissionsHelper;
import com.android.server.location.util.SettingsHelper;
import com.android.server.location.util.UserInfoHelper;
import com.android.server.location.util.UserInfoHelper.UserListener;
@@ -65,7 +65,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
// we store these values because we don't trust the listeners not to give us dupes, not to
// spam us, and because checking the values may be more expensive
private boolean mForeground;
- private boolean mAppOpsAllowed;
+ private boolean mPermitted;
protected GnssListenerRegistration(@Nullable TRequest request,
CallerIdentity callerIdentity, TListener listener) {
@@ -84,24 +84,39 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
return mForeground;
}
- boolean isAppOpsAllowed() {
- return mAppOpsAllowed;
+ boolean isPermitted() {
+ return mPermitted;
}
@Override
protected void onBinderListenerRegister() {
- mAppOpsAllowed = mAppOpsHelper.checkLocationAccess(getIdentity(), PERMISSION_FINE);
+ mPermitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
+ getIdentity());
mForeground = mAppForegroundHelper.isAppForeground(getIdentity().getUid());
}
- boolean onAppOpsChanged(String packageName) {
+ boolean onLocationPermissionsChanged(String packageName) {
if (getIdentity().getPackageName().equals(packageName)) {
- boolean appOpsAllowed = mAppOpsHelper.checkLocationAccess(getIdentity(),
- PERMISSION_FINE);
- if (appOpsAllowed != mAppOpsAllowed) {
- mAppOpsAllowed = appOpsAllowed;
- return true;
- }
+ return onLocationPermissionsChanged();
+ }
+
+ return false;
+ }
+
+ boolean onLocationPermissionsChanged(int uid) {
+ if (getIdentity().getUid() == uid) {
+ return onLocationPermissionsChanged();
+ }
+
+ return false;
+ }
+
+ private boolean onLocationPermissionsChanged() {
+ boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
+ getIdentity());
+ if (permitted != mPermitted) {
+ mPermitted = permitted;
+ return true;
}
return false;
@@ -125,7 +140,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
if (!mForeground) {
flags.add("bg");
}
- if (!mAppOpsAllowed) {
+ if (!mPermitted) {
flags.add("na");
}
if (!flags.isEmpty()) {
@@ -141,7 +156,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
protected final UserInfoHelper mUserInfoHelper;
protected final SettingsHelper mSettingsHelper;
- protected final AppOpsHelper mAppOpsHelper;
+ protected final LocationPermissionsHelper mLocationPermissionsHelper;
protected final AppForegroundHelper mAppForegroundHelper;
protected final LocationManagerInternal mLocationManagerInternal;
@@ -154,14 +169,26 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
private final SettingsHelper.UserSettingChangedListener
mLocationPackageBlacklistChangedListener =
this::onLocationPackageBlacklistChanged;
- private final AppOpsHelper.LocationAppOpListener mAppOpsChangedListener = this::onAppOpsChanged;
+ private final LocationPermissionsHelper.LocationPermissionsListener
+ mLocationPermissionsListener =
+ new LocationPermissionsHelper.LocationPermissionsListener() {
+ @Override
+ public void onLocationPermissionsChanged(String packageName) {
+ GnssListenerMultiplexer.this.onLocationPermissionsChanged(packageName);
+ }
+
+ @Override
+ public void onLocationPermissionsChanged(int uid) {
+ GnssListenerMultiplexer.this.onLocationPermissionsChanged(uid);
+ }
+ };
private final AppForegroundHelper.AppForegroundListener mAppForegroundChangedListener =
this::onAppForegroundChanged;
protected GnssListenerMultiplexer(Injector injector) {
mUserInfoHelper = injector.getUserInfoHelper();
mSettingsHelper = injector.getSettingsHelper();
- mAppOpsHelper = injector.getAppOpsHelper();
+ mLocationPermissionsHelper = injector.getLocationPermissionsHelper();
mAppForegroundHelper = injector.getAppForegroundHelper();
mLocationManagerInternal = Objects.requireNonNull(
LocalServices.getService(LocationManagerInternal.class));
@@ -208,7 +235,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
CallerIdentity identity = registration.getIdentity();
// TODO: this should be checking if the gps provider is enabled, not if location is enabled,
// but this is the same for now.
- return registration.isAppOpsAllowed()
+ return registration.isPermitted()
&& (registration.isForeground() || isBackgroundRestrictionExempt(identity))
&& mUserInfoHelper.isCurrentUserId(identity.getUserId())
&& mSettingsHelper.isLocationEnabled(identity.getUserId())
@@ -241,7 +268,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
mBackgroundThrottlePackageWhitelistChangedListener);
mSettingsHelper.addOnLocationPackageBlacklistChangedListener(
mLocationPackageBlacklistChangedListener);
- mAppOpsHelper.addListener(mAppOpsChangedListener);
+ mLocationPermissionsHelper.addListener(mLocationPermissionsListener);
mAppForegroundHelper.addListener(mAppForegroundChangedListener);
}
@@ -257,7 +284,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
mBackgroundThrottlePackageWhitelistChangedListener);
mSettingsHelper.removeOnLocationPackageBlacklistChangedListener(
mLocationPackageBlacklistChangedListener);
- mAppOpsHelper.removeListener(mAppOpsChangedListener);
+ mLocationPermissionsHelper.removeListener(mLocationPermissionsListener);
mAppForegroundHelper.removeListener(mAppForegroundChangedListener);
}
@@ -279,8 +306,12 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
}
- private void onAppOpsChanged(String packageName) {
- updateRegistrations(registration -> registration.onAppOpsChanged(packageName));
+ private void onLocationPermissionsChanged(String packageName) {
+ updateRegistrations(registration -> registration.onLocationPermissionsChanged(packageName));
+ }
+
+ private void onLocationPermissionsChanged(int uid) {
+ updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid));
}
private void onAppForegroundChanged(int uid, boolean foreground) {
diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
index 58e725ca152d..8e81f29550d6 100644
--- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java
+++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
@@ -18,10 +18,9 @@ package com.android.server.location.gnss;
import static android.location.LocationManager.GPS_PROVIDER;
-import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
-
import android.Manifest;
import android.annotation.Nullable;
+import android.app.AppOpsManager;
import android.content.Context;
import android.location.GnssAntennaInfo;
import android.location.GnssMeasurementCorrections;
@@ -47,10 +46,8 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
-import com.android.server.location.util.AppForegroundHelper;
import com.android.server.location.util.AppOpsHelper;
import com.android.server.location.util.Injector;
-import com.android.server.location.util.SettingsHelper;
import java.io.FileDescriptor;
import java.util.List;
@@ -68,9 +65,7 @@ public class GnssManagerService implements GnssNative.Callbacks {
}
private final Context mContext;
- private final SettingsHelper mSettingsHelper;
private final AppOpsHelper mAppOpsHelper;
- private final AppForegroundHelper mAppForegroundHelper;
private final LocationManagerInternal mLocationManagerInternal;
private final GnssLocationProvider mGnssLocationProvider;
@@ -109,9 +104,7 @@ public class GnssManagerService implements GnssNative.Callbacks {
GnssNative.initialize();
mContext = context.createAttributionContext(ATTRIBUTION_ID);
- mSettingsHelper = injector.getSettingsHelper();
mAppOpsHelper = injector.getAppOpsHelper();
- mAppForegroundHelper = injector.getAppForegroundHelper();
mLocationManagerInternal = LocalServices.getService(LocationManagerInternal.class);
if (gnssLocationProvider == null) {
@@ -192,9 +185,10 @@ public class GnssManagerService implements GnssNative.Callbacks {
public boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName,
String attributionTag) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null);
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION, null);
CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag);
- if (!mAppOpsHelper.checkLocationAccess(identity, PERMISSION_FINE)) {
+ if (!mAppOpsHelper.checkOpNoThrow(AppOpsManager.OP_FINE_LOCATION, identity)) {
return false;
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index 9227a177f861..0815d46a735d 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -16,10 +16,10 @@
package com.android.server.location.gnss;
-import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
import static com.android.server.location.gnss.GnssManagerService.D;
import static com.android.server.location.gnss.GnssManagerService.TAG;
+import android.app.AppOpsManager;
import android.location.GnssMeasurementsEvent;
import android.location.GnssRequest;
import android.location.IGnssMeasurementsListener;
@@ -30,6 +30,7 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+import com.android.server.location.util.AppOpsHelper;
import com.android.server.location.util.Injector;
import com.android.server.location.util.LocationUsageLogger;
import com.android.server.location.util.SettingsHelper;
@@ -47,6 +48,7 @@ public class GnssMeasurementsProvider extends
GnssListenerMultiplexer<GnssRequest, IGnssMeasurementsListener, Boolean> implements
SettingsHelper.GlobalSettingChangedListener {
+ private final AppOpsHelper mAppOpsHelper;
private final LocationUsageLogger mLogger;
private final GnssMeasurementProviderNative mNative;
@@ -57,6 +59,7 @@ public class GnssMeasurementsProvider extends
@VisibleForTesting
public GnssMeasurementsProvider(Injector injector, GnssMeasurementProviderNative aNative) {
super(injector);
+ mAppOpsHelper = injector.getAppOpsHelper();
mLogger = injector.getLocationUsageLogger();
mNative = aNative;
}
@@ -163,7 +166,8 @@ public class GnssMeasurementsProvider extends
*/
public void onMeasurementsAvailable(GnssMeasurementsEvent event) {
deliverToListeners(registration -> {
- if (mAppOpsHelper.noteLocationAccess(registration.getIdentity(), PERMISSION_FINE)) {
+ if (mAppOpsHelper.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION,
+ registration.getIdentity())) {
return listener -> listener.onGnssMeasurementsReceived(event);
} else {
return null;
diff --git a/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java b/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
index a07fbe41c975..7dcffc664f52 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
@@ -16,10 +16,10 @@
package com.android.server.location.gnss;
-import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
import static com.android.server.location.gnss.GnssManagerService.D;
import static com.android.server.location.gnss.GnssManagerService.TAG;
+import android.app.AppOpsManager;
import android.location.GnssNavigationMessage;
import android.location.IGnssNavigationMessageListener;
import android.location.util.identity.CallerIdentity;
@@ -27,6 +27,7 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+import com.android.server.location.util.AppOpsHelper;
import com.android.server.location.util.Injector;
/**
@@ -39,6 +40,7 @@ import com.android.server.location.util.Injector;
public class GnssNavigationMessageProvider extends
GnssListenerMultiplexer<Void, IGnssNavigationMessageListener, Void> {
+ private final AppOpsHelper mAppOpsHelper;
private final GnssNavigationMessageProviderNative mNative;
public GnssNavigationMessageProvider(Injector injector) {
@@ -49,6 +51,7 @@ public class GnssNavigationMessageProvider extends
public GnssNavigationMessageProvider(Injector injector,
GnssNavigationMessageProviderNative aNative) {
super(injector);
+ mAppOpsHelper = injector.getAppOpsHelper();
mNative = aNative;
}
@@ -90,7 +93,8 @@ public class GnssNavigationMessageProvider extends
*/
public void onNavigationMessageAvailable(GnssNavigationMessage event) {
deliverToListeners(registration -> {
- if (mAppOpsHelper.noteLocationAccess(registration.getIdentity(), PERMISSION_FINE)) {
+ if (mAppOpsHelper.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION,
+ registration.getIdentity())) {
return listener -> listener.onGnssNavigationMessageReceived(event);
} else {
return null;
diff --git a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
index d33b05866877..19f79273c992 100644
--- a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
@@ -16,10 +16,10 @@
package com.android.server.location.gnss;
-import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
import static com.android.server.location.gnss.GnssManagerService.D;
import static com.android.server.location.gnss.GnssManagerService.TAG;
+import android.app.AppOpsManager;
import android.location.GnssStatus;
import android.location.IGnssStatusListener;
import android.location.util.identity.CallerIdentity;
@@ -27,6 +27,7 @@ import android.os.IBinder;
import android.stats.location.LocationStatsEnums;
import android.util.Log;
+import com.android.server.location.util.AppOpsHelper;
import com.android.server.location.util.Injector;
import com.android.server.location.util.LocationUsageLogger;
@@ -35,10 +36,12 @@ import com.android.server.location.util.LocationUsageLogger;
*/
public class GnssStatusProvider extends GnssListenerMultiplexer<Void, IGnssStatusListener, Void> {
+ private final AppOpsHelper mAppOpsHelper;
private final LocationUsageLogger mLogger;
public GnssStatusProvider(Injector injector) {
super(injector);
+ mAppOpsHelper = injector.getAppOpsHelper();
mLogger = injector.getLocationUsageLogger();
}
@@ -113,7 +116,8 @@ public class GnssStatusProvider extends GnssListenerMultiplexer<Void, IGnssStatu
*/
public void onSvStatusChanged(GnssStatus gnssStatus) {
deliverToListeners(registration -> {
- if (mAppOpsHelper.noteLocationAccess(registration.getIdentity(), PERMISSION_FINE)) {
+ if (mAppOpsHelper.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION,
+ registration.getIdentity())) {
return listener -> listener.onSvStatusChanged(gnssStatus);
} else {
return null;
@@ -126,7 +130,8 @@ public class GnssStatusProvider extends GnssListenerMultiplexer<Void, IGnssStatu
*/
public void onNmeaReceived(long timestamp, String nmea) {
deliverToListeners(registration -> {
- if (mAppOpsHelper.noteLocationAccess(registration.getIdentity(), PERMISSION_FINE)) {
+ if (mAppOpsHelper.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION,
+ registration.getIdentity())) {
return listener -> listener.onNmeaReceived(timestamp, nmea);
} else {
return null;
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 f5889ceeed6a..528cf8acd5b3 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.os.Binder;
import android.os.Build;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Pair;
@@ -40,8 +41,8 @@ import java.util.function.Predicate;
* A base class to multiplex client listener registrations within system server. Registrations are
* divided into two categories, active registrations and inactive registrations, as defined by
* {@link #isActive(ListenerRegistration)}. If a registration's active state changes,
- * {@link #updateRegistrations(Predicate)} or {@link #updateRegistration(Object, Predicate)} must be
- * invoked and return true for any registration whose active state may have changed.
+ * {@link #updateRegistrations(Predicate)} must be invoked and return true for any registration
+ * whose active state may have changed.
*
* Callbacks invoked for various changes will always be ordered according to this lifecycle list:
*
@@ -217,7 +218,6 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
mRegistrations.put(key, registration);
}
-
if (wasEmpty) {
onRegister();
}
@@ -268,7 +268,8 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
try (UpdateServiceBuffer ignored1 = mUpdateServiceBuffer.acquire();
ReentrancyGuard ignored2 = mReentrancyGuard.acquire()) {
- for (int i = 0; i < mRegistrations.size(); i++) {
+ final int size = mRegistrations.size();
+ for (int i = 0; i < size; i++) {
TKey key = mRegistrations.keyAt(i);
if (predicate.test(key)) {
removeRegistration(key, mRegistrations.valueAt(i));
@@ -287,7 +288,7 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
* completely at some later time.
*/
protected final void removeRegistration(@NonNull Object key,
- @NonNull ListenerRegistration registration) {
+ @NonNull ListenerRegistration<?, ?> registration) {
synchronized (mRegistrations) {
int index = mRegistrations.indexOfKey(key);
if (index < 0) {
@@ -353,7 +354,8 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
}
ArrayList<TRegistration> actives = new ArrayList<>(mRegistrations.size());
- for (int i = 0; i < mRegistrations.size(); i++) {
+ final int size = mRegistrations.size();
+ for (int i = 0; i < size; i++) {
TRegistration registration = mRegistrations.valueAt(i);
if (registration.isActive()) {
actives.add(registration);
@@ -395,7 +397,8 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
protected final void updateService(Predicate<TRegistration> predicate) {
synchronized (mRegistrations) {
boolean updateService = false;
- for (int i = 0; i < mRegistrations.size(); i++) {
+ final int size = mRegistrations.size();
+ for (int i = 0; i < size; i++) {
TRegistration registration = mRegistrations.valueAt(i);
if (predicate.test(registration) && registration.isActive()) {
updateService = true;
@@ -434,7 +437,8 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
try (UpdateServiceBuffer ignored1 = mUpdateServiceBuffer.acquire();
ReentrancyGuard ignored2 = mReentrancyGuard.acquire()) {
- for (int i = 0; i < mRegistrations.size(); i++) {
+ final int size = mRegistrations.size();
+ for (int i = 0; i < size; i++) {
TRegistration registration = mRegistrations.valueAt(i);
if (predicate.test(registration)) {
onRegistrationActiveChanged(registration);
@@ -446,33 +450,6 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
}
}
- /**
- * Evaluates the predicate on the registration with the given key. The predicate should return
- * true if the active state of the registration may have changed as a result. Any
- * {@link #updateService()} invocations made while this method is executing will be deferred
- * until after the method is complete so as to avoid redundant work.
- */
- protected final void updateRegistration(TKey key, @NonNull Predicate<TRegistration> predicate) {
- synchronized (mRegistrations) {
- // since updating a registration can invoke a variety of callbacks, we need to ensure
- // those callbacks themselves do not re-enter, as this could lead to out-of-order
- // callbacks. note that try-with-resources ordering is meaningful here as well. we want
- // to close the reentrancy guard first, as this may generate additional service updates,
- // then close the update service buffer.
- long identity = Binder.clearCallingIdentity();
- try (UpdateServiceBuffer ignored1 = mUpdateServiceBuffer.acquire();
- ReentrancyGuard ignored2 = mReentrancyGuard.acquire()) {
-
- TRegistration registration = mRegistrations.get(key);
- if (registration != null && predicate.test(registration)) {
- onRegistrationActiveChanged(registration);
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- }
-
@GuardedBy("mRegistrations")
private void onRegistrationActiveChanged(TRegistration registration) {
if (Build.IS_DEBUGGABLE) {
@@ -511,7 +488,8 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
synchronized (mRegistrations) {
long identity = Binder.clearCallingIdentity();
try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
- for (int i = 0; i < mRegistrations.size(); i++) {
+ final int size = mRegistrations.size();
+ for (int i = 0; i < size; i++) {
TRegistration registration = mRegistrations.valueAt(i);
if (registration.isActive()) {
ListenerOperation<TListener> operation = function.apply(registration);
@@ -537,7 +515,8 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
synchronized (mRegistrations) {
long identity = Binder.clearCallingIdentity();
try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
- for (int i = 0; i < mRegistrations.size(); i++) {
+ final int size = mRegistrations.size();
+ for (int i = 0; i < size; i++) {
TRegistration registration = mRegistrations.valueAt(i);
if (registration.isActive()) {
execute(registration, operation);
@@ -571,7 +550,8 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
ipw.println("listeners:");
ipw.increaseIndent();
- for (int i = 0; i < mRegistrations.size(); i++) {
+ final int size = mRegistrations.size();
+ for (int i = 0; i < size; i++) {
TRegistration registration = mRegistrations.valueAt(i);
ipw.print(registration);
if (!registration.isActive()) {
@@ -612,23 +592,33 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
*/
private final class ReentrancyGuard implements AutoCloseable {
+ @GuardedBy("mRegistrations")
private int mGuardCount;
- private @Nullable ArrayList<Pair<Object, ListenerRegistration>> mScheduledRemovals;
+ @GuardedBy("mRegistrations")
+ private @Nullable ArraySet<Pair<Object, ListenerRegistration<?, ?>>> mScheduledRemovals;
ReentrancyGuard() {
mGuardCount = 0;
mScheduledRemovals = null;
}
+ @GuardedBy("mRegistrations")
boolean isReentrant() {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mRegistrations));
+ }
return mGuardCount != 0;
}
- void markForRemoval(Object key, ListenerRegistration registration) {
+ @GuardedBy("mRegistrations")
+ void markForRemoval(Object key, ListenerRegistration<?, ?> registration) {
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mRegistrations));
+ }
Preconditions.checkState(isReentrant());
if (mScheduledRemovals == null) {
- mScheduledRemovals = new ArrayList<>(mRegistrations.size());
+ mScheduledRemovals = new ArraySet<>(mRegistrations.size());
}
mScheduledRemovals.add(new Pair<>(key, registration));
}
@@ -640,7 +630,7 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
@Override
public void close() {
- ArrayList<Pair<Object, ListenerRegistration>> scheduledRemovals = null;
+ ArraySet<Pair<Object, ListenerRegistration<?, ?>>> scheduledRemovals = null;
Preconditions.checkState(mGuardCount > 0);
if (--mGuardCount == 0) {
@@ -650,8 +640,10 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener,
if (scheduledRemovals != null) {
try (UpdateServiceBuffer ignored = mUpdateServiceBuffer.acquire()) {
- for (int i = 0; i < scheduledRemovals.size(); i++) {
- Pair<Object, ListenerRegistration> pair = scheduledRemovals.get(i);
+ final int size = scheduledRemovals.size();
+ for (int i = 0; i < size; i++) {
+ Pair<Object, ListenerRegistration<?, ?>> pair = scheduledRemovals.valueAt(
+ i);
removeRegistration(pair.first, pair.second);
}
}
diff --git a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
index e529a7d81b70..6a815ead9f9f 100644
--- a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
@@ -61,7 +61,7 @@ public abstract class RemovableListenerRegistration<TRequest, TListener> extends
* Removes this registration. Does nothing if invoked before {@link #onRegister(Object)} or
* after {@link #onUnregister()}. It is safe to invoke this from within either function.
*/
- public void remove() {
+ public final void remove() {
Object key = mKey;
if (key != null) {
getOwner().removeRegistration(key, this);
diff --git a/services/core/java/com/android/server/location/util/AppOpsHelper.java b/services/core/java/com/android/server/location/util/AppOpsHelper.java
index 3e42f27da78c..1578289d53b4 100644
--- a/services/core/java/com/android/server/location/util/AppOpsHelper.java
+++ b/services/core/java/com/android/server/location/util/AppOpsHelper.java
@@ -16,15 +16,8 @@
package com.android.server.location.util;
-import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
-import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
-
-import android.app.AppOpsManager;
import android.location.util.identity.CallerIdentity;
-import com.android.server.location.LocationPermissions;
-import com.android.server.location.LocationPermissions.PermissionLevel;
-
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -70,84 +63,27 @@ public abstract class AppOpsHelper {
}
/**
- * Checks if the given identity may have locations delivered without noting that a location is
- * being delivered. This is a looser guarantee than
- * {@link #noteLocationAccess(CallerIdentity, int)}, and this function does not validate package
- * arguments and so should not be used with unvalidated arguments or before actually delivering
- * locations.
- *
- * @see AppOpsManager#checkOpNoThrow(int, int, String)
- */
- public final boolean checkLocationAccess(CallerIdentity callerIdentity,
- @PermissionLevel int permissionLevel) {
- if (permissionLevel == LocationPermissions.PERMISSION_NONE) {
- return false;
- }
-
- return checkOpNoThrow(LocationPermissions.asAppOp(permissionLevel), callerIdentity);
- }
-
- /**
- * Notes location access to the given identity, ie, location delivery. This method should be
- * called right before a location is delivered, and if it returns false, the location should not
- * be delivered.
- */
- public final boolean noteLocationAccess(CallerIdentity identity,
- @PermissionLevel int permissionLevel) {
- if (permissionLevel == LocationPermissions.PERMISSION_NONE) {
- return false;
- }
-
- return noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel), identity);
- }
-
- /**
- * Notifies app ops that the given identity is using location at normal/low power levels. If
- * this function returns false, do not later call
- * {@link #stopLocationMonitoring(CallerIdentity)}.
+ * Starts the given appop.
*/
- public final boolean startLocationMonitoring(CallerIdentity identity) {
- return startOpNoThrow(OP_MONITOR_LOCATION, identity);
- }
+ public abstract boolean startOpNoThrow(int appOp, CallerIdentity callerIdentity);
/**
- * Notifies app ops that the given identity is no longer using location at normal/low power
- * levels.
+ * Finishes the given appop.
*/
- public final void stopLocationMonitoring(CallerIdentity identity) {
- finishOp(OP_MONITOR_LOCATION, identity);
- }
+ public abstract void finishOp(int appOp, CallerIdentity callerIdentity);
/**
- * Notifies app ops that the given identity is using location at high levels. If this function
- * returns false, do not later call {@link #stopLocationMonitoring(CallerIdentity)}.
+ * Checks the given appop.
*/
- public final boolean startHighPowerLocationMonitoring(CallerIdentity identity) {
- return startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, identity);
- }
+ public abstract boolean checkOpNoThrow(int appOp, CallerIdentity callerIdentity);
/**
- * Notifies app ops that the given identity is no longer using location at high power levels.
+ * Notes the given appop (and may throw a security exception).
*/
- public final void stopHighPowerLocationMonitoring(CallerIdentity identity) {
- finishOp(OP_MONITOR_HIGH_POWER_LOCATION, identity);
- }
+ public abstract boolean noteOp(int appOp, CallerIdentity callerIdentity);
/**
- * Notes access to any mock location APIs. If this call returns false, access to the APIs should
- * silently fail.
+ * Notes the given appop.
*/
- public final boolean noteMockLocationAccess(CallerIdentity callerIdentity) {
- return noteOp(AppOpsManager.OP_MOCK_LOCATION, callerIdentity);
- }
-
- protected abstract boolean startOpNoThrow(int appOp, CallerIdentity callerIdentity);
-
- protected abstract void finishOp(int appOp, CallerIdentity callerIdentity);
-
- protected abstract boolean checkOpNoThrow(int appOp, CallerIdentity callerIdentity);
-
- protected abstract boolean noteOp(int appOp, CallerIdentity callerIdentity);
-
- protected abstract boolean noteOpNoThrow(int appOp, CallerIdentity callerIdentity);
+ public abstract boolean noteOpNoThrow(int appOp, CallerIdentity callerIdentity);
}
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 e16df5dc26cd..379b303bbfc3 100644
--- a/services/core/java/com/android/server/location/util/Injector.java
+++ b/services/core/java/com/android/server/location/util/Injector.java
@@ -17,6 +17,7 @@
package com.android.server.location.util;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.location.LocationRequestStatistics;
/**
* Injects various location dependencies so that they may be controlled by tests.
@@ -30,15 +31,27 @@ public interface Injector {
/** Returns an AppOpsHelper. */
AppOpsHelper getAppOpsHelper();
+ /** Returns a LocationPermissionsHelper. */
+ LocationPermissionsHelper getLocationPermissionsHelper();
+
/** Returns a SettingsHelper. */
SettingsHelper getSettingsHelper();
/** Returns an AppForegroundHelper. */
AppForegroundHelper getAppForegroundHelper();
- /** Returns a LocationUsageLogger. */
- LocationUsageLogger getLocationUsageLogger();
+ /** Returns a LocationPowerSaveModeHelper. */
+ LocationPowerSaveModeHelper getLocationPowerSaveModeHelper();
+
+ /** Returns a ScreenInteractiveHelper. */
+ ScreenInteractiveHelper getScreenInteractiveHelper();
/** Returns a LocationAttributionHelper. */
LocationAttributionHelper getLocationAttributionHelper();
+
+ /** Returns a LocationUsageLogger. */
+ LocationUsageLogger getLocationUsageLogger();
+
+ /** Returns a LocationRequestStatistics. */
+ LocationRequestStatistics getLocationRequestStatistics();
}
diff --git a/services/core/java/com/android/server/location/util/LocationAttributionHelper.java b/services/core/java/com/android/server/location/util/LocationAttributionHelper.java
index 8fe09412c166..bc3ac0ff2e48 100644
--- a/services/core/java/com/android/server/location/util/LocationAttributionHelper.java
+++ b/services/core/java/com/android/server/location/util/LocationAttributionHelper.java
@@ -16,9 +16,16 @@
package com.android.server.location.util;
+import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
+import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
+
+import static com.android.server.location.LocationManagerService.D;
+import static com.android.server.location.LocationManagerService.TAG;
+
import android.location.util.identity.CallerIdentity;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -83,7 +90,7 @@ public class LocationAttributionHelper {
i -> new ArraySet<>());
boolean empty = keySet.isEmpty();
if (keySet.add(new ProviderListener(provider, key)) && empty) {
- if (!mAppOpsHelper.startLocationMonitoring(identity)) {
+ if (!mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, identity)) {
mAttributions.remove(identity);
}
}
@@ -99,7 +106,7 @@ public class LocationAttributionHelper {
if (keySet != null && keySet.remove(new ProviderListener(provider, key))
&& keySet.isEmpty()) {
mAttributions.remove(identity);
- mAppOpsHelper.stopLocationMonitoring(identity);
+ mAppOpsHelper.finishOp(OP_MONITOR_LOCATION, identity);
}
}
@@ -113,14 +120,18 @@ public class LocationAttributionHelper {
i -> new ArraySet<>());
boolean empty = keySet.isEmpty();
if (keySet.add(new ProviderListener(provider, key)) && empty) {
- if (!mAppOpsHelper.startHighPowerLocationMonitoring(identity)) {
+ if (mAppOpsHelper.startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, identity)) {
+ if (D) {
+ Log.v(TAG, "starting high power location attribution for " + identity);
+ }
+ } else {
mHighPowerAttributions.remove(identity);
}
}
}
/**
- * Report high power location usage has stopped for the given caller on the given provider,
+ * Report high power location usage has stopped for the given caller on the given provider,
* with a unique key.
*/
public synchronized void reportHighPowerLocationStop(CallerIdentity identity, String provider,
@@ -128,8 +139,11 @@ public class LocationAttributionHelper {
Set<ProviderListener> keySet = mHighPowerAttributions.get(identity);
if (keySet != null && keySet.remove(new ProviderListener(provider, key))
&& keySet.isEmpty()) {
+ if (D) {
+ Log.v(TAG, "stopping high power location attribution for " + identity);
+ }
mHighPowerAttributions.remove(identity);
- mAppOpsHelper.stopHighPowerLocationMonitoring(identity);
+ mAppOpsHelper.finishOp(OP_MONITOR_HIGH_POWER_LOCATION, identity);
}
}
}
diff --git a/services/core/java/com/android/server/location/util/LocationPermissionsHelper.java b/services/core/java/com/android/server/location/util/LocationPermissionsHelper.java
new file mode 100644
index 000000000000..daf56797c0c9
--- /dev/null
+++ b/services/core/java/com/android/server/location/util/LocationPermissionsHelper.java
@@ -0,0 +1,107 @@
+/*
+ * 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 com.android.server.location.LocationPermissions.PERMISSION_NONE;
+
+import android.location.util.identity.CallerIdentity;
+
+import com.android.server.location.LocationPermissions;
+import com.android.server.location.LocationPermissions.PermissionLevel;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Provides helpers and listeners for appops.
+ */
+public abstract class LocationPermissionsHelper {
+
+ /**
+ * Listener for current user changes.
+ */
+ public interface LocationPermissionsListener {
+
+ /**
+ * Called when something has changed about location permissions for the given package.
+ */
+ void onLocationPermissionsChanged(String packageName);
+
+ /**
+ * Called when something has changed about location permissions for the given uid.
+ */
+ void onLocationPermissionsChanged(int uid);
+ }
+
+ private final CopyOnWriteArrayList<LocationPermissionsListener> mListeners;
+ private final AppOpsHelper mAppOps;
+
+ public LocationPermissionsHelper(AppOpsHelper appOps) {
+ mListeners = new CopyOnWriteArrayList<>();
+ mAppOps = appOps;
+
+ mAppOps.addListener(this::onAppOpsChanged);
+ }
+
+ protected final void notifyLocationPermissionsChanged(String packageName) {
+ for (LocationPermissionsListener listener : mListeners) {
+ listener.onLocationPermissionsChanged(packageName);
+ }
+ }
+
+ protected final void notifyLocationPermissionsChanged(int uid) {
+ for (LocationPermissionsListener listener : mListeners) {
+ listener.onLocationPermissionsChanged(uid);
+ }
+ }
+
+ private void onAppOpsChanged(String packageName) {
+ notifyLocationPermissionsChanged(packageName);
+ }
+
+ /**
+ * Adds a listener for location permissions events. Callbacks occur on an unspecified thread.
+ */
+ public final void addListener(LocationPermissionsListener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Removes a listener for location permissions events.
+ */
+ public final void removeListener(LocationPermissionsListener listener) {
+ mListeners.remove(listener);
+ }
+
+ /**
+ * Returns true if the given identity may access location at the given permissions level, taking
+ * into account both permissions and appops.
+ */
+ public final boolean hasLocationPermissions(@PermissionLevel int permissionLevel,
+ CallerIdentity identity) {
+ if (permissionLevel == PERMISSION_NONE) {
+ return false;
+ }
+
+ if (!hasPermission(LocationPermissions.asPermission(permissionLevel), identity)) {
+ return false;
+ }
+
+ return mAppOps.checkOpNoThrow(permissionLevel, identity);
+ }
+
+ protected abstract boolean hasPermission(String permission, CallerIdentity callerIdentity);
+}
diff --git a/services/core/java/com/android/server/location/util/LocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/util/LocationPowerSaveModeHelper.java
new file mode 100644
index 000000000000..a9a8c50f11dc
--- /dev/null
+++ b/services/core/java/com/android/server/location/util/LocationPowerSaveModeHelper.java
@@ -0,0 +1,71 @@
+/*
+ * 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.os.PowerManager.LocationPowerSaveMode;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Provides accessors and listeners for location power save mode.
+ */
+public abstract class LocationPowerSaveModeHelper {
+
+ /**
+ * Listener for location power save mode changes.
+ */
+ public interface LocationPowerSaveModeChangedListener {
+ /**
+ * Called when the location power save mode changes.
+ */
+ void onLocationPowerSaveModeChanged(@LocationPowerSaveMode int locationPowerSaveMode);
+ }
+
+ private final CopyOnWriteArrayList<LocationPowerSaveModeChangedListener> mListeners;
+
+ public LocationPowerSaveModeHelper() {
+ mListeners = new CopyOnWriteArrayList<>();
+ }
+
+ /**
+ * Add a listener for changes to location power save mode. Callbacks occur on an unspecified
+ * thread.
+ */
+ public final void addListener(LocationPowerSaveModeChangedListener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Removes a listener for changes to location power save mode.
+ */
+ public final void removeListener(LocationPowerSaveModeChangedListener listener) {
+ mListeners.remove(listener);
+ }
+
+ protected final void notifyLocationPowerSaveModeChanged(
+ @LocationPowerSaveMode int locationPowerSaveMode) {
+ for (LocationPowerSaveModeChangedListener listener : mListeners) {
+ listener.onLocationPowerSaveModeChanged(locationPowerSaveMode);
+ }
+ }
+
+ /**
+ * Returns the current location power save mode.
+ */
+ @LocationPowerSaveMode
+ public abstract int getLocationPowerSaveMode();
+}
diff --git a/services/core/java/com/android/server/location/util/ScreenInteractiveHelper.java b/services/core/java/com/android/server/location/util/ScreenInteractiveHelper.java
new file mode 100644
index 000000000000..d47bce31ed23
--- /dev/null
+++ b/services/core/java/com/android/server/location/util/ScreenInteractiveHelper.java
@@ -0,0 +1,67 @@
+/*
+ * 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 java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Provides accessors and listeners for screen interactive state (screen on/off).
+ */
+public abstract class ScreenInteractiveHelper {
+
+ /**
+ * Listener for screen interactive changes.
+ */
+ public interface ScreenInteractiveChangedListener {
+ /**
+ * Called when the screen interative state changes.
+ */
+ void onScreenInteractiveChanged(boolean isInteractive);
+ }
+
+ private final CopyOnWriteArrayList<ScreenInteractiveChangedListener> mListeners;
+
+ public ScreenInteractiveHelper() {
+ mListeners = new CopyOnWriteArrayList<>();
+ }
+
+ /**
+ * Add a listener for changes to screen interactive state. Callbacks occur on an unspecified
+ * thread.
+ */
+ public final void addListener(ScreenInteractiveChangedListener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Removes a listener for changes to screen interactive state.
+ */
+ public final void removeListener(ScreenInteractiveChangedListener listener) {
+ mListeners.remove(listener);
+ }
+
+ protected final void notifyScreenInteractiveChanged(boolean interactive) {
+ for (ScreenInteractiveChangedListener listener : mListeners) {
+ listener.onScreenInteractiveChanged(interactive);
+ }
+ }
+
+ /**
+ * Returns true if the screen is currently interactive, and false otherwise.
+ */
+ public abstract boolean isInteractive();
+}
diff --git a/services/core/java/com/android/server/location/util/SystemAppOpsHelper.java b/services/core/java/com/android/server/location/util/SystemAppOpsHelper.java
index a95383695ae8..cfb7697a8dfc 100644
--- a/services/core/java/com/android/server/location/util/SystemAppOpsHelper.java
+++ b/services/core/java/com/android/server/location/util/SystemAppOpsHelper.java
@@ -57,7 +57,7 @@ public class SystemAppOpsHelper extends AppOpsHelper {
}
@Override
- protected boolean startOpNoThrow(int appOp, CallerIdentity callerIdentity) {
+ public boolean startOpNoThrow(int appOp, CallerIdentity callerIdentity) {
Preconditions.checkState(mAppOps != null);
long identity = Binder.clearCallingIdentity();
@@ -75,7 +75,7 @@ public class SystemAppOpsHelper extends AppOpsHelper {
}
@Override
- protected void finishOp(int appOp, CallerIdentity callerIdentity) {
+ public void finishOp(int appOp, CallerIdentity callerIdentity) {
Preconditions.checkState(mAppOps != null);
long identity = Binder.clearCallingIdentity();
@@ -91,7 +91,7 @@ public class SystemAppOpsHelper extends AppOpsHelper {
}
@Override
- protected boolean checkOpNoThrow(int appOp, CallerIdentity callerIdentity) {
+ public boolean checkOpNoThrow(int appOp, CallerIdentity callerIdentity) {
Preconditions.checkState(mAppOps != null);
long identity = Binder.clearCallingIdentity();
@@ -106,7 +106,7 @@ public class SystemAppOpsHelper extends AppOpsHelper {
}
@Override
- protected boolean noteOp(int appOp, CallerIdentity callerIdentity) {
+ public boolean noteOp(int appOp, CallerIdentity callerIdentity) {
Preconditions.checkState(mAppOps != null);
long identity = Binder.clearCallingIdentity();
@@ -123,7 +123,7 @@ public class SystemAppOpsHelper extends AppOpsHelper {
}
@Override
- protected boolean noteOpNoThrow(int appOp, CallerIdentity callerIdentity) {
+ public boolean noteOpNoThrow(int appOp, CallerIdentity callerIdentity) {
Preconditions.checkState(mAppOps != null);
long identity = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/location/util/SystemLocationPermissionsHelper.java b/services/core/java/com/android/server/location/util/SystemLocationPermissionsHelper.java
new file mode 100644
index 000000000000..b9c0ddef04ab
--- /dev/null
+++ b/services/core/java/com/android/server/location/util/SystemLocationPermissionsHelper.java
@@ -0,0 +1,65 @@
+/*
+ * 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.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.content.Context;
+import android.location.util.identity.CallerIdentity;
+import android.os.Binder;
+
+import com.android.server.FgThread;
+
+/**
+ * Provides accessors and listeners for location permissions, including appops.
+ */
+public class SystemLocationPermissionsHelper extends LocationPermissionsHelper {
+
+ private final Context mContext;
+
+ private boolean mInited;
+
+ public SystemLocationPermissionsHelper(Context context, AppOpsHelper appOps) {
+ super(appOps);
+ mContext = context;
+ }
+
+ /** Called when system is ready. */
+ public void onSystemReady() {
+ if (mInited) {
+ return;
+ }
+
+ mContext.getPackageManager().addOnPermissionsChangeListener(
+ uid -> {
+ // invoked on ui thread, move to fg thread so ui thread isn't blocked
+ FgThread.getHandler().post(() -> notifyLocationPermissionsChanged(uid));
+ });
+ mInited = true;
+ }
+
+ @Override
+ protected boolean hasPermission(String permission, CallerIdentity callerIdentity) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ return mContext.checkPermission(permission, callerIdentity.getPid(),
+ callerIdentity.getUid()) == PERMISSION_GRANTED;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/util/SystemLocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/util/SystemLocationPowerSaveModeHelper.java
new file mode 100644
index 000000000000..c8d8202157f0
--- /dev/null
+++ b/services/core/java/com/android/server/location/util/SystemLocationPowerSaveModeHelper.java
@@ -0,0 +1,87 @@
+/*
+ * 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.content.Context;
+import android.os.PowerManager;
+import android.os.PowerManager.LocationPowerSaveMode;
+import android.os.PowerManagerInternal;
+import android.os.PowerSaveState;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
+import com.android.server.LocalServices;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+
+/**
+ * Provides accessors and listeners for location power save mode.
+ */
+public class SystemLocationPowerSaveModeHelper extends LocationPowerSaveModeHelper implements
+ Consumer<PowerSaveState> {
+
+ private final Context mContext;
+ private boolean mReady;
+
+ @LocationPowerSaveMode
+ private volatile int mLocationPowerSaveMode;
+
+ public SystemLocationPowerSaveModeHelper(Context context) {
+ mContext = context;
+ }
+
+ /** Called when system is ready. */
+ public void onSystemReady() {
+ if (mReady) {
+ return;
+ }
+
+ LocalServices.getService(PowerManagerInternal.class).registerLowPowerModeObserver(
+ PowerManager.ServiceType.LOCATION, this);
+ mLocationPowerSaveMode = Objects.requireNonNull(
+ mContext.getSystemService(PowerManager.class)).getLocationPowerSaveMode();
+
+ mReady = true;
+ }
+
+ @Override
+ public void accept(PowerSaveState powerSaveState) {
+ int locationPowerSaveMode;
+ if (!powerSaveState.batterySaverEnabled) {
+ locationPowerSaveMode = PowerManager.LOCATION_MODE_NO_CHANGE;
+ } else {
+ locationPowerSaveMode = powerSaveState.locationMode;
+ }
+
+ if (locationPowerSaveMode == mLocationPowerSaveMode) {
+ return;
+ }
+
+ mLocationPowerSaveMode = locationPowerSaveMode;
+
+ // invoked on ui thread, move to fg thread so we don't block the ui thread
+ FgThread.getHandler().post(() -> notifyLocationPowerSaveModeChanged(locationPowerSaveMode));
+ }
+
+ @LocationPowerSaveMode
+ @Override
+ public int getLocationPowerSaveMode() {
+ Preconditions.checkState(mReady);
+ return mLocationPowerSaveMode;
+ }
+}
diff --git a/services/core/java/com/android/server/location/util/SystemScreenInteractiveHelper.java b/services/core/java/com/android/server/location/util/SystemScreenInteractiveHelper.java
new file mode 100644
index 000000000000..70cedac20868
--- /dev/null
+++ b/services/core/java/com/android/server/location/util/SystemScreenInteractiveHelper.java
@@ -0,0 +1,85 @@
+/*
+ * 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.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.UserHandle;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
+
+/**
+ * Provides accessors and listeners for screen interactive state (screen on/off).
+ */
+public class SystemScreenInteractiveHelper extends ScreenInteractiveHelper {
+
+ private final Context mContext;
+
+ private boolean mReady;
+
+ private volatile boolean mIsInteractive;
+
+ public SystemScreenInteractiveHelper(Context context) {
+ mContext = context;
+ }
+
+ /** Called when system is ready. */
+ public void onSystemReady() {
+ if (mReady) {
+ return;
+ }
+
+ IntentFilter screenIntentFilter = new IntentFilter();
+ screenIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+ screenIntentFilter.addAction(Intent.ACTION_SCREEN_ON);
+ mContext.registerReceiverAsUser(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ boolean interactive;
+ if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
+ interactive = true;
+ } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
+ interactive = false;
+ } else {
+ return;
+ }
+
+ onScreenInteractiveChanged(interactive);
+ }
+ }, UserHandle.ALL, screenIntentFilter, null, FgThread.getHandler());
+
+ mReady = true;
+ }
+
+ private void onScreenInteractiveChanged(boolean interactive) {
+ if (interactive == mIsInteractive) {
+ return;
+ }
+
+ mIsInteractive = interactive;
+ notifyScreenInteractiveChanged(interactive);
+ }
+
+ @Override
+ public boolean isInteractive() {
+ Preconditions.checkState(mReady);
+ return mIsInteractive;
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAppOpsHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAppOpsHelper.java
index da794da7f9c9..e947e89c4f94 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAppOpsHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAppOpsHelper.java
@@ -57,7 +57,7 @@ public class FakeAppOpsHelper extends AppOpsHelper {
}
@Override
- protected boolean startOpNoThrow(int appOp, CallerIdentity callerIdentity) {
+ public boolean startOpNoThrow(int appOp, CallerIdentity callerIdentity) {
AppOp myAppOp = getOp(callerIdentity.getPackageName(), appOp);
if (!myAppOp.mAllowed) {
return false;
@@ -68,20 +68,20 @@ public class FakeAppOpsHelper extends AppOpsHelper {
}
@Override
- protected void finishOp(int appOp, CallerIdentity callerIdentity) {
+ public void finishOp(int appOp, CallerIdentity callerIdentity) {
AppOp myAppOp = getOp(callerIdentity.getPackageName(), appOp);
Preconditions.checkState(myAppOp.mStarted);
myAppOp.mStarted = false;
}
@Override
- protected boolean checkOpNoThrow(int appOp, CallerIdentity callerIdentity) {
+ public boolean checkOpNoThrow(int appOp, CallerIdentity callerIdentity) {
AppOp myAppOp = getOp(callerIdentity.getPackageName(), appOp);
return myAppOp.mAllowed;
}
@Override
- protected boolean noteOp(int appOp, CallerIdentity callerIdentity) {
+ public boolean noteOp(int appOp, CallerIdentity callerIdentity) {
if (!noteOpNoThrow(appOp, callerIdentity)) {
throw new SecurityException(
"noteOp not allowed for op " + appOp + " and caller " + callerIdentity);
@@ -91,7 +91,7 @@ public class FakeAppOpsHelper extends AppOpsHelper {
}
@Override
- protected boolean noteOpNoThrow(int appOp, CallerIdentity callerIdentity) {
+ public boolean noteOpNoThrow(int appOp, CallerIdentity callerIdentity) {
AppOp myAppOp = getOp(callerIdentity.getPackageName(), appOp);
if (!myAppOp.mAllowed) {
return false;
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPermissionsHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPermissionsHelper.java
new file mode 100644
index 000000000000..e7d7e310d1e4
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPermissionsHelper.java
@@ -0,0 +1,56 @@
+/*
+ * 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.location.util.identity.CallerIdentity;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Version of LocationPermissionsHelper for testing. All permissions are granted unless notified
+ * otherwise.
+ */
+public class FakeLocationPermissionsHelper extends LocationPermissionsHelper {
+
+ private final HashMap<String, Set<String>> mRevokedPermissions;
+
+ public FakeLocationPermissionsHelper(AppOpsHelper appOps) {
+ super(appOps);
+ mRevokedPermissions = new HashMap<>();
+ }
+
+ public void grantPermission(String packageName, String permission) {
+ getRevokedPermissionsList(packageName).remove(permission);
+ notifyLocationPermissionsChanged(packageName);
+ }
+
+ public void revokePermission(String packageName, String permission) {
+ getRevokedPermissionsList(packageName).add(permission);
+ notifyLocationPermissionsChanged(packageName);
+ }
+
+ @Override
+ protected boolean hasPermission(String permission, CallerIdentity identity) {
+ return !getRevokedPermissionsList(identity.getPackageName()).contains(permission);
+ }
+
+ private Set<String> getRevokedPermissionsList(String packageName) {
+ return mRevokedPermissions.computeIfAbsent(packageName, p -> new HashSet<>());
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPowerSaveModeHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPowerSaveModeHelper.java
new file mode 100644
index 000000000000..3ead5d4f214d
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPowerSaveModeHelper.java
@@ -0,0 +1,49 @@
+/*
+ * 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.os.IPowerManager;
+import android.os.PowerManager.LocationPowerSaveMode;
+
+/**
+ * Version of LocationPowerSaveModeHelper for testing. Power save mode is initialized as "no
+ * change".
+ */
+public class FakeLocationPowerSaveModeHelper extends LocationPowerSaveModeHelper {
+
+ @LocationPowerSaveMode
+ private int mLocationPowerSaveMode;
+
+ public FakeLocationPowerSaveModeHelper() {
+ mLocationPowerSaveMode = IPowerManager.LOCATION_MODE_NO_CHANGE;
+ }
+
+ public void setLocationPowerSaveMode(int locationPowerSaveMode) {
+ if (locationPowerSaveMode == mLocationPowerSaveMode) {
+ return;
+ }
+
+ mLocationPowerSaveMode = locationPowerSaveMode;
+ notifyLocationPowerSaveModeChanged(locationPowerSaveMode);
+ }
+
+ @LocationPowerSaveMode
+ @Override
+ public int getLocationPowerSaveMode() {
+ return mLocationPowerSaveMode;
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeScreenInteractiveHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeScreenInteractiveHelper.java
new file mode 100644
index 000000000000..df697fa1a03c
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeScreenInteractiveHelper.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+/**
+ * Version of ScreenInteractiveHelper for testing. Screen is initialized as interactive (on).
+ */
+public class FakeScreenInteractiveHelper extends ScreenInteractiveHelper {
+
+ private boolean mIsInteractive;
+
+ public FakeScreenInteractiveHelper() {
+ mIsInteractive = true;
+ }
+
+ public void setScreenInteractive(boolean interactive) {
+ if (interactive == mIsInteractive) {
+ return;
+ }
+
+ mIsInteractive = interactive;
+ notifyScreenInteractiveChanged(interactive);
+ }
+
+ public boolean isInteractive() {
+ return mIsInteractive;
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeSettingsHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeSettingsHelper.java
index 726b1b82b699..1d0523f18008 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeSettingsHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeSettingsHelper.java
@@ -90,7 +90,7 @@ public class FakeSettingsHelper extends SettingsHelper {
@Override
public boolean isLocationEnabled(int userId) {
- return mLocationEnabledSetting.getValue(Boolean.class);
+ return mLocationEnabledSetting.getValue(userId, Boolean.class);
}
@Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeUserInfoHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeUserInfoHelper.java
index 336e28c879ba..f5978da416be 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeUserInfoHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeUserInfoHelper.java
@@ -16,7 +16,6 @@
package com.android.server.location.util;
-import android.os.Process;
import android.util.IndentingPrintWriter;
import android.util.IntArray;
import android.util.SparseArray;
@@ -27,19 +26,21 @@ import com.android.internal.util.Preconditions;
import java.io.FileDescriptor;
/**
- * Version of UserInfoHelper for testing. The user this code is running under is set as the current
- * user by default, with no profiles.
+ * Version of UserInfoHelper for testing. By default there is one user that starts in a running
+ * state with a userId of 0;
*/
public class FakeUserInfoHelper extends UserInfoHelper {
+ public static final int DEFAULT_USERID = 0;
+
private final IntArray mRunningUserIds;
private final SparseArray<IntArray> mProfiles;
private int mCurrentUserId;
public FakeUserInfoHelper() {
- mCurrentUserId = Process.myUserHandle().getIdentifier();
- mRunningUserIds = IntArray.wrap(new int[]{mCurrentUserId});
+ mCurrentUserId = DEFAULT_USERID;
+ mRunningUserIds = IntArray.wrap(new int[]{DEFAULT_USERID});
mProfiles = new SparseArray<>();
}
@@ -67,6 +68,10 @@ public class FakeUserInfoHelper extends UserInfoHelper {
dispatchOnUserStopped(userId);
}
+ public void setCurrentUserId(int parentUser) {
+ setCurrentUserIds(parentUser, new int[]{parentUser});
+ }
+
public void setCurrentUserIds(int parentUser, int[] currentProfileUserIds) {
Preconditions.checkArgument(ArrayUtils.contains(currentProfileUserIds, parentUser));
int oldUserId = mCurrentUserId;
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/LocationAttributionHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/util/LocationAttributionHelperTest.java
index e6f625217965..4165b6ee111f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/util/LocationAttributionHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/util/LocationAttributionHelperTest.java
@@ -16,7 +16,11 @@
package com.android.server.location.util;
+import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
+import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
+
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -46,9 +50,7 @@ public class LocationAttributionHelperTest {
public void setUp() {
initMocks(this);
- when(mAppOpsHelper.startLocationMonitoring(any(CallerIdentity.class))).thenReturn(true);
- when(mAppOpsHelper.startHighPowerLocationMonitoring(any(CallerIdentity.class))).thenReturn(
- true);
+ when(mAppOpsHelper.startOpNoThrow(anyInt(), any(CallerIdentity.class))).thenReturn(true);
mHelper = new LocationAttributionHelper(mAppOpsHelper);
}
@@ -63,30 +65,30 @@ public class LocationAttributionHelperTest {
Object key4 = new Object();
mHelper.reportLocationStart(caller1, "gps", key1);
- verify(mAppOpsHelper).startLocationMonitoring(caller1);
- verify(mAppOpsHelper, never()).stopLocationMonitoring(caller1);
+ verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller1);
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller1);
mHelper.reportLocationStart(caller1, "gps", key2);
- verify(mAppOpsHelper).startLocationMonitoring(caller1);
- verify(mAppOpsHelper, never()).stopLocationMonitoring(caller1);
+ verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller1);
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller1);
mHelper.reportLocationStart(caller2, "gps", key3);
- verify(mAppOpsHelper).startLocationMonitoring(caller2);
- verify(mAppOpsHelper, never()).stopLocationMonitoring(caller2);
+ verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller2);
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2);
mHelper.reportLocationStart(caller2, "gps", key4);
- verify(mAppOpsHelper).startLocationMonitoring(caller2);
- verify(mAppOpsHelper, never()).stopLocationMonitoring(caller2);
+ verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller2);
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2);
mHelper.reportLocationStop(caller1, "gps", key2);
- verify(mAppOpsHelper, never()).stopLocationMonitoring(caller1);
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller1);
mHelper.reportLocationStop(caller1, "gps", key1);
- verify(mAppOpsHelper).stopLocationMonitoring(caller1);
+ verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, caller1);
mHelper.reportLocationStop(caller2, "gps", key3);
- verify(mAppOpsHelper, never()).stopLocationMonitoring(caller2);
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2);
mHelper.reportLocationStop(caller2, "gps", key4);
- verify(mAppOpsHelper).stopLocationMonitoring(caller2);
+ verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, caller2);
}
@Test
@@ -99,29 +101,29 @@ public class LocationAttributionHelperTest {
Object key4 = new Object();
mHelper.reportHighPowerLocationStart(caller1, "gps", key1);
- verify(mAppOpsHelper).startHighPowerLocationMonitoring(caller1);
- verify(mAppOpsHelper, never()).stopHighPowerLocationMonitoring(caller1);
+ verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller1);
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1);
mHelper.reportHighPowerLocationStart(caller1, "gps", key2);
- verify(mAppOpsHelper).startHighPowerLocationMonitoring(caller1);
- verify(mAppOpsHelper, never()).stopHighPowerLocationMonitoring(caller1);
+ verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller1);
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1);
mHelper.reportHighPowerLocationStart(caller2, "gps", key3);
- verify(mAppOpsHelper).startHighPowerLocationMonitoring(caller2);
- verify(mAppOpsHelper, never()).stopHighPowerLocationMonitoring(caller2);
+ verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller2);
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2);
mHelper.reportHighPowerLocationStart(caller2, "gps", key4);
- verify(mAppOpsHelper).startHighPowerLocationMonitoring(caller2);
- verify(mAppOpsHelper, never()).stopHighPowerLocationMonitoring(caller2);
+ verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller2);
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2);
mHelper.reportHighPowerLocationStop(caller1, "gps", key2);
- verify(mAppOpsHelper, never()).stopHighPowerLocationMonitoring(caller1);
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1);
mHelper.reportHighPowerLocationStop(caller1, "gps", key1);
- verify(mAppOpsHelper).stopHighPowerLocationMonitoring(caller1);
+ verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1);
mHelper.reportHighPowerLocationStop(caller2, "gps", key3);
- verify(mAppOpsHelper, never()).stopHighPowerLocationMonitoring(caller2);
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2);
mHelper.reportHighPowerLocationStop(caller2, "gps", key4);
- verify(mAppOpsHelper).stopHighPowerLocationMonitoring(caller2);
+ verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/SystemAppOpsHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/util/SystemAppOpsHelperTest.java
index f40d3168cf98..093aa2e0e771 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/util/SystemAppOpsHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/util/SystemAppOpsHelperTest.java
@@ -20,12 +20,8 @@ import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.OP_COARSE_LOCATION;
import static android.app.AppOpsManager.OP_FINE_LOCATION;
import static android.app.AppOpsManager.OP_MOCK_LOCATION;
-import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
-import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
-import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
-
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -34,10 +30,12 @@ import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.initMocks;
+import static org.testng.Assert.assertThrows;
import android.app.AppOpsManager;
import android.content.Context;
@@ -105,41 +103,41 @@ public class SystemAppOpsHelperTest {
}
@Test
- public void testCheckLocationAccess() {
+ public void testCheckOp() {
CallerIdentity identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature");
doReturn(MODE_ALLOWED).when(
mAppOps).checkOpNoThrow(eq(OP_FINE_LOCATION), eq(1000), eq("mypackage"));
- assertThat(mHelper.checkLocationAccess(identity, PERMISSION_FINE)).isTrue();
+ assertThat(mHelper.checkOpNoThrow(OP_FINE_LOCATION, identity)).isTrue();
doReturn(MODE_IGNORED).when(
mAppOps).checkOpNoThrow(eq(OP_FINE_LOCATION), eq(1000), eq("mypackage"));
- assertThat(mHelper.checkLocationAccess(identity, PERMISSION_FINE)).isFalse();
+ assertThat(mHelper.checkOpNoThrow(OP_FINE_LOCATION, identity)).isFalse();
identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature");
doReturn(MODE_ALLOWED).when(
mAppOps).checkOpNoThrow(eq(OP_COARSE_LOCATION), eq(1000), eq("mypackage"));
- assertThat(mHelper.checkLocationAccess(identity, PERMISSION_COARSE)).isTrue();
+ assertThat(mHelper.checkOpNoThrow(OP_COARSE_LOCATION, identity)).isTrue();
doReturn(MODE_IGNORED).when(
mAppOps).checkOpNoThrow(eq(OP_COARSE_LOCATION), eq(1000), eq("mypackage"));
- assertThat(mHelper.checkLocationAccess(identity, PERMISSION_COARSE)).isFalse();
+ assertThat(mHelper.checkOpNoThrow(OP_COARSE_LOCATION, identity)).isFalse();
}
@Test
- public void testNoteLocationAccess() {
+ public void testNoteOpNoThrow() {
CallerIdentity identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature");
doReturn(MODE_ALLOWED).when(
mAppOps).noteOpNoThrow(eq(OP_FINE_LOCATION), eq(1000), eq("mypackage"),
eq("myfeature"), nullable(String.class));
- assertThat(mHelper.noteLocationAccess(identity, PERMISSION_FINE)).isTrue();
+ assertThat(mHelper.noteOpNoThrow(OP_FINE_LOCATION, identity)).isTrue();
doReturn(MODE_IGNORED).when(
mAppOps).noteOpNoThrow(eq(OP_FINE_LOCATION), eq(1000), eq("mypackage"),
eq("myfeature"), nullable(String.class));
- assertThat(mHelper.noteLocationAccess(identity, PERMISSION_FINE)).isFalse();
+ assertThat(mHelper.noteOpNoThrow(OP_FINE_LOCATION, identity)).isFalse();
identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature");
@@ -147,74 +145,55 @@ public class SystemAppOpsHelperTest {
doReturn(MODE_ALLOWED).when(
mAppOps).noteOpNoThrow(eq(OP_COARSE_LOCATION), eq(1000), eq("mypackage"),
eq("myfeature"), nullable(String.class));
- assertThat(mHelper.noteLocationAccess(identity, PERMISSION_COARSE)).isTrue();
+ assertThat(mHelper.noteOpNoThrow(OP_COARSE_LOCATION, identity)).isTrue();
doReturn(MODE_IGNORED).when(
mAppOps).noteOpNoThrow(eq(OP_COARSE_LOCATION), eq(1000), eq("mypackage"),
eq("myfeature"), nullable(String.class));
- assertThat(mHelper.noteLocationAccess(identity, PERMISSION_COARSE)).isFalse();
+ assertThat(mHelper.noteOpNoThrow(OP_COARSE_LOCATION, identity)).isFalse();
}
@Test
- public void testStartLocationMonitoring() {
+ public void testStartOp() {
CallerIdentity identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature");
doReturn(MODE_ALLOWED).when(
mAppOps).startOpNoThrow(eq(OP_MONITOR_LOCATION), eq(1000), eq("mypackage"),
eq(false), eq("myfeature"), nullable(String.class));
- assertThat(mHelper.startLocationMonitoring(identity)).isTrue();
+ assertThat(mHelper.startOpNoThrow(OP_MONITOR_LOCATION, identity)).isTrue();
doReturn(MODE_IGNORED).when(
mAppOps).startOpNoThrow(eq(OP_MONITOR_LOCATION), eq(1000), eq("mypackage"),
eq(false), eq("myfeature"), nullable(String.class));
- assertThat(mHelper.startLocationMonitoring(identity)).isFalse();
+ assertThat(mHelper.startOpNoThrow(OP_MONITOR_LOCATION, identity)).isFalse();
}
@Test
- public void testStartHighPowerLocationMonitoring() {
+ public void testFinishOp() {
CallerIdentity identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature");
- doReturn(MODE_ALLOWED).when(
- mAppOps).startOpNoThrow(eq(OP_MONITOR_HIGH_POWER_LOCATION), eq(1000),
- eq("mypackage"),
- eq(false), eq("myfeature"), nullable(String.class));
- assertThat(mHelper.startHighPowerLocationMonitoring(identity)).isTrue();
-
- doReturn(MODE_IGNORED).when(
- mAppOps).startOpNoThrow(eq(OP_MONITOR_HIGH_POWER_LOCATION), eq(1000),
- eq("mypackage"),
- eq(false), eq("myfeature"), nullable(String.class));
- assertThat(mHelper.startHighPowerLocationMonitoring(identity)).isFalse();
- }
-
- @Test
- public void testStopLocationMonitoring() {
- CallerIdentity identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature");
-
- mHelper.stopLocationMonitoring(identity);
+ mHelper.finishOp(OP_MONITOR_LOCATION, identity);
verify(mAppOps).finishOp(OP_MONITOR_LOCATION, 1000, "mypackage", "myfeature");
}
@Test
- public void testStopHighPowerLocationMonitoring() {
- CallerIdentity identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature");
-
- mHelper.stopHighPowerLocationMonitoring(identity);
- verify(mAppOps).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, 1000, "mypackage", "myfeature");
- }
-
- @Test
- public void testNoteMockLocationAccess() {
+ public void testNoteOp() {
CallerIdentity identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature");
doReturn(MODE_ALLOWED).when(
mAppOps).noteOp(eq(OP_MOCK_LOCATION), eq(1000), eq("mypackage"), eq("myfeature"),
nullable(String.class));
- assertThat(mHelper.noteMockLocationAccess(identity)).isTrue();
+ assertThat(mHelper.noteOp(OP_MOCK_LOCATION, identity)).isTrue();
doReturn(MODE_IGNORED).when(
mAppOps).noteOp(eq(OP_MOCK_LOCATION), eq(1000), eq("mypackage"), eq("myfeature"),
nullable(String.class));
- assertThat(mHelper.noteMockLocationAccess(identity)).isFalse();
+ assertThat(mHelper.noteOp(OP_MOCK_LOCATION, identity)).isFalse();
+
+
+ doThrow(new SecurityException()).when(
+ mAppOps).noteOp(eq(OP_MOCK_LOCATION), eq(1000), eq("mypackage"), eq("myfeature"),
+ nullable(String.class));
+ assertThrows(SecurityException.class, () -> mHelper.noteOp(OP_MOCK_LOCATION, identity));
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/SystemLocationPowerSaveModeHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/util/SystemLocationPowerSaveModeHelperTest.java
new file mode 100644
index 000000000000..2acb70c4b83d
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/util/SystemLocationPowerSaveModeHelperTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF;
+import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
+import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF;
+import static android.os.PowerManager.LOCATION_MODE_NO_CHANGE;
+import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.content.Context;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.PowerSaveState;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.location.util.LocationPowerSaveModeHelper.LocationPowerSaveModeChangedListener;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SystemLocationPowerSaveModeHelperTest {
+
+ private static final long TIMEOUT_MS = 5000;
+ private static final long FAILURE_TIMEOUT_MS = 200;
+
+ @Mock
+ private PowerManagerInternal mPowerManagerInternal;
+
+ private List<Consumer<PowerSaveState>> mListeners = new ArrayList<>();
+
+ private SystemLocationPowerSaveModeHelper mHelper;
+
+ @Before
+ public void setUp() {
+ initMocks(this);
+
+ LocalServices.addService(PowerManagerInternal.class, mPowerManagerInternal);
+
+ doAnswer(invocation -> mListeners.add(invocation.getArgument(1))).when(
+ mPowerManagerInternal).registerLowPowerModeObserver(anyInt(), any(Consumer.class));
+
+ PowerManager powerManager = mock(PowerManager.class);
+ doReturn(LOCATION_MODE_NO_CHANGE).when(powerManager).getLocationPowerSaveMode();
+ Context context = mock(Context.class);
+ doReturn(powerManager).when(context).getSystemService(PowerManager.class);
+
+ mHelper = new SystemLocationPowerSaveModeHelper(context);
+ mHelper.onSystemReady();
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(PowerManagerInternal.class);
+ }
+
+ private void sendPowerSaveState(PowerSaveState powerSaveState) {
+ for (Consumer<PowerSaveState> listener : mListeners) {
+ listener.accept(powerSaveState);
+ }
+ }
+
+ @Test
+ public void testListener() {
+ LocationPowerSaveModeChangedListener listener = mock(
+ LocationPowerSaveModeChangedListener.class);
+ mHelper.addListener(listener);
+
+ sendPowerSaveState(new PowerSaveState.Builder().setLocationMode(
+ LOCATION_MODE_NO_CHANGE).setBatterySaverEnabled(false).build());
+ verify(listener, after(FAILURE_TIMEOUT_MS).never()).onLocationPowerSaveModeChanged(
+ anyInt());
+ assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_NO_CHANGE);
+ sendPowerSaveState(new PowerSaveState.Builder().setLocationMode(
+ LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF).setBatterySaverEnabled(false).build());
+ verify(listener, after(FAILURE_TIMEOUT_MS).never()).onLocationPowerSaveModeChanged(
+ anyInt());
+ assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_NO_CHANGE);
+ sendPowerSaveState(new PowerSaveState.Builder().setLocationMode(
+ LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF).setBatterySaverEnabled(false).build());
+ verify(listener, after(FAILURE_TIMEOUT_MS).never()).onLocationPowerSaveModeChanged(
+ anyInt());
+ assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_NO_CHANGE);
+ sendPowerSaveState(new PowerSaveState.Builder().setLocationMode(
+ LOCATION_MODE_FOREGROUND_ONLY).setBatterySaverEnabled(false).build());
+ verify(listener, after(FAILURE_TIMEOUT_MS).never()).onLocationPowerSaveModeChanged(
+ anyInt());
+ assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_NO_CHANGE);
+ sendPowerSaveState(new PowerSaveState.Builder().setLocationMode(
+ LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF).setBatterySaverEnabled(
+ false).build());
+ verify(listener, after(FAILURE_TIMEOUT_MS).never()).onLocationPowerSaveModeChanged(
+ anyInt());
+ assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_NO_CHANGE);
+
+ sendPowerSaveState(new PowerSaveState.Builder().setLocationMode(
+ LOCATION_MODE_NO_CHANGE).setBatterySaverEnabled(true).build());
+ verify(listener, after(FAILURE_TIMEOUT_MS).never()).onLocationPowerSaveModeChanged(
+ anyInt());
+ assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_NO_CHANGE);
+ sendPowerSaveState(new PowerSaveState.Builder().setLocationMode(
+ LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF).setBatterySaverEnabled(true).build());
+ verify(listener, timeout(TIMEOUT_MS)).onLocationPowerSaveModeChanged(
+ LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF);
+ assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(
+ LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF);
+ sendPowerSaveState(new PowerSaveState.Builder().setLocationMode(
+ LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF).setBatterySaverEnabled(true).build());
+ verify(listener, timeout(TIMEOUT_MS)).onLocationPowerSaveModeChanged(
+ LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF);
+ assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(
+ LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF);
+ sendPowerSaveState(new PowerSaveState.Builder().setLocationMode(
+ LOCATION_MODE_FOREGROUND_ONLY).setBatterySaverEnabled(true).build());
+ verify(listener, timeout(TIMEOUT_MS)).onLocationPowerSaveModeChanged(
+ LOCATION_MODE_FOREGROUND_ONLY);
+ assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_FOREGROUND_ONLY);
+ sendPowerSaveState(new PowerSaveState.Builder().setLocationMode(
+ LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF).setBatterySaverEnabled(
+ true).build());
+ verify(listener, timeout(TIMEOUT_MS)).onLocationPowerSaveModeChanged(
+ LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF);
+ assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(
+ LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF);
+ sendPowerSaveState(new PowerSaveState.Builder().setLocationMode(
+ LOCATION_MODE_NO_CHANGE).setBatterySaverEnabled(true).build());
+ verify(listener, timeout(TIMEOUT_MS)).onLocationPowerSaveModeChanged(
+ LOCATION_MODE_NO_CHANGE);
+ assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_NO_CHANGE);
+ }
+}
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 c22dc104f438..1867be0b9f3b 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
@@ -16,22 +16,32 @@
package com.android.server.location.util;
+import com.android.server.location.LocationRequestStatistics;
+
public class TestInjector implements Injector {
private final FakeUserInfoHelper mUserInfoHelper;
private final FakeAppOpsHelper mAppOpsHelper;
+ private final FakeLocationPermissionsHelper mLocationPermissionsHelper;
private final FakeSettingsHelper mSettingsHelper;
private final FakeAppForegroundHelper mAppForegroundHelper;
- private final LocationUsageLogger mLocationUsageLogger;
+ private final FakeLocationPowerSaveModeHelper mLocationPowerSaveModeHelper;
+ private final FakeScreenInteractiveHelper mScreenInteractiveHelper;
private final LocationAttributionHelper mLocationAttributionHelper;
+ private final LocationUsageLogger mLocationUsageLogger;
+ private final LocationRequestStatistics mLocationRequestStatistics;
public TestInjector() {
mUserInfoHelper = new FakeUserInfoHelper();
mAppOpsHelper = new FakeAppOpsHelper();
+ mLocationPermissionsHelper = new FakeLocationPermissionsHelper(mAppOpsHelper);
mSettingsHelper = new FakeSettingsHelper();
mAppForegroundHelper = new FakeAppForegroundHelper();
- mLocationUsageLogger = new LocationUsageLogger();
+ mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper();
+ mScreenInteractiveHelper = new FakeScreenInteractiveHelper();
mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper);
+ mLocationUsageLogger = new LocationUsageLogger();
+ mLocationRequestStatistics = new LocationRequestStatistics();
}
@Override
@@ -45,6 +55,11 @@ public class TestInjector implements Injector {
}
@Override
+ public FakeLocationPermissionsHelper getLocationPermissionsHelper() {
+ return mLocationPermissionsHelper;
+ }
+
+ @Override
public FakeSettingsHelper getSettingsHelper() {
return mSettingsHelper;
}
@@ -55,12 +70,27 @@ public class TestInjector implements Injector {
}
@Override
- public LocationUsageLogger getLocationUsageLogger() {
- return mLocationUsageLogger;
+ public FakeLocationPowerSaveModeHelper getLocationPowerSaveModeHelper() {
+ return mLocationPowerSaveModeHelper;
+ }
+
+ @Override
+ public FakeScreenInteractiveHelper getScreenInteractiveHelper() {
+ return mScreenInteractiveHelper;
}
@Override
public LocationAttributionHelper getLocationAttributionHelper() {
return mLocationAttributionHelper;
}
+
+ @Override
+ public LocationUsageLogger getLocationUsageLogger() {
+ return mLocationUsageLogger;
+ }
+
+ @Override
+ public LocationRequestStatistics getLocationRequestStatistics() {
+ return mLocationRequestStatistics;
+ }
}