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