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