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)
+}