Add PermissionPolicy implementation for runtime perms

Bug: 182523293
Test: build
Change-Id: Ife6eabea48debdbe3025eaef3bc6a16819eb412b
diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt
index fae1ec3..51e7d3c 100644
--- a/services/permission/java/com/android/server/permission/access/AccessState.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessState.kt
@@ -46,6 +46,8 @@
     val knownPackages: IntMap<IndexedListSet<String>>,
     // A map of userId to packageName
     val deviceAndProfileOwners: IntMap<String>,
+    // Whether the device supports leanback UI
+    var isLeanback: Boolean,
     val privilegedPermissionAllowlistSourcePackageNames: IndexedListSet<String>,
     var permissionAllowlist: PermissionAllowlist,
     val implicitToSourcePermissions: Map<String, Set<String>>,
@@ -60,6 +62,7 @@
         IntMap(),
         IntMap(),
         IntMap(),
+        false,
         IndexedListSet(),
         PermissionAllowlist(),
         IndexedMap(),
@@ -76,6 +79,7 @@
             appIds.copy { it.copy() },
             knownPackages.copy { it.copy() },
             deviceAndProfileOwners.copy { it },
+            isLeanback,
             privilegedPermissionAllowlistSourcePackageNames.copy(),
             permissionAllowlist,
             implicitToSourcePermissions,
diff --git a/services/permission/java/com/android/server/permission/access/permission/Permission.kt b/services/permission/java/com/android/server/permission/access/permission/Permission.kt
index cbf94f5..de2df747 100644
--- a/services/permission/java/com/android/server/permission/access/permission/Permission.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/Permission.kt
@@ -46,11 +46,14 @@
     inline val isAppOp: Boolean
         get() = permissionInfo.protection == PermissionInfo.PROTECTION_FLAG_APPOP
 
+    inline val isRemoved: Boolean
+        get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_REMOVED)
+
     inline val isSoftRestricted: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.FLAG_SOFT_RESTRICTED)
+        get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_SOFT_RESTRICTED)
 
     inline val isHardRestricted: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.FLAG_HARD_RESTRICTED)
+        get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_HARD_RESTRICTED)
 
     inline val isSignature: Boolean
         get() = permissionInfo.protection == PermissionInfo.PROTECTION_SIGNATURE
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
index 6b2b1856..a4708c8 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
@@ -313,6 +313,11 @@
         APP_OP_REVOKED or ONE_TIME or HIBERNATION or USER_SELECTED
 
     /**
+     * Mask for all permission flags about permission exemption.
+     */
+    const val MASK_EXEMPT = INSTALLER_EXEMPT or SYSTEM_EXEMPT or UPGRADE_EXEMPT
+
+    /**
      * Mask for all API permission flags about permission restriction.
      */
     private const val API_MASK_RESTRICTION =
diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
index ac82715..ef2c7c3 100644
--- a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
@@ -36,6 +36,7 @@
 import com.android.server.permission.access.UidUri
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.util.andInv
+import com.android.server.permission.access.util.hasAnyBit
 import com.android.server.permission.access.util.hasBits
 import com.android.server.pm.KnownPackages
 import com.android.server.pm.parsing.PackageInfoUtils
@@ -76,7 +77,9 @@
     override fun MutateStateScope.onUserAdded(userId: Int) {
         newState.systemState.packageStates.forEach { (_, packageState) ->
             evaluateAllPermissionStatesForPackageAndUser(packageState, userId, null)
-            grantImplicitPermissions(packageState, userId)
+        }
+        newState.systemState.appIds.forEachKeyIndexed { _, appId ->
+            inheritImplicitPermissionStates(appId, userId)
         }
     }
 
@@ -88,6 +91,47 @@
         }
     }
 
