Expose app op mode getter/setter/change listener directly on policy.

This allows the compat layer to call them with less ceremony of going
through URIs.

Also call UserState.requestWrite() upon changes.

It is unfortunate that we have to duplicate some code this way, but we
have to do it because Java/Kotlin generics doesn't allow primitive
ints and we want to avoid auto-boxing.

Also made mutateState()'s lambda crossinline so that the caller don't
accidentally stop the state mutation mid-way.

Bug: 182523293
Test: presubmit
Change-Id: I1f61c35352a9c614939976bf98ee2eeb9bb8d0e0
This commit is contained in:
Hai Zhang
2022-12-12 03:41:29 -08:00
parent 8ec0b69dba
commit e455c28fd0
6 changed files with 171 additions and 104 deletions

View File

@@ -140,7 +140,7 @@ class AccessCheckingService(context: Context) : SystemService(context) {
internal inline fun <T> getState(action: GetStateScope.() -> T): T =
GetStateScope(state).action()
internal inline fun mutateState(action: MutateStateScope.() -> Unit) {
internal inline fun mutateState(crossinline action: MutateStateScope.() -> Unit) {
synchronized(stateLock) {
val oldState = state
val newState = oldState.copy()

View File

@@ -241,10 +241,6 @@ class AccessPolicy private constructor(
}
abstract class SchemePolicy {
@Volatile
private var onDecisionChangedListeners = IndexedListSet<OnDecisionChangedListener>()
private val onDecisionChangedListenersLock = Any()
abstract val subjectScheme: String
abstract val objectScheme: String
@@ -257,30 +253,6 @@ abstract class SchemePolicy {
decision: Int
)
fun addOnDecisionChangedListener(listener: OnDecisionChangedListener) {
synchronized(onDecisionChangedListenersLock) {
onDecisionChangedListeners = onDecisionChangedListeners + listener
}
}
fun removeOnDecisionChangedListener(listener: OnDecisionChangedListener) {
synchronized(onDecisionChangedListenersLock) {
onDecisionChangedListeners = onDecisionChangedListeners - listener
}
}
protected fun notifyOnDecisionChangedListeners(
subject: AccessUri,
`object`: AccessUri,
oldDecision: Int,
newDecision: Int
) {
val listeners = onDecisionChangedListeners
listeners.forEachIndexed { _, it ->
it.onDecisionChanged(subject, `object`, oldDecision, newDecision)
}
}
open fun MutateStateScope.onUserAdded(userId: Int) {}
open fun MutateStateScope.onUserRemoved(userId: Int) {}
@@ -313,13 +285,4 @@ abstract class SchemePolicy {
open fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) {}
open fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) {}
fun interface OnDecisionChangedListener {
fun onDecisionChanged(
subject: AccessUri,
`object`: AccessUri,
oldDecision: Int,
newDecision: Int
)
}
}

View File

@@ -16,50 +16,17 @@
package com.android.server.permission.access.appop
import android.app.AppOpsManager
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
import com.android.server.permission.access.AccessUri
import com.android.server.permission.access.AppOpUri
import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.SchemePolicy
import com.android.server.permission.access.UserState
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
abstract class BaseAppOpPolicy(private val persistence: BaseAppOpPersistence) : SchemePolicy() {
override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
`object` as AppOpUri
return getModes(subject)
.getWithDefault(`object`.appOpName, opToDefaultMode(`object`.appOpName))
}
override fun MutateStateScope.setDecision(
subject: AccessUri,
`object`: AccessUri,
decision: Int
) {
`object` as AppOpUri
val modes = getOrCreateModes(subject)
val oldMode = modes.putWithDefault(`object`.appOpName, decision,
opToDefaultMode(`object`.appOpName))
if (modes.isEmpty()) {
removeModes(subject)
}
if (oldMode != decision) {
notifyOnDecisionChangedListeners(subject, `object`, oldMode, decision)
}
}
abstract fun GetStateScope.getModes(subject: AccessUri): IndexedMap<String, Int>?
abstract fun MutateStateScope.getOrCreateModes(subject: AccessUri): IndexedMap<String, Int>
abstract fun MutateStateScope.removeModes(subject: AccessUri)
// TODO need to check that [AppOpsManager.getSystemAlertWindowDefault] works; likely no issue
// since running in system process.
private fun opToDefaultMode(appOpName: String) = AppOpsManager.opToDefaultMode(appOpName)
abstract class BaseAppOpPolicy(
private val persistence: BaseAppOpPersistence
) : SchemePolicy() {
override val objectScheme: String
get() = AppOpUri.SCHEME
override fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) {
with(persistence) { this@parseUserState.parseUserState(userId, userState) }

View File

@@ -16,40 +16,101 @@
package com.android.server.permission.access.appop
import android.app.AppOpsManager
import com.android.server.permission.access.AccessUri
import com.android.server.permission.access.AppOpUri
import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.PackageUri
import com.android.server.permission.access.UserState
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) {
@Volatile
private var onAppOpModeChangedListeners = IndexedListSet<OnAppOpModeChangedListener>()
private val onAppOpModeChangedListenersLock = Any()
override val subjectScheme: String
get() = PackageUri.SCHEME
override val objectScheme: String
get() = AppOpUri.SCHEME
override fun GetStateScope.getModes(subject: AccessUri): IndexedMap<String, Int>? {
override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
subject as PackageUri
return state.userStates[subject.userId]?.packageAppOpModes?.get(subject.packageName)
`object` as AppOpUri
return getAppOpMode(subject.packageName, subject.userId, `object`.appOpName)
}
override fun MutateStateScope.getOrCreateModes(subject: AccessUri): IndexedMap<String, Int> {
override fun MutateStateScope.setDecision(
subject: AccessUri,
`object`: AccessUri,
decision: Int
) {
subject as PackageUri
return newState.userStates.getOrPut(subject.userId) { UserState() }
.packageAppOpModes.getOrPut(subject.packageName) { IndexedMap() }
}
override fun MutateStateScope.removeModes(subject: AccessUri) {
subject as PackageUri
newState.userStates[subject.userId]?.packageAppOpModes?.remove(subject.packageName)
`object` as AppOpUri
setAppOpMode(subject.packageName, subject.userId, `object`.appOpName, decision)
}
override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {
newState.userStates.forEachIndexed { _, _, userState ->
userState.packageAppOpModes -= packageName
userState.requestWrite()
// Skip notifying the change listeners since the package no longer exists.
}
}
fun MutateStateScope.removeAppOpModes(packageName: String, userId: Int): Boolean =
newState.userStates[userId].packageAppOpModes.remove(packageName) != null
fun GetStateScope.getAppOpMode(packageName: String, userId: Int, appOpName: String): Int =
state.userStates[userId].packageAppOpModes[packageName]
.getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName))
fun MutateStateScope.setAppOpMode(
packageName: String,
userId: Int,
appOpName: String,
mode: Int
): Boolean {
val userState = newState.userStates[userId]
val packageAppOpModes = userState.packageAppOpModes
var appOpModes = packageAppOpModes[packageName]
val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
val oldMode = appOpModes.getWithDefault(appOpName, defaultMode)
if (oldMode == mode) {
return false
}
if (appOpModes == null) {
appOpModes = IndexedMap()
packageAppOpModes[packageName] = appOpModes
}
appOpModes.putWithDefault(appOpName, mode, defaultMode)
if (appOpModes.isEmpty()) {
packageAppOpModes -= packageName
}
userState.requestWrite()
onAppOpModeChangedListeners.forEachIndexed { _, it ->
it.onAppOpModeChanged(packageName, userId, appOpName, oldMode, mode)
}
return true
}
fun addOnAppOpModeChangedListener(listener: OnAppOpModeChangedListener) {
synchronized(onAppOpModeChangedListenersLock) {
onAppOpModeChangedListeners = onAppOpModeChangedListeners + listener
}
}
fun removeOnAppOpModeChangedListener(listener: OnAppOpModeChangedListener) {
synchronized(onAppOpModeChangedListenersLock) {
onAppOpModeChangedListeners = onAppOpModeChangedListeners - listener
}
}
fun interface OnAppOpModeChangedListener {
fun onAppOpModeChanged(
packageName: String,
userId: Int,
appOpName: String,
oldMode: Int,
newMode: Int
)
}
}

View File

@@ -16,40 +16,104 @@
package com.android.server.permission.access.appop
import android.app.AppOpsManager
import com.android.server.permission.access.AccessUri
import com.android.server.permission.access.AppOpUri
import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.MutateStateScope
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
class UidAppOpPolicy : BaseAppOpPolicy(UidAppOpPersistence()) {
@Volatile
private var onAppOpModeChangedListeners = IndexedListSet<OnAppOpModeChangedListener>()
private val onAppOpModeChangedListenersLock = Any()
override val subjectScheme: String
get() = UidUri.SCHEME
override val objectScheme: String
get() = AppOpUri.SCHEME
override fun GetStateScope.getModes(subject: AccessUri): IndexedMap<String, Int>? {
override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
subject as UidUri
return state.userStates[subject.userId]?.uidAppOpModes?.get(subject.appId)
`object` as AppOpUri
return getAppOpMode(subject.appId, subject.userId, `object`.appOpName)
}
override fun MutateStateScope.getOrCreateModes(subject: AccessUri): IndexedMap<String, Int> {
override fun MutateStateScope.setDecision(
subject: AccessUri,
`object`: AccessUri,
decision: Int
) {
subject as UidUri
return newState.userStates.getOrPut(subject.userId) { UserState() }
.uidAppOpModes.getOrPut(subject.appId) { IndexedMap() }
}
override fun MutateStateScope.removeModes(subject: AccessUri) {
subject as UidUri
newState.userStates[subject.userId]?.uidAppOpModes?.remove(subject.appId)
`object` as AppOpUri
setAppOpMode(subject.appId, subject.userId, `object`.appOpName, decision)
}
override fun MutateStateScope.onAppIdRemoved(appId: Int) {
newState.userStates.forEachIndexed { _, _, userState ->
userState.uidAppOpModes -= appId
userState.requestWrite()
// Skip notifying the change listeners since the app ID no longer exists.
}
}
fun GetStateScope.getAppOpModes(appId: Int, userId: Int): IndexedMap<String, Int>? =
state.userStates[userId].uidAppOpModes[appId]
fun MutateStateScope.removeAppOpModes(appId: Int, userId: Int): Boolean =
newState.userStates[userId].uidAppOpModes.removeReturnOld(appId) != null
fun GetStateScope.getAppOpMode(appId: Int, userId: Int, appOpName: String): Int =
state.userStates[userId].uidAppOpModes[appId]
.getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName))
fun MutateStateScope.setAppOpMode(
appId: Int,
userId: Int,
appOpName: String,
mode: Int
): Boolean {
val userState = newState.userStates[userId]
val uidAppOpModes = userState.uidAppOpModes
var appOpModes = uidAppOpModes[appId]
val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
val oldMode = appOpModes.getWithDefault(appOpName, defaultMode)
if (oldMode == mode) {
return false
}
if (appOpModes == null) {
appOpModes = IndexedMap()
uidAppOpModes[appId] = appOpModes
}
appOpModes.putWithDefault(appOpName, mode, defaultMode)
if (appOpModes.isEmpty()) {
uidAppOpModes -= appId
}
userState.requestWrite()
onAppOpModeChangedListeners.forEachIndexed { _, it ->
it.onAppOpModeChanged(appId, userId, appOpName, oldMode, mode)
}
return true
}
fun addOnAppOpModeChangedListener(listener: OnAppOpModeChangedListener) {
synchronized(onAppOpModeChangedListenersLock) {
onAppOpModeChangedListeners = onAppOpModeChangedListeners + listener
}
}
fun removeOnAppOpModeChangedListener(listener: OnAppOpModeChangedListener) {
synchronized(onAppOpModeChangedListenersLock) {
onAppOpModeChangedListeners = onAppOpModeChangedListeners - listener
}
}
fun interface OnAppOpModeChangedListener {
fun onAppOpModeChanged(
appId: Int,
userId: Int,
appOpName: String,
oldMode: Int,
newMode: Int
)
}
}

View File

@@ -120,6 +120,18 @@ inline fun <T> IntMap<T>.putWithDefault(key: Int, value: T, defaultValue: T): T
}
}
// SparseArray.removeReturnOld() is @hide, so a backup once we move to APIs.
fun <T> IntMap<T>.removeReturnOld(key: Int): T? {
val index = indexOfKey(key)
return if (index >= 0) {
val oldValue = valueAt(index)
removeAt(index)
oldValue
} else {
null
}
}
inline fun <T> IntMap<T>.removeAllIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
var isChanged = false
for (index in lastIndex downTo 0) {