It depends on the Android version. The reason is that starting from some Android version, Google noticed (or just allowed) that OEMs would be able to change which permission belongs to which permission group. This occurs at least for some Chinese OEMs (not the first time they break behavior, as shown here), according to what I was told by Google on the issue tracker. They can even change it between simple updates to the OS.
So, here's what you have today:
- The normal API (and the adb command of
./adb shell pm list permissions -s
) won't work properly. It will put plenty of permissions
into "UNDEFINED" group. The reason is that Google said it might be
different across devices and OEMs
- You can check on Android source code of these versions and hope that most devices use what's there. It used to be here, but the link is
dead now. I think this one can give you the same information
though (but you need to check on which Android version it's used
for).
- Another way to handle this is to make your own sample POC to check for each permission - which permission group it belongs to. You will do it by having all permissions declared in the manifest (get from the code below), and then request a bunch of permissions. If you get a single dialog, it means that what you've chosen belong to a single group, and you can continue testing more permissions into it and see if they belong too. If not, at least one of them belong to another group.
To filter by the permissions that are only in the system and/or of Android itself, you could use a condition that the package name of the permission has to start with "android" or that the permission group name should start with "android.permission-group." or that the protection level of the permission is PermissionInfo.PROTECTION_SIGNATURE . It depends on what you wish to filter by.
So, a simple function to list them for all Android versions:
enum class PermissionGroup constructor(@param:Permission @field:Permission val permissions: Array<String>) {
//based on https://developer.android.com/reference/android/Manifest.permission_group.html, https://developer.android.com/guide/topics/permissions/requesting.html https://developer.android.com/reference/android/Manifest.permission
// get all permissions and groups here: https://stackoverflow.com/a/51156191/878126 https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
CALENDAR(arrayOf(permission.READ_CALENDAR, permission.WRITE_CALENDAR)),
CAMERA(arrayOf(permission.CAMERA)),
CONTACTS(arrayOf(permission.READ_CONTACTS, permission.WRITE_CONTACTS, permission.GET_ACCOUNTS)),
LOCATION(sequenceOf(permission.ACCESS_COARSE_LOCATION, permission.ACCESS_FINE_LOCATION, if (VERSION.SDK_INT >= VERSION_CODES.Q) permission.ACCESS_BACKGROUND_LOCATION else null)
.filterNotNull().asIterable().toList().toTypedArray()),
MICROPHONE(arrayOf(permission.RECORD_AUDIO, "android.permission.RECORD_BACKGROUND_AUDIO")),
PHONE(sequenceOf(permission.READ_PHONE_STATE, if (VERSION.SDK_INT >= VERSION_CODES.P) permission.ACCEPT_HANDOVER else null,
permission.CALL_PHONE, permission.ADD_VOICEMAIL, permission.USE_SIP,
if (VERSION.SDK_INT >= VERSION_CODES.O) permission.ANSWER_PHONE_CALLS else null, if (VERSION.SDK_INT < VERSION_CODES.P) permission.READ_CALL_LOG else null,
if (VERSION.SDK_INT < VERSION_CODES.P) permission.WRITE_CALL_LOG else null, if (VERSION.SDK_INT < VERSION_CODES.P) permission.PROCESS_OUTGOING_CALLS else null,
if (VERSION.SDK_INT >= VERSION_CODES.O) permission.READ_PHONE_NUMBERS else null).filterNotNull().asIterable().toList().toTypedArray()),
SENSORS(sequenceOf(if (VERSION.SDK_INT >= VERSION_CODES.KITKAT_WATCH) permission.BODY_SENSORS else null,
if (VERSION.SDK_INT >= VERSION_CODES.M && VERSION.SDK_INT < VERSION_CODES.P) permission.USE_FINGERPRINT else null, if (VERSION.SDK_INT >= VERSION_CODES.P) permission.USE_BIOMETRIC else null)
.filterNotNull().asIterable().toList().toTypedArray()),
SMS(sequenceOf(permission.SEND_SMS, permission.RECEIVE_SMS, permission.READ_SMS, permission.RECEIVE_WAP_PUSH, permission.RECEIVE_MMS,"android.permission.READ_CELL_BROADCASTS")
.filterNotNull().asIterable().toList().toTypedArray()),
STORAGE(sequenceOf(permission.READ_EXTERNAL_STORAGE, permission.WRITE_EXTERNAL_STORAGE, if (VERSION.SDK_INT >= VERSION_CODES.Q) permission.ACCESS_MEDIA_LOCATION else null)
.filterNotNull().asIterable().toList().toTypedArray()),
CALL_LOG(if (VERSION.SDK_INT >= VERSION_CODES.P)
arrayOf(permission.READ_CALL_LOG, permission.WRITE_CALL_LOG, permission.PROCESS_OUTGOING_CALLS)
else emptyArray()),
ACTIVITY_RECOGNITION(if (VERSION.SDK_INT >= VERSION_CODES.Q) arrayOf(permission.ACTIVITY_RECOGNITION) else emptyArray()),
NEARBY_DEVICES(if (VERSION.SDK_INT >= VERSION_CODES.S) arrayOf(permission.BLUETOOTH_CONNECT, permission.BLUETOOTH_ADVERTISE, permission.UWB_RANGING, permission.BLUETOOTH_SCAN) else emptyArray())
;
@TargetApi(VERSION_CODES.S)
@StringDef(permission.READ_CALENDAR, permission.WRITE_CALENDAR, permission.CAMERA, permission.READ_CONTACTS, permission.WRITE_CONTACTS,
permission.GET_ACCOUNTS, permission.ACCEPT_HANDOVER, permission.ACCESS_COARSE_LOCATION, permission.ACCESS_FINE_LOCATION, permission.RECORD_AUDIO,
permission.USE_BIOMETRIC, permission.READ_PHONE_STATE, permission.CALL_PHONE, permission.READ_CALL_LOG, permission.WRITE_CALL_LOG, permission.ADD_VOICEMAIL,
permission.USE_SIP, permission.PROCESS_OUTGOING_CALLS, permission.BODY_SENSORS, permission.SEND_SMS, permission.RECEIVE_SMS, permission.READ_SMS, permission.RECEIVE_WAP_PUSH,
permission.RECEIVE_MMS, permission.READ_EXTERNAL_STORAGE, permission.WRITE_EXTERNAL_STORAGE, permission.ANSWER_PHONE_CALLS, permission.ACTIVITY_RECOGNITION,
permission.ACCESS_BACKGROUND_LOCATION, permission.ACCESS_MEDIA_LOCATION, "android.permission.RECORD_BACKGROUND_AUDIO",
permission.BLUETOOTH_CONNECT, permission.BLUETOOTH_ADVERTISE, permission.UWB_RANGING, permission.BLUETOOTH_SCAN,"android.permission.READ_CELL_BROADCASTS"
)
@Retention(AnnotationRetention.SOURCE)
annotation class Permission
}
And finding them:
fun checkPermissions() {
var permissionsCount = 0
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val permissionGroups = packageManager.getAllPermissionGroups(0)
var permissionsGroupsLeft = permissionGroups.size
permissionGroups.forEach { permissionGroup: PermissionGroupInfo ->
val permissionGroupName = permissionGroup.name
if (!permissionGroupName.startsWith("android.permission-group.")) {
if (--permissionsGroupsLeft == 0) {
Log.d("AppLog", "total permissions (of all permissions groups) count:$permissionsCount")
}
return@forEach
}
packageManager.getPlatformPermissionsForGroup(permissionGroupName, mainExecutor) { groupPermissions ->
if (groupPermissions.isNotEmpty()) {
Log.d("AppLog", "permissionGroup:$permissionGroupName")
groupPermissions.forEach { permissionName ->
Log.d("AppLog", "\t\t$permissionName")
++permissionsCount
}
}
if (--permissionsGroupsLeft == 0) {
Log.d("AppLog", "total permissions (of all permissions groups) count:$permissionsCount")
}
}
}
return
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> {
Log.d("AppLog", "cannot use API to query permissions, so guessing it is similar to what we have found about it:")
PermissionGroup.values().forEach { permissionGroup ->
val permissions = permissionGroup.permissions
if (permissions.isEmpty())
return@forEach
Log.d("AppLog", "permissionGroup:${permissionGroup.name}")
permissions.forEach { permissionName ->
Log.d("AppLog", "\t\t${permissionName}")
++permissionsCount
}
}
}
else -> {
val allPermissionGroups = packageManager.getAllPermissionGroups(0)
allPermissionGroups.forEach { permissionGroup ->
val permissionGroupName = permissionGroup.name
if (!permissionGroupName.startsWith("android.permission-group."))
return@forEach
val groupPermissions = packageManager.queryPermissionsByGroup(permissionGroupName, 0)
if (groupPermissions.isEmpty())
return@forEach
Log.d("AppLog", "permissionGroup:$permissionGroupName")
for (permission in groupPermissions) {
val permissionName = permission.name
Log.d("AppLog", "\t\t${permissionName}")
++permissionsCount
}
}
}
}
Log.d("AppLog", "total permissions (of all permissions groups) count:$permissionsCount")
}