+    override fun MutateStateScope.onStorageVolumeMounted(
+        volumeUuid: String?,
+        isSystemUpdated: Boolean
+    ) {
+        // TODO: STOPSHIP: Either make addPermissionGroups() favor system packages
+        // like addPermissions(), or sort system packages before non-system packages for this loop.
+        val changedPermissionNames = IndexedSet<String>()
+        newState.systemState.packageStates.forEach { (_, packageState) ->
+            val androidPackage = packageState.androidPackage
+            if (androidPackage == null || androidPackage.volumeUuid != volumeUuid) {
+                return@forEach
+            }
+            adoptPermissions(packageState, changedPermissionNames)
+            addPermissionGroups(packageState)
+            addPermissions(packageState, changedPermissionNames)
+            trimPermissions(packageState.packageName, changedPermissionNames)
+            trimPermissionStates(packageState.appId)
+        }
+        changedPermissionNames.forEachIndexed { _, permissionName ->
+            evaluatePermissionStateForAllPackages(permissionName, null)
+        }
+
+        newState.systemState.packageStates.forEach { (_, packageState) ->
+            val androidPackage = packageState.androidPackage
+            if (androidPackage == null || androidPackage.volumeUuid != volumeUuid) {
+                return@forEach
+            }
+            val installedPackageState = if (isSystemUpdated) packageState else null
+            evaluateAllPermissionStatesForPackage(packageState, installedPackageState)
+        }
+        newState.systemState.packageStates.forEach { (_, packageState) ->
+            val androidPackage = packageState.androidPackage
+            if (androidPackage == null || androidPackage.volumeUuid != volumeUuid) {
+                return@forEach
+            }
+            newState.systemState.userIds.forEachIndexed { _, userId ->
+                inheritImplicitPermissionStates(packageState.appId, userId)
+            }
+        }
+    }
+
     override fun MutateStateScope.onPackageAdded(packageState: PackageState) {
         val changedPermissionNames = IndexedSet<String>()
         adoptPermissions(packageState, changedPermissionNames)
@@ -95,17 +139,21 @@
         addPermissions(packageState, changedPermissionNames)
         // TODO: revokeStoragePermissionsIfScopeExpandedInternal()
         trimPermissions(packageState.packageName, changedPermissionNames)
+        trimPermissionStates(packageState.appId)
         changedPermissionNames.forEachIndexed { _, permissionName ->
-            evaluatePermissionStateForAllPackages(permissionName, packageState)
+            evaluatePermissionStateForAllPackages(permissionName, null)
         }
-
         evaluateAllPermissionStatesForPackage(packageState, packageState)
         newState.systemState.userIds.forEachIndexed { _, userId ->
-            grantImplicitPermissions(packageState, userId)
+            inheritImplicitPermissionStates(packageState.appId, userId)
         }
+    }
 
-        // TODO: add trimPermissionStates() here for removing the permission states that are
-        // no longer requested. (equivalent to revokeUnusedSharedUserPermissionsLocked())
+    override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {
+        // TODO: STOPSHIP: Remove this check or at least turn into logging.
+        check(packageName !in newState.systemState.disabledSystemPackageStates) {
+            "Package $packageName reported as removed before disabled system package is enabled"
+        }
     }
 
     private fun MutateStateScope.adoptPermissions(
@@ -361,6 +409,20 @@
             }
         }
 
+    private fun MutateStateScope.trimPermissionStates(appId: Int) {
+        val requestedPermissions = IndexedSet<String>()
+        forEachPackageInAppId(appId) {
+            requestedPermissions += it.androidPackage!!.requestedPermissions
+        }
+        newState.userStates.forEachIndexed { _, userId, userState ->
+            userState.uidPermissionFlags[appId].forEachReversedIndexed { _, permissionName, _ ->
+                if (permissionName !in requestedPermissions) {
+                    setPermissionFlags(appId, userId, permissionName, 0)
+                }
+            }
+        }
+    }
+
     private fun MutateStateScope.evaluatePermissionStateForAllPackages(
         permissionName: String,
         installedPackageState: PackageState?
@@ -415,8 +477,17 @@
             // For non-shared-user packages with missing androidPackage, skip evaluation.
             return
         }
