summaryrefslogtreecommitdiff
path: root/services/permission/java
diff options
context:
space:
mode:
Diffstat (limited to 'services/permission/java')
-rw-r--r--services/permission/java/com/android/server/permission/access/AccessCheckingService.kt1
-rw-r--r--services/permission/java/com/android/server/permission/access/AccessPolicy.kt8
-rw-r--r--services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt25
-rw-r--r--services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt25
-rw-r--r--services/permission/java/com/android/server/permission/access/collection/IntBooleanMap.kt171
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/PermissionService.kt114
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt29
-rw-r--r--services/permission/java/com/android/server/permission/access/util/RemoteCallbackListExtensions.kt31
8 files changed, 347 insertions, 57 deletions
diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
index f493b8985c51..306970a22d62 100644
--- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
@@ -161,6 +161,7 @@ class AccessCheckingService(context: Context) : SystemService(context) {
MutateStateScope(oldState, newState).action()
persistence.write(newState)
state = newState
+ with(policy) { GetStateScope(newState).onStateMutated() }
}
}
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index 8027b50fe254..8cf4aeec585c 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -73,6 +73,12 @@ class AccessPolicy private constructor(
}
}
+ fun GetStateScope.onStateMutated() {
+ forEachSchemePolicy {
+ with(it) { onStateMutated() }
+ }
+ }
+
fun MutateStateScope.onUserAdded(userId: Int) {
newState.systemState.userIds += userId
newState.userStates[userId] = UserState()
@@ -284,6 +290,8 @@ abstract class SchemePolicy {
decision: Int
)
+ open fun GetStateScope.onStateMutated() {}
+
open fun MutateStateScope.onUserAdded(userId: Int) {}
open fun MutateStateScope.onUserRemoved(userId: Int) {}
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
index af95fbdb2b6d..7d3578de2fec 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
@@ -48,6 +48,10 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) {
setAppOpMode(subject.packageName, subject.userId, `object`.appOpName, decision)
}
+ override fun GetStateScope.onStateMutated() {
+ onAppOpModeChangedListeners.forEachIndexed { _, it -> it.onStateMutated() }
+ }
+
override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {
newState.userStates.forEachIndexed { _, _, userState ->
userState.packageAppOpModes -= packageName
@@ -113,13 +117,30 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) {
}
}
- fun interface OnAppOpModeChangedListener {
- fun onAppOpModeChanged(
+ /**
+ * Listener for app op mode changes.
+ */
+ abstract class OnAppOpModeChangedListener {
+ /**
+ * Called when an app op mode 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 onAppOpModeChanged(
packageName: String,
userId: Int,
appOpName: String,
oldMode: Int,
newMode: 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()
}
}
diff --git a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
index 93b3a4484dff..0ba9a1e1ee30 100644
--- a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
@@ -48,6 +48,10 @@ class UidAppOpPolicy : BaseAppOpPolicy(UidAppOpPersistence()) {
setAppOpMode(subject.appId, subject.userId, `object`.appOpName, decision)
}
+ override fun GetStateScope.onStateMutated() {
+ onAppOpModeChangedListeners.forEachIndexed { _, it -> it.onStateMutated() }
+ }
+
override fun MutateStateScope.onAppIdRemoved(appId: Int) {
newState.userStates.forEachIndexed { _, _, userState ->
userState.uidAppOpModes -= appId
@@ -113,13 +117,30 @@ class UidAppOpPolicy : BaseAppOpPolicy(UidAppOpPersistence()) {
}
}
- fun interface OnAppOpModeChangedListener {
- fun onAppOpModeChanged(
+ /**
+ * Listener for app op mode changes.
+ */
+ abstract class OnAppOpModeChangedListener {
+ /**
+ * Called when an app op mode 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 onAppOpModeChanged(
appId: Int,
userId: Int,
appOpName: String,
oldMode: Int,
newMode: 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()
}
}
diff --git a/services/permission/java/com/android/server/permission/access/collection/IntBooleanMap.kt b/services/permission/java/com/android/server/permission/access/collection/IntBooleanMap.kt
new file mode 100644
index 000000000000..2f7b9bf721a2
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/IntBooleanMap.kt
@@ -0,0 +1,171 @@
+/*
+ * 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.SparseBooleanArray
+
+typealias IntBooleanMap = SparseBooleanArray
+
+inline fun IntBooleanMap.allIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+inline fun IntBooleanMap.anyIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return true
+ }
+ }
+ return false
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline fun IntBooleanMap.copy(): IntBooleanMap = clone()
+
+inline fun <R> IntBooleanMap.firstNotNullOfOrNullIndexed(transform: (Int, Int, Boolean) -> R): R? {
+ forEachIndexed { index, key, value ->
+ transform(index, key, value)?.let { return it }
+ }
+ return null
+}
+
+inline fun IntBooleanMap.forEachIndexed(action: (Int, Int, Boolean) -> Unit) {
+ for (index in 0 until size) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun IntBooleanMap.forEachKeyIndexed(action: (Int, Int) -> Unit) {
+ for (index in 0 until size) {
+ action(index, keyAt(index))
+ }
+}
+
+inline fun IntBooleanMap.forEachReversedIndexed(action: (Int, Int, Boolean) -> Unit) {
+ for (index in lastIndex downTo 0) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun IntBooleanMap.forEachValueIndexed(action: (Int, Boolean) -> Unit) {
+ for (index in 0 until size) {
+ action(index, valueAt(index))
+ }
+}
+
+inline fun IntBooleanMap.getOrPut(key: Int, defaultValue: () -> Boolean): Boolean {
+ val index = indexOfKey(key)
+ return if (index >= 0) {
+ valueAt(index)
+ } else {
+ defaultValue().also { put(key, it) }
+ }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline fun IntBooleanMap?.getWithDefault(key: Int, defaultValue: Boolean): Boolean {
+ this ?: return defaultValue
+ return get(key, defaultValue)
+}
+
+inline val IntBooleanMap.lastIndex: Int
+ get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun IntBooleanMap.minusAssign(key: Int) {
+ delete(key)
+}
+
+inline fun IntBooleanMap.noneIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline fun IntBooleanMap.putWithDefault(key: Int, value: Boolean, defaultValue: Boolean): Boolean {
+ val index = indexOfKey(key)
+ if (index >= 0) {
+ val oldValue = valueAt(index)
+ if (value != oldValue) {
+ if (value == defaultValue) {
+ removeAt(index)
+ } else {
+ setValueAt(index, value)
+ }
+ }
+ return oldValue
+ } else {
+ if (value != defaultValue) {
+ put(key, value)
+ }
+ return defaultValue
+ }
+}
+
+fun IntBooleanMap.remove(key: Int) {
+ delete(key)
+}
+
+fun IntBooleanMap.remove(key: Int, defaultValue: Boolean): Boolean {
+ val index = indexOfKey(key)
+ return if (index >= 0) {
+ val oldValue = valueAt(index)
+ removeAt(index)
+ oldValue
+ } else {
+ defaultValue
+ }
+}
+
+inline fun IntBooleanMap.removeAllIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+inline fun IntBooleanMap.retainAllIndexed(predicate: (Int, Int, Boolean) -> 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 IntBooleanMap.set(key: Int, value: Boolean) {
+ put(key, value)
+}
+
+inline val IntBooleanMap.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 a70468e2400d..ff7483ffcc60 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
@@ -47,18 +47,18 @@ import com.android.server.LocalManagerRegistry
import com.android.server.LocalServices
import com.android.server.ServiceThread
import com.android.server.SystemConfig
-import com.android.server.pm.PackageManagerLocal
-import com.android.server.pm.permission.PermissionManagerServiceInterface
import com.android.server.permission.access.AccessCheckingService
import com.android.server.permission.access.PermissionUri
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.hasAnyBit
import com.android.server.permission.access.util.hasBits
+import com.android.server.pm.PackageManagerLocal
import com.android.server.pm.UserManagerService
import com.android.server.pm.permission.LegacyPermission
import com.android.server.pm.permission.LegacyPermissionSettings
import com.android.server.pm.permission.LegacyPermissionState
+import com.android.server.pm.permission.PermissionManagerServiceInterface
import com.android.server.pm.permission.PermissionManagerServiceInternal
import com.android.server.pm.pkg.AndroidPackage
import java.io.FileDescriptor
@@ -85,7 +85,7 @@ class PermissionService(
private lateinit var handler: Handler
private lateinit var onPermissionsChangeListeners: OnPermissionsChangeListeners
- private lateinit var permissionFlagsListener: OnPermissionFlagsChangedListener
+ private lateinit var onPermissionFlagsChangedListener: OnPermissionFlagsChangedListener
fun initialize() {
packageManagerInternal = LocalServices.getService(PackageManagerInternal::class.java)
@@ -100,8 +100,8 @@ class PermissionService(
handler = Handler(handlerThread.looper)
onPermissionsChangeListeners = OnPermissionsChangeListeners(FgThread.get().looper)
- permissionFlagsListener = OnPermissionFlagsChangedListener()
- policy.addOnPermissionFlagsChangedListener(permissionFlagsListener)
+ onPermissionFlagsChangedListener = OnPermissionFlagsChangedListener()
+ policy.addOnPermissionFlagsChangedListener(onPermissionFlagsChangedListener)
}
override fun getAllPermissionGroups(flags: Int): List<PermissionGroupInfo> {
@@ -234,9 +234,7 @@ class PermissionService(
with(policy) { getPermissionGroups()[permissionGroupName] }
} ?: return null
- if (!snapshot.isPackageVisibleToUid(
- permissionGroup.packageName, callingUid
- )) {
+ if (!snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) {
return null
}
}
@@ -674,9 +672,11 @@ class PermissionService(
* If you're doing surgery on app code/data, use [PackageFreezer] to guard your work against
* the app being relaunched.
*/
- private fun killUid(appId: Int, userId: Int, reason: String) {
+ private fun killUid(uid: Int, reason: String) {
val activityManager = ActivityManager.getService()
if (activityManager != null) {
+ val appId = UserHandle.getAppId(uid)
+ val userId = UserHandle.getUserId(uid)
val identity = Binder.clearCallingIdentity()
try {
activityManager.killUidForPermissionChange(appId, userId, reason)
@@ -729,7 +729,12 @@ class PermissionService(
* Callback invoked when interesting actions have been taken on a permission.
*/
private inner class OnPermissionFlagsChangedListener :
- UidPermissionPolicy.OnPermissionFlagsChangedListener {
+ UidPermissionPolicy.OnPermissionFlagsChangedListener() {
+ private val runtimePermissionChangedUids = IntSet()
+ // Mapping from UID to whether only notifications permissions are revoked.
+ private val runtimePermissionRevokedUids = IntBooleanMap()
+ private val gidsChangedUids = IntSet()
+
override fun onPermissionFlagsChanged(
appId: Int,
userId: Int,
@@ -741,37 +746,49 @@ class PermissionService(
val permission = service.getState {
with(policy) { getPermissions()[permissionName] }
} ?: return
+ val wasPermissionGranted = PermissionFlags.isPermissionGranted(oldFlags)
+ val isPermissionGranted = PermissionFlags.isPermissionGranted(newFlags)
+
+ if (permission.isRuntime) {
+ // Different from the old implementation, which notifies the listeners when the
+ // permission flags have changed for a non-runtime permission, now we no longer do
+ // that because permission flags are only for runtime permissions and the listeners
+ // aren't being notified of non-runtime permission grant state changes anyway.
+ runtimePermissionChangedUids += uid
+ if (wasPermissionGranted && !isPermissionGranted) {
+ runtimePermissionRevokedUids[uid] =
+ permissionName in NOTIFICATIONS_PERMISSIONS &&
+ runtimePermissionRevokedUids.getWithDefault(uid, true)
+ }
+ }
- val isPermissionGranted = !PermissionFlags.isPermissionGranted(oldFlags) &&
- PermissionFlags.isPermissionGranted(newFlags)
- val isPermissionRevoked = PermissionFlags.isPermissionGranted(oldFlags) &&
- !PermissionFlags.isPermissionGranted(newFlags)
+ if (permission.hasGids && !wasPermissionGranted && isPermissionGranted) {
+ gidsChangedUids += uid
+ }
+ }
- if (isPermissionGranted) {
- if (permission.isRuntime) {
- onPermissionsChangeListeners.onPermissionsChanged(uid)
- }
+ override fun onStateMutated() {
+ runtimePermissionChangedUids.forEachIndexed { _, uid ->
+ onPermissionsChangeListeners.onPermissionsChanged(uid)
+ }
+ runtimePermissionChangedUids.clear()
+
+ runtimePermissionRevokedUids.forEachIndexed {
+ _, uid, areOnlyNotificationsPermissionsRevoked ->
handler.post {
- if (permission.hasGids) {
- killUid(appId, userId, PermissionManager.KILL_APP_REASON_GIDS_CHANGED)
- }
- }
- } else if (isPermissionRevoked) {
- // TODO: STOPSHIP skip kill for revokePostNotificationPermissionWithoutKillForTest
- if (permission.isRuntime) {
- onPermissionsChangeListeners.onPermissionsChanged(uid)
- handler.post {
- if (!(permissionName == Manifest.permission.POST_NOTIFICATIONS &&
- isAppBackupAndRestoreRunning(uid))) {
- killUid(
- appId, userId, PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED
- )
- }
+ if (areOnlyNotificationsPermissionsRevoked &&
+ isAppBackupAndRestoreRunning(uid)) {
+ return@post
}
+ killUid(uid, PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED)
}
- } else if (oldFlags != newFlags) {
- onPermissionsChangeListeners.onPermissionsChanged(uid)
}
+ runtimePermissionRevokedUids.clear()
+
+ gidsChangedUids.forEachIndexed { _, uid ->
+ handler.post { killUid(uid, PermissionManager.KILL_APP_REASON_GIDS_CHANGED) }
+ }
+ gidsChangedUids.clear()
}
private fun isAppBackupAndRestoreRunning(uid: Int): Boolean {
@@ -780,13 +797,13 @@ class PermissionService(
return false
}
return try {
+ val contentResolver = context.contentResolver
val userId = UserHandle.getUserId(uid)
val isInSetup = Settings.Secure.getIntForUser(
- context.contentResolver, Settings.Secure.USER_SETUP_COMPLETE, userId
+ contentResolver, Settings.Secure.USER_SETUP_COMPLETE, userId
) == 0
val isInDeferredSetup = Settings.Secure.getIntForUser(
- context.contentResolver,
- Settings.Secure.USER_SETUP_PERSONALIZATION_STATE, userId
+ contentResolver, Settings.Secure.USER_SETUP_PERSONALIZATION_STATE, userId
) == Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED
isInSetup || isInDeferredSetup
} catch (e: Settings.SettingNotFoundException) {
@@ -809,18 +826,12 @@ class PermissionService(
}
private fun handleOnPermissionsChanged(uid: Int) {
- val count = listeners.beginBroadcast()
- try {
- for (i in 0 until count) {
- val callback = listeners.getBroadcastItem(i)
- try {
- callback.onPermissionsChanged(uid)
- } catch (e: RemoteException) {
- Log.e(LOG_TAG, "Permission listener is dead", e)
- }
+ listeners.broadcast { listener ->
+ try {
+ listener.onPermissionsChanged(uid)
+ } catch (e: RemoteException) {
+ Log.e(LOG_TAG, "Error when calling OnPermissionsChangeListener", e)
}
- } finally {
- listeners.finishBroadcast()
}
}
@@ -837,6 +848,7 @@ class PermissionService(
obtainMessage(MSG_ON_PERMISSIONS_CHANGED, uid, 0).sendToTarget()
}
}
+
companion object {
private const val MSG_ON_PERMISSIONS_CHANGED = 1
}
@@ -852,5 +864,9 @@ class PermissionService(
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
private val BACKGROUND_RATIONALE_CHANGE_ID = 147316723L
+
+ private val NOTIFICATIONS_PERMISSIONS = indexedSetOf(
+ Manifest.permission.POST_NOTIFICATIONS
+ )
}
}
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 e6636e4e78af..1c36bccfa9fd 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
@@ -74,6 +74,10 @@ class UidPermissionPolicy : SchemePolicy() {
setPermissionFlags(subject.appId, subject.userId, `object`.permissionName, decision)
}
+ override fun GetStateScope.onStateMutated() {
+ onPermissionFlagsChangedListeners.forEachIndexed { _, it -> it.onStateMutated() }
+ }
+
override fun MutateStateScope.onUserAdded(userId: Int) {
newState.systemState.packageStates.forEach { (_, packageState) ->
evaluateAllPermissionStatesForPackageAndUser(packageState, userId, null)
@@ -563,7 +567,7 @@ class UidPermissionPolicy : SchemePolicy() {
newFlags = newFlags andInv PermissionFlags.LEGACY_GRANTED
val wasGrantedByImplicit = newFlags.hasBits(PermissionFlags.IMPLICIT_GRANTED)
val isLeanBackNotificationsPermission = newState.systemState.isLeanback &&
- permissionName in NOTIFICATION_PERMISSIONS
+ permissionName in NOTIFICATIONS_PERMISSIONS
val isImplicitPermission = anyPackageInAppId(appId) {
permissionName in it.androidPackage!!.implicitPermissions
}
@@ -1108,18 +1112,35 @@ class UidPermissionPolicy : SchemePolicy() {
Manifest.permission.BLUETOOTH_SCAN
)
- private val NOTIFICATION_PERMISSIONS = indexedSetOf(
+ private val NOTIFICATIONS_PERMISSIONS = indexedSetOf(
Manifest.permission.POST_NOTIFICATIONS
)
}
- fun interface OnPermissionFlagsChangedListener {
- fun onPermissionFlagsChanged(
+ /**
+ * 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()
}
}
diff --git a/services/permission/java/com/android/server/permission/access/util/RemoteCallbackListExtensions.kt b/services/permission/java/com/android/server/permission/access/util/RemoteCallbackListExtensions.kt
new file mode 100644
index 000000000000..61f33e0d65d4
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/util/RemoteCallbackListExtensions.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.util
+
+import android.os.IInterface
+import android.os.RemoteCallbackList
+
+inline fun <T : IInterface> RemoteCallbackList<T>.broadcast(action: (T) -> Unit) {
+ val itemCount = beginBroadcast()
+ try {
+ for (i in 0 until itemCount) {
+ action(getBroadcastItem(i))
+ }
+ } finally {
+ finishBroadcast()
+ }
+}