From 99d6115e9973aa8ef8921316d5113e00e42b7bce Mon Sep 17 00:00:00 2001 From: Svet Ganov Date: Sun, 4 Apr 2021 00:28:52 +0000 Subject: [PATCH] Activity recognition source app op tracking The activity recognition source may access activity recognition in its operation as being the activity recognition source. Accesses from an AR source (for special tags it designates - the APK may contain other components) for location and AR are tracked in a dedicated app op. bug: 182204957 Test: atest CtsActivityRecognitionTestCases Change-Id: If29fba1c51d70a2806a0907a73a96d3d8d7a3100 --- core/api/system-current.txt | 1 + core/api/test-current.txt | 2 + core/java/android/app/AppOpsManager.java | 34 ++- core/res/AndroidManifest.xml | 9 + core/res/res/values/config.xml | 4 +- packages/Shell/AndroidManifest.xml | 1 + .../android/server/policy/AppOpsPolicy.java | 240 ++++++++++++++---- .../java/com/android/server/SystemServer.java | 2 +- 8 files changed, 236 insertions(+), 57 deletions(-) diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 930c2986a329f..807a8bcb7f5cf 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -68,6 +68,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 a2634da67a554..b921418a6a816 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 83e6c4bc014d0..7974a113ba8bc 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4241,6 +4241,15 @@ + + + - + + + diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 4ff3c55e3a701..cc236b4620d07 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 fbf677dd0967c..1d42bee2ab3e3 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -2667,7 +2667,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); }