Merge "Add unit tests for AppIdPermissionPolicy" into main
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
index 823ce45..0547719 100644
--- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
@@ -112,4 +112,4 @@
         @JvmStatic
         fun data(): Array<Action> = Action.values()
     }
-}
\ No newline at end of file
+}
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt
index f085bd7..c44b2c5 100644
--- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt
@@ -43,8 +43,7 @@
     @Parameterized.Parameter(0) lateinit var action: Action
 
     @Before
-    override fun setUp() {
-        super.setUp()
+    fun setUp() {
         if (action == Action.ON_USER_ADDED) {
             createUserState(USER_ID_NEW)
         }
@@ -881,4 +880,4 @@
         @JvmStatic
         fun data(): Array<Action> = Action.values()
     }
-}
\ No newline at end of file
+}
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt
new file mode 100644
index 0000000..e4e3368
--- /dev/null
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2023 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.test
+
+import android.content.pm.PermissionGroupInfo
+import android.content.pm.PermissionInfo
+import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.permission.AppIdPermissionPolicy
+import com.android.server.permission.access.permission.Permission
+import com.android.server.permission.access.permission.PermissionFlags
+import com.android.server.testutils.mock
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+class AppIdPermissionPolicyTest : BaseAppIdPermissionPolicyTest() {
+    @Test
+    fun testOnAppIdRemoved_appIdIsRemoved_permissionFlagsCleared() {
+        val parsedPermission = mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_0)
+        val permissionOwnerPackageState = mockPackageState(
+            APP_ID_0,
+            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(parsedPermission))
+        )
+        val requestingPackageState = mockPackageState(
+            APP_ID_1,
+            mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0))
+        )
+        addPackageState(permissionOwnerPackageState)
+        addPackageState(requestingPackageState)
+        addPermission(parsedPermission)
+        setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.INSTALL_GRANTED)
+
+        mutateState {
+            with(appIdPermissionPolicy) {
+                onAppIdRemoved(APP_ID_1)
+            }
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = 0
+        assertWithMessage(
+            "After onAppIdRemoved() is called for appId $APP_ID_1 that requests a permission" +
+                " owns by appId $APP_ID_0 with existing permission flags. The actual permission" +
+                " flags $actualFlags should be null"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testOnStorageVolumeMounted_nonSystemAppAfterNonSystemUpdate_remainsRevoked() {
+        val permissionOwnerPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage())
+        val installedPackageState = mockPackageState(
+            APP_ID_1,
+            mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0))
+        )
+        addPackageState(permissionOwnerPackageState)
+        addPackageState(installedPackageState)
+        addPermission(defaultPermission)
+        val oldFlags = PermissionFlags.INSTALL_REVOKED
+        setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags)
+
+        mutateState {
+            with(appIdPermissionPolicy) {
+                onStorageVolumeMounted(null, listOf(installedPackageState.packageName), false)
+            }
+        }
+
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After onStorageVolumeMounted() is called for a non-system app that requests a normal" +
+                " permission with existing INSTALL_REVOKED flag after a non-system-update" +
+                " (such as an OTA update), the actual permission flags should remain revoked." +
+                " The actual permission flags $actualFlags should match the expected flags" +
+                " $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testOnPackageRemoved_packageIsRemoved_permissionDefinitionsAndStatesAreUpdated() {
+        val permissionOwnerPackageState = mockPackageState(
+            APP_ID_0,
+            mockAndroidPackage(
+                PACKAGE_NAME_0,
+                requestedPermissions = setOf(PERMISSION_NAME_0),
+                permissions = listOf(defaultPermission)
+            )
+        )
+        val requestingPackageState = mockPackageState(
+            APP_ID_1,
+            mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0))
+        )
+        addPackageState(permissionOwnerPackageState)
+        addPackageState(requestingPackageState)
+        addPermission(defaultPermission)
+        val oldFlags = PermissionFlags.INSTALL_GRANTED
+        setPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0, oldFlags)
+        setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags)
+
+        mutateState {
+            removePackageState(permissionOwnerPackageState)
+            with(appIdPermissionPolicy) {
+                onPackageRemoved(PACKAGE_NAME_0, APP_ID_0)
+            }
+        }
+
+        assertWithMessage(
+            "After onPackageRemoved() is called for a permission owner, the permission" +
+                " definitions owned by this package should be removed"
+        )
+            .that(getPermission(PERMISSION_NAME_0))
+            .isNull()
+
+        val app0ActualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
+        val app0ExpectedNewFlags = 0
+        assertWithMessage(
+            "After onPackageRemoved() is called for a permission owner, the permission states of" +
+                " this app should be trimmed. The actual permission flags $app0ActualFlags should" +
+                " match the expected flags $app0ExpectedNewFlags"
+        )
+            .that(app0ActualFlags)
+            .isEqualTo(app0ExpectedNewFlags)
+
+        val app1ActualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val app1ExpectedNewFlags = PermissionFlags.INSTALL_REVOKED
+        assertWithMessage(
+            "After onPackageRemoved() is called for a permission owner, the permission states of" +
+                " the permission requester should remain unchanged. The actual permission flags" +
+                " $app1ActualFlags should match the expected flags $app1ExpectedNewFlags"
+        )
+            .that(app1ActualFlags)
+            .isEqualTo(app1ExpectedNewFlags)
+    }
+
+    @Test
+    fun testOnPackageInstalled_nonSystemAppIsInstalled_upgradeExemptFlagIsCleared() {
+        val oldFlags = PermissionFlags.SOFT_RESTRICTED or PermissionFlags.UPGRADE_EXEMPT
+        testOnPackageInstalled(
+            oldFlags,
+            permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED
+        ) {}
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.SOFT_RESTRICTED
+        assertWithMessage(
+            "After onPackageInstalled() is called for a non-system app that requests a runtime" +
+                " soft restricted permission, UPGRADE_EXEMPT flag should be removed. The actual" +
+                " permission flags $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testOnPackageInstalled_systemAppIsInstalled_upgradeExemptFlagIsRetained() {
+        val oldFlags = PermissionFlags.SOFT_RESTRICTED or PermissionFlags.UPGRADE_EXEMPT
+        testOnPackageInstalled(
+            oldFlags,
+            permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED,
+            isInstalledPackageSystem = true
+        ) {}
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After onPackageInstalled() is called for a system app that requests a runtime" +
+                " soft restricted permission, UPGRADE_EXEMPT flag should be retained. The actual" +
+                " permission flags $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testOnPackageInstalled_requestedPermissionAlsoRequestedBySystemApp_exemptFlagIsRetained() {
+        val oldFlags = PermissionFlags.SOFT_RESTRICTED or PermissionFlags.UPGRADE_EXEMPT
+        testOnPackageInstalled(
+            oldFlags,
+            permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED
+        ) {
+            val systemAppPackageState = mockPackageState(
+                APP_ID_1,
+                mockAndroidPackage(PACKAGE_NAME_2, requestedPermissions = setOf(PERMISSION_NAME_0)),
+                isSystem = true
+            )
+            addPackageState(systemAppPackageState)
+        }
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After onPackageInstalled() is called for a non-system app that requests a runtime" +
+                " soft restricted permission, and that permission is also requested by a system" +
+                " app in the same appId, UPGRADE_EXEMPT flag should be retained. The actual" +
+                " permission flags $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testOnPackageInstalled_restrictedPermissionsNotExempt_getsRestrictionFlags() {
+        val oldFlags = PermissionFlags.RESTRICTION_REVOKED
+        testOnPackageInstalled(
+            oldFlags,
+            permissionInfoFlags = PermissionInfo.FLAG_HARD_RESTRICTED
+        ) {}
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = oldFlags
+        assertWithMessage(
+            "After onPackageInstalled() is called for a non-system app that requests a runtime" +
+                " hard restricted permission that is not exempted. The actual permission flags" +
+                " $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    @Test
+    fun testOnPackageInstalled_restrictedPermissionsIsExempted_clearsRestrictionFlags() {
+        val oldFlags = PermissionFlags.SOFT_RESTRICTED or PermissionFlags.INSTALLER_EXEMPT
+        testOnPackageInstalled(
+            oldFlags,
+            permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED
+        ) {}
+        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
+        val expectedNewFlags = PermissionFlags.INSTALLER_EXEMPT
+        assertWithMessage(
+            "After onPackageInstalled() is called for a non-system app that requests a runtime" +
+                " soft restricted permission that is exempted. The actual permission flags" +
+                " $actualFlags should match the expected flags $expectedNewFlags"
+        )
+            .that(actualFlags)
+            .isEqualTo(expectedNewFlags)
+    }
+
+    private fun testOnPackageInstalled(
+        oldFlags: Int,
+        permissionInfoFlags: Int = 0,
+        isInstalledPackageSystem: Boolean = false,
+        additionalSetup: () -> Unit
+    ) {
+        val parsedPermission = mockParsedPermission(
+            PERMISSION_NAME_0,
+            PACKAGE_NAME_0,
+            protectionLevel = PermissionInfo.PROTECTION_DANGEROUS,
+            flags = permissionInfoFlags
+        )
+        val permissionOwnerPackageState = mockPackageState(
+            APP_ID_0,
+            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(parsedPermission))
+        )
+        addPackageState(permissionOwnerPackageState)
+        addPermission(parsedPermission)
+
+        additionalSetup()
+
+        mutateState {
+            val installedPackageState = mockPackageState(
+                APP_ID_1,
+                mockAndroidPackage(
+                    PACKAGE_NAME_1,
+                    requestedPermissions = setOf(PERMISSION_NAME_0),
+                ),
+                isSystem = isInstalledPackageSystem,
+            )
+            addPackageState(installedPackageState, newState)
+            setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags, newState)
+            with(appIdPermissionPolicy) {
+                onPackageInstalled(installedPackageState, USER_ID_0)
+            }
+        }
+    }
+
+    @Test
+    fun testOnStateMutated_notEmpty_isCalledForEachListener() {
+        val mockListener = mock<AppIdPermissionPolicy.OnPermissionFlagsChangedListener> {}
+        appIdPermissionPolicy.addOnPermissionFlagsChangedListener(mockListener)
+
+        GetStateScope(oldState).apply {
+            with(appIdPermissionPolicy) {
+                onStateMutated()
+            }
+        }
+
+        verify(mockListener, times(1)).onStateMutated()
+    }
+
+    @Test
+    fun testGetPermissionTrees() {
+        val permissionTrees: IndexedMap<String, Permission>
+        GetStateScope(oldState).apply {
+            with(appIdPermissionPolicy) {
+                permissionTrees = getPermissionTrees()
+            }
+        }
+
+        assertThat(oldState.systemState.permissionTrees).isEqualTo(permissionTrees)
+    }
+
+    @Test
+    fun testFindPermissionTree() {
+        val permissionTree = createSimplePermission(isTree = true)
+        val actualPermissionTree: Permission?
+        oldState.mutateSystemState().mutatePermissionTrees()[PERMISSION_TREE_NAME] = permissionTree
+
+        GetStateScope(oldState).apply {
+            with(appIdPermissionPolicy) {
+                actualPermissionTree = findPermissionTree(PERMISSION_BELONGS_TO_A_TREE)
+            }
+        }
+
+        assertThat(actualPermissionTree).isEqualTo(permissionTree)
+    }
+
+    @Test
+    fun testAddPermissionTree() {
+        val permissionTree = createSimplePermission(isTree = true)
+
+        mutateState {
+            with(appIdPermissionPolicy) {
+                addPermissionTree(permissionTree)
+            }
+        }
+
+        assertThat(newState.systemState.permissionTrees[PERMISSION_TREE_NAME])
+            .isEqualTo(permissionTree)
+    }
+
+    @Test
+    fun testGetPermissionGroups() {
+        val permissionGroups: IndexedMap<String, PermissionGroupInfo>
+        GetStateScope(oldState).apply {
+            with(appIdPermissionPolicy) {
+                permissionGroups = getPermissionGroups()
+            }
+        }
+
+        assertThat(oldState.systemState.permissionGroups).isEqualTo(permissionGroups)
+    }
+
+    @Test
+    fun testGetPermissions() {
+        val permissions: IndexedMap<String, Permission>
+        GetStateScope(oldState).apply {
+            with(appIdPermissionPolicy) {
+                permissions = getPermissions()
+            }
+        }
+
+        assertThat(oldState.systemState.permissions).isEqualTo(permissions)
+    }
+
+    @Test
+    fun testAddPermission() {
+        val permission = createSimplePermission()
+
+        mutateState {
+            with(appIdPermissionPolicy) {
+                addPermission(permission)
+            }
+        }
+
+        assertThat(newState.systemState.permissions[PERMISSION_NAME_0]).isEqualTo(permission)
+    }
+
+    @Test
+    fun testRemovePermission() {
+        val permission = createSimplePermission()
+
+        mutateState {
+            with(appIdPermissionPolicy) {
+                addPermission(permission)
+                removePermission(permission)
+            }
+        }
+
+        assertThat(newState.systemState.permissions[PERMISSION_NAME_0]).isNull()
+    }
+
+    @Test
+    fun testGetUidPermissionFlags() {
+        val uidPermissionFlags: IndexedMap<String, Int>?
+        GetStateScope(oldState).apply {
+            with(appIdPermissionPolicy) {
+                uidPermissionFlags = getUidPermissionFlags(APP_ID_0, USER_ID_0)
+            }
+        }
+
+        assertThat(oldState.userStates[USER_ID_0]!!.appIdPermissionFlags[APP_ID_0])
+            .isEqualTo(uidPermissionFlags)
+    }
+
+    @Test
+    fun testUpdateAndGetPermissionFlags() {
+        val flags = PermissionFlags.INSTALL_GRANTED
+        var actualFlags = 0
+        mutateState {
+            with(appIdPermissionPolicy) {
+                updatePermissionFlags(
+                    APP_ID_0,
+                    USER_ID_0,
+                    PERMISSION_NAME_0,
+                    PermissionFlags.MASK_ALL,
+                    flags
+                )
+                actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
+            }
+        }
+
+        assertThat(actualFlags).isEqualTo(flags)
+    }
+}
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt
index 7966c5c..ec84bc3 100644
--- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt
@@ -34,7 +34,6 @@
 import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.permission.AppIdPermissionPolicy
 import com.android.server.permission.access.permission.Permission
