From 5b1bc594e0e3d30d759c16a90a9d0b29f0367243 Mon Sep 17 00:00:00 2001 From: Evan Severson Date: Mon, 13 Mar 2023 10:12:27 -0700 Subject: [PATCH] Add listeners to appops checking interface Note about locking: AppOpsService calls into the checking service holding the lock when changing mode then the checking service also grabs the same lock. It then dispatches the callback synchronously back to AppOpsService where (in packageModeChanged) that callback grabs the lock again. This is taking advantage of the reentrant property of java locks and protects against mutations that may happen outside of this flow. Test: atest CtsAppOpsTestCases AppOpsServiceTest Bug: 268696066 Change-Id: I41d79fdba0dcdc56a287082e22663fbc85998fb2 --- .../appop/AppOpsCheckingServiceImpl.java | 117 +++++++++++------ .../appop/AppOpsCheckingServiceInterface.java | 42 ++++++ ...AppOpsCheckingServiceLoggingDecorator.java | 12 ++ ...AppOpsCheckingServiceTracingDecorator.java | 22 ++++ .../android/server/appop/AppOpsService.java | 113 ++++++++++------- .../appop/AppOpsServiceTestingShim.java | 24 ++++ .../permission/access/appop/AppOpService.kt | 120 +++++++++++++++--- .../server/appop/AppOpsServiceTest.java | 3 + 8 files changed, 350 insertions(+), 103 deletions(-) diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java index 886add3a3c8e6..012dd9b2995ea 100644 --- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java @@ -102,6 +102,8 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface final SparseArray> mUserPackageModes = new SparseArray<>(); private final LegacyAppOpStateParser mAppOpsStateParser = new LegacyAppOpStateParser(); + @GuardedBy("mLock") + private List mModeChangedListeners = new ArrayList<>(); final AtomicFile mFile; final Runnable mWriteRunner = new Runnable() { @@ -183,31 +185,39 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface @Override public boolean setUidMode(int uid, int op, int mode) { final int defaultMode = AppOpsManager.opToDefaultMode(op); + List listenersCopy; synchronized (mLock) { SparseIntArray opModes = mUidModes.get(uid, null); - if (opModes == null) { - if (mode != defaultMode) { - opModes = new SparseIntArray(); - mUidModes.put(uid, opModes); - opModes.put(op, mode); - scheduleWriteLocked(); + + int previousMode = defaultMode; + if (opModes != null) { + previousMode = opModes.get(op, defaultMode); + } + if (mode == previousMode) { + return false; + } + + if (mode == defaultMode) { + opModes.delete(op); + if (opModes.size() == 0) { + mUidModes.remove(uid); } } else { - if (opModes.indexOfKey(op) >= 0 && opModes.get(op) == mode) { - return false; + if (opModes == null) { + opModes = new SparseIntArray(); + mUidModes.put(uid, opModes); } - if (mode == defaultMode) { - opModes.delete(op); - if (opModes.size() <= 0) { - opModes = null; - mUidModes.delete(uid); - } - } else { - opModes.put(op, mode); - } - scheduleWriteLocked(); + opModes.put(op, mode); } + + scheduleWriteLocked(); + listenersCopy = new ArrayList<>(mModeChangedListeners); } + + for (int i = 0; i < listenersCopy.size(); i++) { + listenersCopy.get(i).onUidModeChanged(uid, op, mode); + } + return true; } @@ -229,35 +239,52 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface @Override public void setPackageMode(String packageName, int op, @Mode int mode, @UserIdInt int userId) { final int defaultMode = AppOpsManager.opToDefaultMode(op); + List listenersCopy; synchronized (mLock) { ArrayMap packageModes = mUserPackageModes.get(userId, null); - if (packageModes == null) { + if (packageModes == null && mode != defaultMode) { packageModes = new ArrayMap<>(); mUserPackageModes.put(userId, packageModes); } - SparseIntArray opModes = packageModes.get(packageName); - if (opModes == null) { - if (mode != defaultMode) { - opModes = new SparseIntArray(); - packageModes.put(packageName, opModes); - opModes.put(op, mode); - scheduleWriteLocked(); + SparseIntArray opModes = null; + int previousMode = defaultMode; + if (packageModes != null) { + opModes = packageModes.get(packageName); + if (opModes != null) { + previousMode = opModes.get(op, defaultMode); + } + } + + if (mode == previousMode) { + return; + } + + if (mode == defaultMode) { + opModes.delete(op); + if (opModes.size() == 0) { + packageModes.remove(packageName); + if (packageModes.size() == 0) { + mUserPackageModes.remove(userId); + } } } else { - if (opModes.indexOfKey(op) >= 0 && opModes.get(op) == mode) { - return; + if (packageModes == null) { + packageModes = new ArrayMap<>(); + mUserPackageModes.put(userId, packageModes); } - if (mode == defaultMode) { - opModes.delete(op); - if (opModes.size() <= 0) { - opModes = null; - packageModes.remove(packageName); - } - } else { - opModes.put(op, mode); + if (opModes == null) { + opModes = new SparseIntArray(); + packageModes.put(packageName, opModes); } - scheduleWriteLocked(); + opModes.put(op, mode); } + + scheduleFastWriteLocked(); + listenersCopy = new ArrayList<>(mModeChangedListeners); + } + + for (int i = 0; i < listenersCopy.size(); i++) { + listenersCopy.get(i).onPackageModeChanged(packageName, userId, op, mode); } } @@ -633,4 +660,18 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface return result; } -} \ No newline at end of file + + @Override + public boolean addAppOpsModeChangedListener(AppOpsModeChangedListener listener) { + synchronized (mLock) { + return mModeChangedListeners.add(listener); + } + } + + @Override + public boolean removeAppOpsModeChangedListener(AppOpsModeChangedListener listener) { + synchronized (mLock) { + return mModeChangedListeners.remove(listener); + } + } +} diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java index 76f1f8ada8304..60d17cd388f6f 100644 --- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java +++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java @@ -158,4 +158,46 @@ public interface AppOpsCheckingServiceInterface { * MODE_FOREGROUND for the passed package name and user ID. */ SparseBooleanArray getForegroundOps(String packageName, int userId); + + /** + * Adds a listener for changes in appop modes. These callbacks should be dispatched + * synchronously. + * + * @param listener The listener to be added. + * @return true if the listener was added. + */ + boolean addAppOpsModeChangedListener(@NonNull AppOpsModeChangedListener listener); + + /** + * Removes a listener for changes in appop modes. + * + * @param listener The listener to be removed. + * @return true if the listener was removed. + */ + boolean removeAppOpsModeChangedListener(@NonNull AppOpsModeChangedListener listener); + + /** + * A listener for changes to the AppOps mode. + */ + interface AppOpsModeChangedListener { + + /** + * Invoked when a UID's appop mode is changed. + * + * @param uid The UID whose appop mode was changed. + * @param code The op code that was changed. + * @param mode The new mode. + */ + void onUidModeChanged(int uid, int code, int mode); + + /** + * Invoked when a package's appop mode is changed. + * + * @param packageName The package name whose appop mode was changed. + * @param userId The user ID for the package. + * @param code The op code that was changed. + * @param mode The new mode. + */ + void onPackageModeChanged(@NonNull String packageName, int userId, int code, int mode); + } } diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java index 32dcb5aff8ea6..3fee59bd1c2f1 100644 --- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java +++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java @@ -141,4 +141,16 @@ public class AppOpsCheckingServiceLoggingDecorator implements AppOpsCheckingServ + ")"); return mService.getForegroundOps(packageName, userId); } + + @Override + public boolean addAppOpsModeChangedListener(AppOpsModeChangedListener listener) { + Log.i(LOG_TAG, "addAppOpsModeChangedListener(listener = " + listener + ")"); + return mService.addAppOpsModeChangedListener(listener); + } + + @Override + public boolean removeAppOpsModeChangedListener(AppOpsModeChangedListener listener) { + Log.i(LOG_TAG, "removeAppOpsModeChangedListener(listener = " + listener + ")"); + return mService.removeAppOpsModeChangedListener(listener); + } } diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java index 91dbc23246ed8..c0cc8b1766134 100644 --- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java +++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java @@ -221,4 +221,26 @@ public class AppOpsCheckingServiceTracingDecorator implements AppOpsCheckingServ Trace.traceEnd(TRACE_TAG); } } + + @Override + public boolean addAppOpsModeChangedListener(AppOpsModeChangedListener listener) { + Trace.traceBegin(TRACE_TAG, + "TaggedTracingAppOpsCheckingServiceInterfaceImpl#addAppOpsModeChangedListener"); + try { + return mService.addAppOpsModeChangedListener(listener); + } finally { + Trace.traceEnd(TRACE_TAG); + } + } + + @Override + public boolean removeAppOpsModeChangedListener(AppOpsModeChangedListener listener) { + Trace.traceBegin(TRACE_TAG, + "TaggedTracingAppOpsCheckingServiceInterfaceImpl#removeAppOpsModeChangedListener"); + try { + return mService.removeAppOpsModeChangedListener(listener); + } finally { + Trace.traceEnd(TRACE_TAG); + } + } } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 903c2ba773ff6..111cbfdf7467d 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -399,6 +399,10 @@ public class AppOpsService extends IAppOpsService.Stub { private AppOpsUidStateTracker mUidStateTracker; + /** Callback to skip on next appop update.*/ + @GuardedBy("this") + private IAppOpsCallback mIgnoredCallback = null; + /** Hands the definition of foreground and uid states */ @GuardedBy("this") public AppOpsUidStateTracker getUidStateTracker() { @@ -923,6 +927,59 @@ public class AppOpsService extends IAppOpsService.Stub { mAppOpsCheckingService = new AppOpsCheckingServiceTracingDecorator( new AppOpsCheckingServiceImpl( storageFile, this, handler, context, mSwitchedOps)); + mAppOpsCheckingService.addAppOpsModeChangedListener( + new AppOpsCheckingServiceInterface.AppOpsModeChangedListener() { + @Override + public void onUidModeChanged(int uid, int code, int mode) { + notifyOpChangedForAllPkgsInUid(code, uid, false); + } + + @Override + public void onPackageModeChanged(String packageName, int userId, int code, + int mode) { + ArraySet repCbs = null; + int uid = -1; + synchronized (AppOpsService.this) { + ArraySet cbs = + mOpModeWatchers.get(code); + if (cbs != null) { + if (repCbs == null) { + repCbs = new ArraySet<>(); + } + repCbs.addAll(cbs); + } + cbs = mPackageModeWatchers.get(packageName); + if (cbs != null) { + if (repCbs == null) { + repCbs = new ArraySet<>(); + } + repCbs.addAll(cbs); + } + if (repCbs != null && mIgnoredCallback != null) { + repCbs.remove(mModeWatchers.get(mIgnoredCallback.asBinder())); + } + uid = getPackageManagerInternal().getPackageUid(packageName, + PackageManager.MATCH_KNOWN_PACKAGES, userId); + Op op = getOpLocked(code, uid, packageName, null, false, null, + /* edit */ false); + if (op != null && mode == AppOpsManager.opToDefaultMode(op.op)) { + // If going into the default mode, prune this op + // if there is nothing else interesting in it. + pruneOpLocked(op, uid, packageName); + } + scheduleFastWriteLocked(); + if (mode != MODE_ERRORED) { + updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid); + } + } + + if (repCbs != null && uid != -1) { + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsService::notifyOpChanged, + AppOpsService.this, repCbs, code, uid, packageName)); + } + } + }); //mAppOpsCheckingService = new AppOpsCheckingServiceLoggingDecorator( // LocalServices.getService(AppOpsCheckingServiceInterface.class)); mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler, @@ -1363,7 +1420,7 @@ public class AppOpsService extends IAppOpsService.Stub { && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) { mHandler.sendMessage(PooledLambda.obtainMessage( AppOpsService::notifyOpChangedForAllPkgsInUid, - this, code, uidState.uid, true, null)); + this, code, uidState.uid, true)); } else if (!uidState.pkgOps.isEmpty()) { final ArraySet listenerSet = mOpModeWatchers.get(code); @@ -1830,6 +1887,7 @@ public class AppOpsService extends IAppOpsService.Stub { previousMode = MODE_DEFAULT; } + mIgnoredCallback = permissionPolicyCallback; if (!uidState.setUidMode(code, mode)) { return; } @@ -1838,8 +1896,7 @@ public class AppOpsService extends IAppOpsService.Stub { } } - notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback); - notifyOpChangedSync(code, uid, null, mode, previousMode); + notifyStorageManagerOpModeChangedSync(code, uid, null, mode, previousMode); } /** @@ -1849,8 +1906,7 @@ public class AppOpsService extends IAppOpsService.Stub { * @param uid The uid the op was changed for * @param onlyForeground Only notify watchers that watch for foreground changes */ - private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground, - @Nullable IAppOpsCallback callbackToIgnore) { + private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground) { String[] uidPackageNames = getPackagesForUid(uid); ArrayMap> callbackSpecs = null; synchronized (this) { @@ -1903,8 +1959,8 @@ public class AppOpsService extends IAppOpsService.Stub { } } - if (callbackSpecs != null && callbackToIgnore != null) { - callbackSpecs.remove(mModeWatchers.get(callbackToIgnore.asBinder())); + if (callbackSpecs != null && mIgnoredCallback != null) { + callbackSpecs.remove(mModeWatchers.get(mIgnoredCallback.asBinder())); } } @@ -2023,8 +2079,8 @@ public class AppOpsService extends IAppOpsService.Stub { } } - private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode, - int previousMode) { + private void notifyStorageManagerOpModeChangedSync(int code, int uid, + @NonNull String packageName, int mode, int previousMode) { final StorageManagerInternal storageManagerInternal = LocalServices.getService(StorageManagerInternal.class); if (storageManagerInternal != null) { @@ -2053,7 +2109,6 @@ public class AppOpsService extends IAppOpsService.Stub { return; } - ArraySet repCbs = null; code = AppOpsManager.opToSwitch(code); PackageVerificationResult pvr; @@ -2070,49 +2125,17 @@ public class AppOpsService extends IAppOpsService.Stub { int previousMode = MODE_DEFAULT; synchronized (this) { - UidState uidState = getUidStateLocked(uid, false); Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ true); if (op != null) { if (op.getMode() != mode) { previousMode = op.getMode(); + mIgnoredCallback = permissionPolicyCallback; op.setMode(mode); - ArraySet cbs = - mOpModeWatchers.get(code); - if (cbs != null) { - if (repCbs == null) { - repCbs = new ArraySet<>(); - } - repCbs.addAll(cbs); - } - cbs = mPackageModeWatchers.get(packageName); - if (cbs != null) { - if (repCbs == null) { - repCbs = new ArraySet<>(); - } - repCbs.addAll(cbs); - } - if (repCbs != null && permissionPolicyCallback != null) { - repCbs.remove(mModeWatchers.get(permissionPolicyCallback.asBinder())); - } - if (mode == AppOpsManager.opToDefaultMode(op.op)) { - // If going into the default mode, prune this op - // if there is nothing else interesting in it. - pruneOpLocked(op, uid, packageName); - } - scheduleFastWriteLocked(); - if (mode != MODE_ERRORED) { - updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid); - } } } } - if (repCbs != null) { - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyOpChanged, - this, repCbs, code, uid, packageName)); - } - notifyOpChangedSync(code, uid, packageName, mode, previousMode); + notifyStorageManagerOpModeChangedSync(code, uid, packageName, mode, previousMode); } private void notifyOpChanged(ArraySet callbacks, int code, @@ -2349,7 +2372,7 @@ public class AppOpsService extends IAppOpsService.Stub { int numChanges = allChanges.size(); for (int i = 0; i < numChanges; i++) { ChangeRec change = allChanges.get(i); - notifyOpChangedSync(change.op, change.uid, change.pkg, + notifyStorageManagerOpModeChangedSync(change.op, change.uid, change.pkg, AppOpsManager.opToDefaultMode(change.op), change.previous_mode); } } diff --git a/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java b/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java index c605375e1d3f5..de73a5514792e 100644 --- a/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java +++ b/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java @@ -201,4 +201,28 @@ public class AppOpsServiceTestingShim implements AppOpsCheckingServiceInterface return newVal; } + + @Override + public boolean addAppOpsModeChangedListener(AppOpsModeChangedListener listener) { + boolean oldVal = mOldImplementation.addAppOpsModeChangedListener(listener); + boolean newVal = mNewImplementation.addAppOpsModeChangedListener(listener); + + if (oldVal != newVal) { + signalImplDifference("addAppOpsModeChangedListener"); + } + + return newVal; + } + + @Override + public boolean removeAppOpsModeChangedListener(AppOpsModeChangedListener listener) { + boolean oldVal = mOldImplementation.removeAppOpsModeChangedListener(listener); + boolean newVal = mNewImplementation.removeAppOpsModeChangedListener(listener); + + if (oldVal != newVal) { + signalImplDifference("removeAppOpsModeChangedListener"); + } + + return newVal; + } } 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 2c290a3bef27d..5b91ad9834df5 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 @@ -20,43 +20,40 @@ import android.app.AppOpsManager import android.os.Handler import android.os.UserHandle import android.util.ArrayMap -import android.util.SparseArray +import android.util.ArraySet import android.util.SparseBooleanArray import android.util.SparseIntArray import com.android.internal.annotations.VisibleForTesting -import com.android.internal.util.ArrayUtils import com.android.server.appop.AppOpsCheckingServiceInterface +import com.android.server.appop.AppOpsCheckingServiceInterface.AppOpsModeChangedListener 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.collection.forEachIndexed +import com.android.server.permission.access.collection.set 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) + private val appIdPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy private val context = service.context private lateinit var handler: Handler - private lateinit var lock: Any - private lateinit var switchedOps: SparseArray + + @Volatile + private var listeners = ArraySet() + private val listenersLock = Any() fun initialize() { // 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 = SparseArray() - for (switchedCode in 0 until AppOpsManager._NUM_OP) { - val switchCode = AppOpsManager.opToSwitch(switchedCode) - switchedOps.put(switchCode, - ArrayUtils.appendInt(switchedOps.get(switchCode), switchedCode)) - } + appIdPolicy.addOnAppOpModeChangedListener(OnAppIdAppOpModeChangedListener()) + packagePolicy.addOnAppOpModeChangedListener(OnPackageAppOpModeChangedListener()) } @VisibleForTesting @@ -90,16 +87,14 @@ class AppOpService( val userId = UserHandle.getUserId(uid) val opName = AppOpsManager.opToPublicName(op) return service.getState { - with(uidPolicy) { getAppOpMode(appId, userId, opName) } + with(appIdPolicy) { getAppOpMode(appId, userId, opName) } } } private fun getUidModes(uid: Int): ArrayMap? { val appId = UserHandle.getAppId(uid) val userId = UserHandle.getUserId(uid) - return service.getState { - with(uidPolicy) { getAppOpModes(appId, userId) } - }?.map + return service.getState { with(appIdPolicy) { getAppOpModes(appId, userId) } }?.map } override fun setUidMode(uid: Int, op: Int, mode: Int): Boolean { @@ -108,7 +103,7 @@ class AppOpService( val opName = AppOpsManager.opToPublicName(op) var wasChanged = false service.mutateState { - wasChanged = with(uidPolicy) { setAppOpMode(appId, userId, opName, mode) } + wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, opName, mode) } } return wasChanged } @@ -137,7 +132,7 @@ class AppOpService( val appId = UserHandle.getAppId(uid) val userId = UserHandle.getUserId(uid) service.mutateState { - with(uidPolicy) { removeAppOpModes(appId, userId) } + with(appIdPolicy) { removeAppOpModes(appId, userId) } } } @@ -194,4 +189,89 @@ class AppOpService( } } } + + override fun addAppOpsModeChangedListener(listener: AppOpsModeChangedListener): Boolean { + synchronized(listenersLock) { + val newListeners = ArraySet(listeners) + val result = newListeners.add(listener) + listeners = newListeners + return result + } + } + + override fun removeAppOpsModeChangedListener(listener: AppOpsModeChangedListener): Boolean { + synchronized(listenersLock) { + val newListeners = ArraySet(listeners) + val result = newListeners.remove(listener) + listeners = newListeners + return result + } + } + + inner class OnAppIdAppOpModeChangedListener : AppIdAppOpPolicy.OnAppOpModeChangedListener() { + // (uid, appOpCode) -> newMode + val pendingChanges = ArrayMap, Int>() + + override fun onAppOpModeChanged( + appId: Int, + userId: Int, + appOpName: String, + oldMode: Int, + newMode: Int + ) { + val uid = UserHandle.getUid(userId, appId) + val appOpCode = AppOpsManager.strOpToOp(appOpName) + val key = Pair(uid, appOpCode) + + pendingChanges[key] = newMode + } + + override fun onStateMutated() { + val listenersLocal = listeners + pendingChanges.forEachIndexed { _, key, mode -> + listenersLocal.forEachIndexed { _, listener -> + val uid = key.first + val appOpCode = key.second + + listener.onUidModeChanged(uid, appOpCode, mode) + } + } + + pendingChanges.clear() + } + } + + private inner class OnPackageAppOpModeChangedListener : + PackageAppOpPolicy.OnAppOpModeChangedListener() { + // (packageName, userId, appOpCode) -> newMode + val pendingChanges = ArrayMap, Int>() + + override fun onAppOpModeChanged( + packageName: String, + userId: Int, + appOpName: String, + oldMode: Int, + newMode: Int + ) { + val appOpCode = AppOpsManager.strOpToOp(appOpName) + val key = Triple(packageName, userId, appOpCode) + + pendingChanges[key] = newMode + } + + override fun onStateMutated() { + val listenersLocal = listeners + pendingChanges.forEachIndexed { _, key, mode -> + listenersLocal.forEachIndexed { _, listener -> + val packageName = key.first + val userId = key.second + val appOpCode = key.third + + listener.onPackageModeChanged(packageName, userId, appOpCode, mode) + } + } + + pendingChanges.clear() + } + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java index 12853cd8a750d..24bc57e5bbac0 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java @@ -40,6 +40,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; @@ -163,6 +164,8 @@ public class AppOpsServiceTest { when(mockPackageManagerInternal.getPackageStateInternal(sMyPackageName)) .thenReturn(mockMyPSInternal); when(mockPackageManagerInternal.getPackage(sMyPackageName)).thenReturn(mockMyPkg); + when(mockPackageManagerInternal.getPackageUid(eq(sMyPackageName), anyLong(), + eq(getUserId(mMyUid)))).thenReturn(mMyUid); doReturn(mockPackageManagerInternal).when( () -> LocalServices.getService(PackageManagerInternal.class));