diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 59b6490e36d48..0772478e2be4c 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -69,6 +69,7 @@ package android {
field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS";
field @Deprecated public static final String BROADCAST_NETWORK_PRIVILEGED = "android.permission.BROADCAST_NETWORK_PRIVILEGED";
+ field public static final String BYPASS_ROLE_QUALIFICATION = "android.permission.BYPASS_ROLE_QUALIFICATION";
field public static final String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED";
field public static final String CAMERA_OPEN_CLOSE_LISTENER = "android.permission.CAMERA_OPEN_CLOSE_LISTENER";
field public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 9931bf9d78c23..906524e387f7c 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -233,6 +233,8 @@ package android.app {
field public static final String KEY_BG_STATE_SETTLE_TIME = "bg_state_settle_time";
field public static final String KEY_FG_SERVICE_STATE_SETTLE_TIME = "fg_service_state_settle_time";
field public static final String KEY_TOP_STATE_SETTLE_TIME = "top_state_settle_time";
+ field public static final String OPSTR_ACTIVITY_RECOGNITION = "android:activity_recognition";
+ field public static final String OPSTR_ACTIVITY_RECOGNITION_SOURCE = "android:activity_recognition_source";
field public static final String OPSTR_MANAGE_ONGOING_CALLS = "android:manage_ongoing_calls";
field public static final String OPSTR_PHONE_CALL_CAMERA = "android:phone_call_camera";
field public static final String OPSTR_PHONE_CALL_MICROPHONE = "android:phone_call_microphone";
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 436007cae5029..8e2626a8139b4 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1225,9 +1225,19 @@ public class AppOpsManager {
/** @hide */
public static final int OP_UWB_RANGING = AppProtoEnums.APP_OP_UWB_RANGING;
+ /**
+ * Activity recognition being accessed by an activity recognition source, which
+ * is a component that already has access since it is the one that detects
+ * activity recognition.
+ *
+ * @hide
+ */
+ public static final int OP_ACTIVITY_RECOGNITION_SOURCE =
+ AppProtoEnums.APP_OP_ACTIVITY_RECOGNITION_SOURCE;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 113;
+ public static final int _NUM_OP = 114;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1478,6 +1488,7 @@ public class AppOpsManager {
public static final String OPSTR_USE_BIOMETRIC = "android:use_biometric";
/** @hide Recognize physical activity. */
+ @TestApi
public static final String OPSTR_ACTIVITY_RECOGNITION = "android:activity_recognition";
/** @hide Financial app read sms. */
@@ -1643,6 +1654,17 @@ public class AppOpsManager {
/** @hide */
public static final String OPSTR_UWB_RANGING = "android:uwb_ranging";
+ /**
+ * Activity recognition being accessed by an activity recognition source, which
+ * is a component that already has access since it is the one that detects
+ * activity recognition.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final String OPSTR_ACTIVITY_RECOGNITION_SOURCE =
+ "android:activity_recognition_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} */
@@ -1853,6 +1875,7 @@ public class AppOpsManager {
OP_MANAGE_MEDIA, // MANAGE_MEDIA
OP_BLUETOOTH_CONNECT, // OP_BLUETOOTH_CONNECT
OP_UWB_RANGING, // OP_UWB_RANGING
+ OP_ACTIVITY_RECOGNITION_SOURCE // OP_ACTIVITY_RECOGNITION_SOURCE
};
/**
@@ -1972,6 +1995,7 @@ public class AppOpsManager {
OPSTR_MANAGE_MEDIA,
OPSTR_BLUETOOTH_CONNECT,
OPSTR_UWB_RANGING,
+ OPSTR_ACTIVITY_RECOGNITION_SOURCE
};
/**
@@ -2091,7 +2115,8 @@ public class AppOpsManager {
"COARSE_LOCATION_SOURCE",
"MANAGE_MEDIA",
"BLUETOOTH_CONNECT",
- "UWB_RANGING"
+ "UWB_RANGING",
+ "ACTIVITY_RECOGNITION_SOURCE"
};
/**
@@ -2213,6 +2238,7 @@ public class AppOpsManager {
Manifest.permission.MANAGE_MEDIA,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.UWB_RANGING,
+ null, // no permission for OP_ACTIVITY_RECOGNITION_SOURCE,
};
/**
@@ -2334,6 +2360,7 @@ public class AppOpsManager {
null, // MANAGE_MEDIA
null, // BLUETOOTH_CONNECT
null, // UWB_RANGING
+ null, // ACTIVITY_RECOGNITION_SOURCE
};
/**
@@ -2454,6 +2481,7 @@ public class AppOpsManager {
null, // MANAGE_MEDIA
null, // BLUETOOTH_CONNECT
null, // UWB_RANGING
+ null // ACTIVITY_RECOGNITION_SOURCE
};
/**
@@ -2573,6 +2601,7 @@ public class AppOpsManager {
AppOpsManager.MODE_DEFAULT, // MANAGE_MEDIA
AppOpsManager.MODE_ALLOWED, // BLUETOOTH_CONNECT
AppOpsManager.MODE_ALLOWED, // UWB_RANGING
+ AppOpsManager.MODE_ALLOWED, // ACTIVITY_RECOGNITION_SOURCE
};
/**
@@ -2696,6 +2725,7 @@ public class AppOpsManager {
false, // MANAGE_MEDIA
false, // BLUETOOTH_CONNECT
false, // UWB_RANGING
+ false, // ACTIVITY_RECOGNITION_SOURCE
};
/**
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d35b435026acc..58abfeb26e075 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4240,6 +4240,15 @@
+
+
+
-
+
+
+
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index d5a5df0c8730a..16937b7d368bd 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -176,6 +176,7 @@
+
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index fa27b4b4487f6..25709d4295d2b 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -21,11 +21,22 @@ import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.app.AppOpsManagerInternal;
import android.app.SyncNotedAppOp;
+import android.app.role.RoleManager;
import android.content.AttributionSource;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.location.LocationManagerInternal;
+import android.net.Uri;
import android.os.IBinder;
+import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.function.HeptFunction;
@@ -35,6 +46,8 @@ import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.TriFunction;
import com.android.server.LocalServices;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -42,9 +55,21 @@ import java.util.concurrent.ConcurrentHashMap;
* This class defines policy for special behaviors around app ops.
*/
public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegate {
+ private static final String LOG_TAG = AppOpsPolicy.class.getName();
+
+ private static final String ACTIVITY_RECOGNITION_TAGS =
+ "android:activity_recognition_allow_listed_tags";
+ private static final String ACTIVITY_RECOGNITION_TAGS_SEPARATOR = ";";
+
@NonNull
private final Object mLock = new Object();
+ @NonNull
+ private final Context mContext;
+
+ @NonNull
+ private final RoleManager mRoleManager;
+
/**
* 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
@@ -60,48 +85,57 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
private final ConcurrentHashMap>> mLocationTags =
new ConcurrentHashMap<>();
- public AppOpsPolicy() {
+ @GuardedBy("mLock - writes only - see above")
+ @NonNull
+ private final ConcurrentHashMap>>
+ mActivityRecognitionTags = new ConcurrentHashMap<>();
+
+ public AppOpsPolicy(@NonNull Context context) {
+ mContext = context;
+ mRoleManager = mContext.getSystemService(RoleManager.class);
+
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);
- }
- }
+ updateAllowListedTagsForPackageLocked(providerTagInfo.getUid(),
+ providerTagInfo.getPackageName(), providerTagInfo.getTags(),
+ mLocationTags);
}
});
+
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ intentFilter.addDataScheme("package");
+
+ context.registerReceiverAsUser(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final Uri uri = intent.getData();
+ if (uri == null) {
+ return;
+ }
+ final String packageName = uri.getSchemeSpecificPart();
+ if (TextUtils.isEmpty(packageName)) {
+ return;
+ }
+ final List activityRecognizers = mRoleManager.getRoleHolders(
+ RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER);
+ if (activityRecognizers.contains(packageName)) {
+ updateActivityRecognizerTags(packageName);
+ }
+ }
+ }, UserHandle.SYSTEM, intentFilter, null, null);
+
+ mRoleManager.addOnRoleHoldersChangedListenerAsUser(context.getMainExecutor(),
+ (String roleName, UserHandle user) -> {
+ if (RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER.equals(roleName)) {
+ initializeActivityRecognizersTags();
+ }
+ }, UserHandle.SYSTEM);
+
+ initializeActivityRecognizersTags();
}
@Override
@@ -121,7 +155,7 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
@Nullable String attributionTag, boolean shouldCollectAsyncNotedOp, @Nullable
String message, boolean shouldCollectMessage, @NonNull HeptFunction superImpl) {
- return superImpl.apply(resolveOpCode(code, uid, packageName, attributionTag), uid,
+ return superImpl.apply(resolveDatasourceOp(code, uid, packageName, attributionTag), uid,
packageName, attributionTag, shouldCollectAsyncNotedOp,
message, shouldCollectMessage);
}
@@ -132,7 +166,7 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
boolean shouldCollectMessage, boolean skipProxyOperation, @NonNull HexFunction superImpl) {
- return superImpl.apply(resolveOpCode(code, attributionSource.getUid(),
+ return superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(),
attributionSource.getPackageName(), attributionSource.getAttributionTag()),
attributionSource, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
skipProxyOperation);
@@ -144,7 +178,7 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @NonNull OctFunction superImpl) {
- return superImpl.apply(token, resolveOpCode(code, attributionSource.getUid(),
+ return superImpl.apply(token, resolveDatasourceOp(code, attributionSource.getUid(),
attributionSource.getPackageName(), attributionSource.getAttributionTag()),
attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, skipProxyOperation);
@@ -154,36 +188,129 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
public void finishProxyOperation(IBinder clientId, int code,
@NonNull AttributionSource attributionSource,
@NonNull TriFunction superImpl) {
- superImpl.apply(clientId, resolveOpCode(code, attributionSource.getUid(),
+ superImpl.apply(clientId, resolveDatasourceOp(code, attributionSource.getUid(),
attributionSource.getPackageName(), attributionSource.getAttributionTag()),
attributionSource);
}
- private int resolveOpCode(int code, int uid, @NonNull String packageName,
+ private int resolveDatasourceOp(int code, int uid, @NonNull String packageName,
@Nullable String attributionTag) {
- if (isHandledOp(code) && attributionTag != null) {
- // 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(attributionTag)) {
- return resolveHandledOp(code);
+ if (attributionTag == null) {
+ return code;
+ }
+ int resolvedCode = resolveLocationOp(code);
+ if (resolvedCode != code) {
+ if (isDatasourceAttributionTag(uid, packageName, attributionTag,
+ mLocationTags)) {
+ return resolvedCode;
+ }
+ } else {
+ resolvedCode = resolveArOp(code);
+ if (resolvedCode != code) {
+ if (isDatasourceAttributionTag(uid, packageName, attributionTag,
+ mActivityRecognitionTags)) {
+ return resolvedCode;
}
}
}
return code;
}
- private static boolean isHandledOp(int code) {
- switch (code) {
- case AppOpsManager.OP_FINE_LOCATION:
- case AppOpsManager.OP_COARSE_LOCATION:
+ private void initializeActivityRecognizersTags() {
+ final List activityRecognizers = mRoleManager.getRoleHolders(
+ RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER);
+ final int recognizerCount = activityRecognizers.size();
+ if (recognizerCount > 0) {
+ for (int i = 0; i < recognizerCount; i++) {
+ final String activityRecognizer = activityRecognizers.get(i);
+ updateActivityRecognizerTags(activityRecognizer);
+ }
+ } else {
+ clearActivityRecognitionTags();
+ }
+ }
+
+ private void clearActivityRecognitionTags() {
+ synchronized (mLock) {
+ mActivityRecognitionTags.clear();
+ }
+ }
+
+ private void updateActivityRecognizerTags(@NonNull String activityRecognizer) {
+ try {
+ final ApplicationInfo recognizerAppInfo = mContext.getPackageManager()
+ .getApplicationInfoAsUser(activityRecognizer, PackageManager.GET_META_DATA,
+ UserHandle.USER_SYSTEM);
+ if (recognizerAppInfo.metaData == null) {
+ return;
+ }
+ final String tagsList = recognizerAppInfo.metaData.getString(ACTIVITY_RECOGNITION_TAGS);
+ if (tagsList != null) {
+ final String[] tags = tagsList.split(ACTIVITY_RECOGNITION_TAGS_SEPARATOR);
+ synchronized (mLock) {
+ updateAllowListedTagsForPackageLocked(recognizerAppInfo.uid,
+ recognizerAppInfo.packageName, new ArraySet<>(tags),
+ mActivityRecognitionTags);
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.wtf(LOG_TAG, "Missing " + RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER
+ + " role holder package " + activityRecognizer);
+ }
+ }
+
+ private static void updateAllowListedTagsForPackageLocked(int uid, String packageName,
+ Set allowListedTags, ConcurrentHashMap>> datastore) {
+ // We make a copy of the per UID state to limit our mutation to one
+ // operation in the underlying concurrent data structure.
+ ArrayMap> uidTags = datastore.get(uid);
+ if (uidTags != null) {
+ uidTags = new ArrayMap<>(uidTags);
+ }
+
+ ArraySet packageTags = (uidTags != null) ? uidTags.get(packageName) : null;
+ if (packageTags != null) {
+ packageTags = new ArraySet<>(packageTags);
+ }
+
+ if (allowListedTags != null && !allowListedTags.isEmpty()) {
+ if (packageTags != null) {
+ packageTags.clear();
+ packageTags.addAll(allowListedTags);
+ } else {
+ packageTags = new ArraySet<>(allowListedTags);
+ }
+ if (uidTags == null) {
+ uidTags = new ArrayMap<>();
+ }
+ uidTags.put(packageName, packageTags);
+ datastore.put(uid, uidTags);
+ } else if (uidTags != null) {
+ uidTags.remove(packageName);
+ if (!uidTags.isEmpty()) {
+ datastore.put(uid, uidTags);
+ } else {
+ datastore.remove(uid);
+ }
+ }
+ }
+
+ private static boolean isDatasourceAttributionTag(int uid, @NonNull String packageName,
+ @NonNull String attributionTag, @NonNull Map>> mappedOps) {
+ // Only a single lookup from the underlying concurrent data structure
+ final ArrayMap> uidTags = mappedOps.get(uid);
+ if (uidTags != null) {
+ final ArraySet packageTags = uidTags.get(packageName);
+ if (packageTags != null && packageTags.contains(attributionTag)) {
return true;
+ }
}
return false;
}
- private static int resolveHandledOp(int code) {
+ private static int resolveLocationOp(int code) {
switch (code) {
case AppOpsManager.OP_FINE_LOCATION:
return AppOpsManager.OP_FINE_LOCATION_SOURCE;
@@ -192,4 +319,11 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
}
return code;
}
+
+ private static int resolveArOp(int code) {
+ if (code == AppOpsManager.OP_ACTIVITY_RECOGNITION) {
+ return AppOpsManager.OP_ACTIVITY_RECOGNITION_SOURCE;
+ }
+ return code;
+ }
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 9b2a1e7a4e07d..aad42082fae31 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2678,7 +2678,7 @@ public final class SystemServer implements Dumpable {
t.traceBegin("RegisterAppOpsPolicy");
try {
- mActivityManagerService.setAppOpsPolicy(new AppOpsPolicy());
+ mActivityManagerService.setAppOpsPolicy(new AppOpsPolicy(mSystemContext));
} catch (Throwable e) {
reportWtf("registering app ops policy", e);
}