-import com.android.server.permission.access.permission.PermissionFlags
 import com.android.server.permission.access.util.hasBits
 import com.android.server.pm.parsing.PackageInfoUtils
 import com.android.server.pm.pkg.AndroidPackage
@@ -45,10 +44,8 @@
 import com.android.server.testutils.any
 import com.android.server.testutils.mock
 import com.android.server.testutils.whenever
-import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Before
 import org.junit.Rule
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyLong
 
@@ -56,7 +53,7 @@
  * Mocking unit test for AppIdPermissionPolicy.
  */
 @RunWith(AndroidJUnit4::class)
-open class BaseAppIdPermissionPolicyTest {
+abstract class BaseAppIdPermissionPolicyTest {
     protected lateinit var oldState: MutableAccessState
     protected lateinit var newState: MutableAccessState
 
@@ -80,7 +77,7 @@
         .build()
 
     @Before
-    open fun setUp() {
+    fun baseSetUp() {
         oldState = MutableAccessState()
         createUserState(USER_ID_0)
         oldState.mutateExternalState().setPackageStates(ArrayMap())
@@ -139,78 +136,6 @@
         }
     }
 
-    @Test
-    fun testOnAppIdRemoved_appIdIsRemoved_permissionFlagsCleared() {
-        val parsedPermission = mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_0)
-        val permissionOwnerPackageState = mockPackageState(
-            APP_ID_0,
-            mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(parsedPermission))
-        )
-        val requestingPackageState = mockPackageState(
-            APP_ID_1,
-            mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0))
-        )
-        addPackageState(permissionOwnerPackageState)
-        addPackageState(requestingPackageState)
-        addPermission(parsedPermission)
-        setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.INSTALL_GRANTED)
-
-        mutateState {
-            with(appIdPermissionPolicy) {
-                onAppIdRemoved(APP_ID_1)
-            }
-        }
-
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = 0
-        assertWithMessage(
-            "After onAppIdRemoved() is called for appId $APP_ID_1 that requests a permission" +
-                " owns by appId $APP_ID_0 with existing permission flags. The actual permission" +
-                " flags $actualFlags should be null"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
-    @Test
-    fun testOnPackageRemoved_packageIsRemoved_permissionsAreTrimmedAndStatesAreEvaluated() {
-        // TODO
-        // shouldn't reuse test cases because it's really different despite it's also for
-        // trim permission states. It's different because it's package removal
-    }
-
-    @Test
-    fun testOnPackageInstalled_nonSystemAppIsInstalled_upgradeExemptFlagIsCleared() {
-        // TODO
-        // should be fine for it to be its own test cases and not to re-use
-        // clearRestrictedPermissionImplicitExemption
-    }
-
-    @Test
-    fun testOnPackageInstalled_systemAppIsInstalled_upgradeExemptFlagIsRetained() {
-        // TODO
-    }
-
-    @Test
-    fun testOnPackageInstalled_requestedPermissionAlsoRequestedBySystemApp_exemptFlagIsRetained() {
-        // TODO
-    }
-
-    @Test
-    fun testOnPackageInstalled_restrictedPermissionsNotExempt_getsRestrictionFlags() {
-        // TODO
-    }
-
-    @Test
-    fun testOnPackageInstalled_restrictedPermissionsIsExempted_clearsRestrictionFlags() {
-        // TODO
-    }
-
-    @Test
-    fun testOnStateMutated_notEmpty_isCalledForEachListener() {
-        // TODO
-    }
-
     /**
      * Mock an AndroidPackage with PACKAGE_NAME_0, PERMISSION_NAME_0 and PERMISSION_GROUP_NAME_0
      */
@@ -221,6 +146,15 @@
             permissions = listOf(defaultPermissionTree, defaultPermission)
         )
 
