diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 7c32c9cd634f8..181bd35acf62f 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -2217,8 +2217,7 @@ public class AppOpsManager { /** Whether noting for an appop should be collected */ private static final @ShouldCollectNoteOp byte[] sAppOpsToNote = new byte[_NUM_OP]; - private static final int[] RUNTIME_AND_APPOP_PERMISSIONS_OPS = { - // RUNTIME PERMISSIONS + private static final int[] RUNTIME_PERMISSION_OPS = { // Contacts OP_READ_CONTACTS, OP_WRITE_CONTACTS, @@ -2275,8 +2274,13 @@ public class AppOpsManager { OP_NEARBY_WIFI_DEVICES, // Notifications OP_POST_NOTIFICATION, + }; - // APPOP PERMISSIONS + /** + * Ops for app op permissions that are setting the per-package mode for certain reasons. Most + * app op permissions should set the per-UID mode instead. + */ + private static final int[] APP_OP_PERMISSION_PACKAGE_OPS = { OP_ACCESS_NOTIFICATIONS, OP_SYSTEM_ALERT_WINDOW, OP_WRITE_SETTINGS, @@ -2285,9 +2289,16 @@ public class AppOpsManager { OP_SMS_FINANCIAL_TRANSACTIONS, OP_MANAGE_IPSEC_TUNNELS, OP_INSTANT_APP_START_FOREGROUND, + OP_LOADER_USAGE_STATS + }; + + /** + * Ops for app op permissions that are setting the per-UID mode for certain reasons. This should + * be preferred over the per-package mode for new app op permissions. + */ + private static final int[] APP_OP_PERMISSION_UID_OPS = { OP_MANAGE_EXTERNAL_STORAGE, OP_INTERACT_ACROSS_PROFILES, - OP_LOADER_USAGE_STATS, OP_MANAGE_ONGOING_CALLS, OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, OP_SCHEDULE_EXACT_ALARM, @@ -2777,7 +2788,17 @@ public class AppOpsManager { sOpStrToOp.put(sAppOpInfos[i].name, i); } } - for (int op : RUNTIME_AND_APPOP_PERMISSIONS_OPS) { + for (int op : RUNTIME_PERMISSION_OPS) { + if (sAppOpInfos[op].permission != null) { + sPermToOp.put(sAppOpInfos[op].permission, op); + } + } + for (int op : APP_OP_PERMISSION_PACKAGE_OPS) { + if (sAppOpInfos[op].permission != null) { + sPermToOp.put(sAppOpInfos[op].permission, op); + } + } + for (int op : APP_OP_PERMISSION_UID_OPS) { if (sAppOpInfos[op].permission != null) { sPermToOp.put(sAppOpInfos[op].permission, op); } @@ -2946,6 +2967,22 @@ public class AppOpsManager { return !sAppOpInfos[op].disableReset; } + /** + * Retrieve whether the op is a per-package op for an app op permission. + * @hide + */ + public static boolean opIsPackageAppOpPermission(int op) { + return ArrayUtils.contains(APP_OP_PERMISSION_PACKAGE_OPS, op); + } + + /** + * Retrieve whether the op is a per-package op for an app op permission. + * @hide + */ + public static boolean opIsUidAppOpPermission(int op) { + return ArrayUtils.contains(APP_OP_PERMISSION_UID_OPS, op); + } + /** * Returns a listenerId suitable for use with {@link #noteOp(int, int, String, String, String)}. * diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 5bea614d471b3..fc22935736a35 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -2096,7 +2096,9 @@ public class AppOpsService extends IAppOpsService.Stub { final int code = opModes.keyAt(j); if (AppOpsManager.opAllowsReset(code)) { int previousMode = opModes.valueAt(j); - uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code)); + int newMode = isUidOpGrantedByRole(uidState.uid, code) ? MODE_ALLOWED : + AppOpsManager.opToDefaultMode(code); + uidState.setUidMode(code, newMode); for (String packageName : getPackagesForUid(uidState.uid)) { callbacks = addCallbacks(callbacks, code, uidState.uid, packageName, previousMode, @@ -2139,10 +2141,15 @@ public class AppOpsService extends IAppOpsService.Stub { deferResetOpToDpm(curOp.op, reqPackageName, reqUserId); continue; } - if (AppOpsManager.opAllowsReset(curOp.op) - && curOp.getMode() != AppOpsManager.opToDefaultMode(curOp.op)) { + if (AppOpsManager.opAllowsReset(curOp.op)) { int previousMode = curOp.getMode(); - curOp.setMode(AppOpsManager.opToDefaultMode(curOp.op)); + int newMode = isPackageOpGrantedByRole(packageName, uidState.uid, + curOp.op) ? MODE_ALLOWED : AppOpsManager.opToDefaultMode( + curOp.op); + if (previousMode == newMode) { + continue; + } + curOp.setMode(newMode); changed = true; uidChanged = true; final int uid = curOp.uidState.uid; @@ -2198,6 +2205,41 @@ public class AppOpsService extends IAppOpsService.Stub { } } + private boolean isUidOpGrantedByRole(int uid, int code) { + if (!AppOpsManager.opIsUidAppOpPermission(code)) { + return false; + } + PackageManager packageManager = mContext.getPackageManager(); + long token = Binder.clearCallingIdentity(); + try { + // Permissions are managed by UIDs, but unfortunately a package name is required in API. + String packageName = ArrayUtils.firstOrNull(packageManager.getPackagesForUid(uid)); + if (packageName == null) { + return false; + } + int permissionFlags = packageManager.getPermissionFlags(AppOpsManager.opToPermission( + code), packageName, UserHandle.getUserHandleForUid(uid)); + return (permissionFlags & PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE) != 0; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private boolean isPackageOpGrantedByRole(@NonNull String packageName, int uid, int code) { + if (!AppOpsManager.opIsPackageAppOpPermission(code)) { + return false; + } + PackageManager packageManager = mContext.getPackageManager(); + long token = Binder.clearCallingIdentity(); + try { + int permissionFlags = packageManager.getPermissionFlags(AppOpsManager.opToPermission( + code), packageName, UserHandle.getUserHandleForUid(uid)); + return (permissionFlags & PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE) != 0; + } finally { + Binder.restoreCallingIdentity(token); + } + } + private boolean shouldDeferResetOpToDpm(int op) { // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission // pre-grants to a role-based mechanism or another general-purpose mechanism.