-        val permission = newState.systemState.permissions[permissionName] ?: return
+        val permission = newState.systemState.permissions[permissionName]
         val oldFlags = getPermissionFlags(appId, userId, permissionName)
+        if (permission == null) {
+            if (oldFlags == 0) {
+                // If the permission definition is missing and we don't have any permission states
+                // for this permission, add the INSTALL_REVOKED flag to ensure that we don't
+                // automatically grant the permission when it's defined
+                setPermissionFlags(appId, userId, permissionName, PermissionFlags.INSTALL_REVOKED)
+            }
+            return
+        }
         if (permission.isNormal) {
             val wasGranted = oldFlags.hasBits(PermissionFlags.INSTALL_GRANTED)
             if (!wasGranted) {
@@ -479,21 +550,98 @@
             }
             setPermissionFlags(appId, userId, permissionName, newFlags)
         } else if (permission.isRuntime) {
-            // TODO: add runtime permissions
+            var newFlags = oldFlags and PermissionFlags.MASK_RUNTIME
+            if (getAppIdTargetSdkVersion(appId, permissionName) < Build.VERSION_CODES.M) {
+                newFlags = newFlags or PermissionFlags.LEGACY_GRANTED
+                // Explicitly check against the old state to determine if this permission is new.
+                val isNewPermission =
+                    getPermissionFlags(appId, userId, permissionName, oldState) == 0
+                if (isNewPermission) {
+                    newFlags = newFlags or PermissionFlags.IMPLICIT
+                }
+            } else {
+                newFlags = newFlags andInv PermissionFlags.LEGACY_GRANTED
+                val wasGrantedByImplicit = newFlags.hasBits(PermissionFlags.IMPLICIT_GRANTED)
+                val isLeanBackNotificationsPermission = newState.systemState.isLeanback &&
+                    permissionName in NOTIFICATION_PERMISSIONS
+                val isImplicitPermission = anyPackageInAppId(appId) {
+                    permissionName in it.androidPackage!!.implicitPermissions
+                }
+                val sourcePermissions = newState.systemState
+                    .implicitToSourcePermissions[permissionName]
+                val isAnySourcePermissionNonRuntime = sourcePermissions?.any {
+                    val sourcePermission = newState.systemState.permissions[it]
+                    checkNotNull(sourcePermission) {
+                        "Unknown source permission $it in split permissions"
+                    }
+                    !sourcePermission.isRuntime
+                } ?: false
+                val shouldGrantByImplicit = isLeanBackNotificationsPermission ||
+                    (isImplicitPermission && isAnySourcePermissionNonRuntime)
+                if (shouldGrantByImplicit) {
+                    newFlags = newFlags or PermissionFlags.IMPLICIT_GRANTED
+                } else {
+                    newFlags = newFlags andInv PermissionFlags.IMPLICIT_GRANTED
+                }
+                val hasImplicitFlag = newFlags.hasBits(PermissionFlags.IMPLICIT)
+                if (!isImplicitPermission && hasImplicitFlag) {
+                    // TODO: We might not want to remove the IMPLICIT flag
+                    // for NOTIFICATION_PERMISSIONS
+                    newFlags = newFlags andInv PermissionFlags.IMPLICIT
+                    var shouldRetainAsNearbyDevices = false
+                    if (permissionName in NEARBY_DEVICES_PERMISSIONS) {
+                        val accessBackgroundLocationFlags = getPermissionFlags(
+                            appId, userId, Manifest.permission.ACCESS_BACKGROUND_LOCATION
+                        )
+                        shouldRetainAsNearbyDevices =
+                            PermissionFlags.isAppOpGranted(accessBackgroundLocationFlags) &&
+                                !accessBackgroundLocationFlags.hasBits(PermissionFlags.IMPLICIT)
+                    }
+                    // These are the permission flags that imply we shouldn't automatically
+                    // modify the permission grant state.
+                    val shouldRetainByMask = newFlags.hasAnyBit(
+                        PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED
+                    )
+                    if (shouldRetainAsNearbyDevices || shouldRetainByMask) {
+                        if (wasGrantedByImplicit) {
+                            newFlags = newFlags or PermissionFlags.RUNTIME_GRANTED
+                        }
+                    } else {
+                        newFlags = newFlags andInv (
+                            PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET or
+                                PermissionFlags.USER_FIXED
+                        )
+                    }
+                }
+            }
+
+            val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
+            val isHardRestricted = permission.isHardRestricted && !isExempt
+            newFlags = if (isHardRestricted) {
+                newFlags or PermissionFlags.RESTRICTION_REVOKED
+            } else {
+                newFlags andInv PermissionFlags.RESTRICTION_REVOKED
+            }
+            val isSoftRestricted = permission.isSoftRestricted && !isExempt
+            newFlags = if (isSoftRestricted) {
+                newFlags or PermissionFlags.SOFT_RESTRICTED
+            } else {
+                newFlags andInv PermissionFlags.SOFT_RESTRICTED
+            }
+            setPermissionFlags(appId, userId, permissionName, newFlags)
         } else {
             Log.e(LOG_TAG, "Unknown protection level ${permission.protectionLevel}" +
                 "for permission ${permission.name} while evaluating permission state" +
                 "for appId $appId and userId $userId")
         }
