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

Commit 60d184bd authored by Hai Zhang's avatar Hai Zhang
Browse files

Add onStateMutated() callback for listener dispatching.

This solves the issue of new state visibility when we invoke
externally provided listeners - they may check the current state when
they are called and they should always see the new state, but
currently they may see the old state if the thread mutating the state
hasn't finished mutation.

The onStateMutated() callback provides a way to invoke the externally
provided listener at the right time (after state mutation is
committed), so that they can always see the new state without worrying
about the race condition. Meanwhile, this also provides a good place
to consolidate the invocation for listeners like
OnPermissionsChangeListener which only needs to be invoked once for
each UID.

Changed the listeners to use abstract classes instead of interfaces
for slightly better performance in accordance with System Health
team's guidelines.

Made the OnPermissionsChangeListener only notified when runtime
permissions are changed. Previously we had the code to notify it when
permission flags for install permissions has changed, however the
PackageManager permission flags actually should only be applied to
runtime permissions. Meanwhile also existing grantRuntimePermission()
may grant a non-runtime permission like a development or role
permission, the existing implementation actually doesn't notify the
listener in this case, so notifying for flags change that doesn't
happen while not notifying for grant state changes makes even less
sense.

Bug: 252884423
Test: presubmit
Change-Id: I1f973e5e3c0ad4aa6ae23f6e238aef5aafa66e2e
parent 46a23cd0
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -161,6 +161,7 @@ class AccessCheckingService(context: Context) : SystemService(context) {
            MutateStateScope(oldState, newState).action()
            persistence.write(newState)
            state = newState
            with(policy) { GetStateScope(newState).onStateMutated() }
        }
    }

