Merge "Move new unified permission subsystem into platform."

This commit is contained in:
Hai Zhang
2022-11-23 07:00:44 +00:00
committed by Android (Google) Code Review
36 changed files with 4066 additions and 8 deletions

View File

@@ -0,0 +1,85 @@
/*
* 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
import com.android.internal.annotations.Keep
import com.android.server.permission.access.external.PackageState
@Keep
class AccessCheckingService {
@Volatile
private lateinit var state: AccessState
private val stateLock = Any()
private val policy = AccessPolicy()
private val persistence = AccessPersistence(policy)
fun init() {
val state = AccessState()
state.systemState.userIds.apply {
// TODO: Get and add all user IDs.
// TODO: Maybe get and add all packages?
}
persistence.read(state)
this.state = state
}
fun getDecision(subject: AccessUri, `object`: AccessUri): Int =
policy.getDecision(subject, `object`, state)
fun setDecision(subject: AccessUri, `object`: AccessUri, decision: Int) {
mutateState { oldState, newState ->
policy.setDecision(subject, `object`, decision, oldState, newState)
}
}
fun onUserAdded(userId: Int) {
mutateState { oldState, newState ->
policy.onUserAdded(userId, oldState, newState)
}
}
fun onUserRemoved(userId: Int) {
mutateState { oldState, newState ->
policy.onUserRemoved(userId, oldState, newState)
}
}
fun onPackageAdded(packageState: PackageState) {
mutateState { oldState, newState ->
policy.onPackageAdded(packageState, oldState, newState)
}
}
fun onPackageRemoved(packageState: PackageState) {
mutateState { oldState, newState ->
policy.onPackageRemoved(packageState, oldState, newState)
}
}
// TODO: Replace (oldState, newState) with Kotlin context receiver once it's stabilized.
private inline fun mutateState(action: (oldState: AccessState, newState: AccessState) -> Unit) {
synchronized(stateLock) {
val oldState = state
val newState = oldState.copy()
action(oldState, newState)
persistence.write(newState)
state = newState
}
}
}

View File

@@ -0,0 +1,114 @@
/*
* 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
import android.util.AtomicFile
import android.util.Log
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.util.PermissionApex
import com.android.server.permission.access.util.parseBinaryXml
import com.android.server.permission.access.util.read
import com.android.server.permission.access.util.serializeBinaryXml
import com.android.server.permission.access.util.writeInlined
import java.io.File
import java.io.FileNotFoundException
class AccessPersistence(
private val policy: AccessPolicy
) {
fun read(state: AccessState) {
readSystemState(state.systemState)
val userStates = state.userStates
state.systemState.userIds.forEachIndexed { _, userId ->
readUserState(userId, userStates[userId])
}
}
private fun readSystemState(systemState: SystemState) {
systemFile.parse {
// This is the canonical way to call an extension function in a different class.
// TODO(b/259469752): Use context receiver for this when it becomes stable.
with(policy) { this@parse.parseSystemState(systemState) }
}
}
private fun readUserState(userId: Int, userState: UserState) {
getUserFile(userId).parse {
with(policy) { this@parse.parseUserState(userId, userState) }
}
}
private inline fun File.parse(block: BinaryXmlPullParser.() -> Unit) {
try {
AtomicFile(this).read { it.parseBinaryXml(block) }
} catch (e: FileNotFoundException) {
Log.i(LOG_TAG, "$this not found")
} catch (e: Exception) {
throw IllegalStateException("Failed to read $this", e)
}
}
fun write(state: AccessState) {
writeState(state.systemState, ::writeSystemState)
state.userStates.forEachIndexed { _, userId, userState ->
writeState(userState) { writeUserState(userId, it) }
}
}
private inline fun <T : WritableState> writeState(state: T, write: (T) -> Unit) {
when (val writeMode = state.writeMode) {
WriteMode.NONE -> {}
WriteMode.SYNC -> write(state)
WriteMode.ASYNC -> TODO()
else -> error(writeMode)
}
}
private fun writeSystemState(systemState: SystemState) {
systemFile.serialize {
with(policy) { this@serialize.serializeSystemState(systemState) }
}
}
private fun writeUserState(userId: Int, userState: UserState) {
getUserFile(userId).serialize {
with(policy) { this@serialize.serializeUserState(userId, userState) }
}
}
private inline fun File.serialize(block: BinaryXmlSerializer.() -> Unit) {
try {
AtomicFile(this).writeInlined { it.serializeBinaryXml(block) }
} catch (e: Exception) {
Log.e(LOG_TAG, "Failed to serialize $this", e)
}
}
private val systemFile: File
get() = File(PermissionApex.systemDataDirectory, FILE_NAME)
private fun getUserFile(userId: Int): File =
File(PermissionApex.getUserDataDirectory(userId), FILE_NAME)
companion object {
private val LOG_TAG = AccessPersistence::class.java.simpleName
private const val FILE_NAME = "access.abx"
}
}

View File

@@ -0,0 +1,255 @@
/*
* 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
import android.util.Log
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
import com.android.server.permission.access.appop.PackageAppOpPolicy
import com.android.server.permission.access.appop.UidAppOpPolicy
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.external.PackageState
import com.android.server.permission.access.permission.UidPermissionPolicy
import com.android.server.permission.access.util.forEachTag
import com.android.server.permission.access.util.tag
import com.android.server.permission.access.util.tagName
class AccessPolicy private constructor(
private val schemePolicies: IndexedMap<String, IndexedMap<String, SchemePolicy>>
) {
constructor() : this(
IndexedMap<String, IndexedMap<String, SchemePolicy>>().apply {
fun addPolicy(policy: SchemePolicy) =
getOrPut(policy.subjectScheme) { IndexedMap() }.put(policy.objectScheme, policy)
addPolicy(UidPermissionPolicy())
addPolicy(UidAppOpPolicy())
addPolicy(PackageAppOpPolicy())
}
)
fun getDecision(subject: AccessUri, `object`: AccessUri, state: AccessState): Int =
getSchemePolicy(subject, `object`).getDecision(subject, `object`, state)
fun setDecision(
subject: AccessUri,
`object`: AccessUri,
decision: Int,
oldState: AccessState,
newState: AccessState
) {
getSchemePolicy(subject, `object`)
.setDecision(subject, `object`, decision, oldState, newState)
}
private fun getSchemePolicy(subject: AccessUri, `object`: AccessUri): SchemePolicy =
checkNotNull(schemePolicies[subject.scheme]?.get(`object`.scheme)) {
"Scheme policy for subject=$subject object=$`object` does not exist"
}
fun onUserAdded(userId: Int, oldState: AccessState, newState: AccessState) {
newState.systemState.userIds += userId
newState.userStates[userId] = UserState()
forEachSchemePolicy { it.onUserAdded(userId, oldState, newState) }
}
fun onUserRemoved(userId: Int, oldState: AccessState, newState: AccessState) {
newState.systemState.userIds -= userId
newState.userStates -= userId
forEachSchemePolicy { it.onUserRemoved(userId, oldState, newState) }
}
fun onPackageAdded(packageState: PackageState, oldState: AccessState, newState: AccessState) {
var isAppIdAdded = false
newState.systemState.apply {
packageStates[packageState.packageName] = packageState
appIds.getOrPut(packageState.appId) {
isAppIdAdded = true
IndexedListSet()
}.add(packageState.packageName)
}
if (isAppIdAdded) {
forEachSchemePolicy { it.onAppIdAdded(packageState.appId, oldState, newState) }
}
forEachSchemePolicy { it.onPackageAdded(packageState, oldState, newState) }
}
fun onPackageRemoved(packageState: PackageState, oldState: AccessState, newState: AccessState) {
var isAppIdRemoved = false
newState.systemState.apply {
packageStates -= packageState.packageName
appIds.apply appIds@{
this[packageState.appId]?.apply {
this -= packageState.packageName
if (isEmpty()) {
this@appIds -= packageState.appId
isAppIdRemoved = true
}
}
}
}
forEachSchemePolicy { it.onPackageRemoved(packageState, oldState, newState) }
if (isAppIdRemoved) {
forEachSchemePolicy { it.onAppIdRemoved(packageState.appId, oldState, newState) }
}
}
fun BinaryXmlPullParser.parseSystemState(systemState: SystemState) {
forEachTag {
when (tagName) {
TAG_ACCESS -> {
forEachTag {
forEachSchemePolicy {
with(it) { this@parseSystemState.parseSystemState(systemState) }
}
}
}
else -> Log.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing system state")
}
}
}
fun BinaryXmlSerializer.serializeSystemState(systemState: SystemState) {
tag(TAG_ACCESS) {
forEachSchemePolicy {
with(it) { this@serializeSystemState.serializeSystemState(systemState) }
}
}
}
fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) {
forEachTag {
when (tagName) {
TAG_ACCESS -> {
forEachTag {
forEachSchemePolicy {
with(it) { this@parseUserState.parseUserState(userId, userState) }
}
}
}
else -> {
Log.w(
LOG_TAG,
"Ignoring unknown tag $tagName when parsing user state for user $userId"
)
}
}
}
}
fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) {
tag(TAG_ACCESS) {
forEachSchemePolicy {
with(it) { this@serializeUserState.serializeUserState(userId, userState) }
}
}
}
private inline fun forEachSchemePolicy(action: (SchemePolicy) -> Unit) {
schemePolicies.forEachValueIndexed { _, objectSchemePolicies ->
objectSchemePolicies.forEachValueIndexed { _, schemePolicy ->
action(schemePolicy)
}
}
}
companion object {
private val LOG_TAG = AccessPolicy::class.java.simpleName
private const val TAG_ACCESS = "access"
}
}
abstract class SchemePolicy {
@Volatile
private var onDecisionChangedListeners = IndexedListSet<OnDecisionChangedListener>()
private val onDecisionChangedListenersLock = Any()
abstract val subjectScheme: String
abstract val objectScheme: String
abstract fun getDecision(subject: AccessUri, `object`: AccessUri, state: AccessState): Int
abstract fun setDecision(
subject: AccessUri,
`object`: AccessUri,
decision: Int,
oldState: AccessState,
newState: AccessState
)
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 onUserAdded(userId: Int, oldState: AccessState, newState: AccessState) {}
open fun onUserRemoved(userId: Int, oldState: AccessState, newState: AccessState) {}
open fun onAppIdAdded(appId: Int, oldState: AccessState, newState: AccessState) {}
open fun onAppIdRemoved(appId: Int, oldState: AccessState, newState: AccessState) {}
open fun onPackageAdded(
packageState: PackageState,
oldState: AccessState,
newState: AccessState
) {}
open fun onPackageRemoved(
packageState: PackageState,
oldState: AccessState,
newState: AccessState
) {}
open fun BinaryXmlPullParser.parseSystemState(systemState: SystemState) {}
open fun BinaryXmlSerializer.serializeSystemState(systemState: SystemState) {}
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

@@ -0,0 +1,126 @@
/*
* 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
import android.content.pm.PermissionGroupInfo
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.data.Permission
import com.android.server.permission.access.external.PackageState
class AccessState private constructor(
val systemState: SystemState,
val userStates: IntMap<UserState>
) {
constructor() : this(SystemState(), IntMap())
fun copy(): AccessState = AccessState(systemState.copy(), userStates.copy { it.copy() })
}
class SystemState private constructor(
val userIds: IntSet,
val packageStates: IndexedMap<String, PackageState>,
val disabledSystemPackageStates: IndexedMap<String, PackageState>,
val appIds: IntMap<IndexedListSet<String>>,
// A map of KnownPackagesInt to a set of known package names
val knownPackages: IntMap<IndexedListSet<String>>,
// A map of userId to packageName
val deviceAndProfileOwners: IntMap<String>,
// A map of packageName to (A map of oem permission name to whether it's granted)
val oemPermissions: IndexedMap<String, IndexedMap<String, Boolean>>,
val privilegedPermissionAllowlistSourcePackageNames: IndexedListSet<String>,
// A map of packageName to a set of vendor priv app permission names
val vendorPrivAppPermissions: Map<String, Set<String>>,
val productPrivAppPermissions: Map<String, Set<String>>,
val systemExtPrivAppPermissions: Map<String, Set<String>>,
val privAppPermissions: Map<String, Set<String>>,
val apexPrivAppPermissions: Map<String, Map<String, Set<String>>>,
val vendorPrivAppDenyPermissions: Map<String, Set<String>>,
val productPrivAppDenyPermissions: Map<String, Set<String>>,
val systemExtPrivAppDenyPermissions: Map<String, Set<String>>,
val apexPrivAppDenyPermissions: Map<String, Map<String, Set<String>>>,
val privAppDenyPermissions: Map<String, Set<String>>,
val implicitToSourcePermissions: Map<String, Set<String>>,
val permissionGroups: IndexedMap<String, PermissionGroupInfo>,
val permissionTrees: IndexedMap<String, Permission>,
val permissions: IndexedMap<String, Permission>
) : WritableState() {
constructor() : this(
IntSet(), IndexedMap(), IndexedMap(), IntMap(), IntMap(), IntMap(), IndexedMap(),
IndexedListSet(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(),
IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(),
IndexedMap(), IndexedMap(), IndexedMap()
)
fun copy(): SystemState =
SystemState(
userIds.copy(),
packageStates.copy { it },
disabledSystemPackageStates.copy { it },
appIds.copy { it.copy() },
knownPackages.copy { it.copy() },
deviceAndProfileOwners.copy { it },
oemPermissions.copy { it.copy { it } },
privilegedPermissionAllowlistSourcePackageNames.copy(),
vendorPrivAppPermissions,
productPrivAppPermissions,
systemExtPrivAppPermissions,
privAppPermissions,
apexPrivAppPermissions,
vendorPrivAppDenyPermissions,
productPrivAppDenyPermissions,
systemExtPrivAppDenyPermissions,
apexPrivAppDenyPermissions,
privAppDenyPermissions,
implicitToSourcePermissions,
permissionGroups.copy { it },
permissionTrees.copy { it },
permissions.copy { it }
)
}
class UserState private constructor(
// A map of (appId to a map of (permissionName to permissionFlags))
val permissionFlags: IntMap<IndexedMap<String, Int>>,
val uidAppOpModes: IntMap<IndexedMap<String, Int>>,
val packageAppOpModes: IndexedMap<String, IndexedMap<String, Int>>
) : 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 } })
}
object WriteMode {
const val NONE = 0
const val SYNC = 1
const val ASYNC = 2
}
abstract class WritableState {
var writeMode: Int = WriteMode.NONE
private set
fun requestWrite(sync: Boolean = false) {
if (sync) {
writeMode = WriteMode.SYNC
} else {
if (writeMode != WriteMode.SYNC) {
writeMode = WriteMode.ASYNC
}
}
}
}

View File

@@ -0,0 +1,83 @@
/*
* 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
import com.android.server.permission.access.external.UserHandle
import com.android.server.permission.access.external.UserHandleCompat
sealed class AccessUri(
val scheme: String
) {
override fun equals(other: Any?): Boolean {
throw NotImplementedError()
}
override fun hashCode(): Int {
throw NotImplementedError()
}
override fun toString(): String {
throw NotImplementedError()
}
}
data class AppOpUri(
val appOpName: String
) : AccessUri(SCHEME) {
override fun toString(): String = "$scheme:///$appOpName"
companion object {
const val SCHEME = "app-op"
}
}
data class PackageUri(
val packageName: String,
val userId: Int
) : AccessUri(SCHEME) {
override fun toString(): String = "$scheme:///$packageName/$userId"
companion object {
const val SCHEME = "package"
}
}
data class PermissionUri(
val permissionName: String
) : AccessUri(SCHEME) {
override fun toString(): String = "$scheme:///$permissionName"
companion object {
const val SCHEME = "permission"
}
}
data class UidUri(
val uid: Int
) : AccessUri(SCHEME) {
val userId: Int
get() = UserHandleCompat.getUserId(uid)
val appId: Int
get() = UserHandle.getAppId(uid)
override fun toString(): String = "$scheme:///$uid"
companion object {
const val SCHEME = "uid"
}
}

View File

@@ -0,0 +1,27 @@
/*
* 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.appop
import android.app.AppOpsManager
object AppOpModes {
const val MODE_ALLOWED = AppOpsManager.MODE_ALLOWED
const val MODE_IGNORED = AppOpsManager.MODE_IGNORED
const val MODE_ERRORED = AppOpsManager.MODE_ERRORED
const val MODE_DEFAULT = AppOpsManager.MODE_DEFAULT
const val MODE_FOREGROUND = AppOpsManager.MODE_FOREGROUND
}

View File

@@ -0,0 +1,73 @@
/*
* 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.appop
import android.util.Log
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
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.attributeInt
import com.android.server.permission.access.util.attributeInterned
import com.android.server.permission.access.util.forEachTag
import com.android.server.permission.access.util.getAttributeIntOrThrow
import com.android.server.permission.access.util.getAttributeValueOrThrow
import com.android.server.permission.access.util.tag
import com.android.server.permission.access.util.tagName
abstract class BaseAppOpPersistence {
abstract fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState)
abstract fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState)
protected fun BinaryXmlPullParser.parseAppOps(appOpModes: IndexedMap<String, Int>) {
forEachTag {
when (tagName) {
TAG_APP_OP -> parseAppOp(appOpModes)
else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state")
}
}
}
private fun BinaryXmlPullParser.parseAppOp(appOpModes: IndexedMap<String, Int>) {
val name = getAttributeValueOrThrow(ATTR_NAME).intern()
val mode = getAttributeIntOrThrow(ATTR_MODE)
appOpModes[name] = mode
}
protected fun BinaryXmlSerializer.serializeAppOps(appOpModes: IndexedMap<String, Int>) {
appOpModes.forEachIndexed { _, name, mode ->
serializeAppOp(name, mode)
}
}
private fun BinaryXmlSerializer.serializeAppOp(name: String, mode: Int) {
tag(TAG_APP_OP) {
attributeInterned(ATTR_NAME, name)
attributeInt(ATTR_MODE, mode)
}
}
companion object {
private val LOG_TAG = BaseAppOpPersistence::class.java.simpleName
private const val TAG_APP_OP = "app-op"
private const val ATTR_NAME = "name"
private const val ATTR_MODE = "mode"
}
}

View File

@@ -0,0 +1,72 @@
/*
* 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.appop
import android.app.AppOpsManager
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.AppOpUri
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 getDecision(subject: AccessUri, `object`: AccessUri, state: AccessState): Int {
`object` as AppOpUri
return getModes(subject, state)
.getWithDefault(`object`.appOpName, opToDefaultMode(`object`.appOpName))
}
override fun setDecision(
subject: AccessUri,
`object`: AccessUri,
decision: Int,
oldState: AccessState,
newState: AccessState
) {
`object` as AppOpUri
val modes = getOrCreateModes(subject, newState)
val oldMode = modes.putWithDefault(`object`.appOpName, decision,
opToDefaultMode(`object`.appOpName))
if (modes.isEmpty()) {
removeModes(subject, newState)
}
if (oldMode != decision) {
notifyOnDecisionChangedListeners(subject, `object`, oldMode, decision)
}
}
abstract fun getModes(subject: AccessUri, state: AccessState): IndexedMap<String, Int>?
abstract fun getOrCreateModes(subject: AccessUri, state: AccessState): IndexedMap<String, Int>
abstract fun removeModes(subject: AccessUri, state: AccessState)
// TODO need to check that [AppOpsManager.getSystemAlertWindowDefault] works; likely no issue
// since running in system process.
private fun opToDefaultMode(appOpName: String) = AppOpsManager.opToDefaultMode(appOpName)
override fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) {
with(persistence) { this@parseUserState.parseUserState(userId, userState) }
}
override fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) {
with(persistence) { this@serializeUserState.serializeUserState(userId, userState) }
}
}

View File

@@ -0,0 +1,84 @@
/*
* 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.appop
import android.util.Log
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
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.attributeInterned
import com.android.server.permission.access.util.forEachTag
import com.android.server.permission.access.util.getAttributeValueOrThrow
import com.android.server.permission.access.util.tag
import com.android.server.permission.access.util.tagName
class PackageAppOpPersistence : BaseAppOpPersistence() {
override fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) {
when (tagName) {
TAG_PACKAGE_APP_OPS -> parsePackageAppOps(userState)
else -> {}
}
}
private fun BinaryXmlPullParser.parsePackageAppOps(userState: UserState) {
forEachTag {
when (tagName) {
TAG_PACKAGE -> parsePackage(userState)
else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state")
}
}
}
private fun BinaryXmlPullParser.parsePackage(userState: UserState) {
val packageName = getAttributeValueOrThrow(ATTR_NAME).intern()
val appOpModes = IndexedMap<String, Int>()
userState.packageAppOpModes[packageName] = appOpModes
parseAppOps(appOpModes)
}
override fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) {
serializePackageAppOps(userState)
}
private fun BinaryXmlSerializer.serializePackageAppOps(userState: UserState) {
tag(TAG_PACKAGE_APP_OPS) {
userState.packageAppOpModes.forEachIndexed { _, packageName, appOpModes ->
serializePackage(packageName, appOpModes)
}
}
}
private fun BinaryXmlSerializer.serializePackage(
packageName: String,
appOpModes: IndexedMap<String, Int>
) {
tag(TAG_PACKAGE) {
attributeInterned(ATTR_NAME, packageName)
serializeAppOps(appOpModes)
}
}
companion object {
private val LOG_TAG = PackageAppOpPersistence::class.java.simpleName
private const val TAG_PACKAGE_APP_OPS = "package-app-ops"
private const val TAG_PACKAGE = "package"
private const val ATTR_NAME = "name"
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.appop
import com.android.server.permission.access.AccessState
import com.android.server.permission.access.AccessUri
import com.android.server.permission.access.AppOpUri
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
import com.android.server.permission.access.external.PackageState
class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) {
override val subjectScheme: String
get() = PackageUri.SCHEME
override val objectScheme: String
get() = AppOpUri.SCHEME
override fun getModes(subject: AccessUri, state: AccessState): IndexedMap<String, Int>? {
subject as PackageUri
return state.userStates[subject.userId]?.packageAppOpModes?.get(subject.packageName)
}
override fun getOrCreateModes(subject: AccessUri, state: AccessState): IndexedMap<String, Int> {
subject as PackageUri
return state.userStates.getOrPut(subject.userId) { UserState() }
.packageAppOpModes.getOrPut(subject.packageName) { IndexedMap() }
}
override fun removeModes(subject: AccessUri, state: AccessState) {
subject as PackageUri
state.userStates[subject.userId]?.packageAppOpModes?.remove(subject.packageName)
}
override fun onPackageRemoved(
packageState: PackageState,
oldState: AccessState,
newState: AccessState
) {
newState.userStates.forEachIndexed { _, _, userState ->
userState.packageAppOpModes -= packageState.packageName
}
}
}

View File

@@ -0,0 +1,81 @@
/*
* 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.appop
import android.util.Log
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
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.attributeInt
import com.android.server.permission.access.util.forEachTag
import com.android.server.permission.access.util.getAttributeIntOrThrow
import com.android.server.permission.access.util.tag
import com.android.server.permission.access.util.tagName
class UidAppOpPersistence : BaseAppOpPersistence() {
override fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) {
when (tagName) {
TAG_UID_APP_OPS -> parseUidAppOps(userState)
else -> {}
}
}
private fun BinaryXmlPullParser.parseUidAppOps(userState: UserState) {
forEachTag {
when (tagName) {
TAG_UID -> parseUid(userState)
else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state")
}
}
}
private fun BinaryXmlPullParser.parseUid(userState: UserState) {
val uid = getAttributeIntOrThrow(ATTR_UID)
val appOpModes = IndexedMap<String, Int>()
userState.uidAppOpModes[uid] = appOpModes
parseAppOps(appOpModes)
}
override fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) {
serializeUidAppOps(userState)
}
private fun BinaryXmlSerializer.serializeUidAppOps(userState: UserState) {
tag(TAG_UID_APP_OPS) {
userState.uidAppOpModes.forEachIndexed { _, uid, appOpModes ->
serializeUid(uid, appOpModes)
}
}
}
private fun BinaryXmlSerializer.serializeUid(uid: Int, appOpModes: IndexedMap<String, Int>) {
tag(TAG_UID) {
attributeInt(ATTR_UID, uid)
serializeAppOps(appOpModes)
}
}
companion object {
private val LOG_TAG = UidAppOpPersistence::class.java.simpleName
private const val TAG_UID_APP_OPS = "uid-app-ops"
private const val TAG_UID = "uid"
private const val ATTR_UID = "uid"
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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.appop
import com.android.server.permission.access.AccessState
import com.android.server.permission.access.AccessUri
import com.android.server.permission.access.AppOpUri
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()) {
override val subjectScheme: String
get() = UidUri.SCHEME
override val objectScheme: String
get() = AppOpUri.SCHEME
override fun getModes(subject: AccessUri, state: AccessState): IndexedMap<String, Int>? {
subject as UidUri
return state.userStates[subject.userId]?.uidAppOpModes?.get(subject.appId)
}
override fun getOrCreateModes(subject: AccessUri, state: AccessState): IndexedMap<String, Int> {
subject as UidUri
return state.userStates.getOrPut(subject.userId) { UserState() }
.uidAppOpModes.getOrPut(subject.appId) { IndexedMap() }
}
override fun removeModes(subject: AccessUri, state: AccessState) {
subject as UidUri
state.userStates[subject.userId]?.uidAppOpModes?.remove(subject.appId)
}
override fun onAppIdRemoved(appId: Int, oldState: AccessState, newState: AccessState) {
newState.userStates.forEachIndexed { _, _, userState ->
userState.uidAppOpModes -= appId
}
}
}

View File

@@ -0,0 +1,89 @@
/*
* 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.collection
typealias IndexedList<T> = ArrayList<T>
inline fun <T> IndexedList<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
for (index in 0 until size) {
if (!predicate(index, this[index])) {
return false
}
}
return true
}
inline fun <T> IndexedList<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
for (index in 0 until size) {
if (predicate(index, this[index])) {
return true
}
}
return false
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T> IndexedList<T>.copy(): IndexedList<T> = IndexedList(this)
inline fun <T> IndexedList<T>.forEachIndexed(action: (Int, T) -> Unit) {
for (index in indices) {
action(index, this[index])
}
}
@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> IndexedList<T>.minus(element: T): IndexedList<T> =
copy().apply { this -= element }
@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> IndexedList<T>.minusAssign(element: T) {
remove(element)
}
inline fun <T> IndexedList<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
for (index in 0 until size) {
if (predicate(index, this[index])) {
return false
}
}
return true
}
@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> IndexedList<T>.plus(element: T): IndexedList<T> =
copy().apply { this += element }
@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> IndexedList<T>.plusAssign(element: T) {
add(element)
}
inline fun <T> IndexedList<T>.removeAllIndexed(predicate: (Int, T) -> Boolean) {
for (index in lastIndex downTo 0) {
if (predicate(index, this[index])) {
removeAt(index)
}
}
}
inline fun <T> IndexedList<T>.retainAllIndexed(predicate: (Int, T) -> Boolean) {
for (index in lastIndex downTo 0) {
if (!predicate(index, this[index])) {
removeAt(index)
}
}
}

View File

@@ -0,0 +1,140 @@
/*
* 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.collection
class IndexedListSet<T> private constructor(
private val list: ArrayList<T>
) : MutableSet<T> {
constructor() : this(ArrayList())
override val size: Int
get() = list.size
override fun contains(element: T): Boolean = list.contains(element)
override fun isEmpty(): Boolean = list.isEmpty()
override fun iterator(): MutableIterator<T> = list.iterator()
override fun containsAll(elements: Collection<T>): Boolean {
throw NotImplementedError()
}
fun elementAt(index: Int): T = list[index]
fun indexOf(element: T): Int = list.indexOf(element)
override fun add(element: T): Boolean =
if (list.contains(element)) {
false
} else {
list.add(element)
true
}
override fun remove(element: T): Boolean = list.remove(element)
override fun clear() {
list.clear()
}
override fun addAll(elements: Collection<T>): Boolean {
throw NotImplementedError()
}
override fun removeAll(elements: Collection<T>): Boolean {
throw NotImplementedError()
}
override fun retainAll(elements: Collection<T>): Boolean {
throw NotImplementedError()
}
fun removeAt(index: Int): T? = list.removeAt(index)
fun copy(): IndexedListSet<T> = IndexedListSet(ArrayList(list))
}
inline fun <T> IndexedListSet<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
for (index in 0 until size) {
if (!predicate(index, elementAt(index))) {
return false
}
}
return true
}
inline fun <T> IndexedListSet<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
for (index in 0 until size) {
if (predicate(index, elementAt(index))) {
return true
}
}
return false
}
inline fun <T> IndexedListSet<T>.forEachIndexed(action: (Int, T) -> Unit) {
for (index in indices) {
action(index, elementAt(index))
}
}
inline val <T> IndexedListSet<T>.lastIndex: Int
get() = size - 1
@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> IndexedListSet<T>.minus(element: T): IndexedListSet<T> =
copy().apply { this -= element }
@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> IndexedListSet<T>.minusAssign(element: T) {
remove(element)
}
inline fun <T> IndexedListSet<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
for (index in 0 until size) {
if (predicate(index, elementAt(index))) {
return false
}
}
return true
}
@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> IndexedListSet<T>.plus(element: T): IndexedListSet<T> =
copy().apply { this += element }
@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> IndexedListSet<T>.plusAssign(element: T) {
add(element)
}
inline fun <T> IndexedListSet<T>.removeAllIndexed(predicate: (Int, T) -> Boolean) {
for (index in lastIndex downTo 0) {
if (predicate(index, elementAt(index))) {
removeAt(index)
}
}
}
inline fun <T> IndexedListSet<T>.retainAllIndexed(predicate: (Int, T) -> Boolean) {
for (index in lastIndex downTo 0) {
if (!predicate(index, elementAt(index))) {
removeAt(index)
}
}
}

View File

@@ -0,0 +1,133 @@
/*
* 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.collection
import android.util.ArrayMap
typealias IndexedMap<K, V> = ArrayMap<K, V>
inline fun <K, V> IndexedMap<K, V>.allIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
for (index in 0 until size) {
if (!predicate(index, keyAt(index), valueAt(index))) {
return false
}
}
return true
}
inline fun <K, V> IndexedMap<K, V>.anyIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
for (index in 0 until size) {
if (predicate(index, keyAt(index), valueAt(index))) {
return true
}
}
return false
}
inline fun <K, V> IndexedMap<K, V>.copy(copyValue: (V) -> V): IndexedMap<K, V> =
IndexedMap(this).apply {
forEachValueIndexed { index, value ->
setValueAt(index, copyValue(value))
}
}
inline fun <K, V, R> IndexedMap<K, V>.firstNotNullOfOrNullIndexed(transform: (Int, K, V) -> R): R? {
for (index in 0 until size) {
transform(index, keyAt(index), valueAt(index))?.let { return it }
}
return null
}
inline fun <K, V> IndexedMap<K, V>.forEachIndexed(action: (Int, K, V) -> Unit) {
for (index in 0 until size) {
action(index, keyAt(index), valueAt(index))
}
}
inline fun <K, V> IndexedMap<K, V>.forEachKeyIndexed(action: (Int, K) -> Unit) {
for (index in 0 until size) {
action(index, keyAt(index))
}
}
inline fun <K, V> IndexedMap<K, V>.forEachValueIndexed(action: (Int, V) -> Unit) {
for (index in 0 until size) {
action(index, valueAt(index))
}
}
inline fun <K, V> IndexedMap<K, V>.getOrPut(key: K, defaultValue: () -> V): V {
get(key)?.let { return it }
return defaultValue().also { put(key, it) }
}
@Suppress("NOTHING_TO_INLINE")
inline fun <K, V> IndexedMap<K, V>?.getWithDefault(key: K, defaultValue: V): V {
this ?: return defaultValue
val index = indexOfKey(key)
return if (index >= 0) valueAt(index) else defaultValue
}
inline val <K, V> IndexedMap<K, V>.lastIndex: Int
get() = size - 1
@Suppress("NOTHING_TO_INLINE")
inline operator fun <K, V> IndexedMap<K, V>.minusAssign(key: K) {
remove(key)
}
@Suppress("NOTHING_TO_INLINE")
inline fun <K, V> IndexedMap<K, V>.putWithDefault(key: K, value: V, defaultValue: V): V {
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
}
}
inline fun <K, V> IndexedMap<K, V>.removeAllIndexed(predicate: (Int, K, V) -> Boolean) {
for (index in lastIndex downTo 0) {
if (predicate(index, keyAt(index), valueAt(index))) {
removeAt(index)
}
}
}
inline fun <K, V> IndexedMap<K, V>.retainAllIndexed(predicate: (Int, K, V) -> Boolean) {
for (index in lastIndex downTo 0) {
if (!predicate(index, keyAt(index), valueAt(index))) {
removeAt(index)
}
}
}
@Suppress("NOTHING_TO_INLINE")
inline operator fun <K, V> IndexedMap<K, V>.set(key: K, value: V) {
put(key, value)
}

View File

@@ -0,0 +1,100 @@
/*
* 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.collection
import android.util.ArraySet
typealias IndexedSet<T> = ArraySet<T>
inline fun <T> IndexedSet<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
for (index in 0 until size) {
if (!predicate(index, elementAt(index))) {
return false
}
}
return true
}
inline fun <T> IndexedSet<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
for (index in 0 until size) {
if (predicate(index, elementAt(index))) {
return true
}
}
return false
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T> IndexedSet<T>.copy(): IndexedSet<T> = IndexedSet(this)
@Suppress("NOTHING_TO_INLINE")
inline fun <T> IndexedSet<T>.elementAt(index: Int): T = valueAt(index)
inline fun <T> IndexedSet<T>.forEachIndexed(action: (Int, T) -> Unit) {
for (index in indices) {
action(index, elementAt(index))
}
}
inline val <T> IndexedSet<T>.lastIndex: Int
get() = size - 1
@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> IndexedSet<T>.minus(element: T): IndexedSet<T> =
copy().apply { this -= element }
@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> IndexedSet<T>.minusAssign(element: T) {
remove(element)
}
inline fun <T> IndexedSet<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
for (index in 0 until size) {
if (predicate(index, elementAt(index))) {
return false
}
}
return true
}
@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> IndexedSet<T>.plus(element: T): IndexedSet<T> =
copy().apply { this += element }
@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> IndexedSet<T>.plusAssign(element: T) {
add(element)
}
inline fun <T> IndexedSet<T>.removeAllIndexed(predicate: (Int, T) -> Boolean) {
for (index in lastIndex downTo 0) {
if (predicate(index, elementAt(index))) {
removeAt(index)
}
}
}
inline fun <T> IndexedSet<T>.retainAllIndexed(predicate: (Int, T) -> Boolean) {
for (index in lastIndex downTo 0) {
if (!predicate(index, elementAt(index))) {
removeAt(index)
}
}
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T> indexedSetOf(vararg elements: T): IndexedSet<T> = IndexedSet(elements.asList())

View File

@@ -0,0 +1,140 @@
/*
* 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.collection
import android.util.SparseArray
typealias IntMap<T> = SparseArray<T>
inline fun <T> IntMap<T>.allIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
for (index in 0 until size) {
if (!predicate(index, keyAt(index), valueAt(index))) {
return false
}
}
return true
}
inline fun <T> IntMap<T>.anyIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
for (index in 0 until size) {
if (predicate(index, keyAt(index), valueAt(index))) {
return true
}
}
return false
}
inline fun <T> IntMap<T>.copy(copyValue: (T) -> T): IntMap<T> =
this.clone().apply {
forEachValueIndexed { index, value ->
setValueAt(index, copyValue(value))
}
}
inline fun <T, R> IntMap<T>.firstNotNullOfOrNullIndexed(transform: (Int, Int, T) -> R): R? {
for (index in 0 until size) {
transform(index, keyAt(index), valueAt(index))?.let { return it }
}
return null
}
inline fun <T> IntMap<T>.forEachIndexed(action: (Int, Int, T) -> Unit) {
for (index in 0 until size) {
action(index, keyAt(index), valueAt(index))
}
}
inline fun <T> IntMap<T>.forEachKeyIndexed(action: (Int, Int) -> Unit) {
for (index in 0 until size) {
action(index, keyAt(index))
}
}
inline fun <T> IntMap<T>.forEachValueIndexed(action: (Int, T) -> Unit) {
for (index in 0 until size) {
action(index, valueAt(index))
}
}
inline fun <T> IntMap<T>.getOrPut(key: Int, defaultValue: () -> T): T {
get(key)?.let { return it }
return defaultValue().also { put(key, it) }
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T> IntMap<T>?.getWithDefault(key: Int, defaultValue: T): T {
this ?: return defaultValue
val index = indexOfKey(key)
return if (index >= 0) valueAt(index) else defaultValue
}
inline val <T> IntMap<T>.lastIndex: Int
get() = size - 1
@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> IntMap<T>.minusAssign(key: Int) {
remove(key)
}
inline fun <T> IntMap<T>.noneIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
for (index in 0 until size) {
if (predicate(index, keyAt(index), valueAt(index))) {
return false
}
}
return true
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T> IntMap<T>.putWithDefault(key: Int, value: T, defaultValue: T): T {
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
}
}
inline fun <T> IntMap<T>.removeAllIndexed(predicate: (Int, Int, T) -> Boolean) {
for (index in lastIndex downTo 0) {
if (predicate(index, keyAt(index), valueAt(index))) {
removeAt(index)
}
}
}
inline fun <T> IntMap<T>.retainAllIndexed(predicate: (Int, Int, T) -> Boolean) {
for (index in lastIndex downTo 0) {
if (!predicate(index, keyAt(index), valueAt(index))) {
removeAt(index)
}
}
}
inline val <T> IntMap<T>.size: Int
get() = size()

View File

@@ -0,0 +1,120 @@
/*
* 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.collection
import android.util.SparseBooleanArray
class IntSet private constructor(
private val array: SparseBooleanArray
) {
constructor() : this(SparseBooleanArray())
val size: Int
get() = array.size()
operator fun contains(element: Int): Boolean = array[element]
fun elementAt(index: Int): Int = array.keyAt(index)
fun indexOf(element: Int): Int = array.indexOfKey(element)
fun add(element: Int) {
array.put(element, true)
}
fun remove(element: Int) {
array.delete(element)
}
fun clear() {
array.clear()
}
fun removeAt(index: Int) {
array.removeAt(index)
}
fun copy(): IntSet = IntSet(array.clone())
}
inline fun IntSet.allIndexed(predicate: (Int, Int) -> Boolean): Boolean {
for (index in 0 until size) {
if (!predicate(index, elementAt(index))) {
return false
}
}
return true
}
inline fun IntSet.anyIndexed(predicate: (Int, Int) -> Boolean): Boolean {
for (index in 0 until size) {
if (predicate(index, elementAt(index))) {
return true
}
}
return false
}
inline fun IntSet.forEachIndexed(action: (Int, Int) -> Unit) {
for (index in 0 until size) {
action(index, elementAt(index))
}
}
inline val IntSet.lastIndex: Int
get() = size - 1
@Suppress("NOTHING_TO_INLINE")
inline operator fun IntSet.minus(element: Int): IntSet = copy().apply { this -= element }
@Suppress("NOTHING_TO_INLINE")
inline operator fun IntSet.minusAssign(element: Int) {
remove(element)
}
inline fun IntSet.noneIndexed(predicate: (Int, Int) -> Boolean): Boolean {
for (index in 0 until size) {
if (predicate(index, elementAt(index))) {
return false
}
}
return true
}
@Suppress("NOTHING_TO_INLINE")
inline operator fun IntSet.plus(element: Int): IntSet = copy().apply { this += element }
@Suppress("NOTHING_TO_INLINE")
inline operator fun IntSet.plusAssign(element: Int) {
add(element)
}
inline fun IntSet.removeAllIndexed(predicate: (Int, Int) -> Boolean) {
for (index in lastIndex downTo 0) {
if (predicate(index, elementAt(index))) {
removeAt(index)
}
}
}
inline fun IntSet.retainAllIndexed(predicate: (Int, Int) -> Boolean) {
for (index in lastIndex downTo 0) {
if (!predicate(index, elementAt(index))) {
removeAt(index)
}
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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
inline fun <T> List<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
for (index in 0 until size) {
if (!predicate(index, this[index])) {
return false
}
}
return true
}
inline fun <T> List<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
for (index in 0 until size) {
if (predicate(index, this[index])) {
return true
}
}
return false
}
inline fun <T> List<T>.forEachIndexed(action: (Int, T) -> Unit) {
for (index in indices) {
action(index, this[index])
}
}
inline fun <T> List<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
for (index in 0 until size) {
if (predicate(index, this[index])) {
return false
}
}
return true
}
inline fun <T> MutableList<T>.removeAllIndexed(predicate: (Int, T) -> Boolean) {
for (index in lastIndex downTo 0) {
if (predicate(index, this[index])) {
removeAt(index)
}
}
}
inline fun <T> MutableList<T>.retainAllIndexed(predicate: (Int, T) -> Boolean) {
for (index in lastIndex downTo 0) {
if (!predicate(index, this[index])) {
removeAt(index)
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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.data
import com.android.server.permission.access.external.AndroidPackage
class Package(
private val androidPackage: AndroidPackage
) {
val name: String
get() = androidPackage.packageName
val adoptPermissions: List<String>
get() = androidPackage.adoptPermissions
val appId: Int
get() = androidPackage.appId
val requestedPermissions: List<String>
get() = androidPackage.requestedPermissions
override fun equals(other: Any?): Boolean {
throw NotImplementedError()
}
override fun hashCode(): Int {
throw NotImplementedError()
}
}

View File

@@ -0,0 +1,130 @@
/*
* 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.data
import android.content.pm.PermissionInfo
import com.android.server.permission.access.util.hasBits
data class Permission(
val permissionInfo: PermissionInfo,
val isReconciled: Boolean,
val type: Int,
val appId: Int
) {
inline val name: String
get() = permissionInfo.name
inline val packageName: String
get() = permissionInfo.packageName
inline val isDynamic: Boolean
get() = type == TYPE_DYNAMIC
inline val isNormal: Boolean
get() = permissionInfo.protection == PermissionInfo.PROTECTION_NORMAL
inline val isRuntime: Boolean
get() = permissionInfo.protection == PermissionInfo.PROTECTION_DANGEROUS
inline val isSoftRestricted: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.FLAG_SOFT_RESTRICTED)
inline val isHardRestricted: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.FLAG_HARD_RESTRICTED)
inline val isSignature: Boolean
get() = permissionInfo.protection == PermissionInfo.PROTECTION_SIGNATURE
inline val isInternal: Boolean
get() = permissionInfo.protection == PermissionInfo.PROTECTION_INTERNAL
inline val isDevelopment: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_DEVELOPMENT)
inline val isInstaller: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_INSTALLER)
inline val isOem: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_OEM)
inline val isPre23: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PRE23)
inline val isPreInstalled: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PREINSTALLED)
inline val isPrivileged: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PRIVILEGED)
inline val isSetup: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_SETUP)
inline val isVerifier: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_VERIFIER)
inline val isVendorPrivileged: Boolean
get() = permissionInfo.protectionFlags
.hasBits(PROTECTION_FLAG_VENDOR_PRIVILEGED)
inline val isSystemTextClassifier: Boolean
get() = permissionInfo.protectionFlags
.hasBits(PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER)
inline val isConfigurator: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_CONFIGURATOR)
inline val isIncidentReportApprover: Boolean
get() = permissionInfo.protectionFlags
.hasBits(PermissionInfo.PROTECTION_FLAG_INCIDENT_REPORT_APPROVER)
inline val isAppPredictor: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR)
inline val isCompanion: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_COMPANION)
inline val isRetailDemo: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO)
inline val isRecents: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RECENTS)
inline val isRole: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_ROLE)
inline val isKnownSigner: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER)
inline val protectionLevel: Int
@Suppress("DEPRECATION")
get() = permissionInfo.protectionLevel
inline val knownCerts: Set<String>
get() = permissionInfo.knownCerts
companion object {
// The permission is defined in an application manifest.
const val TYPE_MANIFEST = 0
// The permission is defined in a system config.
const val TYPE_CONFIG = 1
// The permission is defined dynamically.
const val TYPE_DYNAMIC = 2
// TODO: PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED is a testApi
const val PROTECTION_FLAG_VENDOR_PRIVILEGED = 0x8000
}
}

View File

@@ -0,0 +1,29 @@
/*
* 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.external
class CompatibilityPermissionInfo {
companion object {
val COMPAT_PERMS = arrayOf(CompatibilityPermissionInfo())
}
val name: String
get() = throw NotImplementedError()
val sdkVersion: Int
get() = throw NotImplementedError()
}

View File

@@ -0,0 +1,34 @@
/*
* 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.external
class KnownPackages {
companion object {
const val PACKAGE_SYSTEM = 0
const val PACKAGE_SETUP_WIZARD = 1
const val PACKAGE_INSTALLER = 2
const val PACKAGE_VERIFIER = 4
const val PACKAGE_SYSTEM_TEXT_CLASSIFIER = 6
const val PACKAGE_PERMISSION_CONTROLLER = 7
const val PACKAGE_CONFIGURATOR = 10
const val PACKAGE_INCIDENT_REPORT_APPROVER = 11
const val PACKAGE_APP_PREDICTOR = 12
const val PACKAGE_COMPANION = 15
const val PACKAGE_RETAIL_DEMO = 16
const val PACKAGE_RECENTS = 17
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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.external
import android.content.pm.PermissionGroupInfo
import android.content.pm.PermissionInfo
object PackageInfoUtils {
fun generatePermissionInfo(parsedPermission: ParsedPermission, flags: Long): PermissionInfo {
throw NotImplementedError()
}
fun generatePermissionGroupInfo(
parsedPermissionGroup: ParsedPermissionGroup,
flags: Long
): PermissionGroupInfo {
throw NotImplementedError()
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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.external
import android.util.SparseArray
interface PackageState {
val androidPackage: AndroidPackage?
val appId: Int
val isSystem: Boolean
val isUpdatedSystemApp: Boolean
val packageName: String
val userStates: SparseArray<PackageUserState>
val hasSharedUser: Boolean
val sharedUserAppId: Int
val signingDetails: SigningDetails
}
interface AndroidPackage {
val packageName: String
val apexModuleName: String?
val appId: Int
val isPrivileged: Boolean
val isOem: Boolean
val isVendor: Boolean
val isProduct: Boolean
val isSystemExt: Boolean
val targetSdkVersion: Int
val adoptPermissions: List<String>
val permissions: List<ParsedPermission>
val permissionGroups: List<ParsedPermissionGroup>
val requestedPermissions: List<String>
val implicitPermissions: List<String>
}
interface ParsedPermission {
val name: String
val isTree: Boolean
val packageName: String
val isSignature: Boolean
val protectionLevel: Int
}
interface ParsedPermissionGroup {
val name: String
val packageName: String
}
interface PackageUserState {
val isInstantApp: Boolean
}

View File

@@ -0,0 +1,24 @@
/*
* 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.external
class RoSystemProperties {
companion object {
const val CONTROL_PRIVAPP_PERMISSIONS_DISABLE = false
const val CONTROL_PRIVAPP_PERMISSIONS_ENFORCE = false
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.external
object SigningDetails {
fun hasCommonSignerWithCapability(otherDetails: SigningDetails, flags: Int): Boolean {
throw NotImplementedError()
}
fun hasAncestorOrSelf(oldDetails: SigningDetails): Boolean {
throw NotImplementedError()
}
fun checkCapability(oldDetails: SigningDetails, flags: Int): Boolean {
throw NotImplementedError()
}
fun hasAncestorOrSelfWithDigest(certDigests: Set<String>): Boolean {
throw NotImplementedError()
}
class CertCapabilities {
companion object {
/** grant SIGNATURE permissions to pkgs with this cert */
var PERMISSION = 4
}
}
}

