Merge "Implement app-op compatibility layer"

This commit is contained in:
Jay Sullivan
2022-12-17 20:46:03 +00:00
committed by Android (Google) Code Review
5 changed files with 374 additions and 37 deletions

View File

@@ -218,13 +218,13 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface
}
@Override
public boolean arePackageModesDefault(String packageMode, @UserIdInt int userId) {
public boolean arePackageModesDefault(@NonNull String packageName, @UserIdInt int userId) {
synchronized (mLock) {
ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
if (packageModes == null) {
return true;
}
SparseIntArray opModes = packageModes.get(packageMode);
SparseIntArray opModes = packageModes.get(packageName);
return (opModes == null || opModes.size() <= 0);
}
}

View File

@@ -92,7 +92,9 @@ class SystemState private constructor(
class UserState private constructor(
// A map of (appId to a map of (permissionName to permissionFlags))
val uidPermissionFlags: IntMap<IndexedMap<String, Int>>,
// appId -> opName -> opCode
val uidAppOpModes: IntMap<IndexedMap<String, Int>>,
// packageName -> opName -> opCode
val packageAppOpModes: IndexedMap<String, IndexedMap<String, Int>>
) : WritableState() {
constructor() : this(

View File

@@ -16,88 +16,214 @@
package com.android.server.permission.access.appop
import android.util.ArraySet
import android.Manifest
import android.annotation.UserIdInt
import android.app.AppGlobals
import android.app.AppOpsManager
import android.content.pm.PackageManager
import android.os.Binder
import android.os.Handler
import android.os.RemoteException
import android.os.UserHandle
import android.util.SparseBooleanArray
import android.util.SparseIntArray
import com.android.internal.util.ArrayUtils
import com.android.internal.util.function.pooled.PooledLambda
import com.android.server.appop.AppOpsCheckingServiceInterface
import com.android.server.appop.OnOpModeChangedListener
import com.android.server.permission.access.AccessCheckingService
import com.android.server.permission.access.AppOpUri
import com.android.server.permission.access.PackageUri
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.hasBits
import libcore.util.EmptyArray
import java.io.PrintWriter
class AppOpService(
private val service: AccessCheckingService
) : AppOpsCheckingServiceInterface {
private val packagePolicy = service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME)
as PackageAppOpPolicy
private val uidPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME)
as UidAppOpPolicy
private val context = service.context
private lateinit var handler: Handler
private lateinit var lock: Any
private lateinit var switchedOps: IntMap<IntArray>
fun initialize() {
TODO("Not yet implemented")
// TODO(b/252883039): Wrong handler. Inject main thread handler here.
handler = Handler(context.mainLooper)
// TODO(b/252883039): Wrong lock object. Inject AppOpsService here.
lock = Any()
switchedOps = IntMap()
for (switchedCode in 0 until AppOpsManager._NUM_OP) {
val switchCode = AppOpsManager.opToSwitch(switchedCode)
switchedOps.put(switchCode,
ArrayUtils.appendInt(switchedOps.get(switchCode), switchedCode))
}
}
override fun getNonDefaultUidModes(uid: Int): SparseIntArray {
TODO("Not yet implemented")
return opNameMapToOpIntMap(getUidModes(uid))
}
override fun getUidMode(uid: Int, op: Int): Int {
TODO("Not yet implemented")
val appId = UserHandle.getAppId(uid)
val userId = UserHandle.getUserId(uid)
val opName = AppOpsManager.opToPublicName(op)
return service.getState {
with(uidPolicy) { getAppOpMode(appId, userId, opName) }
}
}
private fun getUidModes(uid: Int): IndexedMap<String, Int>? {
val appId = UserHandle.getAppId(uid)
val userId = UserHandle.getUserId(uid)
return service.getState {
with(uidPolicy) { getAppOpModes(appId, userId) }
}
}
override fun setUidMode(uid: Int, op: Int, mode: Int): Boolean {
TODO("Not yet implemented")
val appId = UserHandle.getAppId(uid)
val userId = UserHandle.getUserId(uid)
val opName = AppOpsManager.opToPublicName(op)
var wasChanged = false
service.mutateState {
wasChanged = with(uidPolicy) { setAppOpMode(appId, userId, opName, mode) }
}
return wasChanged
}
override fun getPackageMode(packageName: String, op: Int, userId: Int): Int {
TODO("Not yet implemented")
val opName = AppOpsManager.opToPublicName(op)
return service.getState {
with(packagePolicy) { getAppOpMode(packageName, userId, opName) }
}
}
private fun getPackageModes(
packageName: String,
userId: Int
): IndexedMap<String, Int>? =
service.getState { with(packagePolicy) { getAppOpModes(packageName, userId) } }
override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) {
TODO("Not yet implemented")
}
override fun removePackage(packageName: String, userId: Int): Boolean {
TODO("Not yet implemented")
val opName = AppOpsManager.opToPublicName(op)
service.mutateState {
with(packagePolicy) { setAppOpMode(packageName, userId, opName, mode) }
}
}
override fun removeUid(uid: Int) {
TODO("Not yet implemented")
val appId = UserHandle.getAppId(uid)
val userId = UserHandle.getUserId(uid)
service.mutateState {
with(uidPolicy) { removeAppOpModes(appId, userId) }
}
}
override fun removePackage(packageName: String, userId: Int): Boolean {
var wasChanged = false
service.mutateState {
wasChanged = with (packagePolicy) { removeAppOpModes(packageName, userId) }
}
return wasChanged
}
private fun opNameMapToOpIntMap(modes: IndexedMap<String, Int>?): SparseIntArray =
if (modes == null) {
SparseIntArray()
} else {
val opIntMap = SparseIntArray(modes.size)
modes.forEachIndexed { _, opName, opMode ->
opIntMap.put(AppOpsManager.strOpToOp(opName), opMode)
}
opIntMap
}
override fun areUidModesDefault(uid: Int): Boolean {
TODO("Not yet implemented")
val modes = getUidModes(uid)
return modes == null || modes.isEmpty()
}
override fun arePackageModesDefault(packageName: String, userId: Int): Boolean {
TODO("Not yet implemented")
val modes = service.getState { getPackageModes(packageName, userId) }
return modes == null || modes.isEmpty()
}
override fun clearAllModes() {
TODO("Not yet implemented")
// We don't need to implement this because it's only called in AppOpsService#readState
// and we have our own persistence.
}
// code -> listeners
private val opModeWatchers = IntMap<IndexedSet<OnOpModeChangedListener>>()
// packageName -> listeners
private val packageModeWatchers = IndexedMap<String, IndexedSet<OnOpModeChangedListener>>()
override fun startWatchingOpModeChanged(changedListener: OnOpModeChangedListener, op: Int) {
TODO("Not yet implemented")
synchronized(lock) {
opModeWatchers.getOrPut(op) { IndexedSet() } += changedListener
}
}
override fun startWatchingPackageModeChanged(
changedListener: OnOpModeChangedListener,
packageName: String
) {
TODO("Not yet implemented")
synchronized(lock) {
packageModeWatchers.getOrPut(packageName) { IndexedSet() } += changedListener
}
}
override fun removeListener(changedListener: OnOpModeChangedListener) {
TODO("Not yet implemented")
synchronized(lock) {
opModeWatchers.removeAllIndexed { _, _, listeners ->
listeners -= changedListener
listeners.isEmpty()
}
packageModeWatchers.removeAllIndexed { _, _, listeners ->
listeners -= changedListener
listeners.isEmpty()
}
}
}
override fun getOpModeChangedListeners(op: Int): ArraySet<OnOpModeChangedListener> {
TODO("Not yet implemented")
override fun getOpModeChangedListeners(op: Int): IndexedSet<OnOpModeChangedListener> {
synchronized(lock) {
val listeners = opModeWatchers[op]
return if (listeners == null) {
IndexedSet()
} else {
IndexedSet(listeners)
}
}
}
override fun getPackageModeChangedListeners(
packageName: String
): ArraySet<OnOpModeChangedListener> {
TODO("Not yet implemented")
): IndexedSet<OnOpModeChangedListener> {
synchronized(lock) {
val listeners = packageModeWatchers[packageName]
return if (listeners == null) {
IndexedSet()
} else {
IndexedSet(listeners)
}
}
}
override fun notifyWatchersOfChange(op: Int, uid: Int) {
TODO("Not yet implemented")
val listeners = getOpModeChangedListeners(op)
listeners.forEachIndexed { _, listener ->
notifyOpChanged(listener, op, uid, null)
}
}
override fun notifyOpChanged(
@@ -106,31 +232,159 @@ class AppOpService(
uid: Int,
packageName: String?
) {
TODO("Not yet implemented")
if (uid != UID_ANY &&
changedListener.watchingUid >= 0 &&
changedListener.watchingUid != uid
) {
return
}
// See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE
val switchedCodes = when (changedListener.watchedOpCode) {
ALL_OPS -> switchedOps.get(op)
AppOpsManager.OP_NONE -> intArrayOf(op)
else -> intArrayOf(changedListener.watchedOpCode)
}
for (switchedCode in switchedCodes) {
// There are features watching for mode changes such as window manager
// and location manager which are in our process. The callbacks in these
// features may require permissions our remote caller does not have.
val identity = Binder.clearCallingIdentity()
try {
if (!shouldIgnoreCallback(switchedCode, changedListener)) {
changedListener.onOpModeChanged(switchedCode, uid, packageName)
}
} catch (e: RemoteException) {
/* ignore */
} finally {
Binder.restoreCallingIdentity(identity)
}
}
}
private fun shouldIgnoreCallback(op: Int, listener: OnOpModeChangedListener): Boolean {
// If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
// as watcher should not use this to signal if the value is changed.
return AppOpsManager.opRestrictsRead(op) && context.checkPermission(
Manifest.permission.MANAGE_APPOPS,
listener.callingPid,
listener.callingUid
) != PackageManager.PERMISSION_GRANTED
}
/**
* Construct a map from each listener (listening to the given op, uid) to all of its associated
* packageNames (by reverse-indexing opModeWatchers and packageModeWatchers), then invoke
* notifyOpChanged for each listener.
*/
override fun notifyOpChangedForAllPkgsInUid(
op: Int,
uid: Int,
onlyForeground: Boolean,
callbackToIgnore: OnOpModeChangedListener?
) {
TODO("Not yet implemented")
val uidPackageNames = getPackagesForUid(uid)
val callbackSpecs = IndexedMap<OnOpModeChangedListener, IndexedSet<String>>()
fun associateListenerWithPackageNames(
listener: OnOpModeChangedListener,
packageNames: Array<String>
) {
val listenerIsForeground =
listener.flags.hasBits(AppOpsManager.WATCH_FOREGROUND_CHANGES)
if (onlyForeground && !listenerIsForeground) {
return
}
val changedPackages = callbackSpecs.getOrPut(listener) { IndexedSet() }
changedPackages.addAll(packageNames)
}
synchronized(lock) {
// Collect all listeners from opModeWatchers and pckageModeWatchers
val listeners = opModeWatchers[op]
listeners?.forEachIndexed { _, listener ->
associateListenerWithPackageNames(listener, uidPackageNames)
}
uidPackageNames.forEachIndexed { _, uidPackageName ->
val packageListeners = packageModeWatchers[uidPackageName]
packageListeners?.forEachIndexed { _, listener ->
associateListenerWithPackageNames(listener, arrayOf(uidPackageName))
}
}
// Remove ignored listeners
if (callbackToIgnore != null) {
callbackSpecs.remove(callbackToIgnore)
}
}
// For each (listener, packageName) pair, invoke notifyOpChanged
callbackSpecs.forEachIndexed { _, listener, reportedPackageNames ->
reportedPackageNames.forEachIndexed { _, reportedPackageName ->
handler.sendMessage(
PooledLambda.obtainMessage(
AppOpService::notifyOpChanged, this, listener,
op, uid, reportedPackageName
)
)
}
}
}
private fun getPackagesForUid(uid: Int): Array<String> {
// Very early during boot the package manager is not yet or not yet fully started. At this
// time there are no packages yet.
return try {
AppGlobals.getPackageManager()?.getPackagesForUid(uid) ?: EmptyArray.STRING
} catch (e: RemoteException) {
EmptyArray.STRING
}
}
override fun evalForegroundUidOps(
uid: Int,
foregroundOps: SparseBooleanArray?
): SparseBooleanArray {
TODO("Not yet implemented")
): SparseBooleanArray? {
synchronized(lock) {
val uidModes = getUidModes(uid)
return evalForegroundOps(uidModes, foregroundOps)
}
}
override fun evalForegroundPackageOps(
packageName: String,
foregroundOps: SparseBooleanArray?,
userId: Int
): SparseBooleanArray {
TODO("Not yet implemented")
@UserIdInt userId: Int
): SparseBooleanArray? {
synchronized(lock) {
val ops = service.getState { getPackageModes(packageName, userId) }
return evalForegroundOps(ops, foregroundOps)
}
}
private fun evalForegroundOps(
ops: IndexedMap<String, Int>?,
foregroundOps: SparseBooleanArray?
): SparseBooleanArray? {
var foregroundOps = foregroundOps
ops?.forEachIndexed { _, opName, opMode ->
if (opMode == AppOpsManager.MODE_FOREGROUND) {
if (foregroundOps == null) {
foregroundOps = SparseBooleanArray()
}
evalForegroundWatchers(opName, foregroundOps!!)
}
}
return foregroundOps
}
private fun evalForegroundWatchers(opName: String, foregroundOps: SparseBooleanArray) {
val opCode = AppOpsManager.strOpToOp(opName)
val listeners = opModeWatchers[opCode]
val hasForegroundListeners = foregroundOps[opCode] || listeners?.anyIndexed { _, listener ->
listener.flags.hasBits(AppOpsManager.WATCH_FOREGROUND_CHANGES)
} ?: false
foregroundOps.put(opCode, hasForegroundListeners)
}
override fun dumpListeners(
@@ -139,10 +393,76 @@ class AppOpService(
dumpPackage: String?,
printWriter: PrintWriter
): Boolean {
TODO("Not yet implemented")
var needSep = false
if (opModeWatchers.size() > 0) {
var printedHeader = false
opModeWatchers.forEachIndexed { _, op, modeChangedListenerSet ->
if (dumpOp >= 0 && dumpOp != op) {
return@forEachIndexed // continue
}
val opName = AppOpsManager.opToName(op)
var printedOpHeader = false
modeChangedListenerSet.forEachIndexed listenerLoop@ { listenerIndex, listener ->
with(printWriter) {
if (dumpPackage != null &&
dumpUid != UserHandle.getAppId(listener.watchingUid)) {
return@listenerLoop // continue
}
needSep = true
if (!printedHeader) {
println(" Op mode watchers:")
printedHeader = true
}
if (!printedOpHeader) {
print(" Op ")
print(opName)
println(":")
printedOpHeader = true
}
print(" #")
print(listenerIndex)
print(opName)
print(": ")
println(listener.toString())
}
}
}
}
if (packageModeWatchers.size > 0 && dumpOp < 0) {
var printedHeader = false
packageModeWatchers.forEachIndexed { _, packageName, listeners ->
with(printWriter) {
if (dumpPackage != null && dumpPackage != packageName) {
return@forEachIndexed // continue
}
needSep = true
if (!printedHeader) {
println(" Package mode watchers:")
printedHeader = true
}
print(" Pkg ")
print(packageName)
println(":")
listeners.forEachIndexed { listenerIndex, listener ->
print(" #")
print(listenerIndex)
print(": ")
println(listener.toString())
}
}
}
}
return needSep
}
companion object {
private val LOG_TAG = AppOpService::class.java.simpleName
// Constant meaning that any UID should be matched when dispatching callbacks
private const val UID_ANY = -2
// If watchedOpCode==ALL_OPS, notify for ops affected by the switch-op
private const val ALL_OPS = -2
}
}

View File

@@ -56,8 +56,17 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) {
}
}
fun MutateStateScope.removeAppOpModes(packageName: String, userId: Int): Boolean =
newState.userStates[userId].packageAppOpModes.remove(packageName) != null
fun GetStateScope.getAppOpModes(packageName: String, userId: Int): IndexedMap<String, Int>? =
state.userStates[userId].packageAppOpModes[packageName]
fun MutateStateScope.removeAppOpModes(packageName: String, userId: Int): Boolean {
val userState = newState.userStates[userId]
val isChanged = userState.packageAppOpModes.remove(packageName) != null
if (isChanged) {
userState.requestWrite()
}
return isChanged
}
fun GetStateScope.getAppOpMode(packageName: String, userId: Int, appOpName: String): Int =
state.userStates[userId].packageAppOpModes[packageName]

View File

@@ -59,8 +59,14 @@ class UidAppOpPolicy : BaseAppOpPolicy(UidAppOpPersistence()) {
fun GetStateScope.getAppOpModes(appId: Int, userId: Int): IndexedMap<String, Int>? =
state.userStates[userId].uidAppOpModes[appId]
fun MutateStateScope.removeAppOpModes(appId: Int, userId: Int): Boolean =
newState.userStates[userId].uidAppOpModes.removeReturnOld(appId) != null
fun MutateStateScope.removeAppOpModes(appId: Int, userId: Int): Boolean {
val userState = newState.userStates[userId]
val isChanged = userState.uidAppOpModes.removeReturnOld(appId) != null
if (isChanged) {
userState.requestWrite()
}
return isChanged
}
fun GetStateScope.getAppOpMode(appId: Int, userId: Int, appOpName: String): Int =
state.userStates[userId].uidAppOpModes[appId]