Merge "Add listeners to appops checking interface"

This commit is contained in:
Evan Severson
2023-04-19 19:52:03 +00:00
committed by Android (Google) Code Review
8 changed files with 350 additions and 103 deletions

View File

@@ -102,6 +102,8 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface
final SparseArray<ArrayMap<String, SparseIntArray>> mUserPackageModes = new SparseArray<>();
private final LegacyAppOpStateParser mAppOpsStateParser = new LegacyAppOpStateParser();
@GuardedBy("mLock")
private List<AppOpsModeChangedListener> 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<AppOpsModeChangedListener> 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<AppOpsModeChangedListener> listenersCopy;
synchronized (mLock) {
ArrayMap<String, SparseIntArray> 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;
}
}
@Override
public boolean addAppOpsModeChangedListener(AppOpsModeChangedListener listener) {
synchronized (mLock) {
return mModeChangedListeners.add(listener);
}
}
@Override
public boolean removeAppOpsModeChangedListener(AppOpsModeChangedListener listener) {
synchronized (mLock) {
return mModeChangedListeners.remove(listener);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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<OnOpModeChangedListener> repCbs = null;
int uid = -1;
synchronized (AppOpsService.this) {
ArraySet<OnOpModeChangedListener> 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<OnOpModeChangedListener> 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<OnOpModeChangedListener, ArraySet<String>> 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<OnOpModeChangedListener> 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<OnOpModeChangedListener> 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<OnOpModeChangedListener> 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);
}
}

View File

@@ -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;
}
}

View File

@@ -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<IntArray>
@Volatile
private var listeners = ArraySet<AppOpsModeChangedListener>()
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<String, Int>? {
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<Pair<Int, Int>, 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<Triple<String, Int, Int>, 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()
}
}
}

View File

@@ -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));