-
-        // TODO: revokePermissionsNoLongerImplicitLocked() for runtime permissions
     }
 
-    private fun MutateStateScope.grantImplicitPermissions(packageState: PackageState, userId: Int) {
-        val appId = packageState.appId
-        val androidPackage = packageState.androidPackage ?: return
-        androidPackage.implicitPermissions.forEachIndexed implicitPermissions@ {
-            _, implicitPermissionName ->
+    private fun MutateStateScope.inheritImplicitPermissionStates(appId: Int, userId: Int) {
+        val implicitPermissions = IndexedSet<String>()
+        forEachPackageInAppId(appId) {
+            implicitPermissions += it.androidPackage!!.implicitPermissions
+        }
+        implicitPermissions.forEachIndexed implicitPermissions@ { _, implicitPermissionName ->
             val implicitPermission = newState.systemState.permissions[implicitPermissionName]
             checkNotNull(implicitPermission) {
                 "Unknown implicit permission $implicitPermissionName in split permissions"
@@ -502,15 +650,14 @@
                 return@implicitPermissions
             }
             // Explicitly check against the old state to determine if this permission is new.
-            val isNewPermission = getPermissionFlags(
-                appId, userId, implicitPermissionName, oldState
-            ) == 0
+            val isNewPermission =
+                getPermissionFlags(appId, userId, implicitPermissionName, oldState) == 0
             if (!isNewPermission) {
                 return@implicitPermissions
             }
             val sourcePermissions = newState.systemState
                 .implicitToSourcePermissions[implicitPermissionName] ?: return@implicitPermissions
-            var newFlags = 0
+            var newFlags = getPermissionFlags(appId, userId, implicitPermissionName)
             sourcePermissions.forEachIndexed sourcePermissions@ { _, sourcePermissionName ->
                 val sourcePermission = newState.systemState.permissions[sourcePermissionName]
                 checkNotNull(sourcePermission) {
@@ -525,12 +672,13 @@
                         newFlags = 0
                     }
                     newFlags = newFlags or (sourceFlags and PermissionFlags.MASK_RUNTIME)
-                    if (!sourcePermission.isRuntime && isSourceGranted) {
-                        newFlags = newFlags or PermissionFlags.IMPLICIT_GRANTED
-                    }
                 }
             }
-            newFlags = newFlags or PermissionFlags.IMPLICIT
+            if (implicitPermissionName in RETAIN_IMPLICIT_FLAGS_PERMISSIONS) {
+                newFlags = newFlags andInv PermissionFlags.IMPLICIT
+            } else {
+                newFlags = newFlags or PermissionFlags.IMPLICIT
+            }
             setPermissionFlags(appId, userId, implicitPermissionName, newFlags)
         }
     }
@@ -618,7 +766,7 @@
         val permissionAllowlist = newState.systemState.permissionAllowlist
         // TODO(b/261913353): STOPSHIP: Add AndroidPackage.apexModuleName. The below is only for
         //  passing compilation but won't actually work.
-        //val apexModuleName = androidPackage.apexModuleName
+        // val apexModuleName = androidPackage.apexModuleName
         val apexModuleName = androidPackage.packageName
         val packageName = androidPackage.packageName
         return when {
@@ -654,6 +802,17 @@
         }
     }
 
