Add persistence and example for permission's system state.
The only missing part is async persistence now.
Bug: 182523293
Test: presubmit
Change-Id: I98e0a0608598bc238e9d38d0de1fa49d3d1d2f23
diff --git a/service/Android.bp b/service/Android.bp
index 39e87a8..f505540 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -94,6 +94,7 @@
static_libs: [
"kotlin-stdlib",
"modules-utils-backgroundthread",
+ "modules-utils-binary-xml",
"modules-utils-os",
"safety-center-config",
"safety-center-internal-data",
diff --git a/service/java/com/android/permission/access/AccessCheckingService.kt b/service/java/com/android/permission/access/AccessCheckingService.kt
index 7187526..426071c 100644
--- a/service/java/com/android/permission/access/AccessCheckingService.kt
+++ b/service/java/com/android/permission/access/AccessCheckingService.kt
@@ -27,7 +27,7 @@
private val policy = AccessPolicy()
- private val persistence = AccessPersistence()
+ private val persistence = AccessPersistence(policy)
fun init() {
val state = AccessState()
diff --git a/service/java/com/android/permission/access/AccessPersistence.kt b/service/java/com/android/permission/access/AccessPersistence.kt
index f3ece8e..751c205 100644
--- a/service/java/com/android/permission/access/AccessPersistence.kt
+++ b/service/java/com/android/permission/access/AccessPersistence.kt
@@ -16,23 +16,52 @@
package com.android.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.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.permission.access.util.PermissionApex
+import com.android.permission.access.util.parseBinaryXml
+import com.android.permission.access.util.read
+import com.android.permission.access.util.serializeBinaryXml
+import com.android.permission.access.util.writeInlined
+import java.io.File
+import java.io.FileNotFoundException
-class AccessPersistence {
+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.getOrPut(userId) { UserState() })
+ readUserState(userId, userStates[userId])
}
}
private fun readSystemState(systemState: SystemState) {
- TODO()
+ 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) {
- TODO()
+ 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) {
@@ -52,10 +81,34 @@
}
private fun writeSystemState(systemState: SystemState) {
- TODO()
+ systemFile.serialize {
+ with(policy) { this@serialize.serializeSystemState(systemState) }
+ }
}
private fun writeUserState(userId: Int, userState: UserState) {
- TODO()
+ 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"
}
}
diff --git a/service/java/com/android/permission/access/AccessPolicy.kt b/service/java/com/android/permission/access/AccessPolicy.kt
index db5b6c1..215052d 100644
--- a/service/java/com/android/permission/access/AccessPolicy.kt
+++ b/service/java/com/android/permission/access/AccessPolicy.kt
@@ -16,11 +16,17 @@
package com.android.permission.access
+import android.util.Log
+import com.android.modules.utils.BinaryXmlPullParser
+import com.android.modules.utils.BinaryXmlSerializer
import com.android.permission.access.appop.PackageAppOpPolicy
import com.android.permission.access.appop.UidAppOpPolicy
import com.android.permission.access.collection.* // ktlint-disable no-wildcard-imports
import com.android.permission.access.external.PackageState
import com.android.permission.access.permission.UidPermissionPolicy
+import com.android.permission.access.util.forEachTag
+import com.android.permission.access.util.tag
+import com.android.permission.access.util.tagName
class AccessPolicy private constructor(
private val schemePolicies: IndexedMap<String, IndexedMap<String, SchemePolicy>>
@@ -29,7 +35,6 @@
IndexedMap<String, IndexedMap<String, SchemePolicy>>().apply {
fun addPolicy(policy: SchemePolicy) =
getOrPut(policy.subjectScheme) { IndexedMap() }.put(policy.objectScheme, policy)
-
addPolicy(UidPermissionPolicy())
addPolicy(UidAppOpPolicy())
addPolicy(PackageAppOpPolicy())
@@ -102,13 +107,70 @@
}
}
- private inline fun forEachSchemePolicy(action: (SchemePolicy) -> Unit) {
- schemePolicies.forEachValueIndexed { _, it ->
- it.forEachValueIndexed { _, it ->
- action(it)
+ 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 {
@@ -174,6 +236,14 @@
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,
diff --git a/service/java/com/android/permission/access/external/PackageState.kt b/service/java/com/android/permission/access/external/PackageState.kt
index b45f86f..8ee94c2 100644
--- a/service/java/com/android/permission/access/external/PackageState.kt
+++ b/service/java/com/android/permission/access/external/PackageState.kt
@@ -32,7 +32,7 @@
interface AndroidPackage {
val packageName: String
- val apexModuleName: String
+ val apexModuleName: String?
val appId: Int
val isPrivileged: Boolean
val isOem: Boolean
diff --git a/service/java/com/android/permission/access/permission/UidPermissionPersistence.kt b/service/java/com/android/permission/access/permission/UidPermissionPersistence.kt
new file mode 100644
index 0000000..71c6bd4
--- /dev/null
+++ b/service/java/com/android/permission/access/permission/UidPermissionPersistence.kt
@@ -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.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.permission.access.SystemState
+import com.android.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.permission.access.data.Permission
+import com.android.permission.access.util.attribute
+import com.android.permission.access.util.attributeInt
+import com.android.permission.access.util.attributeIntHex
+import com.android.permission.access.util.attributeIntHexWithDefault
+import com.android.permission.access.util.attributeInterned
+import com.android.permission.access.util.forEachTag
+import com.android.permission.access.util.getAttributeIntHexOrDefault
+import com.android.permission.access.util.getAttributeIntHexOrThrow
+import com.android.permission.access.util.getAttributeIntOrThrow
+import com.android.permission.access.util.getAttributeValue
+import com.android.permission.access.util.getAttributeValueOrThrow
+import com.android.permission.access.util.tag
+import com.android.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"
+ }
+}
diff --git a/service/java/com/android/permission/access/permission/UidPermissionPolicy.kt b/service/java/com/android/permission/access/permission/UidPermissionPolicy.kt
index d53397d..54ce441 100644
--- a/service/java/com/android/permission/access/permission/UidPermissionPolicy.kt
+++ b/service/java/com/android/permission/access/permission/UidPermissionPolicy.kt
@@ -20,10 +20,13 @@
import android.content.pm.PermissionInfo
import android.os.Build
import android.util.Log
+import com.android.modules.utils.BinaryXmlPullParser
+import com.android.modules.utils.BinaryXmlSerializer
import com.android.permission.access.AccessState
import com.android.permission.access.AccessUri
import com.android.permission.access.PermissionUri
import com.android.permission.access.SchemePolicy
+import com.android.permission.access.SystemState
import com.android.permission.access.UidUri
import com.android.permission.access.UserState
import com.android.permission.access.collection.* // ktlint-disable no-wildcard-imports
@@ -38,11 +41,9 @@
import com.android.permission.access.util.hasBits
import com.android.permission.compat.UserHandleCompat
-private const val PLATFORM_PACKAGE_NAME = "android"
-
-private val LOG_TAG = UidPermissionPolicy::class.java.simpleName
-
class UidPermissionPolicy : SchemePolicy() {
+ private val persistence = UidPermissionPersistence()
+
override val subjectScheme: String
get() = UidUri.SCHEME
@@ -821,4 +822,18 @@
) {
// 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"
+ }
}
diff --git a/service/java/com/android/permission/access/util/AtomicFileExtensions.kt b/service/java/com/android/permission/access/util/AtomicFileExtensions.kt
new file mode 100644
index 0000000..5c34e3a
--- /dev/null
+++ b/service/java/com/android/permission/access/util/AtomicFileExtensions.kt
@@ -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.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
+ }
+ }
+}
diff --git a/service/java/com/android/permission/access/util/BinaryXmlPullParserExtensions.kt b/service/java/com/android/permission/access/util/BinaryXmlPullParserExtensions.kt
new file mode 100644
index 0000000..6be99ec
--- /dev/null
+++ b/service/java/com/android/permission/access/util/BinaryXmlPullParserExtensions.kt
@@ -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.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)
diff --git a/service/java/com/android/permission/access/util/BinaryXmlSerializerExtensions.kt b/service/java/com/android/permission/access/util/BinaryXmlSerializerExtensions.kt
new file mode 100644
index 0000000..4b13eff
--- /dev/null
+++ b/service/java/com/android/permission/access/util/BinaryXmlSerializerExtensions.kt
@@ -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.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)
+ }
+}
diff --git a/service/java/com/android/permission/access/util/PermissionApex.kt b/service/java/com/android/permission/access/util/PermissionApex.kt
new file mode 100644
index 0000000..22ca7e3
--- /dev/null
+++ b/service/java/com/android/permission/access/util/PermissionApex.kt
@@ -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.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)
+}