summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt1
-rw-r--r--core/java/android/app/AppOpsManager.java64
-rw-r--r--location/java/android/location/ILocationManager.aidl3
-rw-r--r--location/java/android/location/LocationManager.java25
-rw-r--r--location/java/android/location/LocationManagerInternal.java75
-rw-r--r--services/core/java/com/android/server/ServiceWatcher.java109
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java9
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java140
-rw-r--r--services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java4
-rw-r--r--services/core/java/com/android/server/location/LocationManagerService.java34
-rw-r--r--services/core/java/com/android/server/location/LocationShellCommand.java4
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssLocationProvider.java3
-rw-r--r--services/core/java/com/android/server/location/provider/AbstractLocationProvider.java44
-rw-r--r--services/core/java/com/android/server/location/provider/DelegateLocationProvider.java2
-rw-r--r--services/core/java/com/android/server/location/provider/LocationProviderManager.java41
-rw-r--r--services/core/java/com/android/server/location/provider/MockLocationProvider.java6
-rw-r--r--services/core/java/com/android/server/location/provider/MockableLocationProvider.java2
-rw-r--r--services/core/java/com/android/server/location/provider/PassiveLocationProvider.java3
-rw-r--r--services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java24
-rw-r--r--services/core/java/com/android/server/policy/AppOpsPolicy.java151
-rw-r--r--services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java4
-rw-r--r--services/java/com/android/server/SystemServer.java9
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java5
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java2
25 files changed, 656 insertions, 111 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 6b27567323d4..5dabba5d8b2d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -19662,6 +19662,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<java.lang.String>);
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 2f3b50b17d51..160844aacc46 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 5e3966032a11..38b48e97771a 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<String> 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 dd5b6e6b4222..95bae5ae7aab 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<String> 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 a6a0e7aa24ff..763835c9cbe2 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.
*
@@ -39,6 +43,21 @@ public abstract class LocationManagerInternal {
}
/**
+ * 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.
*
* @param provider A location provider as listed by {@link LocationManager#getAllProviders()}
@@ -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<String> mLocationTags;
+
+ public LocationTagInfo(int uid, @NonNull String packageName,
+ @Nullable Set<String> 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<String> getTags() {
+ return mLocationTags;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java
index c2d8fa24157a..8a2894c84cc4 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<ServiceInfo> {
+ @Immutable
+ public static final class BoundService implements Comparable<BoundService> {
- 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<ComponentName, BoundService> mPendingBinds = new ArrayMap<>();
+
+ @Nullable
+ private final OnBindRunner mOnBind;
+
+ @Nullable
+ private final Runnable mOnUnbind;
- // write from caller thread only, read anywhere
- private volatile boolean mRegistered;
+ // 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<ResolveInfo> 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 33c0f9d628dc..722c5888453d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1820,6 +1820,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 a776458d63c5..44dcc205a9d0 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;
- }
- if (delegate == null) {
- return noteOperationImpl(code, uid, packageName, attributionTag,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ final CheckOpsDelegate policy;
+ final CheckOpsDelegateDispatcher delegateDispatcher;
+ synchronized (AppOpsService.this) {
+ policy = mAppOpsPolicy;
+ delegateDispatcher = mCheckOpsDelegateDispatcher;
+ }
+ 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 7b400b6e0309..6ea4bd2b1d6d 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 cbbc981d9ed6..57e9fc9cb719 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<LocationProviderManager> 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<String> 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 f0dd8b559d64..21a9b0442b74 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 9216a6b245a6..29da177ee4a1 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 08deb8698608..9ff6e6bc8e32 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<String> locationTags;
+
+ private State(boolean allowed, ProviderProperties properties, CallerIdentity identity,
+ Set<String> 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<String> 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<String> 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();
}
@@ -281,6 +300,13 @@ public abstract class AbstractLocationProvider {
}
/**
+ * Call this method to report a change in provider location tags.
+ */
+ protected void setLocationTags(@Nullable Set<String> locationTags) {
+ setState(state -> state.withLocationTags(locationTags));
+ }
+
+ /**
* Call this method to report a new location.
*/
protected void reportLocation(LocationResult locationResult) {
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 a3ec867220dd..49f6e64a1e0c 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 5a35d7f88ff3..1ecf3ee40a53 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 0d8f64377db0..7660f56f1580 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<String> 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 cb7264e55fa9..4ffa9a509a23 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 a5758a37b983..ee9d35d21798 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 4c97f645aac9..f00478a3488a 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 000000000000..c9653909adb6
--- /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<Integer, ArrayMap<String, ArraySet<String>>> 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<String, ArraySet<String>> uidTags = mLocationTags.get(uid);
+ if (uidTags != null) {
+ uidTags = new ArrayMap<>(uidTags);
+ }
+
+ final String packageName = providerTagInfo.getPackageName();
+ ArraySet<String> packageTags = (uidTags != null) ? uidTags.get(packageName) : null;
+ if (packageTags != null) {
+ packageTags = new ArraySet<>(packageTags);
+ }
+
+ final Set<String> 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<Integer, Integer, String, Boolean, Integer> superImpl) {
+ return superImpl.apply(code, uid, packageName, raw);
+ }
+
+ @Override
+ public int checkAudioOperation(int code, int usage, int uid, String packageName,
+ QuadFunction<Integer, Integer, Integer, String, Integer> 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<Integer, Integer, String, String,
+ Boolean, String, Boolean, Integer> superImpl) {
+ if (isHandledOp(code)) {
+ // Only a single lookup from the underlying concurrent data structure
+ final ArrayMap<String, ArraySet<String>> uidTags = mLocationTags.get(uid);
+ if (uidTags != null) {
+ final ArraySet<String> 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 38211efc1c63..531c62c5b4e9 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 6bbd320017a7..a3d335340e9f 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 68d6557880c4..c4c9ad088e45 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<Runnable> 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 07170dacb4da..e8a0bb51e20f 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 775bdd580157..a1eadbe4a64f 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;
}