+    private fun MutateStateScope.getAppIdTargetSdkVersion(appId: Int, permissionName: String): Int {
+        var targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT
+        forEachPackageInAppId(appId) { packageState ->
+            val androidPackage = packageState.androidPackage!!
+            if (permissionName in androidPackage.requestedPermissions) {
+                targetSdkVersion = targetSdkVersion.coerceAtMost(androidPackage.targetSdkVersion)
+            }
+        }
+        return targetSdkVersion
+    }
+
     private fun MutateStateScope.anyPackageInAppId(
         appId: Int,
         state: AccessState = newState,
@@ -666,6 +825,20 @@
         }
     }
 
+    private fun MutateStateScope.forEachPackageInAppId(
+        appId: Int,
+        state: AccessState = newState,
+        action: (PackageState) -> Unit
+    ) {
+        val packageNames = state.systemState.appIds[appId]
+        packageNames.forEachIndexed { _, packageName ->
+            val packageState = state.systemState.packageStates[packageName]!!
+            if (packageState.androidPackage != null) {
+                action(packageState)
+            }
+        }
+    }
+
     private fun MutateStateScope.shouldGrantPermissionByProtectionFlags(
         packageState: PackageState,
         permission: Permission
@@ -813,13 +986,6 @@
         return uid == ownerUid
     }
 
-    override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {
-        // TODO: STOPSHIP: Remove this check or at least turn into logging.
-        check(packageName !in newState.systemState.disabledSystemPackageStates) {
-            "Package $packageName reported as removed before disabled system package is enabled"
-        }
-    }
-
     override fun BinaryXmlPullParser.parseSystemState(state: AccessState) {
         with(persistence) { this@parseSystemState.parseSystemState(state) }
     }
@@ -928,13 +1094,24 @@
         private const val PLATFORM_PACKAGE_NAME = "android"
 
         // A set of permissions that we don't want to revoke when they are no longer implicit.
-        private val RETAIN_IMPLICIT_GRANT_PERMISSIONS = indexedSetOf(
+        private val RETAIN_IMPLICIT_FLAGS_PERMISSIONS = indexedSetOf(
             Manifest.permission.ACCESS_MEDIA_LOCATION,
             Manifest.permission.ACTIVITY_RECOGNITION,
             Manifest.permission.READ_MEDIA_AUDIO,
             Manifest.permission.READ_MEDIA_IMAGES,
             Manifest.permission.READ_MEDIA_VIDEO,
         )
+
+        // TODO: also add the permission NEARBY_WIFI_DEVICES to this set
+        private val NEARBY_DEVICES_PERMISSIONS = indexedSetOf(
+            Manifest.permission.BLUETOOTH_ADVERTISE,
+            Manifest.permission.BLUETOOTH_CONNECT,
+            Manifest.permission.BLUETOOTH_SCAN
+        )
+
+        private val NOTIFICATION_PERMISSIONS = indexedSetOf(
+            Manifest.permission.POST_NOTIFICATIONS
+        )
     }
 
     fun interface OnPermissionFlagsChangedListener {