+    protected fun createSimplePermission(isTree: Boolean = false): Permission {
+        val parsedPermission = if (isTree) { defaultPermissionTree } else { defaultPermission }
+        val permissionInfo = PackageInfoUtils.generatePermissionInfo(
+            parsedPermission,
+            PackageManager.GET_META_DATA.toLong()
+        )!!
+        return Permission(permissionInfo, true, Permission.TYPE_MANIFEST, APP_ID_0)
+    }
+
     protected inline fun mutateState(action: MutateStateScope.() -> Unit) {
         newState = oldState.toMutable()
         MutateStateScope(oldState, newState).action()
@@ -330,15 +264,26 @@
     ) {
         state.mutateExternalState().apply {
             setPackageStates(
-                packageStates.toMutableMap().apply {
-                    put(packageState.packageName, packageState)
-                }
+                packageStates.toMutableMap().apply { put(packageState.packageName, packageState) }
             )
             mutateAppIdPackageNames().mutateOrPut(packageState.appId) { MutableIndexedListSet() }
                 .add(packageState.packageName)
         }
     }
 
+    protected fun removePackageState(
+        packageState: PackageState,
+        state: MutableAccessState = oldState
+    ) {
+        state.mutateExternalState().apply {
+            setPackageStates(
+                packageStates.toMutableMap().apply { remove(packageState.packageName) }
+            )
+            mutateAppIdPackageNames().mutateOrPut(packageState.appId) { MutableIndexedListSet() }
+                .remove(packageState.packageName)
+        }
+    }
+
     protected fun addDisabledSystemPackageState(
         packageState: PackageState,
         state: MutableAccessState = oldState
@@ -429,6 +374,7 @@
         @JvmStatic protected val PERMISSION_NAME_0 = "permissionName0"
         @JvmStatic protected val PERMISSION_NAME_1 = "permissionName1"
         @JvmStatic protected val PERMISSION_NAME_2 = "permissionName2"
+        @JvmStatic protected val PERMISSION_BELONGS_TO_A_TREE = "permissionTree.permission"
         @JvmStatic protected val PERMISSION_READ_EXTERNAL_STORAGE =
             Manifest.permission.READ_EXTERNAL_STORAGE
         @JvmStatic protected val PERMISSION_POST_NOTIFICATIONS =