View File

@@ -0,0 +1,31 @@
/*
* 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.external
interface UserHandle {
companion object {
fun getAppId(uid: Int): Int {
throw NotImplementedError()
}
}
}
object UserHandleCompat {
fun getUserId(uid: Int): Int {
throw NotImplementedError()
}
}

View File

@@ -0,0 +1,32 @@
/*
* 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
object PermissionFlags {
const val INSTALL_GRANTED = 1 shl 0
const val INSTALL_REVOKED = 1 shl 1
const val PROTECTION_GRANTED = 1 shl 2
const val ROLE_GRANTED = 1 shl 3
// For permissions that are granted in other ways,
// ex: via an API or implicit permissions that inherit from granted install permissions
const val OTHER_GRANTED = 1 shl 4
// For the permissions that are implicit for the package
const val IMPLICIT = 1 shl 5
const val MASK_GRANTED = INSTALL_GRANTED or PROTECTION_GRANTED or OTHER_GRANTED or ROLE_GRANTED
const val MASK_RUNTIME = OTHER_GRANTED or IMPLICIT
}

View File

@@ -0,0 +1,139 @@
/*
* 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.permission
import android.content.pm.PermissionInfo
import android.util.Log
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
import com.android.server.permission.access.SystemState
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.data.Permission
import com.android.server.permission.access.util.attribute
import com.android.server.permission.access.util.attributeInt
import com.android.server.permission.access.util.attributeIntHex
import com.android.server.permission.access.util.attributeIntHexWithDefault
import com.android.server.permission.access.util.attributeInterned
import com.android.server.permission.access.util.forEachTag
import com.android.server.permission.access.util.getAttributeIntHexOrDefault
import com.android.server.permission.access.util.getAttributeIntHexOrThrow
import com.android.server.permission.access.util.getAttributeIntOrThrow
import com.android.server.permission.access.util.getAttributeValue
import com.android.server.permission.access.util.getAttributeValueOrThrow
import com.android.server.permission.access.util.tag
import com.android.server.permission.access.util.tagName
class UidPermissionPersistence {
fun BinaryXmlPullParser.parseSystemState(systemState: SystemState) {
when (tagName) {
TAG_PERMISSION_TREES -> parsePermissions(systemState.permissionTrees)
TAG_PERMISSIONS -> parsePermissions(systemState.permissions)
else -> {}
}
}
private fun BinaryXmlPullParser.parsePermissions(permissions: IndexedMap<String, Permission>) {
forEachTag {
when (val tagName = tagName) {
TAG_PERMISSION -> parsePermission(permissions)
else -> Log.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing permissions")
}
}
}
private fun BinaryXmlPullParser.parsePermission(permissions: IndexedMap<String, Permission>) {
val name = getAttributeValueOrThrow(ATTR_NAME).intern()
@Suppress("DEPRECATION")
val permissionInfo = PermissionInfo().apply {
this.name = name
packageName = getAttributeValueOrThrow(ATTR_PACKAGE_NAME).intern()
protectionLevel = getAttributeIntHexOrThrow(ATTR_PROTECTION_LEVEL)
}
val type = getAttributeIntOrThrow(ATTR_TYPE)
when (type) {
Permission.TYPE_MANIFEST -> {}
Permission.TYPE_CONFIG -> {
Log.w(LOG_TAG, "Ignoring unexpected config permission $name")
return
}
Permission.TYPE_DYNAMIC -> {
permissionInfo.apply {
icon = getAttributeIntHexOrDefault(ATTR_ICON, 0)
nonLocalizedLabel = getAttributeValue(ATTR_LABEL)
}
}
else -> {
Log.w(LOG_TAG, "Ignoring permission $name with unknown type $type")
return
}
}
val permission = Permission(permissionInfo, false, type, 0)
permissions[name] = permission
}
fun BinaryXmlSerializer.serializeSystemState(systemState: SystemState) {
serializePermissions(TAG_PERMISSION_TREES, systemState.permissionTrees)
serializePermissions(TAG_PERMISSIONS, systemState.permissions)
}
private fun BinaryXmlSerializer.serializePermissions(
tagName: String,
permissions: IndexedMap<String, Permission>
) {
tag(tagName) {
permissions.forEachValueIndexed { _, it -> serializePermission(it) }
}
}
private fun BinaryXmlSerializer.serializePermission(permission: Permission) {
val type = permission.type
when (type) {
Permission.TYPE_MANIFEST, Permission.TYPE_DYNAMIC -> {}
Permission.TYPE_CONFIG -> return
else -> {
Log.w(LOG_TAG, "Skipping serializing permission $name with unknown type $type")
return
}
}
tag(TAG_PERMISSION) {
attributeInterned(ATTR_NAME, permission.name)
attributeInterned(ATTR_PACKAGE_NAME, permission.packageName)
attributeIntHex(ATTR_PROTECTION_LEVEL, permission.protectionLevel)
attributeInt(ATTR_TYPE, type)
if (type == Permission.TYPE_DYNAMIC) {
val permissionInfo = permission.permissionInfo
attributeIntHexWithDefault(ATTR_ICON, permissionInfo.icon, 0)
permissionInfo.nonLocalizedLabel?.toString()?.let { attribute(ATTR_LABEL, it) }
}
}
}
companion object {
private val LOG_TAG = UidPermissionPersistence::class.java.simpleName
private const val TAG_PERMISSION = "permission"
private const val TAG_PERMISSION_TREES = "permission-trees"
private const val TAG_PERMISSIONS = "permissions"
private const val ATTR_ICON = "icon"
private const val ATTR_LABEL = "label"
private const val ATTR_NAME = "name"
private const val ATTR_PACKAGE_NAME = "packageName"
private const val ATTR_PROTECTION_LEVEL = "protectionLevel"
private const val ATTR_TYPE = "type"
}
}

View File

@@ -0,0 +1,909 @@
/*
* 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.PermissionInfo
import android.os.Build
import android.os.UserHandle
import android.util.Log
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.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.data.Permission
import com.android.server.permission.access.external.AndroidPackage
import com.android.server.permission.access.external.CompatibilityPermissionInfo
import com.android.server.permission.access.external.KnownPackages
import com.android.server.permission.access.external.PackageInfoUtils
import com.android.server.permission.access.external.PackageState
import com.android.server.permission.access.external.RoSystemProperties
import com.android.server.permission.access.external.SigningDetails
import com.android.server.permission.access.util.hasAnyBit
import com.android.server.permission.access.util.hasBits
class UidPermissionPolicy : SchemePolicy() {
private val persistence = UidPermissionPersistence()
override val subjectScheme: String
get() = UidUri.SCHEME
override val objectScheme: String
get() = PermissionUri.SCHEME
override fun getDecision(subject: AccessUri, `object`: AccessUri, state: AccessState): Int {
subject as UidUri
`object` as PermissionUri
return state.userStates[subject.userId]?.permissionFlags?.get(subject.appId)
?.get(`object`.permissionName) ?: 0
}
override fun setDecision(
subject: AccessUri,
`object`: AccessUri,
decision: Int,
oldState: AccessState,
newState: AccessState
) {
subject as UidUri
`object` as PermissionUri
val uidFlags = newState.userStates.getOrPut(subject.userId) { UserState() }
.permissionFlags.getOrPut(subject.appId) { IndexedMap() }
uidFlags[`object`.permissionName] = decision
}
override fun onUserAdded(userId: Int, oldState: AccessState, newState: AccessState) {
newState.systemState.packageStates.forEachValueIndexed { _, packageState ->
evaluateAllPermissionStatesForPackageAndUser(
packageState, null, userId, oldState, newState
)
grantImplicitPermissions(packageState, userId, oldState, newState)
}
}
override fun onAppIdAdded(appId: Int, oldState: AccessState, newState: AccessState) {
newState.userStates.forEachIndexed { _, _, userState ->
userState.permissionFlags.getOrPut(appId) { IndexedMap() }
}
}
override fun onAppIdRemoved(appId: Int, oldState: AccessState, newState: AccessState) {
newState.userStates.forEachIndexed { _, _, userState -> userState.permissionFlags -= appId }
}
override fun onPackageAdded(
packageState: PackageState,
oldState: AccessState,
newState: AccessState
) {
val changedPermissionNames = IndexedSet<String>()
adoptPermissions(packageState, changedPermissionNames, newState)
addPermissionGroups(packageState, newState)
addPermissions(packageState, changedPermissionNames, newState)
// TODO: revokeStoragePermissionsIfScopeExpandedInternal()
trimPermissions(packageState.packageName, newState)
changedPermissionNames.forEachIndexed { _, it ->
evaluatePermissionStateForAllPackages(it, packageState, oldState, newState)
}
evaluateAllPermissionStatesForPackage(packageState, packageState, oldState, newState)
newState.systemState.userIds.forEachIndexed { _, it ->
grantImplicitPermissions(packageState, it, oldState, newState)
}
// TODO: add trimPermissionStates() here for removing the permission states that are
// no longer requested. (equivalent to revokeUnusedSharedUserPermissionsLocked())
}
private fun adoptPermissions(
packageState: PackageState,
changedPermissionNames: IndexedSet<String>,
newState: AccessState
) {
val `package` = packageState.androidPackage!!
`package`.adoptPermissions.forEachIndexed { _, originalPackageName ->
val packageName = `package`.packageName
if (!canAdoptPermissions(packageName, originalPackageName, newState)) {
return@forEachIndexed
}
newState.systemState.permissions.let { permissions ->
permissions.forEachIndexed { i, permissionName, oldPermission ->
if (oldPermission.packageName != originalPackageName) {
return@forEachIndexed
}
@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(i, newPermission)
}
}
}
}
private fun canAdoptPermissions(
packageName: String,
originalPackageName: String,
newState: AccessState
): 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 addPermissionGroups(packageState: PackageState, newState: AccessState) {
// 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) {
Log.w(
LOG_TAG, "Ignoring permission group $permissionGroupName declared in package" +
" ${newPermissionGroup.packageName}: already declared in another package" +
" ${oldPermissionGroup.packageName}"
)
return@forEachIndexed
}
newState.systemState.permissionGroups[permissionGroupName] = newPermissionGroup
}
}
private fun addPermissions(
packageState: PackageState,
changedPermissionNames: IndexedSet<String>,
newState: AccessState
) {
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 permissionName = newPermissionInfo.name
val oldPermission = if (parsedPermission.isTree) {
newState.systemState.permissionTrees[permissionName]
} else {
newState.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 = getPermissionTree(permissionName, newState)
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.
Permission(newPermissionInfo, true, Permission.TYPE_CONFIG, packageState.appId)
} else if (newState.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
}
}
// TODO: Notify re-evaluation of this permission.
Permission(
newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId
)
} else {
Log.w(
LOG_TAG, "Ignoring permission $permissionName declared in system package" +
" $newPackageName: already declared in another system package" +
" $oldPackageName")
return@forEachIndexed
}
} else {
// TODO: STOPSHIP: Clear permission state on type or group change.
// 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.
Permission(newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId)
}
changedPermissionNames += permissionName
if (parsedPermission.isTree) {
newState.systemState.permissionTrees[permissionName] = newPermission
} else {
newState.systemState.permissions[permissionName] = newPermission
}
}
}
private fun trimPermissions(
packageName: String,
newState: AccessState,
) {
val packageState = newState.systemState.packageStates[packageName]
val androidPackage = packageState?.androidPackage
if (packageState != null && androidPackage == null) {
return
}
newState.systemState.permissionTrees.removeAllIndexed {
_, permissionTreeName, permissionTree ->
permissionTree.packageName == packageName && (
packageState == null || androidPackage!!.permissions.noneIndexed { _, it ->
it.isTree && it.name == permissionTreeName
}
)
}
newState.systemState.permissions.removeAllIndexed { i, permissionName, permission ->
val updatedPermission = updatePermissionIfDynamic(permission, newState)
newState.systemState.permissions.setValueAt(i, updatedPermission)
if (updatedPermission.packageName == packageName && (
packageState == null || androidPackage!!.permissions.noneIndexed { _, it ->
!it.isTree && it.name == permissionName
}
)) {
if (!isPermissionDeclaredByDisabledSystemPackage(permission, newState)) {
newState.userStates.forEachIndexed { _, userId, userState ->
userState.permissionFlags.forEachKeyIndexed { _, appId ->
setPermissionFlags(
appId, permissionName, getPermissionFlags(
appId, permissionName, userId, newState
) and PermissionFlags.INSTALL_REVOKED, userId, newState
)
}
}
}
true
} else {
false
}
}
}
private fun isPermissionDeclaredByDisabledSystemPackage(
permission: Permission,
newState: AccessState
): 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 updatePermissionIfDynamic(
permission: Permission,
newState: AccessState
): Permission {
if (!permission.isDynamic) {
return permission
}
val permissionTree = getPermissionTree(permission.name, newState) ?: return permission
@Suppress("DEPRECATION")
return permission.copy(
permissionInfo = PermissionInfo(permission.permissionInfo).apply {
packageName = permissionTree.packageName
}, appId = permissionTree.appId, isReconciled = true
)
}
private fun getPermissionTree(permissionName: String, newState: AccessState): Permission? =
newState.systemState.permissionTrees.firstNotNullOfOrNullIndexed {
_, permissionTreeName, permissionTree ->
if (permissionName.startsWith(permissionTreeName) &&
permissionName.length > permissionTreeName.length &&
permissionName[permissionTreeName.length] == '.') {
permissionTree
} else {
null
}
}
private fun evaluatePermissionStateForAllPackages(
permissionName: String,
installedPackageState: PackageState?,
oldState: AccessState,
newState: AccessState
) {
newState.systemState.userIds.forEachIndexed { _, userId ->
oldState.userStates[userId]?.permissionFlags?.forEachIndexed {
_, appId, permissionFlags ->
if (permissionName in permissionFlags) {
evaluatePermissionState(
appId, permissionName, installedPackageState, userId, oldState, newState
)
}
}
}
}
private fun evaluateAllPermissionStatesForPackage(
packageState: PackageState,
installedPackageState: PackageState?,
oldState: AccessState,
newState: AccessState
) {
newState.systemState.userIds.forEachIndexed { _, userId ->
evaluateAllPermissionStatesForPackageAndUser(
packageState, installedPackageState, userId, oldState, newState
)
}
}
private fun evaluateAllPermissionStatesForPackageAndUser(
packageState: PackageState,
installedPackageState: PackageState?,
userId: Int,
oldState: AccessState,
newState: AccessState
) {
packageState.androidPackage?.requestedPermissions?.forEachIndexed { _, it ->
evaluatePermissionState(
packageState.appId, it, installedPackageState, userId, oldState, newState
)
}
}
private fun evaluatePermissionState(
appId: Int,
permissionName: String,
installedPackageState: PackageState?,
userId: Int,
oldState: AccessState,
newState: AccessState
) {
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] ?: return
val oldFlags = getPermissionFlags(appId, permissionName, userId, newState)
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, newState) {
it.isSystem && permissionName in it.androidPackage!!.requestedPermissions
}
val isCompatibilityPermission = anyPackageInAppId(appId, newState) {
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, permissionName, newFlags, userId, newState)
}
} 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, newState) {
checkPrivilegedPermissionAllowlist(it, permission, newState)
}
)
val shouldGrantBySignature = permission.isSignature && (
anyPackageInAppId(appId, newState) {
shouldGrantPermissionBySignature(it, permission, newState)
}
)
val shouldGrantByProtectionFlags = anyPackageInAppId(appId, newState) {
shouldGrantPermissionByProtectionFlags(it, permission, newState)
}
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.OTHER_GRANTED)
}
if (permission.isRole) {
newFlags = newFlags or (oldFlags and PermissionFlags.ROLE_GRANTED)
}
setPermissionFlags(appId, permissionName, newFlags, userId, newState)
} else if (permission.isRuntime) {
// TODO: add runtime permissions
} else {
Log.e(LOG_TAG, "Unknown protection level ${permission.protectionLevel}" +
"for permission ${permission.name} while evaluating permission state" +
"for appId $appId and userId $userId")
}
// TODO: revokePermissionsNoLongerImplicitLocked() for runtime permissions
}
private fun grantImplicitPermissions(
packageState: PackageState,
userId: Int,
oldState: AccessState,
newState: AccessState
) {
val appId = packageState.appId
val androidPackage = packageState.androidPackage ?: return
androidPackage.implicitPermissions.forEachIndexed implicitPermissions@ {
_, implicitPermissionName ->
val implicitPermission = newState.systemState.permissions[implicitPermissionName]
checkNotNull(implicitPermission) {
"Unknown implicit permission $implicitPermissionName in split permissions"
}
if (!implicitPermission.isRuntime) {
return@implicitPermissions
}
val isNewPermission = getPermissionFlags(
appId, implicitPermissionName, userId, oldState
) == 0
if (!isNewPermission) {
return@implicitPermissions
}
val sourcePermissions = newState.systemState
.implicitToSourcePermissions[implicitPermissionName] ?: return@implicitPermissions
var newFlags = 0
sourcePermissions.forEachIndexed sourcePermissions@ { _, sourcePermissionName ->
val sourcePermission = newState.systemState.permissions[sourcePermissionName]
checkNotNull(sourcePermission) {
"Unknown source permission $sourcePermissionName in split permissions"
}
val sourceFlags = getPermissionFlags(appId, sourcePermissionName, userId, newState)
val isSourceGranted = sourceFlags.hasAnyBit(PermissionFlags.MASK_GRANTED)
val isNewGranted = newFlags.hasAnyBit(PermissionFlags.MASK_GRANTED)
val isGrantingNewFromRevoke = isSourceGranted && !isNewGranted
if (isSourceGranted == isNewGranted || isGrantingNewFromRevoke) {
if (isGrantingNewFromRevoke) {
newFlags = 0
}
newFlags = newFlags or (sourceFlags and PermissionFlags.MASK_RUNTIME)
if (!sourcePermission.isRuntime && isSourceGranted) {
newFlags = newFlags or PermissionFlags.OTHER_GRANTED
}
}
}
newFlags = newFlags or PermissionFlags.IMPLICIT
setPermissionFlags(appId, implicitPermissionName, newFlags, userId, newState)
}
}
private fun getPermissionFlags(
appId: Int,
permissionName: String,
userId: Int,
state: AccessState
): Int = state.userStates[userId].permissionFlags[appId].getWithDefault(permissionName, 0)
private fun setPermissionFlags(
appId: Int,
permissionName: String,
flags: Int,
userId: Int,
newState: AccessState
) {
newState.userStates[userId].permissionFlags[appId]!!
.putWithDefault(permissionName, flags, 0)
}
private fun isCompatibilityPermissionForPackage(
androidPackage: AndroidPackage,
permissionName: String
): Boolean {
for (info: CompatibilityPermissionInfo in CompatibilityPermissionInfo.COMPAT_PERMS) {
if (info.name == permissionName && androidPackage.targetSdkVersion < info.sdkVersion) {
Log.i(
LOG_TAG, "Auto-granting $permissionName to old package" +
" ${androidPackage.packageName}"
)
return true
}
}
return false
}
private fun shouldGrantPermissionBySignature(
packageState: PackageState,
permission: Permission,
newState: AccessState
): Boolean {
// check if the package is allow 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 sourceSigningDetails = newState.systemState
.packageStates[permission.packageName]?.signingDetails
val platformSigningDetails = newState.systemState
.packageStates[PLATFORM_PACKAGE_NAME]!!.signingDetails
return sourceSigningDetails?.hasCommonSignerWithCapability(packageState.signingDetails,
SigningDetails.CertCapabilities.PERMISSION) == true ||
packageState.signingDetails.hasAncestorOrSelf(platformSigningDetails) ||
platformSigningDetails.checkCapability(packageState.signingDetails,
SigningDetails.CertCapabilities.PERMISSION)
}
private fun checkPrivilegedPermissionAllowlist(
packageState: PackageState,
permission: Permission,
newState: AccessState
): Boolean {
if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) {
return true
}
if (packageState.packageName == PLATFORM_PACKAGE_NAME) {
return true
}
val androidPackage = packageState.androidPackage!!
if (!androidPackage.isPrivileged) {
return true
}
if (permission.packageName !in
newState.systemState.privilegedPermissionAllowlistSourcePackageNames) {
return true
}
if (isInSystemConfigPrivAppPermissions(androidPackage, permission.name, newState)) {
return true
}
if (isInSystemConfigPrivAppDenyPermissions(androidPackage, permission.name, newState)) {
return false
}
// Updated system apps do not need to be allowlisted
if (packageState.isUpdatedSystemApp) {
return true
}
// TODO: Enforce the allowlist on boot
return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE
}
private fun isInSystemConfigPrivAppPermissions(
androidPackage: AndroidPackage,
permissionName: String,
newState: AccessState
): Boolean {
val apexModuleName = androidPackage.apexModuleName
val systemState = newState.systemState
val packageName = androidPackage.packageName
val permissionNames = when {
androidPackage.isVendor -> systemState.vendorPrivAppPermissions[packageName]
androidPackage.isProduct -> systemState.productPrivAppPermissions[packageName]
androidPackage.isSystemExt -> systemState.systemExtPrivAppPermissions[packageName]
apexModuleName != null -> {
val apexPrivAppPermissions = systemState.apexPrivAppPermissions[apexModuleName]
?.get(packageName)
val privAppPermissions = systemState.privAppPermissions[packageName]
when {
apexPrivAppPermissions == null -> privAppPermissions
privAppPermissions == null -> apexPrivAppPermissions
else -> apexPrivAppPermissions + privAppPermissions
}
}
else -> systemState.privAppPermissions[packageName]
}
return permissionNames?.contains(permissionName) == true
}
private fun isInSystemConfigPrivAppDenyPermissions(
androidPackage: AndroidPackage,
permissionName: String,
newState: AccessState
): Boolean {
// Different from the previous implementation, which may incorrectly use the APEX package
// name, we now use the APEX module name to be consistent with the allowlist.
val apexModuleName = androidPackage.apexModuleName
val systemState = newState.systemState
val packageName = androidPackage.packageName
val permissionNames = when {
androidPackage.isVendor -> systemState.vendorPrivAppDenyPermissions[packageName]
androidPackage.isProduct -> systemState.productPrivAppDenyPermissions[packageName]
androidPackage.isSystemExt -> systemState.systemExtPrivAppDenyPermissions[packageName]
// Different from the previous implementation, which ignores the regular priv app
// denylist in this case, we now respect it as well to be consistent with the allowlist.
apexModuleName != null -> {
val apexPrivAppDenyPermissions = systemState
.apexPrivAppDenyPermissions[apexModuleName]?.get(packageName)
val privAppDenyPermissions = systemState.privAppDenyPermissions[packageName]
when {
apexPrivAppDenyPermissions == null -> privAppDenyPermissions
privAppDenyPermissions == null -> apexPrivAppDenyPermissions
else -> apexPrivAppDenyPermissions + privAppDenyPermissions
}
}
else -> systemState.privAppDenyPermissions[packageName]
}
return permissionNames?.contains(permissionName) == true
}
private fun anyPackageInAppId(
appId: Int,
newState: AccessState,
predicate: (PackageState) -> Boolean
): Boolean {
val packageNames = newState.systemState.appIds[appId]
return packageNames.anyIndexed { _, packageName ->
val packageState = newState.systemState.packageStates[packageName]!!
packageState.androidPackage != null && predicate(packageState)
}
}
private fun shouldGrantPermissionByProtectionFlags(
packageState: PackageState,
permission: Permission,
newState: AccessState
): 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 disabledSystemPackage = newState.systemState
.disabledSystemPackageStates[packageState.packageName]?.androidPackage
disabledSystemPackage != null &&
permission.name in disabledSystemPackage.requestedPermissions &&
shouldGrantPrivilegedOrOemPermission(
disabledSystemPackage, permission, newState
)
} else {
shouldGrantPrivilegedOrOemPermission(androidPackage, permission, newState)
}
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 &&
packageState.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, newState)) {
// 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
}
return false
}
private fun shouldGrantPrivilegedOrOemPermission(
androidPackage: AndroidPackage,
permission: Permission,
state: AccessState
): Boolean {
val permissionName = permission.name
val packageName = androidPackage.packageName
when {
permission.isPrivileged -> {
if (androidPackage.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 (androidPackage.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 (androidPackage.isOem) {
val isOemAllowlisted = state.systemState
.oemPermissions[packageName]?.get(permissionName)
checkNotNull(isOemAllowlisted) {
"OEM permission $permissionName requested by package" +
" $packageName must be explicitly declared granted or not"
}
return isOemAllowlisted
}
}
}
return false
}
private fun isDeviceOrProfileOwnerUid(uid: Int, state: AccessState): Boolean {
val userId = UserHandle.getUserId(uid)
val ownerPackageName = state.systemState.deviceAndProfileOwners[userId] ?: return false
val ownerPackageState = state.systemState.packageStates[ownerPackageName] ?: return false
val ownerUid = UserHandle.getUid(userId, ownerPackageState.appId)
return uid == ownerUid
}
override fun onPackageRemoved(
packageState: PackageState,
oldState: AccessState,
newState: AccessState
) {
// TODO
}
override fun BinaryXmlPullParser.parseSystemState(systemState: SystemState) {
with(persistence) { this@parseSystemState.parseSystemState(systemState) }
}
override fun BinaryXmlSerializer.serializeSystemState(systemState: SystemState) {
with(persistence) { this@serializeSystemState.serializeSystemState(systemState) }
}
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_GRANT_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,
)
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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.util.AtomicFile
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
/**
* Read from an [AtomicFile] and close everything safely when done.
*/
@Throws(IOException::class)
inline fun AtomicFile.read(block: (FileInputStream) -> Unit) {
openRead().use(block)
}
/**
* Write to an [AtomicFile] and close everything safely when done.
*/
@Throws(IOException::class)
// Renamed to writeInlined() to avoid conflict with the hidden AtomicFile.write() that isn't inline.
inline fun AtomicFile.writeInlined(block: (FileOutputStream) -> Unit) {
startWrite().use {
try {
block(it)
finishWrite(it)
} catch (t: Throwable) {
failWrite(it)
throw t
}
}
}

