blob: f13e6b907c98a35c6e7708c845dbb05c30d907a6 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.permission.access.permission
import android.Manifest
import android.content.pm.PackageManager
import android.content.pm.PermissionGroupInfo
import android.content.pm.PermissionInfo
import android.content.pm.SigningDetails
import android.os.Build
import android.os.UserHandle
import android.util.Log
import com.android.internal.os.RoSystemProperties
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
import com.android.server.permission.access.AccessState
import com.android.server.permission.access.AccessUri
import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.PermissionUri
import com.android.server.permission.access.SchemePolicy
import com.android.server.permission.access.UidUri
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.util.andInv
import com.android.server.permission.access.util.hasAnyBit
import com.android.server.permission.access.util.hasBits
import com.android.server.permission.access.util.isInternal
import com.android.server.permission.access.util.isRuntime
import com.android.server.pm.KnownPackages
import com.android.server.pm.parsing.PackageInfoUtils
import com.android.server.pm.permission.CompatibilityPermissionInfo
import com.android.server.pm.pkg.AndroidPackage
import com.android.server.pm.pkg.PackageState
class UidPermissionPolicy : SchemePolicy() {
private val persistence = UidPermissionPersistence()
@Volatile
private var onPermissionFlagsChangedListeners =
IndexedListSet<OnPermissionFlagsChangedListener>()
private val onPermissionFlagsChangedListenersLock = Any()
private val privilegedPermissionAllowlistViolations = IndexedSet<String>()
override val subjectScheme: String
get() = UidUri.SCHEME
override val objectScheme: String
get() = PermissionUri.SCHEME
override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
subject as UidUri
`object` as PermissionUri
return getPermissionFlags(subject.appId, subject.userId, `object`.permissionName)
}
override fun MutateStateScope.setDecision(
subject: AccessUri,
`object`: AccessUri,
decision: Int
) {
subject as UidUri
`object` as PermissionUri
setPermissionFlags(subject.appId, subject.userId, `object`.permissionName, decision)
}
override fun GetStateScope.onStateMutated() {
onPermissionFlagsChangedListeners.forEachIndexed { _, it -> it.onStateMutated() }
}
override fun MutateStateScope.onInitialized() {
newState.systemState.configPermissions.forEach { (permissionName, permissionEntry) ->
val permissions = newState.systemState.permissions
val oldPermission = permissions[permissionName]
val newPermission = if (oldPermission != null) {
if (permissionEntry.gids != null) {
oldPermission.copy(
gids = permissionEntry.gids, areGidsPerUser = permissionEntry.perUser
)
} else {
return@forEach
}
} else {
@Suppress("DEPRECATION")
val permissionInfo = PermissionInfo().apply {
name = permissionName
packageName = PLATFORM_PACKAGE_NAME
protectionLevel = PermissionInfo.PROTECTION_SIGNATURE
}
if (permissionEntry.gids != null) {
Permission(
permissionInfo, false, Permission.TYPE_CONFIG, 0, permissionEntry.gids,
permissionEntry.perUser
)
} else {
Permission(permissionInfo, false, Permission.TYPE_CONFIG, 0)
}
}
permissions[permissionName] = newPermission
}
}
override fun MutateStateScope.onUserAdded(userId: Int) {
newState.systemState.packageStates.forEach { (_, packageState) ->
evaluateAllPermissionStatesForPackageAndUser(packageState, userId, null)
}
newState.systemState.appIds.forEachKeyIndexed { _, appId ->
inheritImplicitPermissionStates(appId, userId)
}
}
override fun MutateStateScope.onAppIdRemoved(appId: Int) {
newState.userStates.forEachValueIndexed { _, userState ->
userState.uidPermissionFlags -= appId
userState.requestWrite()
// Skip notifying the change listeners since the app ID no longer exists.
}
}
override fun MutateStateScope.onStorageVolumeMounted(
volumeUuid: String?,
isSystemUpdated: Boolean
) {
val changedPermissionNames = IndexedSet<String>()
newState.systemState.packageStates.forEach { (_, packageState) ->
val androidPackage = packageState.androidPackage
if (androidPackage == null || androidPackage.volumeUuid != volumeUuid) {
return@forEach
}
adoptPermissions(packageState, changedPermissionNames)
addPermissionGroups(packageState)
addPermissions(packageState, changedPermissionNames)
trimPermissions(packageState.packageName, changedPermissionNames)
trimPermissionStates(packageState.appId)
}
changedPermissionNames.forEachIndexed { _, permissionName ->
evaluatePermissionStateForAllPackages(permissionName, null)
}
newState.systemState.packageStates.forEach { (_, packageState) ->
val androidPackage = packageState.androidPackage
if (androidPackage == null || androidPackage.volumeUuid != volumeUuid) {
return@forEach
}
val installedPackageState = if (isSystemUpdated) packageState else null
evaluateAllPermissionStatesForPackage(packageState, installedPackageState)
}
newState.systemState.packageStates.forEach { (_, packageState) ->
val androidPackage = packageState.androidPackage
if (androidPackage == null || androidPackage.volumeUuid != volumeUuid) {
return@forEach
}
newState.systemState.userIds.forEachIndexed { _, userId ->
inheritImplicitPermissionStates(packageState.appId, userId)
}
}
}
override fun MutateStateScope.onPackageAdded(packageState: PackageState) {
val changedPermissionNames = IndexedSet<String>()
adoptPermissions(packageState, changedPermissionNames)
addPermissionGroups(packageState)
addPermissions(packageState, changedPermissionNames)
// TODO: revokeStoragePermissionsIfScopeExpandedInternal()
// TODO: revokeSystemAlertWindowIfUpgradedPast23()
trimPermissions(packageState.packageName, changedPermissionNames)
trimPermissionStates(packageState.appId)
changedPermissionNames.forEachIndexed { _, permissionName ->
evaluatePermissionStateForAllPackages(permissionName, null)
}
evaluateAllPermissionStatesForPackage(packageState, packageState)
newState.systemState.userIds.forEachIndexed { _, userId ->
inheritImplicitPermissionStates(packageState.appId, userId)
}
}
override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {
// TODO: STOPSHIP: Remove this check or at least turn into logging.
check(packageName !in newState.systemState.disabledSystemPackageStates) {
"Package $packageName reported as removed before disabled system package is enabled"
}
val changedPermissionNames = IndexedSet<String>()
trimPermissions(packageName, changedPermissionNames)
trimPermissionStates(appId)
changedPermissionNames.forEachIndexed { _, permissionName ->
evaluatePermissionStateForAllPackages(permissionName, null)
}
}
override fun MutateStateScope.onPackageUninstalled(
packageName: String,
appId: Int,
userId: Int
) {
resetRuntimePermissions(packageName, appId, userId)
}
fun MutateStateScope.resetRuntimePermissions(
packageName: String,
appId: Int,
userId: Int
) {
val androidPackage = newState.systemState.packageStates[packageName]?.androidPackage
?: return
androidPackage.requestedPermissions.forEachIndexed { _, permissionName ->
val permission = newState.systemState.permissions[permissionName]
?: return@forEachIndexed
if (permission.isRemoved) {
return@forEachIndexed
}
val isRequestedByOtherPackages = anyPackageInAppId(appId) { packageState ->
packageState.packageName != packageName &&
permissionName in packageState.androidPackage!!.requestedPermissions
}
if (isRequestedByOtherPackages) {
return@forEachIndexed
}
val oldFlags = getPermissionFlags(appId, userId, permissionName)
if (oldFlags.hasAnyBit(SYSTEM_OR_POLICY_FIXED_MASK)) {
return@forEachIndexed
}
var newFlags = oldFlags
newFlags = if (
newFlags.hasBits(PermissionFlags.ROLE) || newFlags.hasBits(PermissionFlags.PREGRANT)
) {
newFlags or PermissionFlags.RUNTIME_GRANTED
} else {
newFlags andInv PermissionFlags.RUNTIME_GRANTED
}
newFlags = newFlags andInv USER_SETTABLE_MASK
if (newFlags.hasBits(PermissionFlags.LEGACY_GRANTED)) {
newFlags = newFlags or PermissionFlags.IMPLICIT
}
setPermissionFlags(appId, userId, permissionName, newFlags)
}
}
private fun MutateStateScope.adoptPermissions(
packageState: PackageState,
changedPermissionNames: IndexedSet<String>
) {
val `package` = packageState.androidPackage!!
`package`.adoptPermissions.forEachIndexed { _, originalPackageName ->
val packageName = `package`.packageName
if (!canAdoptPermissions(packageName, originalPackageName)) {
return@forEachIndexed
}
val systemState = newState.systemState
val permissions = systemState.permissions
permissions.forEachIndexed permissions@ {
permissionIndex, permissionName, oldPermission ->
if (oldPermission.packageName != originalPackageName) {
return@permissions
}
@Suppress("DEPRECATION")
val newPermissionInfo = PermissionInfo().apply {
name = oldPermission.permissionInfo.name
this.packageName = packageName
protectionLevel = oldPermission.permissionInfo.protectionLevel
}
// Different from the old implementation, which removes the GIDs upon permission
// adoption, but adds them back on the next boot, we now just consistently keep the
// GIDs.
val newPermission = oldPermission.copy(
permissionInfo = newPermissionInfo, isReconciled = false, appId = 0
)
permissions.setValueAt(permissionIndex, newPermission)
systemState.requestWrite()
changedPermissionNames += permissionName
}
}
}
private fun MutateStateScope.canAdoptPermissions(
packageName: String,
originalPackageName: String
): Boolean {
val originalPackageState = newState.systemState.packageStates[originalPackageName]
?: return false
if (!originalPackageState.isSystem) {
Log.w(
LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" +
" original package not in system partition"
)
return false
}
if (originalPackageState.androidPackage != null) {
Log.w(
LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" +
" original package still exists"
)
return false
}
return true
}
private fun MutateStateScope.addPermissionGroups(packageState: PackageState) {
// Different from the old implementation, which decides whether the app is an instant app by
// the install flags, now for consistent behavior we allow adding permission groups if the
// app is non-instant in at least one user.
val isInstantApp = packageState.userStates.allIndexed { _, _, it -> it.isInstantApp }
if (isInstantApp) {
Log.w(
LOG_TAG, "Ignoring permission groups declared in package" +
" ${packageState.packageName}: instant apps cannot declare permission groups"
)
return
}
packageState.androidPackage!!.permissionGroups.forEachIndexed { _, parsedPermissionGroup ->
val newPermissionGroup = PackageInfoUtils.generatePermissionGroupInfo(
parsedPermissionGroup, PackageManager.GET_META_DATA.toLong()
)!!
// TODO: Clear permission state on group take-over?
val permissionGroupName = newPermissionGroup.name
val oldPermissionGroup = newState.systemState.permissionGroups[permissionGroupName]
if (oldPermissionGroup != null &&
newPermissionGroup.packageName != oldPermissionGroup.packageName) {
val newPackageName = newPermissionGroup.packageName
val oldPackageName = oldPermissionGroup.packageName
// Different from the old implementation, which defines permission group on
// a first-come-first-serve basis, and relies on system apps being scanned before
// non-system apps, we now allow system apps to override permission groups similar
// to permissions so that we no longer need to rely on the scan order.
if (!packageState.isSystem) {
Log.w(
LOG_TAG, "Ignoring permission group $permissionGroupName declared in" +
" package $newPackageName: already declared in another" +
" package $oldPackageName"
)
return@forEachIndexed
}
if (newState.systemState.packageStates[oldPackageName]?.isSystem == true) {
Log.w(
LOG_TAG, "Ignoring permission group $permissionGroupName declared in" +
" system package $newPackageName: already declared in another" +
" system package $oldPackageName"
)
return@forEachIndexed
}
Log.w(
LOG_TAG, "Overriding permission group $permissionGroupName with" +
" new declaration in system package $newPackageName: originally" +
" declared in another package $oldPackageName"
)
}
newState.systemState.permissionGroups[permissionGroupName] = newPermissionGroup
}
}
private fun MutateStateScope.addPermissions(
packageState: PackageState,
changedPermissionNames: IndexedSet<String>
) {
packageState.androidPackage!!.permissions.forEachIndexed { _, parsedPermission ->
// TODO:
// parsedPermission.flags = parsedPermission.flags andInv PermissionInfo.FLAG_INSTALLED
// TODO: This seems actually unused.
// if (packageState.androidPackage.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
// parsedPermission.setParsedPermissionGroup(
// newState.systemState.permissionGroup[parsedPermission.group]
// )
// }
val newPermissionInfo = PackageInfoUtils.generatePermissionInfo(
parsedPermission, PackageManager.GET_META_DATA.toLong()
)!!
// TODO: newPermissionInfo.flags |= PermissionInfo.FLAG_INSTALLED
val systemState = newState.systemState
val permissionName = newPermissionInfo.name
val oldPermission = if (parsedPermission.isTree) {
systemState.permissionTrees[permissionName]
} else {
systemState.permissions[permissionName]
}
// Different from the old implementation, which may add an (incomplete) signature
// permission inside another package's permission tree, we now consistently ignore such
// permissions.
val permissionTree = findPermissionTree(permissionName)
val newPackageName = newPermissionInfo.packageName
if (permissionTree != null && newPackageName != permissionTree.packageName) {
Log.w(
LOG_TAG, "Ignoring permission $permissionName declared in package" +
" $newPackageName: base permission tree ${permissionTree.name} is" +
" declared in another package ${permissionTree.packageName}"
)
return@forEachIndexed
}
val newPermission = if (oldPermission != null &&
newPackageName != oldPermission.packageName) {
val oldPackageName = oldPermission.packageName
// Only allow system apps to redefine non-system permissions.
if (!packageState.isSystem) {
Log.w(
LOG_TAG, "Ignoring permission $permissionName declared in package" +
" $newPackageName: already declared in another package" +
" $oldPackageName"
)
return@forEachIndexed
}
if (oldPermission.type == Permission.TYPE_CONFIG && !oldPermission.isReconciled) {
// It's a config permission and has no owner, take ownership now.
oldPermission.copy(
permissionInfo = newPermissionInfo, isReconciled = true,
appId = packageState.appId
)
} else if (systemState.packageStates[oldPackageName]?.isSystem != true) {
Log.w(
LOG_TAG, "Overriding permission $permissionName with new declaration in" +
" system package $newPackageName: originally declared in another" +
" package $oldPackageName"
)
// Remove permission state on owner change.
systemState.userIds.forEachIndexed { _, userId ->
systemState.appIds.forEachKeyIndexed { _, appId ->
setPermissionFlags(appId, userId, permissionName, 0)
}
}
// Different from the old implementation, which removes the GIDs upon permission
// override, but adds them back on the next boot, we now just consistently keep
// the GIDs.
Permission(
newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId,
oldPermission.gids, oldPermission.areGidsPerUser
)
} else {
Log.w(
LOG_TAG, "Ignoring permission $permissionName declared in system package" +
" $newPackageName: already declared in another system package" +
" $oldPackageName"
)
return@forEachIndexed
}
} else {
if (oldPermission != null) {
val isPermissionGroupChanged = newPermissionInfo.isRuntime &&
newPermissionInfo.group != null &&
newPermissionInfo.group != oldPermission.groupName
val isPermissionTypeChanged = oldPermission.type != Permission.TYPE_CONFIG && (
(newPermissionInfo.isRuntime && !oldPermission.isRuntime) ||
(newPermissionInfo.isInternal && !oldPermission.isInternal)
)
if (isPermissionGroupChanged || isPermissionTypeChanged) {
systemState.userIds.forEachIndexed { _, userId ->
systemState.appIds.forEachKeyIndexed { _, appId ->
if (isPermissionGroupChanged) {
// We might auto-grant permissions if any permission of
// the group is already granted. Hence if the group of
// a granted permission changes we need to revoke it to
// avoid having permissions of the new group auto-granted.
Log.w(
LOG_TAG, "Revoking runtime permission $permissionName for" +
" appId $appId and userId $userId as the permission" +
" group changed from ${oldPermission.groupName}" +
" to ${newPermissionInfo.group}"
)
}
if (isPermissionTypeChanged) {
Log.w(
LOG_TAG, "Revoking permission $permissionName for" +
" appId $appId and userId $userId as the permission" +
" type changed."
)
}
setPermissionFlags(appId, userId, permissionName, 0)
}
}
}
}
// Different from the old implementation, which doesn't update the permission
// definition upon app update, but does update it on the next boot, we now
// consistently update the permission definition upon app update.
@Suppress("IfThenToElvis")
if (oldPermission != null) {
oldPermission.copy(
permissionInfo = newPermissionInfo, isReconciled = true,
appId = packageState.appId
)
} else {
Permission(
newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId
)
}
}
if (parsedPermission.isTree) {
systemState.permissionTrees[permissionName] = newPermission
} else {
systemState.permissions[permissionName] = newPermission
}
systemState.requestWrite()
changedPermissionNames += permissionName
}
}
private fun MutateStateScope.trimPermissions(
packageName: String,
changedPermissionNames: IndexedSet<String>
) {
val systemState = newState.systemState
val packageState = systemState.packageStates[packageName]
val androidPackage = packageState?.androidPackage
if (packageState != null && androidPackage == null) {
return
}
val disabledSystemPackage = systemState.disabledSystemPackageStates[packageName]
?.androidPackage
// Unlike in the previous implementation, we now also retain permission trees defined by
// disabled system packages for consistency with permissions.
val isPermissionTreeRemoved = systemState.permissionTrees.removeAllIndexed {
_, permissionTreeName, permissionTree ->
permissionTree.packageName == packageName && (
packageState == null || androidPackage!!.permissions.noneIndexed { _, it ->
it.isTree && it.name == permissionTreeName
}
) && (
disabledSystemPackage?.permissions?.anyIndexed {
it.isTree && it.name == permissionTreeName
} != true
)
}
if (isPermissionTreeRemoved) {
systemState.requestWrite()
}
systemState.permissions.removeAllIndexed { permissionIndex, permissionName, permission ->
val updatedPermission = updatePermissionIfDynamic(permission)
newState.systemState.permissions.setValueAt(permissionIndex, updatedPermission)
if (updatedPermission.packageName == packageName && (
packageState == null || androidPackage!!.permissions.noneIndexed { _, it ->
!it.isTree && it.name == permissionName
}
) && (
disabledSystemPackage?.permissions?.anyIndexed {
!it.isTree && it.name == permissionName
} != true
)) {
// Different from the old implementation where we keep the permission state if the
// permission is declared by a disabled system package (ag/15189282), we now
// shouldn't be notified when the updated system package is removed but the disabled
// system package isn't re-enabled yet, so we don't need to maintain that brittle
// special case either.
systemState.userIds.forEachIndexed { _, userId ->
systemState.appIds.forEachKeyIndexed { _, appId ->
setPermissionFlags(appId, userId, permissionName, 0)
}
}
changedPermissionNames += permissionName
systemState.requestWrite()
true
} else {
false
}
}
}
private fun MutateStateScope.updatePermissionIfDynamic(permission: Permission): Permission {
if (!permission.isDynamic) {
return permission
}
val permissionTree = findPermissionTree(permission.name) ?: return permission
@Suppress("DEPRECATION")
return permission.copy(
permissionInfo = PermissionInfo(permission.permissionInfo).apply {
packageName = permissionTree.packageName
}, appId = permissionTree.appId, isReconciled = true
)
}
private fun MutateStateScope.trimPermissionStates(appId: Int) {
val requestedPermissions = IndexedSet<String>()
forEachPackageInAppId(appId) {
// Note that we still trim the permission states requested by disabled system packages.
// Because in the previous implementation:
// despite revokeSharedUserPermissionsForLeavingPackageInternal() retains permissions
// requested by disabled system packages, revokeUnusedSharedUserPermissionsLocked(),
// which is call upon app update installation, didn't do such preservation.
// Hence, permissions only requested by disabled system packages were still trimmed in
// the previous implementation.
requestedPermissions += it.androidPackage!!.requestedPermissions
}
newState.userStates.forEachIndexed { _, userId, userState ->
userState.uidPermissionFlags[appId]?.forEachReversedIndexed { _, permissionName, _ ->
if (permissionName !in requestedPermissions) {
setPermissionFlags(appId, userId, permissionName, 0)
}
}
}
}
private fun MutateStateScope.evaluatePermissionStateForAllPackages(
permissionName: String,
installedPackageState: PackageState?
) {
val systemState = newState.systemState
systemState.userIds.forEachIndexed { _, userId ->
systemState.appIds.forEachKeyIndexed { _, appId ->
val isPermissionRequested = anyPackageInAppId(appId) { packageState ->
permissionName in packageState.androidPackage!!.requestedPermissions
}
if (isPermissionRequested) {
evaluatePermissionState(appId, userId, permissionName, installedPackageState)
}
}
}
}
private fun MutateStateScope.evaluateAllPermissionStatesForPackage(
packageState: PackageState,
installedPackageState: PackageState?
) {
newState.systemState.userIds.forEachIndexed { _, userId ->
evaluateAllPermissionStatesForPackageAndUser(
packageState, userId, installedPackageState
)
}
}
private fun MutateStateScope.evaluateAllPermissionStatesForPackageAndUser(
packageState: PackageState,
userId: Int,
installedPackageState: PackageState?
) {
packageState.androidPackage?.requestedPermissions?.forEachIndexed { _, permissionName ->
evaluatePermissionState(
packageState.appId, userId, permissionName, installedPackageState
)
}
}
private fun MutateStateScope.evaluatePermissionState(
appId: Int,
userId: Int,
permissionName: String,
installedPackageState: PackageState?
) {
val packageNames = newState.systemState.appIds[appId]
val hasMissingPackage = packageNames.anyIndexed { _, packageName ->
newState.systemState.packageStates[packageName]!!.androidPackage == null
}
if (packageNames.size == 1 && hasMissingPackage) {
// For non-shared-user packages with missing androidPackage, skip evaluation.
return
}
val permission = newState.systemState.permissions[permissionName]
val oldFlags = getPermissionFlags(appId, userId, permissionName)
if (permission == null) {
if (oldFlags == 0) {
// If the permission definition is missing and we don't have any permission states
// for this permission, add the INSTALL_REVOKED flag to ensure that we don't
// automatically grant the permission when it's defined
setPermissionFlags(appId, userId, permissionName, PermissionFlags.INSTALL_REVOKED)
}
return
}
if (permission.isNormal) {
val wasGranted = oldFlags.hasBits(PermissionFlags.INSTALL_GRANTED)
if (!wasGranted) {
val wasRevoked = oldFlags.hasBits(PermissionFlags.INSTALL_REVOKED)
val isRequestedByInstalledPackage = installedPackageState != null &&
permissionName in installedPackageState.androidPackage!!.requestedPermissions
val isRequestedBySystemPackage = anyPackageInAppId(appId) {
it.isSystem && permissionName in it.androidPackage!!.requestedPermissions
}
val isCompatibilityPermission = anyPackageInAppId(appId) {
isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName)
}
// If this is an existing, non-system package,
// then we can't add any new permissions to it.
// Except if this is a permission that was added to the platform
val newFlags = if (!wasRevoked || isRequestedByInstalledPackage ||
isRequestedBySystemPackage || isCompatibilityPermission) {
PermissionFlags.INSTALL_GRANTED
} else {
PermissionFlags.INSTALL_REVOKED
}
setPermissionFlags(appId, userId, permissionName, newFlags)
}
} else if (permission.isSignature || permission.isInternal) {
val wasProtectionGranted = oldFlags.hasBits(PermissionFlags.PROTECTION_GRANTED)
var newFlags = if (hasMissingPackage && wasProtectionGranted) {
// Keep the non-runtime permission grants for shared UID with missing androidPackage
PermissionFlags.PROTECTION_GRANTED
} else {
val mayGrantByPrivileged = !permission.isPrivileged || (
anyPackageInAppId(appId) {
checkPrivilegedPermissionAllowlist(it, permission)
}
)
val shouldGrantBySignature = permission.isSignature && (
anyPackageInAppId(appId) {
shouldGrantPermissionBySignature(it, permission)
}
)
val shouldGrantByProtectionFlags = anyPackageInAppId(appId) {
shouldGrantPermissionByProtectionFlags(it, permission)
}
if (mayGrantByPrivileged &&
(shouldGrantBySignature || shouldGrantByProtectionFlags)) {
PermissionFlags.PROTECTION_GRANTED
} else {
0
}
}
// Different from the old implementation, which seemingly allows granting an
// unallowlisted privileged permission via development or role but revokes it upon next
// reconciliation, we now properly allows that because the privileged protection flag
// should only affect the other static flags, but not dynamic flags like development or
// role. This may be useful in the case of an updated system app.
if (permission.isDevelopment) {
newFlags = newFlags or (oldFlags and PermissionFlags.RUNTIME_GRANTED)
}
if (permission.isRole) {
newFlags = newFlags or (
oldFlags and (PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED)
)
}
setPermissionFlags(appId, userId, permissionName, newFlags)
} else if (permission.isRuntime) {
var newFlags = oldFlags and PermissionFlags.MASK_RUNTIME
if (getAppIdTargetSdkVersion(appId, permissionName) < Build.VERSION_CODES.M) {
if (permission.isRuntimeOnly) {
// Different from the old implementation, which simply skips a runtime-only
// permission, we now only allow holding on to the restriction related flags,
// since such flags may only be set one-time in some cases, and disallow all
// other flags thus keeping it revoked.
newFlags = newFlags and PermissionFlags.MASK_EXEMPT
} else {
newFlags = newFlags or PermissionFlags.LEGACY_GRANTED
// Explicitly check against the old state to determine if this permission is
// new.
val isNewPermission =
getOldStatePermissionFlags(appId, userId, permissionName) == 0
if (isNewPermission) {
newFlags = newFlags or PermissionFlags.IMPLICIT
}
}
} else {
newFlags = newFlags andInv PermissionFlags.LEGACY_GRANTED
val wasGrantedByImplicit = newFlags.hasBits(PermissionFlags.IMPLICIT_GRANTED)
val isLeanbackNotificationsPermission = newState.systemState.isLeanback &&
permissionName in NOTIFICATIONS_PERMISSIONS
val isImplicitPermission = anyPackageInAppId(appId) {
permissionName in it.androidPackage!!.implicitPermissions
}
val sourcePermissions = newState.systemState
.implicitToSourcePermissions[permissionName]
val isAnySourcePermissionNonRuntime = sourcePermissions?.any {
val sourcePermission = newState.systemState.permissions[it]
checkNotNull(sourcePermission) {
"Unknown source permission $it in split permissions"
}
!sourcePermission.isRuntime
} ?: false
val shouldGrantByImplicit = isLeanbackNotificationsPermission ||
(isImplicitPermission && isAnySourcePermissionNonRuntime)
if (shouldGrantByImplicit) {
newFlags = newFlags or PermissionFlags.IMPLICIT_GRANTED
} else {
newFlags = newFlags andInv PermissionFlags.IMPLICIT_GRANTED
}
val hasImplicitFlag = newFlags.hasBits(PermissionFlags.IMPLICIT)
if (!isImplicitPermission && hasImplicitFlag) {
// TODO: We might not want to remove the IMPLICIT flag
// for NOTIFICATION_PERMISSIONS
newFlags = newFlags andInv PermissionFlags.IMPLICIT
var shouldRetainAsNearbyDevices = false
if (permissionName in NEARBY_DEVICES_PERMISSIONS) {
val accessBackgroundLocationFlags = getPermissionFlags(
appId, userId, Manifest.permission.ACCESS_BACKGROUND_LOCATION
)
shouldRetainAsNearbyDevices =
PermissionFlags.isAppOpGranted(accessBackgroundLocationFlags) &&
!accessBackgroundLocationFlags.hasBits(PermissionFlags.IMPLICIT)
}
val shouldRetainByMask = newFlags.hasAnyBit(SYSTEM_OR_POLICY_FIXED_MASK)
if (shouldRetainAsNearbyDevices || shouldRetainByMask) {
if (wasGrantedByImplicit) {
newFlags = newFlags or PermissionFlags.RUNTIME_GRANTED
}
} else {
newFlags = newFlags andInv (
PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET or
PermissionFlags.USER_FIXED
)
}
}
}
val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
val isHardRestricted = permission.isHardRestricted && !isExempt
newFlags = if (isHardRestricted) {
newFlags or PermissionFlags.RESTRICTION_REVOKED
} else {
newFlags andInv PermissionFlags.RESTRICTION_REVOKED
}
val isSoftRestricted = permission.isSoftRestricted && !isExempt
newFlags = if (isSoftRestricted) {
newFlags or PermissionFlags.SOFT_RESTRICTED
} else {
newFlags andInv PermissionFlags.SOFT_RESTRICTED
}
setPermissionFlags(appId, userId, permissionName, newFlags)
} else {
Log.e(LOG_TAG, "Unknown protection level ${permission.protectionLevel}" +
"for permission ${permission.name} while evaluating permission state" +
"for appId $appId and userId $userId")
}
}
private fun MutateStateScope.inheritImplicitPermissionStates(appId: Int, userId: Int) {
val implicitPermissions = IndexedSet<String>()
forEachPackageInAppId(appId) {
implicitPermissions += it.androidPackage!!.implicitPermissions
}
implicitPermissions.forEachIndexed implicitPermissions@ { _, implicitPermissionName ->
val implicitPermission = newState.systemState.permissions[implicitPermissionName]
checkNotNull(implicitPermission) {
"Unknown implicit permission $implicitPermissionName in split permissions"
}
if (!implicitPermission.isRuntime) {
return@implicitPermissions
}
// Explicitly check against the old state to determine if this permission is new.
val isNewPermission =
getOldStatePermissionFlags(appId, userId, implicitPermissionName) == 0
if (!isNewPermission) {
return@implicitPermissions
}
val sourcePermissions = newState.systemState
.implicitToSourcePermissions[implicitPermissionName] ?: return@implicitPermissions
var newFlags = getPermissionFlags(appId, userId, implicitPermissionName)
sourcePermissions.forEachIndexed sourcePermissions@ { _, sourcePermissionName ->
val sourcePermission = newState.systemState.permissions[sourcePermissionName]
checkNotNull(sourcePermission) {
"Unknown source permission $sourcePermissionName in split permissions"
}
val sourceFlags = getPermissionFlags(appId, userId, sourcePermissionName)
val isSourceGranted = PermissionFlags.isPermissionGranted(sourceFlags)
val isNewGranted = PermissionFlags.isPermissionGranted(newFlags)
val isGrantingNewFromRevoke = isSourceGranted && !isNewGranted
if (isSourceGranted == isNewGranted || isGrantingNewFromRevoke) {
if (isGrantingNewFromRevoke) {
newFlags = 0
}
newFlags = newFlags or (sourceFlags and PermissionFlags.MASK_RUNTIME)
}
}
if (implicitPermissionName in RETAIN_IMPLICIT_FLAGS_PERMISSIONS) {
newFlags = newFlags andInv PermissionFlags.IMPLICIT
} else {
newFlags = newFlags or PermissionFlags.IMPLICIT
}
setPermissionFlags(appId, userId, implicitPermissionName, newFlags)
}
}
private fun isCompatibilityPermissionForPackage(
androidPackage: AndroidPackage,
permissionName: String
): Boolean {
for (compatibilityPermission in CompatibilityPermissionInfo.COMPAT_PERMS) {
if (compatibilityPermission.name == permissionName &&
androidPackage.targetSdkVersion < compatibilityPermission.sdkVersion) {
Log.i(
LOG_TAG, "Auto-granting $permissionName to old package" +
" ${androidPackage.packageName}"
)
return true
}
}
return false
}
private fun MutateStateScope.shouldGrantPermissionBySignature(
packageState: PackageState,
permission: Permission
): Boolean {
// Check if the package is allowed to use this signature permission. A package is allowed
// to use a signature permission if:
// - it has the same set of signing certificates as the source package
// - or its signing certificate was rotated from the source package's certificate
// - or its signing certificate is a previous signing certificate of the defining
// package, and the defining package still trusts the old certificate for permissions
// - or it shares a common signing certificate in its lineage with the defining package,
// and the defining package still trusts the old certificate for permissions
// - or it shares the above relationships with the system package
val packageSigningDetails = packageState.androidPackage!!.signingDetails
val sourceSigningDetails = newState.systemState
.packageStates[permission.packageName]?.androidPackage?.signingDetails
val platformSigningDetails = newState.systemState
.packageStates[PLATFORM_PACKAGE_NAME]!!.androidPackage!!.signingDetails
return sourceSigningDetails?.hasCommonSignerWithCapability(packageSigningDetails,
SigningDetails.CertCapabilities.PERMISSION) == true ||
packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) ||
platformSigningDetails.checkCapability(packageSigningDetails,
SigningDetails.CertCapabilities.PERMISSION)
}
private fun MutateStateScope.checkPrivilegedPermissionAllowlist(
packageState: PackageState,
permission: Permission
): Boolean {
if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) {
return true
}
if (packageState.packageName == PLATFORM_PACKAGE_NAME) {
return true
}
if (!packageState.isPrivileged) {
return true
}
if (permission.packageName !in newState.systemState.privilegedPermissionAllowlistPackages) {
return true
}
val allowlistState = getPrivilegedPermissionAllowlistState(packageState, permission.name)
if (allowlistState != null) {
return allowlistState
}
// Updated system apps do not need to be allowlisted
if (packageState.isUpdatedSystemApp) {
return true
}
// Only enforce the privileged permission allowlist on boot
if (!newState.systemState.isSystemReady) {
// Apps that are in updated apex's do not need to be allowlisted
if (!packageState.isApkInUpdatedApex) {
Log.w(
LOG_TAG, "Privileged permission ${permission.name} for package" +
" ${packageState.packageName} (${packageState.path}) not in" +
" privileged permission allowlist"
)
if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
privilegedPermissionAllowlistViolations += "${packageState.packageName}" +
" (${packageState.path}): ${permission.name}"
}
}
}
return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE
}
/**
* Get the whether a privileged permission is explicitly allowed or denied for a package in the
* allowlist, or `null` if it's not in the allowlist.
*/
private fun MutateStateScope.getPrivilegedPermissionAllowlistState(
packageState: PackageState,
permissionName: String
): Boolean? {
val permissionAllowlist = newState.systemState.permissionAllowlist
// TODO(b/261913353): STOPSHIP: Add AndroidPackage.apexModuleName.
// val apexModuleName = androidPackage.apexModuleName
val apexModuleName = permissionAllowlist.apexPrivilegedAppAllowlists
.firstNotNullOfOrNullIndexed { _, apexModuleName, apexAllowlist ->
if (packageState.packageName in apexAllowlist) apexModuleName else null
}
val packageName = packageState.packageName
return when {
packageState.isVendor -> permissionAllowlist.getVendorPrivilegedAppAllowlistState(
packageName, permissionName
)
packageState.isProduct -> permissionAllowlist.getProductPrivilegedAppAllowlistState(
packageName, permissionName
)
packageState.isSystemExt ->
permissionAllowlist.getSystemExtPrivilegedAppAllowlistState(
packageName, permissionName
)
apexModuleName != null -> {
val nonApexAllowlistState = permissionAllowlist.getPrivilegedAppAllowlistState(
packageName, permissionName
)
if (nonApexAllowlistState != null) {
// TODO(andreionea): Remove check as soon as all apk-in-apex
// permission allowlists are migrated.
Log.w(
LOG_TAG, "Package $packageName is an APK in APEX but has permission" +
" allowlist on the system image, please bundle the allowlist in the" +
" $apexModuleName APEX instead"
)
}
val apexAllowlistState = permissionAllowlist.getApexPrivilegedAppAllowlistState(
apexModuleName, packageName, permissionName
)
apexAllowlistState ?: nonApexAllowlistState
}
else -> permissionAllowlist.getPrivilegedAppAllowlistState(packageName, permissionName)
}
}
private fun MutateStateScope.getAppIdTargetSdkVersion(appId: Int, permissionName: String): Int {
var targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT
forEachPackageInAppId(appId) { packageState ->
val androidPackage = packageState.androidPackage!!
if (permissionName in androidPackage.requestedPermissions) {
targetSdkVersion = targetSdkVersion.coerceAtMost(androidPackage.targetSdkVersion)
}
}
return targetSdkVersion
}
private inline fun MutateStateScope.anyPackageInAppId(
appId: Int,
state: AccessState = newState,
predicate: (PackageState) -> Boolean
): Boolean {
val packageNames = state.systemState.appIds[appId]
return packageNames.anyIndexed { _, packageName ->
val packageState = state.systemState.packageStates[packageName]!!
packageState.androidPackage != null && predicate(packageState)
}
}
private inline fun MutateStateScope.forEachPackageInAppId(
appId: Int,
state: AccessState = newState,
action: (PackageState) -> Unit
) {
val packageNames = state.systemState.appIds[appId]
packageNames.forEachIndexed { _, packageName ->
val packageState = state.systemState.packageStates[packageName]!!
if (packageState.androidPackage != null) {
action(packageState)
}
}
}
private fun MutateStateScope.shouldGrantPermissionByProtectionFlags(
packageState: PackageState,
permission: Permission
): Boolean {
val androidPackage = packageState.androidPackage!!
val knownPackages = newState.systemState.knownPackages
val packageName = packageState.packageName
if ((permission.isPrivileged || permission.isOem) && packageState.isSystem) {
val shouldGrant = if (packageState.isUpdatedSystemApp) {
// For updated system applications, a privileged/oem permission
// is granted only if it had been defined by the original application.
val disabledSystemPackageState = newState.systemState
.disabledSystemPackageStates[packageState.packageName]
val disabledSystemPackage = disabledSystemPackageState?.androidPackage
disabledSystemPackage != null &&
permission.name in disabledSystemPackage.requestedPermissions &&
shouldGrantPrivilegedOrOemPermission(disabledSystemPackageState, permission)
} else {
shouldGrantPrivilegedOrOemPermission(packageState, permission)
}
if (shouldGrant) {
return true
}
}
if (permission.isPre23 && androidPackage.targetSdkVersion < Build.VERSION_CODES.M) {
// If this was a previously normal/dangerous permission that got moved
// to a system permission as part of the runtime permission redesign, then
// we still want to blindly grant it to old apps.
return true
}
if (permission.isInstaller && (
packageName in knownPackages[KnownPackages.PACKAGE_INSTALLER] ||
packageName in knownPackages[KnownPackages.PACKAGE_PERMISSION_CONTROLLER]
)) {
// If this permission is to be granted to the system installer and
// this app is an installer or permission controller, then it gets the permission.
return true
}
if (permission.isVerifier &&
packageName in knownPackages[KnownPackages.PACKAGE_VERIFIER]) {
// If this permission is to be granted to the system verifier and
// this app is a verifier, then it gets the permission.
return true
}
if (permission.isPreInstalled && packageState.isSystem) {
// Any pre-installed system app is allowed to get this permission.
return true
}
if (permission.isKnownSigner &&
androidPackage.signingDetails.hasAncestorOrSelfWithDigest(permission.knownCerts)) {
// If the permission is to be granted to a known signer then check if any of this
// app's signing certificates are in the trusted certificate digest Set.
return true
}
if (permission.isSetup &&
packageName in knownPackages[KnownPackages.PACKAGE_SETUP_WIZARD]) {
// If this permission is to be granted to the system setup wizard and
// this app is a setup wizard, then it gets the permission.
return true
}
if (permission.isSystemTextClassifier &&
packageName in knownPackages[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER]) {
// Special permissions for the system default text classifier.
return true
}
if (permission.isConfigurator &&
packageName in knownPackages[KnownPackages.PACKAGE_CONFIGURATOR]) {
// Special permissions for the device configurator.
return true
}
if (permission.isIncidentReportApprover &&
packageName in knownPackages[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER]) {
// If this permission is to be granted to the incident report approver and
// this app is the incident report approver, then it gets the permission.
return true
}
if (permission.isAppPredictor &&
packageName in knownPackages[KnownPackages.PACKAGE_APP_PREDICTOR]) {
// Special permissions for the system app predictor.
return true
}
if (permission.isCompanion &&
packageName in knownPackages[KnownPackages.PACKAGE_COMPANION]) {
// Special permissions for the system companion device manager.
return true
}
if (permission.isRetailDemo &&
packageName in knownPackages[KnownPackages.PACKAGE_RETAIL_DEMO] &&
isDeviceOrProfileOwnerUid(packageState.appId)) {
// Special permission granted only to the OEM specified retail demo app.
// Note that the original code was passing app ID as UID, so this behavior is kept
// unchanged.
return true
}
if (permission.isRecents &&
packageName in knownPackages[KnownPackages.PACKAGE_RECENTS]) {
// Special permission for the recents app.
return true
}
// TODO(b/261913353): STOPSHIP: Add AndroidPackage.apexModuleName.
// This should be androidPackage.apexModuleName instead
if (permission.isModule && androidPackage.packageName != null) {
// Special permission granted for APKs inside APEX modules.
return true
}
return false
}
private fun MutateStateScope.shouldGrantPrivilegedOrOemPermission(
packageState: PackageState,
permission: Permission
): Boolean {
val permissionName = permission.name
val packageName = packageState.packageName
when {
permission.isPrivileged -> {
if (packageState.isPrivileged) {
// In any case, don't grant a privileged permission to privileged vendor apps,
// if the permission's protectionLevel does not have the extra vendorPrivileged
// flag.
if (packageState.isVendor && !permission.isVendorPrivileged) {
Log.w(
LOG_TAG, "Permission $permissionName cannot be granted to privileged" +
" vendor app $packageName because it isn't a vendorPrivileged" +
" permission"
)
return false
}
return true
}
}
permission.isOem -> {
if (packageState.isOem) {
val allowlistState = newState.systemState.permissionAllowlist
.getOemAppAllowlistState(packageName, permissionName)
checkNotNull(allowlistState) {
"OEM permission $permissionName requested by package" +
" $packageName must be explicitly declared granted or not"
}
return allowlistState
}
}
}
return false
}
private fun MutateStateScope.isDeviceOrProfileOwnerUid(uid: Int): Boolean {
val userId = UserHandle.getUserId(uid)
val ownerPackageName = newState.systemState.deviceAndProfileOwners[userId] ?: return false
val ownerPackageState = newState.systemState.packageStates[ownerPackageName] ?: return false
val ownerUid = UserHandle.getUid(userId, ownerPackageState.appId)
return uid == ownerUid
}
override fun MutateStateScope.onSystemReady() {
if (!privilegedPermissionAllowlistViolations.isEmpty()) {
throw IllegalStateException("Signature|privileged permissions not in privileged" +
" permission allowlist: $privilegedPermissionAllowlistViolations")
}
}
override fun BinaryXmlPullParser.parseSystemState(state: AccessState) {
with(persistence) { this@parseSystemState.parseSystemState(state) }
}
override fun BinaryXmlSerializer.serializeSystemState(state: AccessState) {
with(persistence) { this@serializeSystemState.serializeSystemState(state) }
}
override fun BinaryXmlPullParser.parseUserState(state: AccessState, userId: Int) {
with(persistence) { this@parseUserState.parseUserState(state, userId) }
}
override fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) {
with(persistence) { this@serializeUserState.serializeUserState(state, userId) }
}
fun GetStateScope.getPermissionTrees(): IndexedMap<String, Permission> =
state.systemState.permissionTrees
fun GetStateScope.findPermissionTree(permissionName: String): Permission? =
state.systemState.permissionTrees.firstNotNullOfOrNullIndexed {
_, permissionTreeName, permissionTree ->
if (permissionName.startsWith(permissionTreeName) &&
permissionName.length > permissionTreeName.length &&
permissionName[permissionTreeName.length] == '.') {
permissionTree
} else {
null
}
}
fun MutateStateScope.addPermissionTree(permission: Permission) {
newState.systemState.permissionTrees[permission.name] = permission
newState.systemState.requestWrite()
}
/**
* returns all permission group definitions available in the system
*/
fun GetStateScope.getPermissionGroups(): IndexedMap<String, PermissionGroupInfo> =
state.systemState.permissionGroups
/**
* returns all permission definitions available in the system
*/
fun GetStateScope.getPermissions(): IndexedMap<String, Permission> =
state.systemState.permissions
fun MutateStateScope.addPermission(permission: Permission, sync: Boolean = false) {
newState.systemState.permissions[permission.name] = permission
newState.systemState.requestWrite(sync)
}
fun MutateStateScope.removePermission(permission: Permission) {
newState.systemState.permissions -= permission.name
newState.systemState.requestWrite()
}
fun GetStateScope.getUidPermissionFlags(appId: Int, userId: Int): IndexedMap<String, Int>? =
state.userStates[userId]?.uidPermissionFlags?.get(appId)
fun GetStateScope.getPermissionFlags(
appId: Int,
userId: Int,
permissionName: String
): Int = getPermissionFlags(state, appId, userId, permissionName)
private fun MutateStateScope.getOldStatePermissionFlags(
appId: Int,
userId: Int,
permissionName: String
): Int = getPermissionFlags(oldState, appId, userId, permissionName)
private fun getPermissionFlags(
state: AccessState,
appId: Int,
userId: Int,
permissionName: String
): Int =
state.userStates[userId]?.uidPermissionFlags?.get(appId).getWithDefault(permissionName, 0)
fun MutateStateScope.setPermissionFlags(
appId: Int,
userId: Int,
permissionName: String,
flags: Int
): Boolean =
updatePermissionFlags(appId, userId, permissionName, PermissionFlags.MASK_ALL, flags)
fun MutateStateScope.updatePermissionFlags(
appId: Int,
userId: Int,
permissionName: String,
flagMask: Int,
flagValues: Int
): Boolean {
val userState = newState.userStates[userId]
val uidPermissionFlags = userState.uidPermissionFlags
var permissionFlags = uidPermissionFlags[appId]
val oldFlags = permissionFlags.getWithDefault(permissionName, 0)
val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask)
if (oldFlags == newFlags) {
return false
}
if (permissionFlags == null) {
permissionFlags = IndexedMap()
uidPermissionFlags[appId] = permissionFlags
}
permissionFlags.putWithDefault(permissionName, newFlags, 0)
if (permissionFlags.isEmpty()) {
uidPermissionFlags -= appId
}
userState.requestWrite()
onPermissionFlagsChangedListeners.forEachIndexed { _, it ->
it.onPermissionFlagsChanged(appId, userId, permissionName, oldFlags, newFlags)
}
return true
}
fun addOnPermissionFlagsChangedListener(listener: OnPermissionFlagsChangedListener) {
synchronized(onPermissionFlagsChangedListenersLock) {
onPermissionFlagsChangedListeners = onPermissionFlagsChangedListeners + listener
}
}
fun removeOnPermissionFlagsChangedListener(listener: OnPermissionFlagsChangedListener) {
synchronized(onPermissionFlagsChangedListenersLock) {
onPermissionFlagsChangedListeners = onPermissionFlagsChangedListeners - listener
}
}
companion object {
private val LOG_TAG = UidPermissionPolicy::class.java.simpleName
private const val PLATFORM_PACKAGE_NAME = "android"
// A set of permissions that we don't want to revoke when they are no longer implicit.
private val RETAIN_IMPLICIT_FLAGS_PERMISSIONS = indexedSetOf(
Manifest.permission.ACCESS_MEDIA_LOCATION,
Manifest.permission.ACTIVITY_RECOGNITION,
Manifest.permission.READ_MEDIA_AUDIO,
Manifest.permission.READ_MEDIA_IMAGES,
Manifest.permission.READ_MEDIA_VIDEO,
)
// TODO: also add the permission NEARBY_WIFI_DEVICES to this set
private val NEARBY_DEVICES_PERMISSIONS = indexedSetOf(
Manifest.permission.BLUETOOTH_ADVERTISE,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.BLUETOOTH_SCAN
)
private val NOTIFICATIONS_PERMISSIONS = indexedSetOf(
Manifest.permission.POST_NOTIFICATIONS
)
/**
* Mask for all permission flags that can be set by the user
*/
private const val USER_SETTABLE_MASK =
PermissionFlags.USER_SET or
PermissionFlags.USER_FIXED or
PermissionFlags.APP_OP_REVOKED or
PermissionFlags.ONE_TIME or
PermissionFlags.HIBERNATION or
PermissionFlags.USER_SELECTED
/**
* Mask for all permission flags that imply we shouldn't automatically modify the
* permission grant state.
*/
private const val SYSTEM_OR_POLICY_FIXED_MASK =
PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED
}
/**
* Listener for permission flags changes.
*/
abstract class OnPermissionFlagsChangedListener {
/**
* Called when a permission flags change has been made to the upcoming new state.
*
* Implementations should keep this method fast to avoid stalling the locked state mutation,
* and only call external code after [onStateMutated] when the new state has actually become
* the current state visible to external code.
*/
abstract fun onPermissionFlagsChanged(
appId: Int,
userId: Int,
permissionName: String,
oldFlags: Int,
newFlags: Int
)
/**
* Called when the upcoming new state has become the current state.
*
* Implementations should keep this method fast to avoid stalling the locked state mutation.
*/
abstract fun onStateMutated()
}
}