summaryrefslogtreecommitdiff
path: root/services/permission/java
diff options
context:
space:
mode:
author Jay Sullivan <jaysullivan@google.com> 2022-12-17 20:46:03 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2022-12-17 20:46:03 +0000
commit1dc4baaa6fab601292dadabdf5cb9cf8ec8df2ce (patch)
tree62076377834f94576fb54adc7649458f30e3a416 /services/permission/java
parent72c4f6d3de5de4842e969c6aec4384b305eebad5 (diff)
parent8f61ec1d8516aeb6ed393258d3d839b41c81e4b3 (diff)
Merge "Implement app-op compatibility layer"
Diffstat (limited to 'services/permission/java')
-rw-r--r--services/permission/java/com/android/server/permission/access/AccessState.kt2
-rw-r--r--services/permission/java/com/android/server/permission/access/appop/AppOpService.kt380
-rw-r--r--services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt13
-rw-r--r--services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt10
4 files changed, 371 insertions, 34 deletions
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 71347d2a43d7..6924d5139737 100644
--- a/services/permission/java/com/android/server/permission/access/AccessState.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessState.kt
@@ -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(
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
index b8d6aa3b4e49..f2cff62d3b8c 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
@@ -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")
+ val opName = AppOpsManager.opToPublicName(op)
+ service.mutateState {
+ with(packagePolicy) { setAppOpMode(packageName, userId, opName, mode) }
+ }
}
- override fun removePackage(packageName: String, userId: Int): Boolean {
- TODO("Not yet implemented")
+ override fun removeUid(uid: Int) {
+ val appId = UserHandle.getAppId(uid)
+ val userId = UserHandle.getUserId(uid)
+ service.mutateState {
+ with(uidPolicy) { removeAppOpModes(appId, userId) }
+ }
}
- override fun removeUid(uid: Int) {
- TODO("Not yet implemented")
+ 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
}
}
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
index 607e5120fb37..af95fbdb2b6d 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
@@ -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]
diff --git a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
index 0b0103815e12..93b3a4484dff 100644
--- a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
@@ -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]