Evaluate runtime permissions state for their appop
Bug: 266164193
Test: Boot + appops migration
+ atest CtsAppOpsTestCases \
CtsAppOps2TestCases \
CtsPermissionTestCases \
CtsPermissionMultiDeviceTestCases \
CtsPermissionMultiUserTestCases \
CtsPermissionPolicyTestCases \
CtsPermissionUiTestCases \
CtsRoleTestCases \
CtsSafetyCenterTestCases \
--retry-any-failure 5
Change-Id: Ic3d91b7b2ce6c9da3cca498e0b3f94d9e7a85b8a
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 60143cc..bb619a0 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -78,3 +78,10 @@
description: "This flag is used to enabled the Wallet Role for all users on the device"
bug: "283989236"
}
+
+flag {
+ name: "runtime_permission_appops_mapping"
+ namespace: "permissions"
+ description: "Use runtime permission state to determine appop state"
+ bug: "266164193"
+}
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
index 8f464d4..3ee7430 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
@@ -17,29 +17,62 @@
package com.android.server.permission.access.appop
import android.app.AppOpsManager
+import android.os.Binder
import android.os.Handler
import android.os.UserHandle
+import android.permission.flags.Flags
import android.util.ArrayMap
import android.util.ArraySet
+import android.util.LongSparseArray
+import android.util.Slog
+import android.util.SparseArray
import android.util.SparseBooleanArray
import android.util.SparseIntArray
import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.util.IntPair
import com.android.server.appop.AppOpsCheckingServiceInterface
import com.android.server.appop.AppOpsCheckingServiceInterface.AppOpsModeChangedListener
import com.android.server.permission.access.AccessCheckingService
import com.android.server.permission.access.AppOpUri
+import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.PackageUri
+import com.android.server.permission.access.PermissionUri
import com.android.server.permission.access.UidUri
+import com.android.server.permission.access.appop.AppOpModes.MODE_ALLOWED
+import com.android.server.permission.access.appop.AppOpModes.MODE_FOREGROUND
+import com.android.server.permission.access.appop.AppOpModes.MODE_IGNORED
import com.android.server.permission.access.collection.forEachIndexed
import com.android.server.permission.access.collection.set
+import com.android.server.permission.access.permission.AppIdPermissionPolicy
+import com.android.server.permission.access.permission.PermissionFlags
+import com.android.server.permission.access.permission.PermissionService
class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingServiceInterface {
private val packagePolicy =
service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME) as PackageAppOpPolicy
private val appIdPolicy =
service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy
+ private val permissionPolicy =
+ service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy
private val context = service.context
+
+ // Maps appop code to its runtime permission
+ private val runtimeAppOpToPermissionNames = SparseArray<String>()
+
+ // Maps runtime permission to its appop codes
+ private val runtimePermissionNameToAppOp = ArrayMap<String, Int>()
+
+ private var foregroundableOps = SparseBooleanArray()
+
+ /* Maps foreground permissions to their background permission. Background permissions aren't
+ required to be runtime */
+ private val foregroundToBackgroundPermissionName = ArrayMap<String, String>()
+
+ /* Maps background permissions to their foreground permissions. Background permissions aren't
+ required to be runtime */
+ private val backgroundToForegroundPermissionNames = ArrayMap<String, ArraySet<String>>()
+
private lateinit var handler: Handler
@Volatile private var listeners = ArraySet<AppOpsModeChangedListener>()
@@ -68,11 +101,58 @@
}
override fun systemReady() {
- // Not implemented because upgrades are handled automatically.
+ if (useRuntimePermissionAppOpMapping()) {
+ createPermissionAppOpMapping()
+ permissionPolicy.addOnPermissionFlagsChangedListener(OnPermissionFlagsChangedListener())
+ }
+ }
+
+ private fun createPermissionAppOpMapping() {
+ val permissions = service.getState { with(permissionPolicy) { getPermissions() } }
+
+ for (appOpCode in 0 until AppOpsManager._NUM_OP) {
+ AppOpsManager.opToPermission(appOpCode)?.let { permissionName ->
+ // Multiple ops might map to a single permission but only one is considered the
+ // runtime appop calculations.
+ if (appOpCode == AppOpsManager.permissionToOpCode(permissionName)) {
+ val permission = permissions[permissionName]!!
+ if (permission.isRuntime) {
+ runtimePermissionNameToAppOp[permissionName] = appOpCode
+ runtimeAppOpToPermissionNames[appOpCode] = permissionName
+ permission.permissionInfo.backgroundPermission?.let {
+ backgroundPermissionName ->
+ // Note: background permission may not be runtime,
+ // e.g. microphone/camera.
+ foregroundableOps[appOpCode] = true
+ foregroundToBackgroundPermissionName[permissionName] =
+ backgroundPermissionName
+ backgroundToForegroundPermissionNames
+ .getOrPut(backgroundPermissionName, ::ArraySet)
+ .add(permissionName)
+ }
+ }
+ }
+ }
+ }
}
override fun getNonDefaultUidModes(uid: Int, persistentDeviceId: String): SparseIntArray {
- return opNameMapToOpSparseArray(getUidModes(uid))
+ val appId = UserHandle.getAppId(uid)
+ val userId = UserHandle.getUserId(uid)
+ service.getState {
+ val modes =
+ with(appIdPolicy) { opNameMapToOpSparseArray(getAppOpModes(appId, userId)?.map) }
+ if (useRuntimePermissionAppOpMapping()) {
+ runtimePermissionNameToAppOp.forEachIndexed { _, permissionName, appOpCode ->
+ val mode = getUidModeFromPermissionState(appId, userId, permissionName)
+ if (mode != AppOpsManager.opToDefaultMode(appOpCode)) {
+ modes[appOpCode] = mode
+ }
+ }
+ }
+
+ return modes
+ }
}
override fun getNonDefaultPackageModes(packageName: String, userId: Int): SparseIntArray {
@@ -83,7 +163,13 @@
val appId = UserHandle.getAppId(uid)
val userId = UserHandle.getUserId(uid)
val opName = AppOpsManager.opToPublicName(op)
- return service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } }
+ val permissionName = runtimeAppOpToPermissionNames[op]
+
+ return if (!useRuntimePermissionAppOpMapping() || permissionName == null) {
+ service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } }
+ } else {
+ service.getState { getUidModeFromPermissionState(appId, userId, permissionName) }
+ }
}
private fun getUidModes(uid: Int): ArrayMap<String, Int>? {
@@ -92,13 +178,63 @@
return service.getState { with(appIdPolicy) { getAppOpModes(appId, userId) } }?.map
}
- override fun setUidMode(uid: Int, persistentDeviceId: String, op: Int, mode: Int): Boolean {
+ private fun GetStateScope.getUidModeFromPermissionState(
+ appId: Int,
+ userId: Int,
+ permissionName: String
+ ): Int {
+ val permissionFlags =
+ with(permissionPolicy) { getPermissionFlags(appId, userId, permissionName) }
+ val backgroundPermissionName = foregroundToBackgroundPermissionName[permissionName]
+ val backgroundPermissionFlags =
+ if (backgroundPermissionName != null) {
+ with(permissionPolicy) {
+ getPermissionFlags(appId, userId, backgroundPermissionName)
+ }
+ } else {
+ PermissionFlags.RUNTIME_GRANTED
+ }
+ val result = evaluateModeFromPermissionFlags(permissionFlags, backgroundPermissionFlags)
+ if (result != MODE_IGNORED) {
+ return result
+ }
+
+ val fullerPermissionName =
+ PermissionService.getFullerPermission(permissionName) ?: return result
+ return getUidModeFromPermissionState(appId, userId, fullerPermissionName)
+ }
+
+ private fun evaluateModeFromPermissionFlags(
+ foregroundFlags: Int,
+ backgroundFlags: Int = PermissionFlags.RUNTIME_GRANTED
+ ): Int =
+ if (PermissionFlags.isAppOpGranted(foregroundFlags)) {
+ if (PermissionFlags.isAppOpGranted(backgroundFlags)) {
+ MODE_ALLOWED
+ } else {
+ MODE_FOREGROUND
+ }
+ } else {
+ MODE_IGNORED
+ }
+
+ override fun setUidMode(uid: Int, persistentDeviceId: String, code: Int, mode: Int): Boolean {
+ if (useRuntimePermissionAppOpMapping() && code in runtimeAppOpToPermissionNames) {
+ Slog.w(
+ LOG_TAG,
+ "Cannot set UID mode for runtime permission app op, uid = $uid," +
+ " code = ${AppOpsManager.opToName(code)}, mode = ${AppOpsManager.modeToName(mode)}",
+ RuntimeException()
+ )
+ return false
+ }
+
val appId = UserHandle.getAppId(uid)
val userId = UserHandle.getUserId(uid)
- val opName = AppOpsManager.opToPublicName(op)
- var wasChanged = false
+ val appOpName = AppOpsManager.opToPublicName(code)
+ var wasChanged: Boolean
service.mutateState {
- wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, opName, mode) }
+ wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, appOpName, mode) }
}
return wasChanged
}
@@ -113,10 +249,22 @@
private fun getPackageModes(packageName: String, userId: Int): ArrayMap<String, Int>? =
service.getState { with(packagePolicy) { getAppOpModes(packageName, userId) } }?.map
- override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) {
- val opName = AppOpsManager.opToPublicName(op)
+ override fun setPackageMode(packageName: String, appOpCode: Int, mode: Int, userId: Int) {
+ val appOpName = AppOpsManager.opToPublicName(appOpCode)
+
+ if (
+ useRuntimePermissionAppOpMapping() && runtimeAppOpToPermissionNames.contains(appOpCode)
+ ) {
+ Slog.w(
+ LOG_TAG,
+ "(packageName=$packageName, userId=$userId)'s appop state" +
+ " for runtime op $appOpName should not be set directly.",
+ RuntimeException()
+ )
+ return
+ }
service.mutateState {
- with(packagePolicy) { setAppOpMode(packageName, userId, opName, mode) }
+ with(packagePolicy) { setAppOpMode(packageName, userId, appOpName, mode) }
}
}
@@ -127,7 +275,7 @@
}
override fun removePackage(packageName: String, userId: Int): Boolean {
- var wasChanged = false
+ var wasChanged: Boolean
service.mutateState {
wasChanged = with(packagePolicy) { removeAppOpModes(packageName, userId) }
}
@@ -157,6 +305,13 @@
this[AppOpsManager.strOpToOp(op)] = true
}
}
+ if (useRuntimePermissionAppOpMapping()) {
+ foregroundableOps.forEachIndexed { _, op, _ ->
+ if (getUidMode(uid, persistentDeviceId, op) == AppOpsManager.MODE_FOREGROUND) {
+ this[op] = true
+ }
+ }
+ }
}
}
@@ -167,6 +322,13 @@
this[AppOpsManager.strOpToOp(op)] = true
}
}
+ if (useRuntimePermissionAppOpMapping()) {
+ foregroundableOps.forEachIndexed { _, op, _ ->
+ if (getPackageMode(packageName, op, userId) == AppOpsManager.MODE_FOREGROUND) {
+ this[op] = true
+ }
+ }
+ }
}
}
@@ -188,9 +350,10 @@
}
}
- inner class OnAppIdAppOpModeChangedListener : AppIdAppOpPolicy.OnAppOpModeChangedListener() {
+ private inner class OnAppIdAppOpModeChangedListener :
+ AppIdAppOpPolicy.OnAppOpModeChangedListener() {
// (uid, appOpCode) -> newMode
- val pendingChanges = ArrayMap<Pair<Int, Int>, Int>()
+ private val pendingChanges = LongSparseArray<Int>()
override fun onAppOpModeChanged(
appId: Int,
@@ -201,7 +364,7 @@
) {
val uid = UserHandle.getUid(userId, appId)
val appOpCode = AppOpsManager.strOpToOp(appOpName)
- val key = Pair(uid, appOpCode)
+ val key = IntPair.of(uid, appOpCode)
pendingChanges[key] = newMode
}
@@ -210,8 +373,8 @@
val listenersLocal = listeners
pendingChanges.forEachIndexed { _, key, mode ->
listenersLocal.forEachIndexed { _, listener ->
- val uid = key.first
- val appOpCode = key.second
+ val uid = IntPair.first(key)
+ val appOpCode = IntPair.second(key)
listener.onUidModeChanged(uid, appOpCode, mode)
}
@@ -224,7 +387,7 @@
private inner class OnPackageAppOpModeChangedListener :
PackageAppOpPolicy.OnAppOpModeChangedListener() {
// (packageName, userId, appOpCode) -> newMode
- val pendingChanges = ArrayMap<Triple<String, Int, Int>, Int>()
+ private val pendingChanges = ArrayMap<Triple<String, Int, Int>, Int>()
override fun onAppOpModeChanged(
packageName: String,
@@ -254,4 +417,115 @@
pendingChanges.clear()
}
}
+
+ private inner class OnPermissionFlagsChangedListener :
+ AppIdPermissionPolicy.OnPermissionFlagsChangedListener {
+ // (uid, appOpCode) -> newMode
+ private val pendingChanges = LongSparseArray<Int>()
+
+ override fun onPermissionFlagsChanged(
+ appId: Int,
+ userId: Int,
+ permissionName: String,
+ oldFlags: Int,
+ newFlags: Int
+ ) {
+ backgroundToForegroundPermissionNames[permissionName]?.let { foregroundPermissions ->
+ // This is a background permission; there may be multiple foreground permissions
+ // affected.
+ foregroundPermissions.forEachIndexed { _, foregroundPermissionName ->
+ runtimePermissionNameToAppOp[foregroundPermissionName]?.let { appOpCode ->
+ val foregroundPermissionFlags =
+ getPermissionFlags(appId, userId, foregroundPermissionName)
+ addPendingChangedModeIfNeeded(
+ appId,
+ userId,
+ appOpCode,
+ foregroundPermissionFlags,
+ oldFlags,
+ foregroundPermissionFlags,
+ newFlags
+ )
+ }
+ }
+ }
+ ?: foregroundToBackgroundPermissionName[permissionName]?.let { backgroundPermission
+ ->
+ runtimePermissionNameToAppOp[permissionName]?.let { appOpCode ->
+ val backgroundPermissionFlags =
+ getPermissionFlags(appId, userId, backgroundPermission)
+ addPendingChangedModeIfNeeded(
+ appId,
+ userId,
+ appOpCode,
+ oldFlags,
+ backgroundPermissionFlags,
+ newFlags,
+ backgroundPermissionFlags
+ )
+ }
+ }
+ ?: runtimePermissionNameToAppOp[permissionName]?.let { appOpCode ->
+ addPendingChangedModeIfNeeded(
+ appId,
+ userId,
+ appOpCode,
+ oldFlags,
+ PermissionFlags.RUNTIME_GRANTED,
+ newFlags,
+ PermissionFlags.RUNTIME_GRANTED
+ )
+ }
+ }
+
+ private fun getPermissionFlags(appId: Int, userId: Int, permissionName: String): Int =
+ service.getState {
+ with(permissionPolicy) { getPermissionFlags(appId, userId, permissionName) }
+ }
+
+ private fun addPendingChangedModeIfNeeded(
+ appId: Int,
+ userId: Int,
+ appOpCode: Int,
+ oldForegroundFlags: Int,
+ oldBackgroundFlags: Int,
+ newForegroundFlags: Int,
+ newBackgroundFlags: Int,
+ ) {
+ val oldMode = evaluateModeFromPermissionFlags(oldForegroundFlags, oldBackgroundFlags)
+ val newMode = evaluateModeFromPermissionFlags(newForegroundFlags, newBackgroundFlags)
+
+ if (oldMode != newMode) {
+ val uid = UserHandle.getUid(userId, appId)
+ pendingChanges[IntPair.of(uid, appOpCode)] = newMode
+ }
+ }
+
+ override fun onStateMutated() {
+ val listenersLocal = listeners
+ pendingChanges.forEachIndexed { _, key, mode ->
+ listenersLocal.forEachIndexed { _, listener ->
+ val uid = IntPair.first(key)
+ val appOpCode = IntPair.second(key)
+
+ listener.onUidModeChanged(uid, appOpCode, mode)
+ }
+ }
+
+ pendingChanges.clear()
+ }
+ }
+
+ companion object {
+ private val LOG_TAG = AppOpService::class.java.simpleName
+
+ private fun useRuntimePermissionAppOpMapping(): Boolean {
+ val token = Binder.clearCallingIdentity()
+ return try {
+ Flags.runtimePermissionAppopsMapping()
+ } finally {
+ Binder.restoreCallingIdentity(token)
+ }
+ }
+ }
}
diff --git a/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt
new file mode 100644
index 0000000..827dd0e
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022 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.collection
+
+import android.util.LongSparseArray
+
+inline fun <T> LongSparseArray<T>.allIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+inline fun <T> LongSparseArray<T>.anyIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return true
+ }
+ }
+ return false
+}
+
+inline fun <T> LongSparseArray<T>.forEachIndexed(action: (Int, Long, T) -> Unit) {
+ for (index in 0 until size) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun <T> LongSparseArray<T>.forEachReversedIndexed(action: (Int, Long, T) -> Unit) {
+ for (index in lastIndex downTo 0) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun <T> LongSparseArray<T>.getOrPut(key: Long, defaultValue: () -> T): T {
+ val index = indexOfKey(key)
+ return if (index >= 0) {
+ valueAt(index)
+ } else {
+ defaultValue().also { put(key, it) }
+ }
+}
+
+inline val <T> LongSparseArray<T>.lastIndex: Int
+ get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> LongSparseArray<T>.minusAssign(key: Long) {
+ delete(key)
+}
+
+inline fun <T> LongSparseArray<T>.noneIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+inline fun <T> LongSparseArray<T>.removeAllIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+inline fun <T> LongSparseArray<T>.retainAllIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+inline val <T> LongSparseArray<T>.size: Int
+ get() = size()
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> LongSparseArray<T>.set(key: Long, value: T) {
+ put(key, value)
+}
diff --git a/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt
new file mode 100644
index 0000000..a582431
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2023 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.collection
+
+import android.util.SparseIntArray
+
+inline fun SparseIntArray.allIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+inline fun SparseIntArray.anyIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return true
+ }
+ }
+ return false
+}
+
+inline fun SparseIntArray.forEachIndexed(action: (Int, Int, Int) -> Unit) {
+ for (index in 0 until size) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun SparseIntArray.forEachReversedIndexed(action: (Int, Int, Int) -> Unit) {
+ for (index in lastIndex downTo 0) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun SparseIntArray.getOrPut(key: Int, defaultValue: () -> Int): Int {
+ val index = indexOfKey(key)
+ return if (index >= 0) {
+ valueAt(index)
+ } else {
+ defaultValue().also { put(key, it) }
+ }
+}
+
+inline val SparseIntArray.lastIndex: Int
+ get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun SparseIntArray.minusAssign(key: Int) {
+ delete(key)
+}
+
+inline fun SparseIntArray.noneIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+fun SparseIntArray.remove(key: Int) {
+ delete(key)
+}
+
+fun SparseIntArray.remove(key: Int, defaultValue: Int): Int {
+ val index = indexOfKey(key)
+ return if (index >= 0) {
+ val oldValue = valueAt(index)
+ removeAt(index)
+ oldValue
+ } else {
+ defaultValue
+ }
+}
+
+inline fun SparseIntArray.removeAllIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+inline fun SparseIntArray.retainAllIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun SparseIntArray.set(key: Int, value: Int) {
+ put(key, value)
+}
+
+inline val SparseIntArray.size: Int
+ get() = size()
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index f469ab5..b162a1b 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -2870,5 +2870,8 @@
} else {
emptySet<String>()
}
+
+ fun getFullerPermission(permissionName: String): String? =
+ FULLER_PERMISSIONS[permissionName]
}
}