View File

@@ -0,0 +1,300 @@
/*
* 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 com.android.modules.utils.BinaryXmlPullParser
import java.io.IOException
import java.io.InputStream
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
/**
* Parse content from [InputStream] with [BinaryXmlPullParser].
*/
@Throws(IOException::class, XmlPullParserException::class)
inline fun InputStream.parseBinaryXml(block: BinaryXmlPullParser.() -> Unit) {
BinaryXmlPullParser().apply {
setInput(this@parseBinaryXml, null)
block()
}
}
/**
* Iterate through child tags of the current tag.
* <p>
* Attributes for the current tag needs to be accessed before this method is called because this
* method will advance the parser past the start tag of the current tag. The code inspecting each
* child tag may access the attributes of the child tag, and/or call [forEachTag] recursively to
* inspect grandchild tags, which will naturally leave the parser at either the start tag or the end
* tag of the child tag it inspected.
*
* @see BinaryXmlPullParser.next
* @see BinaryXmlPullParser.getEventType
* @see BinaryXmlPullParser.getDepth
*/
@Throws(IOException::class, XmlPullParserException::class)
inline fun BinaryXmlPullParser.forEachTag(block: BinaryXmlPullParser.() -> Unit) {
when (val eventType = eventType) {
// Document start or start tag of the parent tag.
XmlPullParser.START_DOCUMENT, XmlPullParser.START_TAG -> nextTagOrEnd()
else -> throw XmlPullParserException("Unexpected event type $eventType")
}
while (true) {
when (val eventType = eventType) {
// Start tag of a child tag.
XmlPullParser.START_TAG -> {
val childDepth = depth
block()
// block() should leave the parser at either the start tag (no grandchild tags
// expected) or the end tag (grandchild tags parsed with forEachTag()) of this child
// tag.
val postBlockDepth = depth
if (postBlockDepth != childDepth) {
throw XmlPullParserException(
"Unexpected post-block depth $postBlockDepth, expected $childDepth"
)
}
// Skip the parser to the end tag of this child tag.
while (true) {
when (val childEventType = this.eventType) {
// Start tag of either this child tag or a grandchild tag.
XmlPullParser.START_TAG -> nextTagOrEnd()
XmlPullParser.END_TAG -> {
if (depth > childDepth) {
// End tag of a grandchild tag.
nextTagOrEnd()
} else {
// End tag of this child tag.
break
}
}
else ->
throw XmlPullParserException("Unexpected event type $childEventType")
}
}
// Skip the end tag of this child tag.
nextTagOrEnd()
}
// End tag of the parent tag, or document end.
XmlPullParser.END_TAG, XmlPullParser.END_DOCUMENT -> break
else -> throw XmlPullParserException("Unexpected event type $eventType")
}
}
}
/**
* Advance the parser until the current event is one of [XmlPullParser.START_TAG],
* [XmlPullParser.START_TAG] and [XmlPullParser.START_TAG]
*
* @see BinaryXmlPullParser.next
*/
@Throws(IOException::class, XmlPullParserException::class)
@Suppress("NOTHING_TO_INLINE")
inline fun BinaryXmlPullParser.nextTagOrEnd(): Int {
while (true) {
when (val eventType = next()) {
XmlPullParser.START_TAG, XmlPullParser.END_TAG, XmlPullParser.END_DOCUMENT ->
return eventType
else -> continue
}
}
}
/**
* @see BinaryXmlPullParser.getName
*/
inline val BinaryXmlPullParser.tagName: String
get() = name
/**
* Check whether an attribute exists for the current tag.
*/
@Suppress("NOTHING_TO_INLINE")
inline fun BinaryXmlPullParser.hasAttribute(name: String): Boolean = getAttributeIndex(name) != -1
/**
* @see BinaryXmlPullParser.getAttributeIndex
*/
@Suppress("NOTHING_TO_INLINE")
inline fun BinaryXmlPullParser.getAttributeIndex(name: String): Int = getAttributeIndex(null, name)
/**
* @see BinaryXmlPullParser.getAttributeIndexOrThrow
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(XmlPullParserException::class)
inline fun BinaryXmlPullParser.getAttributeIndexOrThrow(name: String): Int =
getAttributeIndexOrThrow(null, name)
/**
* @see BinaryXmlPullParser.getAttributeValue
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(XmlPullParserException::class)
inline fun BinaryXmlPullParser.getAttributeValue(name: String): String? =
getAttributeValue(null, name)
/**
* @see BinaryXmlPullParser.getAttributeValue
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(XmlPullParserException::class)
inline fun BinaryXmlPullParser.getAttributeValueOrThrow(name: String): String =
getAttributeValue(getAttributeIndexOrThrow(name))
/**
* @see BinaryXmlPullParser.getAttributeBytesHex
*/
@Suppress("NOTHING_TO_INLINE")
inline fun BinaryXmlPullParser.getAttributeBytesHex(name: String): ByteArray? =
getAttributeBytesHex(null, name, null)
/**
* @see BinaryXmlPullParser.getAttributeBytesHex
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(XmlPullParserException::class)
inline fun BinaryXmlPullParser.getAttributeBytesHexOrThrow(name: String): ByteArray =
getAttributeBytesHex(null, name)
/**
* @see BinaryXmlPullParser.getAttributeBytesBase64
*/
@Suppress("NOTHING_TO_INLINE")
inline fun BinaryXmlPullParser.getAttributeBytesBase64(name: String): ByteArray? =
getAttributeBytesBase64(null, name, null)
/**
* @see BinaryXmlPullParser.getAttributeBytesBase64
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(XmlPullParserException::class)
inline fun BinaryXmlPullParser.getAttributeBytesBase64OrThrow(name: String): ByteArray =
getAttributeBytesBase64(null, name)
/**
* @see BinaryXmlPullParser.getAttributeInt
*/
@Suppress("NOTHING_TO_INLINE")
inline fun BinaryXmlPullParser.getAttributeIntOrDefault(name: String, defaultValue: Int): Int =
getAttributeInt(null, name, defaultValue)
/**
* @see BinaryXmlPullParser.getAttributeInt
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(XmlPullParserException::class)
inline fun BinaryXmlPullParser.getAttributeIntOrThrow(name: String): Int =
getAttributeInt(null, name)
/**
* @see BinaryXmlPullParser.getAttributeIntHex
*/
@Suppress("NOTHING_TO_INLINE")
inline fun BinaryXmlPullParser.getAttributeIntHexOrDefault(name: String, defaultValue: Int): Int =
getAttributeIntHex(null, name, defaultValue)
/**
* @see BinaryXmlPullParser.getAttributeIntHex
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(XmlPullParserException::class)
inline fun BinaryXmlPullParser.getAttributeIntHexOrThrow(name: String): Int =
getAttributeIntHex(null, name)
/**
* @see BinaryXmlPullParser.getAttributeLong
*/
@Suppress("NOTHING_TO_INLINE")
inline fun BinaryXmlPullParser.getAttributeLongOrDefault(name: String, defaultValue: Long): Long =
getAttributeLong(null, name, defaultValue)
/**
* @see BinaryXmlPullParser.getAttributeLong
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(XmlPullParserException::class)
inline fun BinaryXmlPullParser.getAttributeLongOrThrow(name: String): Long =
getAttributeLong(null, name)
/**
* @see BinaryXmlPullParser.getAttributeLongHex
*/
@Suppress("NOTHING_TO_INLINE")
inline fun BinaryXmlPullParser.getAttributeLongHexOrDefault(
name: String,
defaultValue: Long
): Long = getAttributeLongHex(null, name, defaultValue)
/**
* @see BinaryXmlPullParser.getAttributeLongHex
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(XmlPullParserException::class)
inline fun BinaryXmlPullParser.getAttributeLongHexOrThrow(name: String): Long =
getAttributeLongHex(null, name)
/**
* @see BinaryXmlPullParser.getAttributeFloat
*/
@Suppress("NOTHING_TO_INLINE")
inline fun BinaryXmlPullParser.getAttributeFloatOrDefault(
name: String,
defaultValue: Float
): Float = getAttributeFloat(null, name, defaultValue)
/**
* @see BinaryXmlPullParser.getAttributeFloat
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(XmlPullParserException::class)
inline fun BinaryXmlPullParser.getAttributeFloatOrThrow(name: String): Float =
getAttributeFloat(null, name)
/**
* @see BinaryXmlPullParser.getAttributeDouble
*/
@Suppress("NOTHING_TO_INLINE")
inline fun BinaryXmlPullParser.getAttributeDoubleOrDefault(
name: String,
defaultValue: Double
): Double = getAttributeDouble(null, name, defaultValue)
/**
* @see BinaryXmlPullParser.getAttributeDouble
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(XmlPullParserException::class)
inline fun BinaryXmlPullParser.getAttributeDoubleOrThrow(name: String): Double =
getAttributeDouble(null, name)
/**
* @see BinaryXmlPullParser.getAttributeBoolean
*/
@Suppress("NOTHING_TO_INLINE")
inline fun BinaryXmlPullParser.getAttributeBooleanOrDefault(
name: String,
defaultValue: Boolean
): Boolean = getAttributeBoolean(null, name, defaultValue)
/**
* @see BinaryXmlPullParser.getAttributeBoolean
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(XmlPullParserException::class)
inline fun BinaryXmlPullParser.getAttributeBooleanOrThrow(name: String): Boolean =
getAttributeBoolean(null, name)

View File

@@ -0,0 +1,262 @@
/*
* 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 com.android.modules.utils.BinaryXmlSerializer
import java.io.IOException
import java.io.OutputStream
/**
* Serialize content into [OutputStream] with [BinaryXmlSerializer].
*/
@Throws(IOException::class)
inline fun OutputStream.serializeBinaryXml(block: BinaryXmlSerializer.() -> Unit) {
BinaryXmlSerializer().apply {
setOutput(this@serializeBinaryXml, null)
document(block)
}
}
/**
* Write a document with [BinaryXmlSerializer].
*
* @see BinaryXmlSerializer.startDocument
* @see BinaryXmlSerializer.endDocument
*/
@Throws(IOException::class)
inline fun BinaryXmlSerializer.document(block: BinaryXmlSerializer.() -> Unit) {
startDocument(null, true)
block()
endDocument()
}
/**
* Write a tag with [BinaryXmlSerializer].
*
* @see BinaryXmlSerializer.startTag
* @see BinaryXmlSerializer.endTag
*/
@Throws(IOException::class)
inline fun BinaryXmlSerializer.tag(name: String, block: BinaryXmlSerializer.() -> Unit) {
startTag(null, name)
block()
endTag(null, name)
}
/**
* @see BinaryXmlSerializer.attribute
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(IOException::class)
inline fun BinaryXmlSerializer.attribute(name: String, value: String) {
attribute(null, name, value)
}
/**
* @see BinaryXmlSerializer.attributeInterned
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(IOException::class)
inline fun BinaryXmlSerializer.attributeInterned(name: String, value: String) {
attributeInterned(null, name, value)
}
/**
* @see BinaryXmlSerializer.attributeBytesHex
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(IOException::class)
inline fun BinaryXmlSerializer.attributeBytesHex(name: String, value: ByteArray) {
attributeBytesHex(null, name, value)
}
/**
* @see BinaryXmlSerializer.attributeBytesBase64
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(IOException::class)
inline fun BinaryXmlSerializer.attributeBytesBase64(name: String, value: ByteArray) {
attributeBytesBase64(null, name, value)
}
/**
* @see BinaryXmlSerializer.attributeInt
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(IOException::class)
inline fun BinaryXmlSerializer.attributeInt(name: String, value: Int) {
attributeInt(null, name, value)
}
/**
* @see BinaryXmlSerializer.attributeInt
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(IOException::class)
inline fun BinaryXmlSerializer.attributeIntWithDefault(
name: String,
value: Int,
defaultValue: Int
) {
if (value != defaultValue) {
attributeInt(null, name, value)
}
}
/**
* @see BinaryXmlSerializer.attributeIntHex
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(IOException::class)
inline fun BinaryXmlSerializer.attributeIntHex(name: String, value: Int) {
attributeIntHex(null, name, value)
}
/**
* @see BinaryXmlSerializer.attributeIntHex
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(IOException::class)
inline fun BinaryXmlSerializer.attributeIntHexWithDefault(
name: String,
value: Int,
defaultValue: Int
) {
if (value != defaultValue) {
attributeIntHex(null, name, value)
}
}
/**
* @see BinaryXmlSerializer.attributeLong
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(IOException::class)
inline fun BinaryXmlSerializer.attributeLong(name: String, value: Long) {
attributeLong(null, name, value)
}
/**
* @see BinaryXmlSerializer.attributeLong
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(IOException::class)
inline fun BinaryXmlSerializer.attributeLongWithDefault(
name: String,
value: Long,
defaultValue: Long
) {
if (value != defaultValue) {
attributeLong(null, name, value)
}
}
/**
* @see BinaryXmlSerializer.attributeLongHex
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(IOException::class)
inline fun BinaryXmlSerializer.attributeLongHex(name: String, value: Long) {
attributeLongHex(null, name, value)
}
/**
* @see BinaryXmlSerializer.attributeLongHex
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(IOException::class)
inline fun BinaryXmlSerializer.attributeLongHexWithDefault(
name: String,
value: Long,
defaultValue: Long
) {
if (value != defaultValue) {
attributeLongHex(null, name, value)
}
}
/**
* @see BinaryXmlSerializer.attributeFloat
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(IOException::class)
inline fun BinaryXmlSerializer.attributeFloat(name: String, value: Float) {
attributeFloat(null, name, value)
}
/**
* @see BinaryXmlSerializer.attributeFloat
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(IOException::class)
inline fun BinaryXmlSerializer.attributeFloatWithDefault(
name: String,
value: Float,
defaultValue: Float
) {
if (value != defaultValue) {
attributeFloat(null, name, value)
}
}
/**
* @see BinaryXmlSerializer.attributeDouble
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(IOException::class)
inline fun BinaryXmlSerializer.attributeDouble(name: String, value: Double) {
attributeDouble(null, name, value)
}
/**
* @see BinaryXmlSerializer.attributeDouble
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(IOException::class)
inline fun BinaryXmlSerializer.attributeDoubleWithDefault(
name: String,
value: Double,
defaultValue: Double
) {
if (value != defaultValue) {
attributeDouble(null, name, value)
}
}
/**
* @see BinaryXmlSerializer.attributeBoolean
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(IOException::class)
inline fun BinaryXmlSerializer.attributeBoolean(name: String, value: Boolean) {
attributeBoolean(null, name, value)
}
/**
* @see BinaryXmlSerializer.attributeBoolean
*/
@Suppress("NOTHING_TO_INLINE")
@Throws(IOException::class)
inline fun BinaryXmlSerializer.attributeBooleanWithDefault(
name: String,
value: Boolean,
defaultValue: Boolean
) {
if (value != defaultValue) {
attributeBoolean(null, name, value)
}
}

