Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c7460b36 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Optimize check for requested permissions."

parents fc48e853 4ff9f6d4
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -64,6 +64,18 @@ inline fun <T> IndexedList<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boole
operator fun <T> IndexedList<T>.plus(element: T): MutableIndexedList<T> =
    toMutable().apply { this += element }

// Using Int instead of <R> to avoid autoboxing, since we only have the use case for Int.
inline fun <T> IndexedList<T>.reduceIndexed(
    initialValue: Int,
    accumulator: (Int, Int, T) -> Int
): Int {
    var value = initialValue
    forEachIndexed { index, element ->
        value = accumulator(value, index, element)
    }
    return value
}

@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> MutableIndexedList<T>.minusAssign(element: T) {
    remove(element)
+12 −0
Original line number Diff line number Diff line
@@ -64,6 +64,18 @@ inline fun <T> IndexedListSet<T>.noneIndexed(predicate: (Int, T) -> Boolean): Bo
operator fun <T> IndexedListSet<T>.plus(element: T): MutableIndexedListSet<T> =
    toMutable().apply { this += element }

// Using Int instead of <R> to avoid autoboxing, since we only have the use case for Int.
inline fun <T> IndexedListSet<T>.reduceIndexed(
    initialValue: Int,
    accumulator: (Int, Int, T) -> Int
): Int {
    var value = initialValue
    forEachIndexed { index, element ->
        value = accumulator(value, index, element)
    }
    return value
}

@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> MutableIndexedListSet<T>.minusAssign(element: T) {
    remove(element)
+95 −82
Original line number Diff line number Diff line
@@ -205,8 +205,9 @@ class AppIdPermissionPolicy : SchemePolicy() {
            if (!permission.isHardOrSoftRestricted) {
                return@forEach
            }
            val isRequestedBySystemPackage =
                anyRequestingPackageInAppId(appId, permissionName) { it.isSystem }
            val isRequestedBySystemPackage = anyPackageInAppId(appId) {
                it.isSystem && permissionName in it.androidPackage!!.requestedPermissions
            }
            if (isRequestedBySystemPackage) {
                return@forEach
            }
@@ -247,8 +248,9 @@ class AppIdPermissionPolicy : SchemePolicy() {
            if (permission.isRemoved) {
                return@forEach
            }
            val isRequestedByOtherPackages = anyRequestingPackageInAppId(appId, permissionName) {
                it.packageName != packageName
            val isRequestedByOtherPackages = anyPackageInAppId(appId) {
                it.packageName != packageName &&
                    permissionName in it.androidPackage!!.requestedPermissions
            }
            if (isRequestedByOtherPackages) {
                return@forEach
@@ -630,32 +632,45 @@ class AppIdPermissionPolicy : SchemePolicy() {
            // information about the package before OTA anyway.
            return
        }

        // If the app is updated, and has scoped storage permissions, then it is possible that the
        // app updated in an attempt to get unscoped storage. If so, revoke all storage permissions.
        newState.userStates.forEachIndexed { _, userId, userState ->
            userState.appIdPermissionFlags[appId]?.forEachReversedIndexed {
                _, permissionName, oldFlags ->
                if (permissionName !in STORAGE_AND_MEDIA_PERMISSIONS || oldFlags == 0) {
                    return@forEachReversedIndexed
        val oldTargetSdkVersion =
            reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT, oldState) {
                targetSdkVersion, packageState ->
                targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion)
            }
                val oldTargetSdkVersion = getAppIdTargetSdkVersion(appId, permissionName, oldState)
                val newTargetSdkVersion = getAppIdTargetSdkVersion(appId, permissionName, newState)
        val newTargetSdkVersion =
            reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT, newState) {
                targetSdkVersion, packageState ->
                targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion)
            }
        @Suppress("ConvertTwoComparisonsToRangeCheck")
        val isTargetSdkVersionDowngraded = oldTargetSdkVersion >= Build.VERSION_CODES.Q &&
            newTargetSdkVersion < Build.VERSION_CODES.Q
        @Suppress("ConvertTwoComparisonsToRangeCheck")
        val isTargetSdkVersionUpgraded = oldTargetSdkVersion < Build.VERSION_CODES.Q &&
            newTargetSdkVersion >= Build.VERSION_CODES.Q
                val oldIsRequestLegacyExternalStorage = anyRequestingPackageInAppId(
                    appId, permissionName, oldState
                ) { it.androidPackage!!.isRequestLegacyExternalStorage }
                val newIsRequestLegacyExternalStorage = anyRequestingPackageInAppId(
                    appId, permissionName, newState
                ) { it.androidPackage!!.isRequestLegacyExternalStorage }
        val oldIsRequestLegacyExternalStorage = anyPackageInAppId(appId, oldState) {
            it.androidPackage!!.isRequestLegacyExternalStorage
        }
        val newIsRequestLegacyExternalStorage = anyPackageInAppId(appId, newState) {
            it.androidPackage!!.isRequestLegacyExternalStorage
        }
        val isNewlyRequestingLegacyExternalStorage = !isTargetSdkVersionUpgraded &&
            !oldIsRequestLegacyExternalStorage && newIsRequestLegacyExternalStorage
                if ((isNewlyRequestingLegacyExternalStorage || isTargetSdkVersionDowngraded) &&
        val shouldRevokeStorageAndMediaPermissions = isNewlyRequestingLegacyExternalStorage ||
            isTargetSdkVersionDowngraded
        if (shouldRevokeStorageAndMediaPermissions) {
            newState.userStates.forEachIndexed { _, userId, userState ->
                userState.appIdPermissionFlags[appId]?.forEachReversedIndexed {
                    _, permissionName, oldFlags ->
                    if (permissionName in STORAGE_AND_MEDIA_PERMISSIONS &&
                        oldFlags.hasBits(PermissionFlags.RUNTIME_GRANTED)) {
                    Slog.v(LOG_TAG, "Revoking storage permission: $permissionName for appId: " +
                            " $appId and user: $userId")
                        Slog.v(
                            LOG_TAG, "Revoking storage permission: $permissionName for appId: " +
                                " $appId and user: $userId"
                        )
                        val newFlags = oldFlags andInv (
                            PermissionFlags.RUNTIME_GRANTED or USER_SETTABLE_MASK
                        )
@@ -664,6 +679,7 @@ class AppIdPermissionPolicy : SchemePolicy() {
                }
            }
        }
    }

    private fun MutateStateScope.evaluatePermissionStateForAllPackages(
        permissionName: String,
@@ -672,8 +688,9 @@ class AppIdPermissionPolicy : SchemePolicy() {
        val externalState = newState.externalState
        externalState.userIds.forEachIndexed { _, userId ->
            externalState.appIdPackageNames.forEachIndexed { _, appId, _ ->
                val isPermissionRequested =
                    anyRequestingPackageInAppId(appId, permissionName) { true }
                val isPermissionRequested = anyPackageInAppId(appId) {
                    permissionName in it.androidPackage!!.requestedPermissions
                }
                if (isPermissionRequested) {
                    evaluatePermissionState(appId, userId, permissionName, installedPackageState)
                }
@@ -711,8 +728,21 @@ class AppIdPermissionPolicy : SchemePolicy() {
        installedPackageState: PackageState?
    ) {
        val packageNames = newState.externalState.appIdPackageNames[appId]!!
        val hasMissingPackage = packageNames.anyIndexed { _, packageName ->
            newState.externalState.packageStates[packageName]!!.androidPackage == null
        // Repeatedly checking whether a permission is requested can actually be costly, so we cache
        // the result for this method which is frequently called during boot, instead of calling
        // anyPackageInAppId() and checking requested permissions multiple times.
        val requestingPackageStates = MutableIndexedList<PackageState>()
        var hasMissingPackage = false
        packageNames.forEachIndexed { _, packageName ->
            val packageState = newState.externalState.packageStates[packageName]!!
            val androidPackage = packageState.androidPackage
            if (androidPackage != null) {
                if (permissionName in androidPackage.requestedPermissions) {
                    requestingPackageStates += packageState
                }
            } else {
                hasMissingPackage = true
            }
        }
        if (packageNames.size == 1 && hasMissingPackage) {
            // For non-shared-user packages with missing androidPackage, skip evaluation.
@@ -736,8 +766,8 @@ class AppIdPermissionPolicy : SchemePolicy() {
                val isRequestedByInstalledPackage = installedPackageState != null &&
                    permissionName in installedPackageState.androidPackage!!.requestedPermissions
                val isRequestedBySystemPackage =
                    anyRequestingPackageInAppId(appId, permissionName) { it.isSystem }
                val isCompatibilityPermission = anyRequestingPackageInAppId(appId, permissionName) {
                    requestingPackageStates.anyIndexed { _, it -> it.isSystem }
                val isCompatibilityPermission = requestingPackageStates.anyIndexed { _, it ->
                    isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName)
                }
                // If this is an existing, non-system package,
@@ -762,18 +792,15 @@ class AppIdPermissionPolicy : SchemePolicy() {
                // Keep the non-runtime permission grants for shared UID with missing androidPackage
                PermissionFlags.PROTECTION_GRANTED
            } else {
                val mayGrantByPrivileged = !permission.isPrivileged || (
                    anyRequestingPackageInAppId(appId, permissionName) {
                val mayGrantByPrivileged = !permission.isPrivileged ||
                    requestingPackageStates.anyIndexed { _, it ->
                        checkPrivilegedPermissionAllowlist(it, permission)
                    }
                )
                val shouldGrantBySignature = permission.isSignature && (
                    anyRequestingPackageInAppId(appId, permissionName) {
                val shouldGrantBySignature = permission.isSignature &&
                    requestingPackageStates.anyIndexed { _, it ->
                        shouldGrantPermissionBySignature(it, permission)
                    }
                )
                val shouldGrantByProtectionFlags =
                    anyRequestingPackageInAppId(appId, permissionName) {
                val shouldGrantByProtectionFlags = requestingPackageStates.anyIndexed { _, it ->
                    shouldGrantPermissionByProtectionFlags(it, permission)
                }
                if (mayGrantByPrivileged &&
@@ -805,7 +832,12 @@ class AppIdPermissionPolicy : SchemePolicy() {
        } else if (permission.isRuntime) {
            var newFlags = oldFlags and PermissionFlags.MASK_RUNTIME
            val wasRevoked = newFlags != 0 && !PermissionFlags.isPermissionGranted(newFlags)
            if (getAppIdTargetSdkVersion(appId, permissionName) < Build.VERSION_CODES.M) {
            val targetSdkVersion =
                requestingPackageStates.reduceIndexed(Build.VERSION_CODES.CUR_DEVELOPMENT) {
                    targetSdkVersion, _, packageState ->
                    targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion)
                }
            if (targetSdkVersion < Build.VERSION_CODES.M) {
                if (permission.isRuntimeOnly) {
                    // Different from the old implementation, which simply skips a runtime-only
                    // permission, we now only allow holding on to the restriction related flags,
@@ -837,7 +869,7 @@ class AppIdPermissionPolicy : SchemePolicy() {
                val wasGrantedByImplicit = newFlags.hasBits(PermissionFlags.IMPLICIT_GRANTED)
                val isLeanbackNotificationsPermission = newState.externalState.isLeanback &&
                    permissionName in NOTIFICATIONS_PERMISSIONS
                val isImplicitPermission = anyRequestingPackageInAppId(appId, permissionName) {
                val isImplicitPermission = requestingPackageStates.anyIndexed { _, it ->
                    permissionName in it.androidPackage!!.implicitPermissions
                }
                val sourcePermissions = newState.externalState
@@ -1108,18 +1140,6 @@ class AppIdPermissionPolicy : SchemePolicy() {
        }
    }

    private fun MutateStateScope.getAppIdTargetSdkVersion(
        appId: Int,
        permissionName: String,
        state: AccessState = newState
    ): Int {
        var targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT
        forEachRequestingPackageInAppId(appId, permissionName, state) {
            targetSdkVersion = targetSdkVersion.coerceAtMost(it.androidPackage!!.targetSdkVersion)
        }
        return targetSdkVersion
    }

    private inline fun MutateStateScope.anyPackageInAppId(
        appId: Int,
        state: AccessState = newState,
@@ -1128,22 +1148,10 @@ class AppIdPermissionPolicy : SchemePolicy() {
        val packageNames = state.externalState.appIdPackageNames[appId]!!
        return packageNames.anyIndexed { _, packageName ->
            val packageState = state.externalState.packageStates[packageName]!!
            val androidPackage = packageState.androidPackage
            androidPackage != null && predicate(packageState)
            packageState.androidPackage != null && predicate(packageState)
        }
    }

    private inline fun MutateStateScope.anyRequestingPackageInAppId(
        appId: Int,
        permissionName: String,
        state: AccessState = newState,
        predicate: (PackageState) -> Boolean
    ): Boolean =
        anyPackageInAppId(appId, state) { packageState ->
            permissionName in packageState.androidPackage!!.requestedPermissions &&
                predicate(packageState)
        }

    private inline fun MutateStateScope.forEachPackageInAppId(
        appId: Int,
        state: AccessState = newState,
@@ -1158,15 +1166,20 @@ class AppIdPermissionPolicy : SchemePolicy() {
        }
    }

    private inline fun MutateStateScope.forEachRequestingPackageInAppId(
    // Using Int instead of <T> to avoid autoboxing, since we only have the use case for Int.
    private inline fun MutateStateScope.reducePackageInAppId(
        appId: Int,
        permissionName: String,
        initialValue: Int,
        state: AccessState = newState,
        action: (PackageState) -> Unit
    ) {
        forEachPackageInAppId(appId, state) { packageState ->
            if (permissionName in packageState.androidPackage!!.requestedPermissions) {
                action(packageState)
        accumulator: (Int, PackageState) -> Int
    ): Int {
        val packageNames = state.externalState.appIdPackageNames[appId]!!
        return packageNames.reduceIndexed(initialValue) { value, _, packageName ->
            val packageState = state.externalState.packageStates[packageName]!!
            if (packageState.androidPackage != null) {
                accumulator(value, packageState)
            } else {
                value
            }
        }
    }