+8 −0
Original line number Diff line number Diff line
@@ -73,6 +73,12 @@ class AccessPolicy private constructor(
        }
    }

    fun GetStateScope.onStateMutated() {
        forEachSchemePolicy {
            with(it) { onStateMutated() }
        }
    }

    fun MutateStateScope.onUserAdded(userId: Int) {
        newState.systemState.userIds += userId
        newState.userStates[userId] = UserState()
@@ -284,6 +290,8 @@ abstract class SchemePolicy {
        decision: Int
    )

    open fun GetStateScope.onStateMutated() {}

    open fun MutateStateScope.onUserAdded(userId: Int) {}

    open fun MutateStateScope.onUserRemoved(userId: Int) {}
+23 −2
Original line number Diff line number Diff line
@@ -48,6 +48,10 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) {
        setAppOpMode(subject.packageName, subject.userId, `object`.appOpName, decision)
    }

    override fun GetStateScope.onStateMutated() {
        onAppOpModeChangedListeners.forEachIndexed { _, it -> it.onStateMutated() }
    }

    override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {
        newState.userStates.forEachIndexed { _, _, userState ->
            userState.packageAppOpModes -= packageName
@@ -113,13 +117,30 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) {
        }
    }

    fun interface OnAppOpModeChangedListener {
        fun onAppOpModeChanged(
    /**
     * Listener for app op mode changes.
     */
    abstract class OnAppOpModeChangedListener {
        /**
         * Called when an app op mode change has been made to the upcoming new state.
         *
         * Implementations should keep this method fast to avoid stalling the locked state mutation,
         * and only call external code after [onStateMutated] when the new state has actually become
         * the current state visible to external code.
         */
        abstract fun onAppOpModeChanged(
            packageName: String,
            userId: Int,
            appOpName: String,
            oldMode: Int,
            newMode: Int
        )

        /**
         * Called when the upcoming new state has become the current state.
         *
         * Implementations should keep this method fast to avoid stalling the locked state mutation.
         */
        abstract fun onStateMutated()
    }
}
+23 −2
Original line number Diff line number Diff line
@@ -48,6 +48,10 @@ class UidAppOpPolicy : BaseAppOpPolicy(UidAppOpPersistence()) {
        setAppOpMode(subject.appId, subject.userId, `object`.appOpName, decision)
    }

    override fun GetStateScope.onStateMutated() {
        onAppOpModeChangedListeners.forEachIndexed { _, it -> it.onStateMutated() }
    }

    override fun MutateStateScope.onAppIdRemoved(appId: Int) {
        newState.userStates.forEachIndexed { _, _, userState ->
            userState.uidAppOpModes -= appId
@@ -113,13 +117,30 @@ class UidAppOpPolicy : BaseAppOpPolicy(UidAppOpPersistence()) {
        }
    }

    fun interface OnAppOpModeChangedListener {
        fun onAppOpModeChanged(
    /**
     * Listener for app op mode changes.
     */
    abstract class OnAppOpModeChangedListener {
        /**
         * Called when an app op mode change has been made to the upcoming new state.
         *
         * Implementations should keep this method fast to avoid stalling the locked state mutation,
         * and only call external code after [onStateMutated] when the new state has actually become
         * the current state visible to external code.
         */
        abstract fun onAppOpModeChanged(
            appId: Int,
            userId: Int,
            appOpName: String,
            oldMode: Int,
            newMode: Int
        )

        /**
         * Called when the upcoming new state has become the current state.
         *
         * Implementations should keep this method fast to avoid stalling the locked state mutation.
         */
        abstract fun onStateMutated()
    }
}
+171 −0
Original line number Diff line number Diff line
/*
 * 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.server.permission.access.collection

import android.util.SparseBooleanArray

typealias IntBooleanMap = SparseBooleanArray

inline fun IntBooleanMap.allIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
    forEachIndexed { index, key, value ->
        if (!predicate(index, key, value)) {
            return false
        }
    }
    return true
}

inline fun IntBooleanMap.anyIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
    forEachIndexed { index, key, value ->
        if (predicate(index, key, value)) {
            return true
        }
    }
    return false
}

@Suppress("NOTHING_TO_INLINE")
inline fun IntBooleanMap.copy(): IntBooleanMap = clone()

inline fun <R> IntBooleanMap.firstNotNullOfOrNullIndexed(transform: (Int, Int, Boolean) -> R): R? {
    forEachIndexed { index, key, value ->
        transform(index, key, value)?.let { return it }
    }
    return null
}

inline fun IntBooleanMap.forEachIndexed(action: (Int, Int, Boolean) -> Unit) {
    for (index in 0 until size) {
        action(index, keyAt(index), valueAt(index))
    }
}

inline fun IntBooleanMap.forEachKeyIndexed(action: (Int, Int) -> Unit) {
    for (index in 0 until size) {
        action(index, keyAt(index))
    }
}

inline fun IntBooleanMap.forEachReversedIndexed(action: (Int, Int, Boolean) -> Unit) {
    for (index in lastIndex downTo 0) {
        action(index, keyAt(index), valueAt(index))
    }
}

inline fun IntBooleanMap.forEachValueIndexed(action: (Int, Boolean) -> Unit) {
    for (index in 0 until size) {
        action(index, valueAt(index))
    }
}

inline fun IntBooleanMap.getOrPut(key: Int, defaultValue: () -> Boolean): Boolean {
    val index = indexOfKey(key)
    return if (index >= 0) {
        valueAt(index)
    } else {
        defaultValue().also { put(key, it) }
    }
}

@Suppress("NOTHING_TO_INLINE")
inline fun IntBooleanMap?.getWithDefault(key: Int, defaultValue: Boolean): Boolean {
    this ?: return defaultValue
    return get(key, defaultValue)
}

inline val IntBooleanMap.lastIndex: Int
    get() = size - 1

@Suppress("NOTHING_TO_INLINE")
inline operator fun IntBooleanMap.minusAssign(key: Int) {
    delete(key)
}

inline fun IntBooleanMap.noneIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
    forEachIndexed { index, key, value ->
        if (predicate(index, key, value)) {
            return false
        }
    }
    return true
}

@Suppress("NOTHING_TO_INLINE")
inline fun IntBooleanMap.putWithDefault(key: Int, value: Boolean, defaultValue: Boolean): Boolean {
    val index = indexOfKey(key)
    if (index >= 0) {
        val oldValue = valueAt(index)
        if (value != oldValue) {
            if (value == defaultValue) {
                removeAt(index)
            } else {
                setValueAt(index, value)
            }
        }
        return oldValue
    } else {
        if (value != defaultValue) {
            put(key, value)
        }
        return defaultValue
    }
}

fun IntBooleanMap.remove(key: Int) {
    delete(key)
}

fun IntBooleanMap.remove(key: Int, defaultValue: Boolean): Boolean {
    val index = indexOfKey(key)
    return if (index >= 0) {
        val oldValue = valueAt(index)
        removeAt(index)
        oldValue
    } else {
        defaultValue
    }
}

inline fun IntBooleanMap.removeAllIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
    var isChanged = false
    forEachReversedIndexed { index, key, value ->
        if (predicate(index, key, value)) {
            removeAt(index)
            isChanged = true
        }
    }
    return isChanged
}

inline fun IntBooleanMap.retainAllIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
    var isChanged = false
    forEachReversedIndexed { index, key, value ->
        if (!predicate(index, key, value)) {
            removeAt(index)
            isChanged = true
        }
    }
    return isChanged
}

@Suppress("NOTHING_TO_INLINE")
inline operator fun IntBooleanMap.set(key: Int, value: Boolean) {
    put(key, value)
}

inline val IntBooleanMap.size: Int
    get() = size()
Loading