Merge "Assorted cleanup and refactoring"

This commit is contained in:
Soonil Nagarkar
2020-07-24 03:34:19 +00:00
committed by Android (Google) Code Review
36 changed files with 1247 additions and 355 deletions

View File

@@ -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) {

View File

@@ -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() {
}

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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) {}
}

View File

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

View File

@@ -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
@@ -2956,6 +2980,11 @@ public class LocationManagerService extends ILocationManager.Stub {
return mAppOpsHelper;
}
@Override
public LocationPermissionsHelper getLocationPermissionsHelper() {
return mLocationPermissionsHelper;
}
@Override
public SettingsHelper getSettingsHelper() {
return mSettingsHelper;
@@ -2971,9 +3000,24 @@ public class LocationManagerService extends ILocationManager.Stub {
return mLocationUsageLogger;
}
@Override
public LocationPowerSaveModeHelper getLocationPowerSaveModeHelper() {
return mLocationPowerSaveModeHelper;
}
@Override
public ScreenInteractiveHelper getScreenInteractiveHelper() {
return mScreenInteractiveHelper;
}
@Override
public LocationAttributionHelper getLocationAttributionHelper() {
return mLocationAttributionHelper;
}
@Override
public LocationRequestStatistics getLocationRequestStatistics() {
return mLocationRequestStatistics;
}
}
}

View File

@@ -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) {}

View File

@@ -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));
}
}

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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)
* Starts the given appop.
*/
public final boolean checkLocationAccess(CallerIdentity callerIdentity,
@PermissionLevel int permissionLevel) {
if (permissionLevel == LocationPermissions.PERMISSION_NONE) {
return false;
}
return checkOpNoThrow(LocationPermissions.asAppOp(permissionLevel), callerIdentity);
}
public abstract boolean startOpNoThrow(int appOp, CallerIdentity 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.
* Finishes the given appop.
*/
public final boolean noteLocationAccess(CallerIdentity identity,
@PermissionLevel int permissionLevel) {
if (permissionLevel == LocationPermissions.PERMISSION_NONE) {
return false;
}
return noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel), identity);
}
public abstract void finishOp(int appOp, CallerIdentity callerIdentity);
/**
* 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)}.
* Checks the given appop.
*/
public final boolean startLocationMonitoring(CallerIdentity identity) {
return startOpNoThrow(OP_MONITOR_LOCATION, identity);
}
public abstract boolean checkOpNoThrow(int appOp, CallerIdentity callerIdentity);
/**
* Notifies app ops that the given identity is no longer using location at normal/low power
* levels.
* Notes the given appop (and may throw a security exception).
*/
public final void stopLocationMonitoring(CallerIdentity identity) {
finishOp(OP_MONITOR_LOCATION, identity);
}
public abstract boolean noteOp(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)}.
* Notes the given appop.
*/
public final boolean startHighPowerLocationMonitoring(CallerIdentity identity) {
return startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, identity);
}
/**
* Notifies app ops that the given identity is no longer using location at high power levels.
*/
public final void stopHighPowerLocationMonitoring(CallerIdentity identity) {
finishOp(OP_MONITOR_HIGH_POWER_LOCATION, identity);
}
/**
* Notes access to any mock location APIs. If this call returns false, access to the APIs should
* silently fail.
*/
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);
}

View File

@@ -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();
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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<>());
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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
@@ -44,6 +54,11 @@ public class TestInjector implements Injector {
return mAppOpsHelper;
}
@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;
}
}