View File

@@ -14,13 +14,10 @@
* limitations under the License.
*/
package com.android.server.permission
package com.android.server.permission.access.util
import com.android.internal.annotations.Keep
import com.android.server.pm.permission.PermissionManagerServiceInterface
fun Int.hasAnyBit(bits: Int): Boolean = this and bits != 0
/**
* Modern implementation of [PermissionManagerServiceInterface].
*/
@Keep
class ModernPermissionManagerServiceImpl
fun Int.hasBits(bits: Int): Boolean = this and bits == bits
infix fun Int.andInv(other: Int): Int = this and other.inv()

View File

@@ -0,0 +1,40 @@
/*
* 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.content.ApexEnvironment
import android.os.UserHandle
import java.io.File
object PermissionApex {
private const val MODULE_NAME = "com.android.permission"
/**
* @see ApexEnvironment.getDeviceProtectedDataDir
*/
val systemDataDirectory: File
get() = apexEnvironment.deviceProtectedDataDir
/**
* @see ApexEnvironment.getDeviceProtectedDataDirForUser
*/
fun getUserDataDirectory(userId: Int): File =
apexEnvironment.getDeviceProtectedDataDirForUser(UserHandle.of(userId))
private val apexEnvironment: ApexEnvironment
get() = ApexEnvironment.getApexEnvironment(MODULE_NAME)
}