Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5b1bc594 authored by Evan Severson's avatar Evan Severson
Browse files

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
parent befba4b4
Loading
Loading
Loading
Loading
+79 −38
Original line number Diff line number Diff line
@@ -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);
            }
            } else {
                if (opModes.indexOfKey(op) >= 0 && opModes.get(op) == mode) {
            if (mode == previousMode) {
                return false;
            }

            if (mode == defaultMode) {
                opModes.delete(op);
                    if (opModes.size() <= 0) {
                        opModes = null;
                        mUidModes.delete(uid);
                if (opModes.size() == 0) {
                    mUidModes.remove(uid);
                }
            } else {
                if (opModes == null) {
                    opModes = new SparseIntArray();
                    mUidModes.put(uid, opModes);
                }
                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);
                }
            } else {
                if (opModes.indexOfKey(op) >= 0 && opModes.get(op) == mode) {
            }

            if (mode == previousMode) {
                return;
            }

            if (mode == defaultMode) {
                opModes.delete(op);
                    if (opModes.size() <= 0) {
                        opModes = null;
                if (opModes.size() == 0) {
                    packageModes.remove(packageName);
                    if (packageModes.size() == 0) {
                        mUserPackageModes.remove(userId);
                    }
                }
            } else {
                if (packageModes == null) {
                    packageModes = new ArrayMap<>();
                    mUserPackageModes.put(userId, packageModes);
                }
                if (opModes == null) {
                    opModes = new SparseIntArray();
                    packageModes.put(packageName, opModes);
                }
                opModes.put(op, mode);
            }
                scheduleWriteLocked();

            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);
        }
    }
}
+42 −0
Original line number Diff line number Diff line
@@ -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);
    }
}
+12 −0
Original line number Diff line number Diff line
@@ -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);
    }
}
+22 −0
Original line number Diff line number Diff line
@@ -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);
        }
    }
}
+68 −45
Original line number Diff line number Diff line
@@ -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);
        }
    }
Loading