From c6abc0023f7291ece958d943e5666c69c289cec0 Mon Sep 17 00:00:00 2001 From: Hai Zhang Date: Mon, 12 Dec 2022 01:41:11 -0800 Subject: [PATCH] Various refactors to UidPermissionPolicy code. - Use custom flag getter, setter, updater and change listener, since this allows the compat layer to call it more efficiently. The URI oriented methods are now only for future unified APIs, if there will be any. - Made the change listener actually working by calling it in the updatePermissionFlags() method which should be the one and only method that mutates a permission flag. - Made the persistence actually working by calling requestWrite() in relevant methods. - Added the boolean return value for removeAllIndexed() and retainAllIndexed() for consistency with kotlin-stdlib APIs, and used it when deciding whether to call requestWrite(). - Renamed UserState.permissionFlags to uidPermissionFlags. Now we have a consistent way to reference different levels in the hierarchy - the top level is uidPermissionFlags, the map from permission name to flags is permissionFlags, while the flags is just flags. - Switched to keeping only app ID states with non-empty maps. This is more consistent with what we do with app ops and possible future parameterized permissions, and may also save a little bit of memory for things like RRO packages. This also means we should always iterate over the user IDs and app IDs in system state instead of on userStates or uidPermissoinFlags, which may not be complete and may be modified during iteration. - Moved the userId parameter to be following appId or packageState, instead of always being at the end. It used to make sense for always being at the end because some parts of the system may not be multi-user aware so an optional userId parameter at the end made more sense. However we are always multi-user aware and passing the userId alone at the end simply started to look too unnatural. - Refactored trimPermissions() to better handle permission removal, and delegating setting INSTALL_REVOKED to evalutePermissionState() for consistency. Bug: 182523293 Test: presubmit Change-Id: I3aa177e52190c5996c6d9ecbc7030c6d7b5ae429 --- .../server/permission/access/AccessState.kt | 9 +- .../access/collection/IndexedList.kt | 10 +- .../access/collection/IndexedListSet.kt | 10 +- .../access/collection/IndexedMap.kt | 10 +- .../access/collection/IndexedSet.kt | 10 +- .../permission/access/collection/IntMap.kt | 10 +- .../permission/access/collection/IntSet.kt | 10 +- .../permission/access/collection/List.kt | 10 +- .../access/permission/PermissionFlags.kt | 1 + .../access/permission/UidPermissionPolicy.kt | 267 +++++++++++------- 10 files changed, 231 insertions(+), 116 deletions(-) diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt index 4c794931cf981..4a2c78a86a937 100644 --- a/services/permission/java/com/android/server/permission/access/AccessState.kt +++ b/services/permission/java/com/android/server/permission/access/AccessState.kt @@ -94,14 +94,17 @@ class SystemState private constructor( class UserState private constructor( // A map of (appId to a map of (permissionName to permissionFlags)) - val permissionFlags: IntMap>, + val uidPermissionFlags: IntMap>, val uidAppOpModes: IntMap>, val packageAppOpModes: IndexedMap> ) : WritableState() { constructor() : this(IntMap(), IntMap(), IndexedMap()) - fun copy(): UserState = UserState(permissionFlags.copy { it.copy { it } }, - uidAppOpModes.copy { it.copy { it } }, packageAppOpModes.copy { it.copy { it } }) + fun copy(): UserState = UserState( + uidPermissionFlags.copy { it.copy { it } }, + uidAppOpModes.copy { it.copy { it } }, + packageAppOpModes.copy { it.copy { it } } + ) } object WriteMode { diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt index 5ba435c79e2f1..9cb2e8660e78b 100644 --- a/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt +++ b/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt @@ -72,18 +72,24 @@ inline operator fun IndexedList.plusAssign(element: T) { add(element) } -inline fun IndexedList.removeAllIndexed(predicate: (Int, T) -> Boolean) { +inline fun IndexedList.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean { + var isChanged = false for (index in lastIndex downTo 0) { if (predicate(index, this[index])) { removeAt(index) + isChanged = true } } + return isChanged } -inline fun IndexedList.retainAllIndexed(predicate: (Int, T) -> Boolean) { +inline fun IndexedList.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean { + var isChanged = false for (index in lastIndex downTo 0) { if (!predicate(index, this[index])) { removeAt(index) + isChanged = true } } + return isChanged } diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt index ac552fff6cdb6..1c42c50e0241f 100644 --- a/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt +++ b/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt @@ -123,18 +123,24 @@ inline operator fun IndexedListSet.plusAssign(element: T) { add(element) } -inline fun IndexedListSet.removeAllIndexed(predicate: (Int, T) -> Boolean) { +inline fun IndexedListSet.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean { + var isChanged = false for (index in lastIndex downTo 0) { if (predicate(index, elementAt(index))) { removeAt(index) + isChanged = true } } + return isChanged } -inline fun IndexedListSet.retainAllIndexed(predicate: (Int, T) -> Boolean) { +inline fun IndexedListSet.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean { + var isChanged = false for (index in lastIndex downTo 0) { if (!predicate(index, elementAt(index))) { removeAt(index) + isChanged = true } } + return isChanged } diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt index 1251666089f5d..2448ff0755bea 100644 --- a/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt +++ b/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt @@ -111,20 +111,26 @@ inline fun IndexedMap.putWithDefault(key: K, value: V, defaultValue } } -inline fun IndexedMap.removeAllIndexed(predicate: (Int, K, V) -> Boolean) { +inline fun IndexedMap.removeAllIndexed(predicate: (Int, K, V) -> Boolean): Boolean { + var isChanged = false for (index in lastIndex downTo 0) { if (predicate(index, keyAt(index), valueAt(index))) { removeAt(index) + isChanged = true } } + return isChanged } -inline fun IndexedMap.retainAllIndexed(predicate: (Int, K, V) -> Boolean) { +inline fun IndexedMap.retainAllIndexed(predicate: (Int, K, V) -> Boolean): Boolean { + var isChanged = false for (index in lastIndex downTo 0) { if (!predicate(index, keyAt(index), valueAt(index))) { removeAt(index) + isChanged = true } } + return isChanged } @Suppress("NOTHING_TO_INLINE") diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt index 36d8ff0c00873..faaa6d3a23f10 100644 --- a/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt +++ b/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt @@ -80,20 +80,26 @@ inline operator fun IndexedSet.plusAssign(element: T) { add(element) } -inline fun IndexedSet.removeAllIndexed(predicate: (Int, T) -> Boolean) { +inline fun IndexedSet.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean { + var isChanged = false for (index in lastIndex downTo 0) { if (predicate(index, elementAt(index))) { removeAt(index) + isChanged = true } } + return isChanged } -inline fun IndexedSet.retainAllIndexed(predicate: (Int, T) -> Boolean) { +inline fun IndexedSet.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean { + var isChanged = false for (index in lastIndex downTo 0) { if (!predicate(index, elementAt(index))) { removeAt(index) + isChanged = true } } + return isChanged } @Suppress("NOTHING_TO_INLINE") diff --git a/services/permission/java/com/android/server/permission/access/collection/IntMap.kt b/services/permission/java/com/android/server/permission/access/collection/IntMap.kt index 9051c66120d8b..0044b7359ef31 100644 --- a/services/permission/java/com/android/server/permission/access/collection/IntMap.kt +++ b/services/permission/java/com/android/server/permission/access/collection/IntMap.kt @@ -120,20 +120,26 @@ inline fun IntMap.putWithDefault(key: Int, value: T, defaultValue: T): T } } -inline fun IntMap.removeAllIndexed(predicate: (Int, Int, T) -> Boolean) { +inline fun IntMap.removeAllIndexed(predicate: (Int, Int, T) -> Boolean): Boolean { + var isChanged = false for (index in lastIndex downTo 0) { if (predicate(index, keyAt(index), valueAt(index))) { removeAt(index) + isChanged = true } } + return isChanged } -inline fun IntMap.retainAllIndexed(predicate: (Int, Int, T) -> Boolean) { +inline fun IntMap.retainAllIndexed(predicate: (Int, Int, T) -> Boolean): Boolean { + var isChanged = false for (index in lastIndex downTo 0) { if (!predicate(index, keyAt(index), valueAt(index))) { removeAt(index) + isChanged = true } } + return isChanged } inline val IntMap.size: Int diff --git a/services/permission/java/com/android/server/permission/access/collection/IntSet.kt b/services/permission/java/com/android/server/permission/access/collection/IntSet.kt index 7cc2fe498b301..0d75a4c87a895 100644 --- a/services/permission/java/com/android/server/permission/access/collection/IntSet.kt +++ b/services/permission/java/com/android/server/permission/access/collection/IntSet.kt @@ -113,18 +113,24 @@ operator fun IntSet.plusAssign(array: IntArray) { array.forEach { this += it } } -inline fun IntSet.removeAllIndexed(predicate: (Int, Int) -> Boolean) { +inline fun IntSet.removeAllIndexed(predicate: (Int, Int) -> Boolean): Boolean { + var isChanged = false for (index in lastIndex downTo 0) { if (predicate(index, elementAt(index))) { removeAt(index) + isChanged = true } } + return isChanged } -inline fun IntSet.retainAllIndexed(predicate: (Int, Int) -> Boolean) { +inline fun IntSet.retainAllIndexed(predicate: (Int, Int) -> Boolean): Boolean { + var isChanged = false for (index in lastIndex downTo 0) { if (!predicate(index, elementAt(index))) { removeAt(index) + isChanged = true } } + return isChanged } diff --git a/services/permission/java/com/android/server/permission/access/collection/List.kt b/services/permission/java/com/android/server/permission/access/collection/List.kt index dc28642bc2c6b..d35e69e88e72c 100644 --- a/services/permission/java/com/android/server/permission/access/collection/List.kt +++ b/services/permission/java/com/android/server/permission/access/collection/List.kt @@ -49,18 +49,24 @@ inline fun List.noneIndexed(predicate: (Int, T) -> Boolean): Boolean { return true } -inline fun MutableList.removeAllIndexed(predicate: (Int, T) -> Boolean) { +inline fun MutableList.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean { + var isChanged = false for (index in lastIndex downTo 0) { if (predicate(index, this[index])) { removeAt(index) + isChanged = true } } + return isChanged } -inline fun MutableList.retainAllIndexed(predicate: (Int, T) -> Boolean) { +inline fun MutableList.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean { + var isChanged = false for (index in lastIndex downTo 0) { if (!predicate(index, this[index])) { removeAt(index) + isChanged = true } } + return isChanged } diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt index bb1a86c7b32d2..1b055202af7c0 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt @@ -27,6 +27,7 @@ object PermissionFlags { // For the permissions that are implicit for the package const val IMPLICIT = 1 shl 5 + const val MASK_ALL = 0.inv() const val MASK_GRANTED = INSTALL_GRANTED or PROTECTION_GRANTED or OTHER_GRANTED or ROLE_GRANTED const val MASK_RUNTIME = OTHER_GRANTED or IMPLICIT } diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt index 74e4f21e926a2..3d6d2ce341419 100644 --- a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt @@ -35,8 +35,8 @@ import com.android.server.permission.access.PermissionUri import com.android.server.permission.access.SchemePolicy import com.android.server.permission.access.SystemState import com.android.server.permission.access.UidUri -import com.android.server.permission.access.UserState 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.pm.KnownPackages @@ -48,6 +48,11 @@ import com.android.server.pm.pkg.PackageState class UidPermissionPolicy : SchemePolicy() { private val persistence = UidPermissionPersistence() + @Volatile + private var onPermissionFlagsChangedListeners = + IndexedListSet() + private val onPermissionFlagsChangedListenersLock = Any() + override val subjectScheme: String get() = UidUri.SCHEME @@ -57,8 +62,7 @@ class UidPermissionPolicy : SchemePolicy() { override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int { subject as UidUri `object` as PermissionUri - return state.userStates[subject.userId]?.permissionFlags?.get(subject.appId) - ?.get(`object`.permissionName) ?: 0 + return getPermissionFlags(subject.appId, subject.userId, `object`.permissionName) } override fun MutateStateScope.setDecision( @@ -68,26 +72,22 @@ class UidPermissionPolicy : SchemePolicy() { ) { subject as UidUri `object` as PermissionUri - val uidFlags = newState.userStates.getOrPut(subject.userId) { UserState() } - .permissionFlags.getOrPut(subject.appId) { IndexedMap() } - uidFlags[`object`.permissionName] = decision + setPermissionFlags(subject.appId, subject.userId, `object`.permissionName, decision) } override fun MutateStateScope.onUserAdded(userId: Int) { newState.systemState.packageStates.forEach { (_, packageState) -> - evaluateAllPermissionStatesForPackageAndUser(packageState, null, userId) + evaluateAllPermissionStatesForPackageAndUser(packageState, userId, null) grantImplicitPermissions(packageState, userId) } } - override fun MutateStateScope.onAppIdAdded(appId: Int) { - newState.userStates.forEachIndexed { _, _, userState -> - userState.permissionFlags.getOrPut(appId) { IndexedMap() } - } - } - override fun MutateStateScope.onAppIdRemoved(appId: Int) { - newState.userStates.forEachIndexed { _, _, userState -> userState.permissionFlags -= appId } + newState.userStates.forEachValueIndexed { _, userState -> + userState.uidPermissionFlags -= appId + userState.requestWrite() + // Skip notifying the change listeners since the app ID no longer exists. + } } override fun MutateStateScope.onPackageAdded(packageState: PackageState) { @@ -96,7 +96,7 @@ class UidPermissionPolicy : SchemePolicy() { addPermissionGroups(packageState) addPermissions(packageState, changedPermissionNames) // TODO: revokeStoragePermissionsIfScopeExpandedInternal() - trimPermissions(packageState.packageName) + trimPermissions(packageState.packageName, changedPermissionNames) changedPermissionNames.forEachIndexed { _, permissionName -> evaluatePermissionStateForAllPackages(permissionName, packageState) } @@ -120,22 +120,23 @@ class UidPermissionPolicy : SchemePolicy() { if (!canAdoptPermissions(packageName, originalPackageName)) { return@forEachIndexed } - newState.systemState.permissions.let { 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 - } - val newPermission = Permission(newPermissionInfo, false, oldPermission.type, 0) - changedPermissionNames += permissionName - permissions.setValueAt(permissionIndex, newPermission) + 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 + } + val newPermission = Permission(newPermissionInfo, false, oldPermission.type, 0) + permissions.setValueAt(permissionIndex, newPermission) + systemState.requestWrite() + changedPermissionNames += permissionName } } } @@ -212,11 +213,12 @@ class UidPermissionPolicy : SchemePolicy() { 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) { - newState.systemState.permissionTrees[permissionName] + systemState.permissionTrees[permissionName] } else { - newState.systemState.permissions[permissionName] + 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 @@ -246,19 +248,18 @@ class UidPermissionPolicy : SchemePolicy() { if (oldPermission.type == Permission.TYPE_CONFIG && !oldPermission.isReconciled) { // It's a config permission and has no owner, take ownership now. Permission(newPermissionInfo, true, Permission.TYPE_CONFIG, packageState.appId) - } else if (newState.systemState.packageStates[oldPackageName]?.isSystem != true) { + } 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. - newState.userStates.forEachValueIndexed { _, userState -> - userState.permissionFlags.forEachValueIndexed { _, permissionFlags -> - permissionFlags -= newPermissionInfo.name + systemState.userIds.forEachIndexed { _, userId -> + systemState.appIds.forEachKeyIndexed { _, appId -> + setPermissionFlags(appId, userId, permissionName, 0) } } - // TODO: Notify re-evaluation of this permission. Permission( newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId ) @@ -277,23 +278,28 @@ class UidPermissionPolicy : SchemePolicy() { Permission(newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId) } - changedPermissionNames += permissionName if (parsedPermission.isTree) { - newState.systemState.permissionTrees[permissionName] = newPermission + systemState.permissionTrees[permissionName] = newPermission } else { - newState.systemState.permissions[permissionName] = newPermission + systemState.permissions[permissionName] = newPermission } + systemState.requestWrite() + changedPermissionNames += permissionName } } - private fun MutateStateScope.trimPermissions(packageName: String) { - val packageState = newState.systemState.packageStates[packageName] + private fun MutateStateScope.trimPermissions( + packageName: String, + changedPermissionNames: IndexedSet + ) { + val systemState = newState.systemState + val packageState = systemState.packageStates[packageName] val androidPackage = packageState?.androidPackage if (packageState != null && androidPackage == null) { return } - newState.systemState.permissionTrees.removeAllIndexed { + val isPermissionTreeRemoved = systemState.permissionTrees.removeAllIndexed { _, permissionTreeName, permissionTree -> permissionTree.packageName == packageName && ( packageState == null || androidPackage!!.permissions.noneIndexed { _, it -> @@ -301,26 +307,30 @@ class UidPermissionPolicy : SchemePolicy() { } ) } + if (isPermissionTreeRemoved) { + systemState.requestWrite() + } - newState.systemState.permissions.removeAllIndexed { i, permissionName, permission -> + systemState.permissions.removeAllIndexed { permissionIndex, permissionName, permission -> val updatedPermission = updatePermissionIfDynamic(permission) - newState.systemState.permissions.setValueAt(i, updatedPermission) + newState.systemState.permissions.setValueAt(permissionIndex, updatedPermission) if (updatedPermission.packageName == packageName && ( packageState == null || androidPackage!!.permissions.noneIndexed { _, it -> !it.isTree && it.name == permissionName } )) { - if (!isPermissionDeclaredByDisabledSystemPackage(permission)) { - newState.userStates.forEachIndexed { _, userId, userState -> - userState.permissionFlags.forEachKeyIndexed { _, appId -> - setPermissionFlags( - appId, permissionName, getPermissionFlags( - appId, permissionName, userId - ) and PermissionFlags.INSTALL_REVOKED, userId - ) - } + // 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 @@ -328,16 +338,6 @@ class UidPermissionPolicy : SchemePolicy() { } } - private fun MutateStateScope.isPermissionDeclaredByDisabledSystemPackage( - permission: Permission - ): Boolean { - val disabledSystemPackage = newState.systemState - .disabledSystemPackageStates[permission.packageName]?.androidPackage ?: return false - return disabledSystemPackage.permissions.anyIndexed { _, it -> - it.name == permission.name && it.protectionLevel == permission.protectionLevel - } - } - private fun MutateStateScope.updatePermissionIfDynamic(permission: Permission): Permission { if (!permission.isDynamic) { return permission @@ -367,11 +367,14 @@ class UidPermissionPolicy : SchemePolicy() { permissionName: String, installedPackageState: PackageState? ) { - newState.systemState.userIds.forEachIndexed { _, userId -> - oldState.userStates[userId]?.permissionFlags?.forEachIndexed { - _, appId, permissionFlags -> - if (permissionName in permissionFlags) { - evaluatePermissionState(appId, permissionName, installedPackageState, userId) + 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) } } } @@ -383,28 +386,28 @@ class UidPermissionPolicy : SchemePolicy() { ) { newState.systemState.userIds.forEachIndexed { _, userId -> evaluateAllPermissionStatesForPackageAndUser( - packageState, installedPackageState, userId + packageState, userId, installedPackageState ) } } private fun MutateStateScope.evaluateAllPermissionStatesForPackageAndUser( packageState: PackageState, - installedPackageState: PackageState?, - userId: Int + userId: Int, + installedPackageState: PackageState? ) { packageState.androidPackage?.requestedPermissions?.forEachIndexed { _, permissionName -> evaluatePermissionState( - packageState.appId, permissionName, installedPackageState, userId + packageState.appId, userId, permissionName, installedPackageState ) } } private fun MutateStateScope.evaluatePermissionState( appId: Int, + userId: Int, permissionName: String, - installedPackageState: PackageState?, - userId: Int + installedPackageState: PackageState? ) { val packageNames = newState.systemState.appIds[appId] val hasMissingPackage = packageNames.anyIndexed { _, packageName -> @@ -415,7 +418,7 @@ class UidPermissionPolicy : SchemePolicy() { return } val permission = newState.systemState.permissions[permissionName] ?: return - val oldFlags = getPermissionFlags(appId, permissionName, userId) + val oldFlags = getPermissionFlags(appId, userId, permissionName) if (permission.isNormal) { val wasGranted = oldFlags.hasBits(PermissionFlags.INSTALL_GRANTED) if (!wasGranted) { @@ -437,7 +440,7 @@ class UidPermissionPolicy : SchemePolicy() { } else { PermissionFlags.INSTALL_REVOKED } - setPermissionFlags(appId, permissionName, newFlags, userId) + setPermissionFlags(appId, userId, permissionName, newFlags) } } else if (permission.isSignature || permission.isInternal) { val wasProtectionGranted = oldFlags.hasBits(PermissionFlags.PROTECTION_GRANTED) @@ -476,7 +479,7 @@ class UidPermissionPolicy : SchemePolicy() { if (permission.isRole) { newFlags = newFlags or (oldFlags and PermissionFlags.ROLE_GRANTED) } - setPermissionFlags(appId, permissionName, newFlags, userId) + setPermissionFlags(appId, userId, permissionName, newFlags) } else if (permission.isRuntime) { // TODO: add runtime permissions } else { @@ -502,7 +505,7 @@ class UidPermissionPolicy : SchemePolicy() { } // Explicitly check against the old state to determine if this permission is new. val isNewPermission = getPermissionFlags( - appId, implicitPermissionName, userId, oldState + appId, userId, implicitPermissionName, oldState ) == 0 if (!isNewPermission) { return@implicitPermissions @@ -515,7 +518,7 @@ class UidPermissionPolicy : SchemePolicy() { checkNotNull(sourcePermission) { "Unknown source permission $sourcePermissionName in split permissions" } - val sourceFlags = getPermissionFlags(appId, sourcePermissionName, userId) + val sourceFlags = getPermissionFlags(appId, userId, sourcePermissionName) val isSourceGranted = sourceFlags.hasAnyBit(PermissionFlags.MASK_GRANTED) val isNewGranted = newFlags.hasAnyBit(PermissionFlags.MASK_GRANTED) val isGrantingNewFromRevoke = isSourceGranted && !isNewGranted @@ -530,27 +533,10 @@ class UidPermissionPolicy : SchemePolicy() { } } newFlags = newFlags or PermissionFlags.IMPLICIT - setPermissionFlags(appId, implicitPermissionName, newFlags, userId) + setPermissionFlags(appId, userId, implicitPermissionName, newFlags) } } - private fun MutateStateScope.getPermissionFlags( - appId: Int, - permissionName: String, - userId: Int, - state: AccessState = newState - ): Int = state.userStates[userId].permissionFlags[appId].getWithDefault(permissionName, 0) - - private fun MutateStateScope.setPermissionFlags( - appId: Int, - permissionName: String, - flags: Int, - userId: Int - ) { - newState.userStates[userId].permissionFlags[appId]!! - .putWithDefault(permissionName, flags, 0) - } - private fun isCompatibilityPermissionForPackage( androidPackage: AndroidPackage, permissionName: String @@ -847,7 +833,10 @@ class UidPermissionPolicy : SchemePolicy() { } override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) { - // TODO + // 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" + } } override fun BinaryXmlPullParser.parseSystemState(systemState: SystemState) { @@ -864,6 +853,76 @@ class UidPermissionPolicy : SchemePolicy() { fun GetStateScope.getPermission(permissionName: String): Permission? = state.systemState.permissions[permissionName] + fun GetStateScope.getPermissionFlags( + appId: Int, + userId: Int, + permissionName: String + ): Int = getPermissionFlags(state, appId, userId, permissionName) + + private fun MutateStateScope.getPermissionFlags( + appId: Int, + userId: Int, + permissionName: String, + state: AccessState = newState + ): Int = getPermissionFlags(state, appId, userId, permissionName) + + private fun getPermissionFlags( + state: AccessState, + appId: Int, + userId: Int, + permissionName: String + ): Int = state.userStates[userId].uidPermissionFlags[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 + 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 @@ -878,4 +937,14 @@ class UidPermissionPolicy : SchemePolicy() { Manifest.permission.READ_MEDIA_VIDEO, ) } + + fun interface OnPermissionFlagsChangedListener { + fun onPermissionFlagsChanged( + appId: Int, + userId: Int, + permissionName: String, + oldFlags: Int, + newFlags: Int + ) + } }