diff options
| author | 2022-12-15 15:51:26 -0800 | |
|---|---|---|
| committer | 2022-12-16 16:54:23 -0800 | |
| commit | 8f61ec1d8516aeb6ed393258d3d839b41c81e4b3 (patch) | |
| tree | 1cdc6d983af33813a0d208313216350a65b33e2c /services/permission/java | |
| parent | 2127ea68e3700cc5edaa1af60bbbc3f9d075a774 (diff) | |
Implement app-op compatibility layer
The main goal of the app-op compatibility layer (AppOpService.kt) is
to provide an adapter on top of AccessCheckingService which implements
the AppOpsCheckingServiceInterface, to be called by AppOpsService.java.
Change-Id: Ic62c83107f67a68045dc449646c5e15853dcbeaf
Test: presubmit
Bug: 252883039
Diffstat (limited to 'services/permission/java')
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 fae1ec3ba702..fb0cc34ba659 100644 --- a/services/permission/java/com/android/server/permission/access/AccessState.kt +++ b/services/permission/java/com/android/server/permission/access/AccessState.kt @@ -88,7 +88,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] |