From 4700428b6f48ab5907673d983a6f7770a7137107 Mon Sep 17 00:00:00 2001 From: Svet Ganov Date: Wed, 17 Feb 2021 08:56:51 +0000 Subject: [PATCH] Location provider app ops remapping The platform has the concept of a location provider which could be a plugin implemented by another package. The implementation of a location provider plugin can make calls to APIs that require a location permission, e.g. fusing data from different sources. However, such accesses are counted in app ops as a location access but the accessor is in this case the data source. It is also possible that the package that provides the implementation of a location provider also hosts other funcionality which may need to call APIs that require a location permission. This change allows a location provider to specify app op attribution tags which which could be used when calling location to singal to the OS that the access is for the location providing functionality of the location provider. For location accesses of the provider package that are not related to providing location to the OS the provider can use any other non delcared as location attribution tags. Accesses with the location attribution tags would be counted in app ops but instead of towards the OP_COARSE_LOCATION/OP_FINE_LOCATION the would be counted towards dedicated OP_COARSE_LOCATION_PROVIDER/ OP_FINE_LOCATION_PROVIDER ops. This would allow proper classification while enabling auditability and tracking via the standard app op APIs. Test: atest android.location.cts.fine.LocationManagerFineTest#testLocationAttributionTagBlaming Bug:179062648 Change-Id: I36739ab42cedc94e1aa7a3bfd3b9aa213f5b3e97 --- core/api/current.txt | 1 + core/java/android/app/AppOpsManager.java | 64 +++++++- .../android/location/ILocationManager.aidl | 3 +- .../android/location/LocationManager.java | 25 ++- .../location/LocationManagerInternal.java | 75 +++++++++ .../com/android/server/ServiceWatcher.java | 109 ++++++++----- .../server/am/ActivityManagerService.java | 9 ++ .../android/server/appop/AppOpsService.java | 138 ++++++++++++---- .../HardwareActivityRecognitionProxy.java | 4 +- .../location/LocationManagerService.java | 34 +++- .../server/location/LocationShellCommand.java | 4 +- .../location/gnss/GnssLocationProvider.java | 3 +- .../provider/AbstractLocationProvider.java | 44 +++-- .../provider/DelegateLocationProvider.java | 2 +- .../provider/LocationProviderManager.java | 41 +++++ .../provider/MockLocationProvider.java | 6 +- .../provider/MockableLocationProvider.java | 2 +- .../provider/PassiveLocationProvider.java | 3 +- .../provider/proxy/ProxyLocationProvider.java | 24 ++- .../android/server/policy/AppOpsPolicy.java | 151 ++++++++++++++++++ .../RealLocationTimeZoneProviderProxy.java | 4 +- .../java/com/android/server/SystemServer.java | 9 ++ .../provider/LocationProviderManagerTest.java | 5 +- .../MockableLocationProviderTest.java | 3 +- .../server/location/test/FakeProvider.java | 2 +- 25 files changed, 655 insertions(+), 110 deletions(-) create mode 100644 services/core/java/com/android/server/policy/AppOpsPolicy.java diff --git a/core/api/current.txt b/core/api/current.txt index 160ad7a4d5553..a29b38492cfe8 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -19660,6 +19660,7 @@ package android.location { method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void addProximityAlert(double, double, float, long, @NonNull android.app.PendingIntent); method public void addTestProvider(@NonNull String, boolean, boolean, boolean, boolean, boolean, boolean, boolean, int, int); method public void addTestProvider(@NonNull String, @NonNull android.location.provider.ProviderProperties); + method public void addTestProvider(@NonNull String, @NonNull android.location.provider.ProviderProperties, @NonNull java.util.Set); method @Deprecated public void clearTestProviderEnabled(@NonNull String); method @Deprecated public void clearTestProviderLocation(@NonNull String); method @Deprecated public void clearTestProviderStatus(@NonNull String); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 2f3b50b17d514..160844aacc46b 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1141,23 +1141,20 @@ public class AppOpsManager { * * @hide */ - // TODO: Add as AppProtoEnums - public static final int OP_PHONE_CALL_MICROPHONE = 100; + public static final int OP_PHONE_CALL_MICROPHONE = AppProtoEnums.APP_OP_PHONE_CALL_MICROPHONE; /** * Phone call is using camera * * @hide */ - // TODO: Add as AppProtoEnums - public static final int OP_PHONE_CALL_CAMERA = 101; + public static final int OP_PHONE_CALL_CAMERA = AppProtoEnums.APP_OP_PHONE_CALL_CAMERA; /** * Audio is being recorded for hotword detection. * * @hide */ - // TODO: Add as AppProtoEnums - public static final int OP_RECORD_AUDIO_HOTWORD = 102; + public static final int OP_RECORD_AUDIO_HOTWORD = AppProtoEnums.APP_OP_RECORD_AUDIO_HOTWORD; /** * Manage credentials in the system KeyChain. @@ -1184,10 +1181,29 @@ public class AppOpsManager { */ public static final int OP_SCHEDULE_EXACT_ALARM = AppProtoEnums.APP_OP_SCHEDULE_EXACT_ALARM; + /** + * Fine location being accessed by a location source, which is + * a component that already has location data since it is the one + * that produces location, which is it is a data source for + * location data. + * + * @hide + */ + public static final int OP_FINE_LOCATION_SOURCE = AppProtoEnums.APP_OP_FINE_LOCATION_SOURCE; + + /** + * Coarse location being accessed by a location source, which is + * a component that already has location data since it is the one + * that produces location, which is it is a data source for + * location data. + * + * @hide + */ + public static final int OP_COARSE_LOCATION_SOURCE = AppProtoEnums.APP_OP_COARSE_LOCATION_SOURCE; /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 108; + public static final int _NUM_OP = 110; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -1567,6 +1583,24 @@ public class AppOpsManager { */ public static final String OPSTR_SCHEDULE_EXACT_ALARM = "android:schedule_exact_alarm"; + /** + * Fine location being accessed by a location source, which is + * a component that already has location since it is the one that + * produces location. + * + * @hide + */ + public static final String OPSTR_FINE_LOCATION_SOURCE = "android:fine_location_source"; + + /** + * Coarse location being accessed by a location source, which is + * a component that already has location since it is the one that + * produces location. + * + * @hide + */ + public static final String OPSTR_COARSE_LOCATION_SOURCE = "android:coarse_location_source"; + /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */ @@ -1767,6 +1801,8 @@ public class AppOpsManager { OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER OP_RECORD_AUDIO_OUTPUT, // RECORD_AUDIO_OUTPUT OP_SCHEDULE_EXACT_ALARM, // SCHEDULE_EXACT_ALARM + OP_FINE_LOCATION, // OP_FINE_LOCATION_SOURCE + OP_COARSE_LOCATION, // OP_COARSE_LOCATION_SOURCE }; /** @@ -1881,6 +1917,8 @@ public class AppOpsManager { OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, OPSTR_RECORD_AUDIO_OUTPUT, OPSTR_SCHEDULE_EXACT_ALARM, + OPSTR_FINE_LOCATION_SOURCE, + OPSTR_COARSE_LOCATION_SOURCE, }; /** @@ -1996,6 +2034,8 @@ public class AppOpsManager { "USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER", "RECORD_AUDIO_OUTPUT", "SCHEDULE_EXACT_ALARM", + "FINE_LOCATION_SOURCE", + "COARSE_LOCATION_SOURCE", }; /** @@ -2112,6 +2152,8 @@ public class AppOpsManager { Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, null, // no permission for OP_RECORD_AUDIO_OUTPUT Manifest.permission.SCHEDULE_EXACT_ALARM, + null, // no permission for OP_ACCESS_FINE_LOCATION_SOURCE, + null, // no permission for OP_ACCESS_COARSE_LOCATION_SOURCE, }; /** @@ -2228,6 +2270,8 @@ public class AppOpsManager { null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER null, // RECORD_AUDIO_OUTPUT null, // SCHEDULE_EXACT_ALARM + null, // ACCESS_FINE_LOCATION_SOURCE + null, // ACCESS_COARSE_LOCATION_SOURCE }; /** @@ -2343,6 +2387,8 @@ public class AppOpsManager { null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER null, // RECORD_AUDIO_OUTPUT null, // SCHEDULE_EXACT_ALARM + null, // ACCESS_FINE_LOCATION_SOURCE + null, // ACCESS_COARSE_LOCATION_SOURCE }; /** @@ -2457,6 +2503,8 @@ public class AppOpsManager { AppOpsManager.MODE_DEFAULT, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO_OUTPUT AppOpsManager.MODE_DEFAULT, // SCHEDULE_EXACT_ALARM + AppOpsManager.MODE_ALLOWED, // ACCESS_FINE_LOCATION_SOURCE + AppOpsManager.MODE_ALLOWED, // ACCESS_COARSE_LOCATION_SOURCE }; /** @@ -2575,6 +2623,8 @@ public class AppOpsManager { true, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER false, // RECORD_AUDIO_OUTPUT false, // SCHEDULE_EXACT_ALARM + false, // ACCESS_FINE_LOCATION_SOURCE + false, // ACCESS_COARSE_LOCATION_SOURCE }; /** diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index 5e3966032a11b..38b48e97771a6 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -117,7 +117,8 @@ interface ILocationManager boolean isLocationEnabledForUser(int userId); void setLocationEnabledForUser(boolean enabled, int userId); - void addTestProvider(String name, in ProviderProperties properties, String packageName, @nullable String attributionTag); + void addTestProvider(String name, in ProviderProperties properties, + in List locationTags, String packageName, @nullable String attributionTag); void removeTestProvider(String provider, String packageName, @nullable String attributionTag); void setTestProviderLocation(String provider, in Location location, String packageName, @nullable String attributionTag); void setTestProviderEnabled(String provider, boolean enabled, String packageName, @nullable String attributionTag); diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index dd5b6e6b42228..95bae5ae7aab2 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -76,6 +76,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -1957,12 +1958,32 @@ public class LocationManager { * allowed} for your app. */ public void addTestProvider(@NonNull String provider, @NonNull ProviderProperties properties) { + addTestProvider(provider, properties, Collections.emptySet()); + } + + /** + * Creates a test location provider and adds it to the set of active providers. This provider + * will replace any provider with the same name that exists prior to this call. + * + * @param provider the provider name + * @param properties the provider properties + * @param locationTags the attribution tags for accessing location from the provider + * + * @throws IllegalArgumentException if provider is null + * @throws IllegalArgumentException if properties is null + * @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION + * mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED + * allowed} for your app. + */ + public void addTestProvider(@NonNull String provider, @NonNull ProviderProperties properties, + @NonNull Set locationTags) { Preconditions.checkArgument(provider != null, "invalid null provider"); Preconditions.checkArgument(properties != null, "invalid null properties"); + Preconditions.checkArgument(locationTags != null, "invalid null location tags"); try { - mService.addTestProvider(provider, properties, mContext.getOpPackageName(), - mContext.getFeatureId()); + mService.addTestProvider(provider, properties, new ArrayList<>(locationTags), + mContext.getOpPackageName(), mContext.getFeatureId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/location/java/android/location/LocationManagerInternal.java b/location/java/android/location/LocationManagerInternal.java index a6a0e7aa24ff8..763835c9cbe23 100644 --- a/location/java/android/location/LocationManagerInternal.java +++ b/location/java/android/location/LocationManagerInternal.java @@ -21,6 +21,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.location.util.identity.CallerIdentity; +import com.android.internal.annotations.Immutable; + +import java.util.Set; + /** * Location manager local system service interface. * @@ -38,6 +42,21 @@ public abstract class LocationManagerInternal { void onProviderEnabledChanged(String provider, int userId, boolean enabled); } + /** + * Interface for getting callbacks when a location provider's location tags change. + * + * @see LocationTagInfo + */ + public interface OnProviderLocationTagsChangeListener { + + /** + * Called when the location tags for a provider change. + * + * @param providerLocationTagInfo The tag info for a provider. + */ + void onLocationTagsChanged(@NonNull LocationTagInfo providerLocationTagInfo); + } + /** * Returns true if the given provider is enabled for the given user. * @@ -88,4 +107,60 @@ public abstract class LocationManagerInternal { * provider, and the elapsed nanos since boot the current time was computed at. */ public abstract @Nullable LocationTime getGnssTimeMillis(); + + /** + * Sets a listener for changes in the location providers' tags. Passing + * {@code null} clears the current listener. + * + * @param listener The listener. + */ + public abstract void setOnProviderLocationTagsChangeListener( + @Nullable OnProviderLocationTagsChangeListener listener); + + /** + * This class represents the location permission tags used by the location provider + * packages in a given UID. These tags are strictly used for accessing state guarded + * by the location permission(s) by a location provider which are required for the + * provider to fulfill its function as being a location provider. + */ + @Immutable + public static class LocationTagInfo { + private final int mUid; + + @NonNull + private final String mPackageName; + + @Nullable + private final Set mLocationTags; + + public LocationTagInfo(int uid, @NonNull String packageName, + @Nullable Set locationTags) { + mUid = uid; + mPackageName = packageName; + mLocationTags = locationTags; + } + + /** + * @return The UID for which tags are related. + */ + public int getUid() { + return mUid; + } + + /** + * @return The package for which tags are related. + */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + /** + * @return The tags for the package used for location related accesses. + */ + @Nullable + public Set getTags() { + return mLocationTags; + } + } } diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java index c2d8fa24157a2..8a2894c84cc45 100644 --- a/services/core/java/com/android/server/ServiceWatcher.java +++ b/services/core/java/com/android/server/ServiceWatcher.java @@ -45,14 +45,18 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; +import android.util.ArrayMap; import android.util.Log; +import com.android.internal.annotations.Immutable; import com.android.internal.content.PackageMonitor; import com.android.internal.util.Preconditions; +import com.android.internal.util.function.pooled.PooledLambda; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.function.Predicate; @@ -87,26 +91,30 @@ public class ServiceWatcher implements ServiceConnection { /** Function to run on binder interface when first bound. */ public interface OnBindRunner { /** Called to run client code with the binder. */ - void run(IBinder binder, ComponentName service) throws RemoteException; + void run(IBinder binder, BoundService service) throws RemoteException; } /** * Information on the service ServiceWatcher has selected as the best option for binding. */ - private static final class ServiceInfo implements Comparable { + @Immutable + public static final class BoundService implements Comparable { - public static final ServiceInfo NONE = new ServiceInfo(Integer.MIN_VALUE, null, - UserHandle.USER_NULL, false); + public static final BoundService NONE = new BoundService(Integer.MIN_VALUE, null, + false, null, -1); public final int version; - @Nullable public final ComponentName component; - @UserIdInt public final int userId; + @Nullable + public final ComponentName component; public final boolean serviceIsMultiuser; + public final int uid; + @Nullable + public final Bundle metadata; - ServiceInfo(ResolveInfo resolveInfo, int currentUserId) { + BoundService(ResolveInfo resolveInfo) { Preconditions.checkArgument(resolveInfo.serviceInfo.getComponentName() != null); - Bundle metadata = resolveInfo.serviceInfo.metaData; + metadata = resolveInfo.serviceInfo.metaData; if (metadata != null) { version = metadata.getInt(EXTRA_SERVICE_VERSION, Integer.MIN_VALUE); serviceIsMultiuser = metadata.getBoolean(EXTRA_SERVICE_IS_MULTIUSER, false); @@ -116,16 +124,17 @@ public class ServiceWatcher implements ServiceConnection { } component = resolveInfo.serviceInfo.getComponentName(); - userId = serviceIsMultiuser ? UserHandle.USER_SYSTEM : currentUserId; + uid = resolveInfo.serviceInfo.applicationInfo.uid; } - private ServiceInfo(int version, @Nullable ComponentName component, int userId, - boolean serviceIsMultiuser) { + private BoundService(int version, @Nullable ComponentName component, + boolean serviceIsMultiuser, @Nullable Bundle metadata, int uid) { Preconditions.checkArgument(component != null || version == Integer.MIN_VALUE); this.version = version; this.component = component; - this.userId = userId; this.serviceIsMultiuser = serviceIsMultiuser; + this.metadata = metadata; + this.uid = uid; } public @Nullable String getPackageName() { @@ -137,21 +146,21 @@ public class ServiceWatcher implements ServiceConnection { if (this == o) { return true; } - if (!(o instanceof ServiceInfo)) { + if (!(o instanceof BoundService)) { return false; } - ServiceInfo that = (ServiceInfo) o; - return version == that.version && userId == that.userId + BoundService that = (BoundService) o; + return version == that.version && uid == that.uid && Objects.equals(component, that.component); } @Override public int hashCode() { - return Objects.hash(version, component, userId); + return Objects.hash(version, component, uid); } @Override - public int compareTo(ServiceInfo that) { + public int compareTo(BoundService that) { // ServiceInfos with higher version numbers always win (having a version number > // MIN_VALUE implies having a non-null component). if version numbers are equal, a // non-null component wins over a null component. if the version numbers are equal and @@ -164,10 +173,11 @@ public class ServiceWatcher implements ServiceConnection { } else if (component != null && that.component == null) { ret = 1; } else { - if (userId != UserHandle.USER_SYSTEM && that.userId == UserHandle.USER_SYSTEM) { + if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM + && UserHandle.getUserId(that.uid) == UserHandle.USER_SYSTEM) { ret = -1; - } else if (userId == UserHandle.USER_SYSTEM - && that.userId != UserHandle.USER_SYSTEM) { + } else if (UserHandle.getUserId(uid) == UserHandle.USER_SYSTEM + && UserHandle.getUserId(that.uid) != UserHandle.USER_SYSTEM) { ret = 1; } } @@ -180,7 +190,8 @@ public class ServiceWatcher implements ServiceConnection { if (component == null) { return "none"; } else { - return component.toShortString() + "@" + version + "[u" + userId + "]"; + return component.toShortString() + "@" + version + "[u" + + UserHandle.getUserId(uid) + "]"; } } } @@ -227,17 +238,23 @@ public class ServiceWatcher implements ServiceConnection { } }; - @Nullable private final OnBindRunner mOnBind; - @Nullable private final Runnable mOnUnbind; + // read/write from handler thread only + private final Map mPendingBinds = new ArrayMap<>(); - // write from caller thread only, read anywhere - private volatile boolean mRegistered; + @Nullable + private final OnBindRunner mOnBind; + + @Nullable + private final Runnable mOnUnbind; + + // read/write from handler thread only + private boolean mRegistered; // read/write from handler thread only private int mCurrentUserId; // write from handler thread only, read anywhere - private volatile ServiceInfo mTargetService; + private volatile BoundService mTargetService; private volatile IBinder mBinder; public ServiceWatcher(Context context, String action, @@ -274,7 +291,7 @@ public class ServiceWatcher implements ServiceConnection { mCurrentUserId = UserHandle.USER_NULL; - mTargetService = ServiceInfo.NONE; + mTargetService = BoundService.NONE; mBinder = null; } @@ -299,6 +316,11 @@ public class ServiceWatcher implements ServiceConnection { * Starts the process of determining the best matching service and maintaining a binding to it. */ public void register() { + mHandler.sendMessage(PooledLambda.obtainMessage(ServiceWatcher::registerInternal, + ServiceWatcher.this)); + } + + private void registerInternal() { Preconditions.checkState(!mRegistered); mPackageMonitor.register(mContext, UserHandle.ALL, true, mHandler); @@ -309,6 +331,8 @@ public class ServiceWatcher implements ServiceConnection { mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, intentFilter, null, mHandler); + // TODO: This makes the behavior of the class unpredictable as the caller needs + // to know the internal impl detail that calling register would pick the current user. mCurrentUserId = ActivityManager.getCurrentUser(); mRegistered = true; @@ -320,6 +344,11 @@ public class ServiceWatcher implements ServiceConnection { * Stops the process of determining the best matching service and releases any binding. */ public void unregister() { + mHandler.sendMessage(PooledLambda.obtainMessage(ServiceWatcher::unregisterInternal, + ServiceWatcher.this)); + } + + private void unregisterInternal() { Preconditions.checkState(mRegistered); mRegistered = false; @@ -333,7 +362,7 @@ public class ServiceWatcher implements ServiceConnection { private void onBestServiceChanged(boolean forceRebind) { Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - ServiceInfo bestServiceInfo = ServiceInfo.NONE; + BoundService bestServiceInfo = BoundService.NONE; if (mRegistered) { List resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser( @@ -344,7 +373,7 @@ public class ServiceWatcher implements ServiceConnection { if (!mServiceCheckPredicate.test(resolveInfo)) { continue; } - ServiceInfo serviceInfo = new ServiceInfo(resolveInfo, mCurrentUserId); + BoundService serviceInfo = new BoundService(resolveInfo); if (serviceInfo.compareTo(bestServiceInfo) > 0) { bestServiceInfo = serviceInfo; } @@ -356,21 +385,22 @@ public class ServiceWatcher implements ServiceConnection { } } - private void rebind(ServiceInfo newServiceInfo) { + private void rebind(BoundService newServiceInfo) { Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - if (!mTargetService.equals(ServiceInfo.NONE)) { + if (!mTargetService.equals(BoundService.NONE)) { if (D) { Log.d(TAG, "[" + mIntent.getAction() + "] unbinding from " + mTargetService); } mContext.unbindService(this); onServiceDisconnected(mTargetService.component); - mTargetService = ServiceInfo.NONE; + mPendingBinds.remove(mTargetService.component); + mTargetService = BoundService.NONE; } mTargetService = newServiceInfo; - if (mTargetService.equals(ServiceInfo.NONE)) { + if (mTargetService.equals(BoundService.NONE)) { return; } @@ -381,10 +411,12 @@ public class ServiceWatcher implements ServiceConnection { Intent bindIntent = new Intent(mIntent).setComponent(mTargetService.component); if (!mContext.bindServiceAsUser(bindIntent, this, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE, - mHandler, UserHandle.of(mTargetService.userId))) { - mTargetService = ServiceInfo.NONE; + mHandler, UserHandle.of(UserHandle.getUserId(mTargetService.uid)))) { + mTargetService = BoundService.NONE; Log.e(TAG, getLogPrefix() + " unexpected bind failure - retrying later"); mHandler.postDelayed(() -> onBestServiceChanged(false), RETRY_DELAY_MS); + } else { + mPendingBinds.put(mTargetService.component, mTargetService); } } @@ -397,10 +429,15 @@ public class ServiceWatcher implements ServiceConnection { Log.d(TAG, getLogPrefix() + " connected to " + component.toShortString()); } + final BoundService boundService = mPendingBinds.remove(component); + if (boundService == null) { + return; + } + mBinder = binder; if (mOnBind != null) { try { - mOnBind.run(binder, component); + mOnBind.run(binder, boundService); } catch (RuntimeException | RemoteException e) { // binders may propagate some specific non-RemoteExceptions from the other side // through the binder as well - we cannot allow those to crash the system server diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 083a963bc8fd6..0f27f27dde536 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1819,6 +1819,15 @@ public class ActivityManagerService extends IActivityManager.Stub ncl.start(); } + /** + * Sets a policy for handling app ops. + * + * @param appOpsPolicy The policy. + */ + public void setAppOpsPolicy(@Nullable CheckOpsDelegate appOpsPolicy) { + mAppOpsService.setAppOpsPolicy(appOpsPolicy); + } + public IAppOpsService getAppOpsService() { return mAppOpsService; } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index a776458d63c59..44dcc205a9d00 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -339,7 +339,10 @@ public class AppOpsService extends IAppOpsService.Stub { SparseIntArray mProfileOwners; @GuardedBy("this") - private CheckOpsDelegate mCheckOpsDelegate; + private CheckOpsDelegate mAppOpsPolicy; + + @GuardedBy("this") + private CheckOpsDelegateDispatcher mCheckOpsDelegateDispatcher; /** * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never @@ -1770,6 +1773,17 @@ public class AppOpsService extends IAppOpsService.Stub { mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); } + /** + * Sets a policy for handling app ops. + * + * @param appOpsPolicy The policy. + */ + public void setAppOpsPolicy(@Nullable CheckOpsDelegate appOpsPolicy) { + synchronized (AppOpsService.this) { + mAppOpsPolicy = appOpsPolicy; + } + } + public void packageRemoved(int uid, String packageName) { synchronized (this) { UidState uidState = mUidStates.get(uid); @@ -2868,13 +2882,19 @@ public class AppOpsService extends IAppOpsService.Stub { public CheckOpsDelegate getAppOpsServiceDelegate() { synchronized (this) { - return mCheckOpsDelegate; + return (mCheckOpsDelegateDispatcher != null) + ? mCheckOpsDelegateDispatcher.getCheckOpsDelegate() + : null; } } public void setAppOpsServiceDelegate(CheckOpsDelegate delegate) { synchronized (this) { - mCheckOpsDelegate = delegate; + if (delegate != null) { + mCheckOpsDelegateDispatcher = new CheckOpsDelegateDispatcher(delegate); + } else { + mCheckOpsDelegateDispatcher = null; + } } } @@ -2889,19 +2909,28 @@ public class AppOpsService extends IAppOpsService.Stub { } private int checkOperationInternal(int code, int uid, String packageName, boolean raw) { - final CheckOpsDelegate delegate; - synchronized (this) { - delegate = mCheckOpsDelegate; + final CheckOpsDelegate policy; + final CheckOpsDelegateDispatcher delegateDispatcher; + synchronized (AppOpsService.this) { + policy = mAppOpsPolicy; + delegateDispatcher = mCheckOpsDelegateDispatcher; } - if (delegate == null) { - return checkOperationImpl(code, uid, packageName, raw); + if (policy != null) { + if (delegateDispatcher != null) { + return policy.checkOperation(code, uid, packageName, raw, + delegateDispatcher::checkOperationImpl); + } else { + return policy.checkOperation(code, uid, packageName, raw, + AppOpsService.this::checkOperationImpl); + } + } else if (delegateDispatcher != null) { + delegateDispatcher.getCheckOpsDelegate().checkOperation(code, uid, + packageName, raw, AppOpsService.this::checkOperationImpl); } - return delegate.checkOperation(code, uid, packageName, raw, - AppOpsService.this::checkOperationImpl); + return checkOperationImpl(code, uid, packageName, raw); } - private int checkOperationImpl(int code, int uid, String packageName, - boolean raw) { + private int checkOperationImpl(int code, int uid, String packageName, boolean raw) { verifyIncomingOp(code); verifyIncomingPackage(packageName, UserHandle.getUserId(uid)); @@ -2956,15 +2985,25 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public int checkAudioOperation(int code, int usage, int uid, String packageName) { - final CheckOpsDelegate delegate; - synchronized (this) { - delegate = mCheckOpsDelegate; + final CheckOpsDelegate policy; + final CheckOpsDelegateDispatcher delegateDispatcher; + synchronized (AppOpsService.this) { + policy = mAppOpsPolicy; + delegateDispatcher = mCheckOpsDelegateDispatcher; } - if (delegate == null) { - return checkAudioOperationImpl(code, usage, uid, packageName); + if (policy != null) { + if (delegateDispatcher != null) { + return policy.checkAudioOperation(code, usage, uid, packageName, + delegateDispatcher::checkAudioOperationImpl); + } else { + return policy.checkAudioOperation(code, usage, uid, packageName, + AppOpsService.this::checkAudioOperationImpl); + } + } else if (delegateDispatcher != null) { + delegateDispatcher.getCheckOpsDelegate().checkAudioOperation(code, usage, + uid, packageName, AppOpsService.this::checkAudioOperationImpl); } - return delegate.checkAudioOperation(code, usage, uid, packageName, - AppOpsService.this::checkAudioOperationImpl); + return checkAudioOperationImpl(code, usage, uid, packageName); } private int checkAudioOperationImpl(int code, int usage, int uid, String packageName) { @@ -3078,17 +3117,29 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public int noteOperation(int code, int uid, String packageName, String attributionTag, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage) { - final CheckOpsDelegate delegate; - synchronized (this) { - delegate = mCheckOpsDelegate; + final CheckOpsDelegate policy; + final CheckOpsDelegateDispatcher delegateDispatcher; + synchronized (AppOpsService.this) { + policy = mAppOpsPolicy; + delegateDispatcher = mCheckOpsDelegateDispatcher; } - if (delegate == null) { - return noteOperationImpl(code, uid, packageName, attributionTag, - shouldCollectAsyncNotedOp, message, shouldCollectMessage); + if (policy != null) { + if (delegateDispatcher != null) { + return policy.noteOperation(code, uid, packageName, attributionTag, + shouldCollectAsyncNotedOp, message, shouldCollectMessage, + delegateDispatcher::noteOperationImpl); + } else { + return policy.noteOperation(code, uid, packageName, attributionTag, + shouldCollectAsyncNotedOp, message, shouldCollectMessage, + AppOpsService.this::noteOperationImpl); + } + } else if (delegateDispatcher != null) { + delegateDispatcher.getCheckOpsDelegate().noteOperation(code, uid, packageName, + attributionTag, shouldCollectAsyncNotedOp, message, shouldCollectMessage, + AppOpsService.this::noteOperationImpl); } - return delegate.noteOperation(code, uid, packageName, attributionTag, - shouldCollectAsyncNotedOp, message, shouldCollectMessage, - AppOpsService.this::noteOperationImpl); + return noteOperationImpl(code, uid, packageName, attributionTag, + shouldCollectAsyncNotedOp, message, shouldCollectMessage); } private int noteOperationImpl(int code, int uid, @Nullable String packageName, @@ -6589,7 +6640,6 @@ public class AppOpsService extends IAppOpsService.Stub { } } - /** * Async task for writing note op stack trace, op code, package name and version to file * More specifically, writes all the collected ops from {@link #mNoteOpCallerStacktraces} @@ -6726,4 +6776,34 @@ public class AppOpsService extends IAppOpsService.Stub { } } } + + private final class CheckOpsDelegateDispatcher { + private final @NonNull CheckOpsDelegate mCheckOpsDelegate; + + CheckOpsDelegateDispatcher(@NonNull CheckOpsDelegate checkOpsDelegate) { + mCheckOpsDelegate = checkOpsDelegate; + } + + public @NonNull CheckOpsDelegate getCheckOpsDelegate() { + return mCheckOpsDelegate; + } + + public int checkOperationImpl(int code, int uid, String packageName, boolean raw) { + return mCheckOpsDelegate.checkOperation(code, uid, packageName, raw, + AppOpsService.this::checkOperationImpl); + } + + public int checkAudioOperationImpl(int code, int usage, int uid, String packageName) { + return mCheckOpsDelegate.checkAudioOperation(code, usage, uid, packageName, + AppOpsService.this::checkAudioOperationImpl); + } + + public int noteOperationImpl(int code, int uid, @Nullable String packageName, + @Nullable String featureId, boolean shouldCollectAsyncNotedOp, + @Nullable String message, boolean shouldCollectMessage) { + return mCheckOpsDelegate.noteOperation(code, uid, packageName, featureId, + shouldCollectAsyncNotedOp, message, shouldCollectMessage, + AppOpsService.this::noteOperationImpl); + } + } } diff --git a/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java index 7b400b6e03093..6ea4bd2b1d6d0 100644 --- a/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java +++ b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java @@ -17,7 +17,6 @@ package com.android.server.location; import android.annotation.Nullable; -import android.content.ComponentName; import android.content.Context; import android.hardware.location.ActivityRecognitionHardware; import android.hardware.location.IActivityRecognitionHardwareClient; @@ -27,6 +26,7 @@ import android.os.RemoteException; import android.util.Log; import com.android.server.ServiceWatcher; +import com.android.server.ServiceWatcher.BoundService; /** * Proxy class to bind GmsCore to the ActivityRecognitionHardware. @@ -82,7 +82,7 @@ public class HardwareActivityRecognitionProxy { return resolves; } - private void onBind(IBinder binder, ComponentName service) throws RemoteException { + private void onBind(IBinder binder, BoundService service) throws RemoteException { String descriptor = binder.getInterfaceDescriptor(); if (IActivityRecognitionHardwareWatcher.class.getCanonicalName().equals(descriptor)) { diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index cbbc981d9ed6a..57e9fc9cb7194 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -63,6 +63,7 @@ import android.location.LastLocationRequest; import android.location.Location; import android.location.LocationManager; import android.location.LocationManagerInternal; +import android.location.LocationManagerInternal.OnProviderLocationTagsChangeListener; import android.location.LocationProvider; import android.location.LocationRequest; import android.location.LocationTime; @@ -82,6 +83,7 @@ import android.os.WorkSource.WorkChain; import android.provider.Settings; import android.stats.location.LocationStatsEnums; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.Log; @@ -255,6 +257,9 @@ public class LocationManagerService extends ILocationManager.Stub { final CopyOnWriteArrayList mProviderManagers = new CopyOnWriteArrayList<>(); + @GuardedBy("mLock") + private @Nullable OnProviderLocationTagsChangeListener mOnProviderLocationTagsChangeListener; + LocationManagerService(Context context, Injector injector, LocationEventLog eventLog) { mContext = context.createAttributionContext(ATTRIBUTION_TAG); mInjector = injector; @@ -319,8 +324,9 @@ public class LocationManagerService extends ILocationManager.Stub { Preconditions.checkState(getLocationProviderManager(manager.getName()) == null); manager.startManager(); + manager.setOnProviderLocationTagsChangeListener( + mOnProviderLocationTagsChangeListener); if (realProvider != null) { - // custom logic wrapping all non-passive providers if (manager != mPassiveManager) { boolean enableStationaryThrottling = Settings.Global.getInt( @@ -331,7 +337,6 @@ public class LocationManagerService extends ILocationManager.Stub { mInjector, realProvider, mEventLog); } } - manager.setRealProvider(realProvider); } mProviderManagers.add(manager); @@ -456,8 +461,9 @@ public class LocationManagerService extends ILocationManager.Stub { .setPowerUsage(Integer.parseInt(fragments[8])) .setAccuracy(Integer.parseInt(fragments[9])) .build(); - getOrAddLocationProviderManager(name).setMockProvider( - new MockLocationProvider(properties, CallerIdentity.fromContext(mContext))); + final LocationProviderManager manager = getOrAddLocationProviderManager(name); + manager.setMockProvider(new MockLocationProvider(properties, + CallerIdentity.fromContext(mContext), /*locationTags*/ null)); } } @@ -1147,15 +1153,16 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public void addTestProvider(String provider, ProviderProperties properties, - String packageName, String attributionTag) { + List locationTags, String packageName, String attributionTag) { // unsafe is ok because app ops will verify the package name CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag); if (!mInjector.getAppOpsHelper().noteOp(AppOpsManager.OP_MOCK_LOCATION, identity)) { return; } - getOrAddLocationProviderManager(provider).setMockProvider( - new MockLocationProvider(properties, identity)); + final LocationProviderManager manager = getOrAddLocationProviderManager(provider); + manager.setMockProvider(new MockLocationProvider(properties, identity, + (locationTags != null) ? new ArraySet<>(locationTags) : null)); } @Override @@ -1392,6 +1399,19 @@ public class LocationManagerService extends ILocationManager.Stub { return new LocationTime(location.getTime(), location.getElapsedRealtimeNanos()); } + + @Override + public void setOnProviderLocationTagsChangeListener( + @Nullable OnProviderLocationTagsChangeListener listener) { + synchronized (mLock) { + mOnProviderLocationTagsChangeListener = listener; + final int providerCount = mProviderManagers.size(); + for (int i = 0; i < providerCount; i++) { + final LocationProviderManager manager = mProviderManagers.get(i); + manager.setOnProviderLocationTagsChangeListener(listener); + } + } + } } private static class SystemInjector implements Injector { diff --git a/services/core/java/com/android/server/location/LocationShellCommand.java b/services/core/java/com/android/server/location/LocationShellCommand.java index f0dd8b559d649..21a9b0442b74d 100644 --- a/services/core/java/com/android/server/location/LocationShellCommand.java +++ b/services/core/java/com/android/server/location/LocationShellCommand.java @@ -75,8 +75,8 @@ class LocationShellCommand extends BasicShellCommandHandler { case "add-test-provider": { String provider = getNextArgRequired(); ProviderProperties properties = parseTestProviderProviderProperties(); - mService.addTestProvider(provider, properties, mContext.getOpPackageName(), - mContext.getFeatureId()); + mService.addTestProvider(provider, properties, /*locationTags*/ null, + mContext.getOpPackageName(), mContext.getFeatureId()); return 0; } case "remove-test-provider": { diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index 9216a6b245a65..29da177ee4a14 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -371,7 +371,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements public GnssLocationProvider(Context context, Injector injector, GnssNative gnssNative, GnssMetrics gnssMetrics) { - super(FgThread.getExecutor(), CallerIdentity.fromContext(context), PROPERTIES); + super(FgThread.getExecutor(), CallerIdentity.fromContext(context), PROPERTIES, + /*locationTags*/ null); mContext = context; mGnssNative = gnssNative; diff --git a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java index 08deb8698608c..9ff6e6bc8e328 100644 --- a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java @@ -29,6 +29,7 @@ import com.android.internal.util.Preconditions; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Objects; +import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; import java.util.function.UnaryOperator; @@ -67,7 +68,7 @@ public abstract class AbstractLocationProvider { * Default state value for a location provider that is disabled with no properties and an * empty provider package list. */ - public static final State EMPTY_STATE = new State(false, null, null); + public static final State EMPTY_STATE = new State(false, null, null, null); /** * The provider's allowed state. @@ -84,10 +85,14 @@ public abstract class AbstractLocationProvider { */ @Nullable public final CallerIdentity identity; - private State(boolean allowed, ProviderProperties properties, CallerIdentity identity) { + @Nullable public final Set locationTags; + + private State(boolean allowed, ProviderProperties properties, CallerIdentity identity, + Set locationTags) { this.allowed = allowed; this.properties = properties; this.identity = identity; + this.locationTags = locationTags; } /** @@ -97,7 +102,7 @@ public abstract class AbstractLocationProvider { if (allowed == this.allowed) { return this; } else { - return new State(allowed, properties, identity); + return new State(allowed, properties, identity, locationTags); } } @@ -108,7 +113,7 @@ public abstract class AbstractLocationProvider { if (Objects.equals(properties, this.properties)) { return this; } else { - return new State(allowed, properties, identity); + return new State(allowed, properties, identity, locationTags); } } @@ -119,10 +124,22 @@ public abstract class AbstractLocationProvider { if (Objects.equals(identity, this.identity)) { return this; } else { - return new State(allowed, properties, identity); + return new State(allowed, properties, identity, locationTags); } } + /** + * Returns a state the same as the current but with location tags set as specified. + */ + public State withLocationTags(@Nullable Set locationTags) { + if (Objects.equals(locationTags, this.locationTags)) { + return this; + } else { + return new State(allowed, properties, identity, locationTags); + } + } + + @Override public boolean equals(Object o) { if (this == o) { @@ -133,12 +150,13 @@ public abstract class AbstractLocationProvider { } State state = (State) o; return allowed == state.allowed && properties == state.properties - && Objects.equals(identity, state.identity); + && Objects.equals(identity, state.identity) + && Objects.equals(locationTags, state.locationTags); } @Override public int hashCode() { - return Objects.hash(allowed, properties, identity); + return Objects.hash(allowed, properties, identity, locationTags); } } @@ -195,13 +213,14 @@ public abstract class AbstractLocationProvider { * An optional identity and properties may be provided to initialize the location provider. */ protected AbstractLocationProvider(Executor executor, @Nullable CallerIdentity identity, - @Nullable ProviderProperties properties) { + @Nullable ProviderProperties properties, @Nullable Set locationTags) { Preconditions.checkArgument(identity == null || identity.getListenerId() == null); mExecutor = executor; mInternalState = new AtomicReference<>(new InternalState(null, State.EMPTY_STATE .withIdentity(identity) - .withProperties(properties))); + .withProperties(properties).withLocationTags(locationTags)) + ); mController = new Controller(); } @@ -280,6 +299,13 @@ public abstract class AbstractLocationProvider { setState(state -> state.withIdentity(identity)); } + /** + * Call this method to report a change in provider location tags. + */ + protected void setLocationTags(@Nullable Set locationTags) { + setState(state -> state.withLocationTags(locationTags)); + } + /** * Call this method to report a new location. */ diff --git a/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java b/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java index a3ec867220dd8..49f6e64a1e0ca 100644 --- a/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java @@ -40,7 +40,7 @@ class DelegateLocationProvider extends AbstractLocationProvider private boolean mInitialized = false; DelegateLocationProvider(Executor executor, AbstractLocationProvider delegate) { - super(executor, null, null); + super(executor, null, null, null); mDelegate = delegate; } diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java index 5a35d7f88ff35..1ecf3ee40a53f 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -52,6 +52,8 @@ import android.location.LastLocationRequest; import android.location.Location; import android.location.LocationManager; import android.location.LocationManagerInternal; +import android.location.LocationManagerInternal.LocationTagInfo; +import android.location.LocationManagerInternal.OnProviderLocationTagsChangeListener; import android.location.LocationManagerInternal.ProviderEnabledListener; import android.location.LocationRequest; import android.location.LocationResult; @@ -85,6 +87,7 @@ import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; +import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.location.LocationPermissions; @@ -117,6 +120,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Predicate; @@ -1288,6 +1292,9 @@ public class LocationProviderManager extends @GuardedBy("mLock") private @Nullable OnAlarmListener mDelayedRegister; + @GuardedBy("mLock") + private @Nullable OnProviderLocationTagsChangeListener mOnLocationTagsChangeListener; + public LocationProviderManager(Context context, Injector injector, LocationEventLog eventLog, String name, @Nullable PassiveLocationProviderManager passiveManager) { mContext = context; @@ -1448,6 +1455,19 @@ public class LocationProviderManager extends } } + /** + * Registers a listener for the location tags of the provider. + * + * @param listener The listener + */ + public void setOnProviderLocationTagsChangeListener( + @Nullable OnProviderLocationTagsChangeListener listener) { + Preconditions.checkArgument(mOnLocationTagsChangeListener == null || listener == null); + synchronized (mLock) { + mOnLocationTagsChangeListener = listener; + } + } + public void setMockProvider(@Nullable MockLocationProvider provider) { synchronized (mLock) { Preconditions.checkState(mState != STATE_STOPPED); @@ -2244,6 +2264,27 @@ public class LocationProviderManager extends if (oldState.allowed != newState.allowed) { onEnabledChanged(UserHandle.USER_ALL); } + + if (!Objects.equals(oldState.locationTags, newState.locationTags)) { + if (mOnLocationTagsChangeListener != null) { + if (oldState.identity != null) { + FgThread.getHandler().sendMessage(PooledLambda.obtainMessage( + OnProviderLocationTagsChangeListener::onLocationTagsChanged, + mOnLocationTagsChangeListener, new LocationTagInfo( + oldState.identity.getUid(), oldState.identity.getPackageName(), + Collections.emptySet()) + )); + } + if (newState.identity != null) { + FgThread.getHandler().sendMessage(PooledLambda.obtainMessage( + OnProviderLocationTagsChangeListener::onLocationTagsChanged, + mOnLocationTagsChangeListener, new LocationTagInfo( + newState.identity.getUid(), newState.identity.getPackageName(), + newState.locationTags) + )); + } + } + } } @GuardedBy("mLock") diff --git a/services/core/java/com/android/server/location/provider/MockLocationProvider.java b/services/core/java/com/android/server/location/provider/MockLocationProvider.java index 0d8f64377db0f..7660f56f15805 100644 --- a/services/core/java/com/android/server/location/provider/MockLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/MockLocationProvider.java @@ -28,6 +28,7 @@ import android.os.Bundle; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.Set; /** * A mock location provider used by LocationManagerService to implement test providers. @@ -38,9 +39,10 @@ public class MockLocationProvider extends AbstractLocationProvider { @Nullable private Location mLocation; - public MockLocationProvider(ProviderProperties properties, CallerIdentity identity) { + public MockLocationProvider(ProviderProperties properties, CallerIdentity identity, + @Nullable Set locationTags) { // using a direct executor is ok because this class has no locks that could deadlock - super(DIRECT_EXECUTOR, identity, properties); + super(DIRECT_EXECUTOR, identity, properties, locationTags); } /** Sets the allowed state of this mock provider. */ diff --git a/services/core/java/com/android/server/location/provider/MockableLocationProvider.java b/services/core/java/com/android/server/location/provider/MockableLocationProvider.java index cb7264e55fa9d..4ffa9a509a23d 100644 --- a/services/core/java/com/android/server/location/provider/MockableLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/MockableLocationProvider.java @@ -75,7 +75,7 @@ public class MockableLocationProvider extends AbstractLocationProvider { public MockableLocationProvider(Object ownerLock) { // using a direct executor is acceptable because all inbound calls are delegated to the // actual provider implementations which will use their own executors - super(DIRECT_EXECUTOR, null, null); + super(DIRECT_EXECUTOR, null, null, null); mOwnerLock = ownerLock; mRequest = ProviderRequest.EMPTY_REQUEST; } diff --git a/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java b/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java index a5758a37b9839..ee9d35d21798d 100644 --- a/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java @@ -47,7 +47,8 @@ public class PassiveLocationProvider extends AbstractLocationProvider { public PassiveLocationProvider(Context context) { // using a direct executor is ok because this class has no locks that could deadlock - super(DIRECT_EXECUTOR, CallerIdentity.fromContext(context), PROPERTIES); + super(DIRECT_EXECUTOR, CallerIdentity.fromContext(context), PROPERTIES, + /*locationTags*/ null); setAllowed(true); } diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java index 4c97f645aac95..f00478a3488a0 100644 --- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java @@ -18,6 +18,7 @@ package com.android.server.location.provider.proxy; import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; @@ -32,10 +33,12 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; +import android.util.ArraySet; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import com.android.server.ServiceWatcher; +import com.android.server.ServiceWatcher.BoundService; import com.android.server.location.provider.AbstractLocationProvider; import java.io.FileDescriptor; @@ -49,6 +52,9 @@ import java.util.Objects; */ public class ProxyLocationProvider extends AbstractLocationProvider { + private static final String KEY_LOCATION_TAGS = "android:location_allow_listed_tags"; + private static final String LOCATION_TAGS_SEPARATOR = ";"; + /** * Creates and registers this proxy. If no suitable service is available for the proxy, returns * null. @@ -84,7 +90,7 @@ public class ProxyLocationProvider extends AbstractLocationProvider { int nonOverlayPackageResId) { // safe to use direct executor since our locks are not acquired in a code path invoked by // our owning provider - super(DIRECT_EXECUTOR, null, null); + super(DIRECT_EXECUTOR, null, null, null); mContext = context; mServiceWatcher = new ServiceWatcher(context, action, this::onBind, @@ -94,22 +100,34 @@ public class ProxyLocationProvider extends AbstractLocationProvider { mRequest = ProviderRequest.EMPTY_REQUEST; } + private void updateLocationTagInfo(@NonNull BoundService boundService) { + if (boundService.metadata != null) { + final String tagsList = boundService.metadata.getString(KEY_LOCATION_TAGS); + if (tagsList != null) { + final String[] tags = tagsList.split(LOCATION_TAGS_SEPARATOR); + setLocationTags(new ArraySet<>(tags)); + } + } + } + private boolean checkServiceResolves() { return mServiceWatcher.checkServiceResolves(); } - private void onBind(IBinder binder, ComponentName service) throws RemoteException { + private void onBind(IBinder binder, BoundService boundService) throws RemoteException { ILocationProvider provider = ILocationProvider.Stub.asInterface(binder); synchronized (mLock) { mProxy = new Proxy(); - mService = service; + mService = boundService.component; provider.setLocationProviderManager(mProxy); ProviderRequest request = mRequest; if (!request.equals(ProviderRequest.EMPTY_REQUEST)) { provider.setRequest(request); } + + updateLocationTagInfo(boundService); } } diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java new file mode 100644 index 0000000000000..c9653909adb61 --- /dev/null +++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2021 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.policy; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.app.AppOpsManagerInternal; +import android.location.LocationManagerInternal; +import android.util.ArrayMap; +import android.util.ArraySet; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.function.HeptFunction; +import com.android.internal.util.function.QuadFunction; +import com.android.server.LocalServices; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * This class defines policy for special behaviors around app ops. + */ +public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegate { + @NonNull + private final Object mLock = new Object(); + + /** + * The locking policy around the location tags is a bit special. Since we want to + * avoid grabbing the lock on every op note we are taking the approach where the + * read and write are being done via a thread-safe data structure such that the + * lookup/insert are single thread-safe calls. When we update the cached state we + * use a lock to ensure the update's lookup and store calls are done atomically, + * so multiple writers would not interleave. The tradeoff is we make is that the + * concurrent data structure would use boxing/unboxing of integers but this is + * preferred to locking. + */ + @GuardedBy("mLock - writes only - see above") + @NonNull + private final ConcurrentHashMap>> mLocationTags = + new ConcurrentHashMap(); + + public AppOpsPolicy() { + final LocationManagerInternal locationManagerInternal = LocalServices.getService( + LocationManagerInternal.class); + locationManagerInternal.setOnProviderLocationTagsChangeListener((providerTagInfo) -> { + synchronized (mLock) { + final int uid = providerTagInfo.getUid(); + // We make a copy of the per UID state to limit our mutation to one + // operation in the underlying concurrent data structure. + ArrayMap> uidTags = mLocationTags.get(uid); + if (uidTags != null) { + uidTags = new ArrayMap<>(uidTags); + } + + final String packageName = providerTagInfo.getPackageName(); + ArraySet packageTags = (uidTags != null) ? uidTags.get(packageName) : null; + if (packageTags != null) { + packageTags = new ArraySet<>(packageTags); + } + + final Set providerTags = providerTagInfo.getTags(); + if (providerTags != null && !providerTags.isEmpty()) { + if (packageTags != null) { + packageTags.clear(); + packageTags.addAll(providerTags); + } else { + packageTags = new ArraySet<>(providerTags); + } + if (uidTags == null) { + uidTags = new ArrayMap<>(); + } + uidTags.put(packageName, packageTags); + mLocationTags.put(uid, uidTags); + } else if (uidTags != null) { + uidTags.remove(packageName); + if (!uidTags.isEmpty()) { + mLocationTags.put(uid, uidTags); + } else { + mLocationTags.remove(uid); + } + } + } + }); + } + + @Override + public int checkOperation(int code, int uid, String packageName, boolean raw, + QuadFunction superImpl) { + return superImpl.apply(code, uid, packageName, raw); + } + + @Override + public int checkAudioOperation(int code, int usage, int uid, String packageName, + QuadFunction superImpl) { + return superImpl.apply(code, usage, uid, packageName); + } + + @Override + public int noteOperation(int code, int uid, @Nullable String packageName, + @Nullable String featureId, boolean shouldCollectAsyncNotedOp, @Nullable String message, + boolean shouldCollectMessage, @NonNull HeptFunction superImpl) { + if (isHandledOp(code)) { + // Only a single lookup from the underlying concurrent data structure + final ArrayMap> uidTags = mLocationTags.get(uid); + if (uidTags != null) { + final ArraySet packageTags = uidTags.get(packageName); + if (packageTags != null && packageTags.contains(featureId)) { + return superImpl.apply(resolveLocationOp(code), uid, packageName, featureId, + shouldCollectAsyncNotedOp, message, shouldCollectMessage); + } + } + } + return superImpl.apply(code, uid, packageName, featureId, shouldCollectAsyncNotedOp, + message, shouldCollectMessage); + } + + private static boolean isHandledOp(int code) { + switch (code) { + case AppOpsManager.OP_FINE_LOCATION: + case AppOpsManager.OP_COARSE_LOCATION: + return true; + } + return false; + } + + private static int resolveLocationOp(int code) { + switch (code) { + case AppOpsManager.OP_FINE_LOCATION: + return AppOpsManager.OP_FINE_LOCATION_SOURCE; + case AppOpsManager.OP_COARSE_LOCATION: + return AppOpsManager.OP_COARSE_LOCATION_SOURCE; + } + return code; + } +} diff --git a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java index 38211efc1c63d..531c62c5b4e9f 100644 --- a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java +++ b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java @@ -25,7 +25,6 @@ import static com.android.server.timezonedetector.location.LocationTimeZoneManag import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -41,6 +40,7 @@ import android.util.IndentingPrintWriter; import com.android.internal.annotations.GuardedBy; import com.android.server.ServiceWatcher; +import com.android.server.ServiceWatcher.BoundService; import java.util.Objects; import java.util.function.Predicate; @@ -123,7 +123,7 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy { return resolves; } - private void onBind(IBinder binder, ComponentName componentName) { + private void onBind(IBinder binder, BoundService boundService) { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 6bbd320017a77..a3d335340e9f4 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -162,6 +162,7 @@ import com.android.server.pm.ShortcutService; import com.android.server.pm.UserManagerService; import com.android.server.pm.dex.SystemServerDexLoadReporter; import com.android.server.pm.verify.domain.DomainVerificationService; +import com.android.server.policy.AppOpsPolicy; import com.android.server.policy.PermissionPolicyService; import com.android.server.policy.PhoneWindowManager; import com.android.server.policy.role.RoleServicePlatformHelperImpl; @@ -2656,6 +2657,14 @@ public final class SystemServer implements Dumpable { } t.traceEnd(); + t.traceBegin("RegisterAppOpsPolicy"); + try { + mActivityManagerService.setAppOpsPolicy(new AppOpsPolicy()); + } catch (Throwable e) { + reportWtf("registering app ops policy", e); + } + t.traceEnd(); + // No dependency on Webview preparation in system server. But this should // be completed before allowing 3rd party final String WEBVIEW_PREPARATION = "WebViewFactoryPreparation"; diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java index 68d6557880c43..c4c9ad088e459 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java @@ -352,7 +352,8 @@ public class LocationProviderManagerTest { @Test public void testGetLastLocation_ClearOnMockRemoval() { - MockLocationProvider mockProvider = new MockLocationProvider(PROPERTIES, PROVIDER_IDENTITY); + MockLocationProvider mockProvider = new MockLocationProvider(PROPERTIES, PROVIDER_IDENTITY, + null); mockProvider.setAllowed(true); mManager.setMockProvider(mockProvider); @@ -1048,7 +1049,7 @@ public class LocationProviderManagerTest { private final ArrayList mFlushCallbacks = new ArrayList<>(); TestProvider(ProviderProperties properties, CallerIdentity identity) { - super(DIRECT_EXECUTOR, identity, properties); + super(DIRECT_EXECUTOR, identity, properties, null); } public void setProviderAllowed(boolean allowed) { diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java index 07170dacb4da8..e8a0bb51e20f5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java @@ -71,7 +71,8 @@ public class MockableLocationProviderTest { .setPowerUsage(POWER_USAGE_LOW) .setAccuracy(ACCURACY_FINE) .build(), - CallerIdentity.forTest(0, 1, "testpackage", "test")); + CallerIdentity.forTest(0, 1, "testpackage", "test"), + null); mProvider = new MockableLocationProvider(lock); mProvider.getController().setListener(mListener); diff --git a/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java b/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java index 775bdd5801571..a1eadbe4a64f7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java @@ -40,7 +40,7 @@ public class FakeProvider extends AbstractLocationProvider { private final FakeProviderInterface mFakeInterface; public FakeProvider(FakeProviderInterface fakeInterface) { - super(Runnable::run, null, null); + super(Runnable::run, null, null, null); mFakeInterface = fakeInterface; }